diff --git a/.drone.yml b/.drone.yml index a51b535..dbf8000 100644 --- a/.drone.yml +++ b/.drone.yml @@ -58,6 +58,36 @@ steps: event: push branch: master +--- +kind: pipeline +type: docker +name: Build Docker Image (s2oj-remote-judger) + +trigger: + branch: + - master + +steps: + - name: tags + image: alpine + commands: + - echo -n "latest, $DRONE_BRANCH, ${DRONE_COMMIT_SHA:0:8}" > .tags + + - name: docker + image: plugins/docker + settings: + registry: git.m.ac + repo: git.m.ac/baoshuo/s2oj-remote-judger + context: remote_judger + dockerfile: remote_judger/Dockerfile + username: baoshuo + password: + from_secret: GITMAC_SECRET + cache_from: git.m.ac/baoshuo/s2oj-remote-judger:latest + when: + event: push + branch: master + --- kind: pipeline type: docker diff --git a/.editorconfig b/.editorconfig index a318561..002128d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,12 @@ root = true [*] indent_style = tab indent_size = 4 +end_of_line = lf [*.y{,a}ml] indent_style = space indent_size = 2 + +[remote_judger/**.{js,ts}] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c8034ef..682fe8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,9 +3,10 @@ name: Build & Push Docker Images on: push: branches: - - 'master' - tags: - - 'v*' + - master + pull_request: + branches: + - master workflow_dispatch: env: @@ -13,88 +14,31 @@ env: IMAGE_BASENAME: ${{ github.repository }} jobs: - build-db: - name: Build Database Image + build: + name: Build Image runs-on: ubuntu-latest + permissions: contents: read packages: write - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - name: Log in to the Container registry - uses: docker/login-action@v2.0.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + strategy: + matrix: + include: + - image_name: db + context: db + dockerfile: db/Dockerfile + - image_name: judger + context: judger + dockerfile: judger/Dockerfile + - image_name: remote-judger + context: remote_judger + dockerfile: remote_judger/Dockerfile + - image_name: web + context: . + dockerfile: web/Dockerfile + fail-fast: false - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4.0.1 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_BASENAME }}-db - tags: | - latest - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha,prefix= - - - name: Build and push Docker image - uses: docker/build-push-action@v3.1.1 - with: - context: db - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - build-judger: - name: Build Judger Image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v2.0.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4.0.1 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_BASENAME }}-judger - tags: | - latest - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha,prefix= - - - name: Build and push Docker image - uses: docker/build-push-action@v3.1.1 - with: - context: judger - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - build-web: - name: Build Web Image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write steps: - name: Checkout repository uses: actions/checkout@v3 @@ -104,7 +48,7 @@ jobs: sed -i "s/'s2oj-version' => 'dev'/'s2oj-version' => '$(echo "${{ github.sha }}" | cut -c1-7)'/g" web/app/.default-config.php - name: Log in to the Container registry - uses: docker/login-action@v2.0.0 + uses: docker/login-action@v2.1.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -112,9 +56,9 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v4.0.1 + uses: docker/metadata-action@v4.3.0 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_BASENAME }}-web + images: ${{ env.REGISTRY }}/${{ env.IMAGE_BASENAME }}-${{ matrix.image_name }} tags: | latest type=ref,event=branch @@ -124,10 +68,10 @@ jobs: type=sha,prefix= - name: Build and push Docker image - uses: docker/build-push-action@v3.1.1 + uses: docker/build-push-action@v3.3.0 with: - context: . - file: web/Dockerfile - push: true + context: ${{ matrix.context }} + file: ${{ matrix.dockerfile }} + push: ${{ github.event_name == 'push' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index e4d53e4..c89a998 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ docker-compose.local.yml .config.php .config.development.php .config.local.php +*.development.env +*.local.env diff --git a/db/app_uoj233.sql b/db/app_uoj233.sql index 12b362a..03e94c4 100644 --- a/db/app_uoj233.sql +++ b/db/app_uoj233.sql @@ -622,8 +622,10 @@ CREATE TABLE `problems` ( `ac_num` int NOT NULL DEFAULT '0', `submit_num` int NOT NULL DEFAULT '0', `difficulty` int NOT NULL DEFAULT '-1', + `type` varchar(20) NOT NULL DEFAULT 'local', `assigned_to_judger` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'any', PRIMARY KEY (`id`), + KEY `type` (`type`), KEY `assigned_to_judger` (`assigned_to_judger`), KEY `uploader` (`uploader`), KEY `difficulty` (`difficulty`), @@ -648,6 +650,7 @@ UNLOCK TABLES; /*!40101 SET character_set_client = utf8mb4 */; CREATE TABLE `problems_contents` ( `id` int NOT NULL, + `remote_content` longtext COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `statement` longtext COLLATE utf8mb4_unicode_ci NOT NULL, `statement_md` longtext COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`) @@ -989,7 +992,8 @@ INSERT INTO `upgrades` (`name`, `status`, `updated_at`) VALUES ('16_list_v3', 'up', now()), ('18_user_permissions', 'up', now()), ('20_problem_difficulty', 'up', now()), - ('21_problem_difficulty', 'up', now()); + ('21_problem_difficulty', 'up', now()), + ('28_remote_judge', 'up', now()); /*!40000 ALTER TABLE `upgrades` ENABLE KEYS */; UNLOCK TABLES; diff --git a/docker-compose.development.yml b/docker-compose.development.yml index 31daa8d..3b694da 100644 --- a/docker-compose.development.yml +++ b/docker-compose.development.yml @@ -42,6 +42,23 @@ services: - SOCKET_PORT=2333 - SOCKET_PASSWORD=_judger_socket_password_ + uoj-remote-judger: + build: + context: ./remote_judger/ + dockerfile: Dockerfile + args: + - USE_MIRROR=1 + container_name: uoj-remote-judger + restart: always + env_file: + - remote-judger.development.env + environment: + - DEV=true + - UOJ_PROTOCOL=http + - UOJ_HOST=uoj-web + - UOJ_JUDGER_NAME=remote_judger + - UOJ_JUDGER_PASSWORD=_judger_password_ + uoj-web: build: context: ./ diff --git a/docker-compose.yml b/docker-compose.yml index 1da0339..57c96ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,16 @@ services: - SOCKET_PORT=2333 - SOCKET_PASSWORD=_judger_socket_password_ + uoj-remote-judger: + image: git.m.ac/baoshuo/s2oj-remote-judger + container_name: uoj-remote-judger + restart: always + environment: + - UOJ_PROTOCOL=http + - UOJ_HOST=uoj-web + - UOJ_JUDGER_NAME=remote_judger + - UOJ_JUDGER_PASSWORD=_judger_password_ + uoj-web: image: git.m.ac/baoshuo/s2oj-web container_name: uoj-web diff --git a/judger/add_judger.sql b/judger/add_judger.sql index 164db47..ab0cad3 100644 --- a/judger/add_judger.sql +++ b/judger/add_judger.sql @@ -1,2 +1,2 @@ USE `app_uoj233`; -insert into judger_info (judger_name, password, ip) values ('compose_judger', '_judger_password_', 'uoj-judger'); +insert into judger_info (judger_name, password, ip, display_name, description) values ('compose_judger', '_judger_password_', 'uoj-judger', '内置评测机', '用于评测本地题目的评测机。'); diff --git a/remote_judger/.gitignore b/remote_judger/.gitignore new file mode 100644 index 0000000..123b7be --- /dev/null +++ b/remote_judger/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +*-error.log diff --git a/remote_judger/.prettierrc b/remote_judger/.prettierrc new file mode 100644 index 0000000..fb15891 --- /dev/null +++ b/remote_judger/.prettierrc @@ -0,0 +1,14 @@ +{ + "arrowParens": "avoid", + "bracketSpacing": true, + "endOfLine": "lf", + "jsxSingleQuote": false, + "printWidth": 80, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "useTabs": false, + "organizeImportsSkipDestructiveCodeActions": true +} diff --git a/remote_judger/Dockerfile b/remote_judger/Dockerfile new file mode 100644 index 0000000..5587f77 --- /dev/null +++ b/remote_judger/Dockerfile @@ -0,0 +1,12 @@ +FROM node:18.13.0 + +WORKDIR /opt/s2oj_remote_judger +COPY package*.json ./ + +RUN npm ci + +COPY . . + +RUN npm run build + +CMD [ "node", "--experimental-specifier-resolution=node", "dist/entrypoint.js" ] diff --git a/remote_judger/README b/remote_judger/README new file mode 100644 index 0000000..ef3ed2b --- /dev/null +++ b/remote_judger/README @@ -0,0 +1,5 @@ +本模块借鉴了以下项目的源码: + +- https://github.com/hydro-dev/Hydro/blob/feb51804766e35dbd13f7cb74fda95c0b783c49d/packages/vjudge/ + +在此表示感谢。 diff --git a/remote_judger/add_judger.sql b/remote_judger/add_judger.sql new file mode 100644 index 0000000..0d764e3 --- /dev/null +++ b/remote_judger/add_judger.sql @@ -0,0 +1,2 @@ +USE `app_uoj233`; +insert into judger_info (judger_name, password, ip, display_name, description) values ('remote_judger', '_judger_password_', 'uoj-remote-judger', '远端评测机', '用于桥接远端 OJ 评测机的虚拟评测机。'); diff --git a/remote_judger/package-lock.json b/remote_judger/package-lock.json new file mode 100644 index 0000000..0e1e96d --- /dev/null +++ b/remote_judger/package-lock.json @@ -0,0 +1,2600 @@ +{ + "name": "s2oj-remote-judger", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "s2oj-remote-judger", + "version": "0.0.0", + "license": "AGPL-3.0", + "dependencies": { + "fs-extra": "^11.1.0", + "jsdom": "^21.0.0", + "math-sum": "^2.0.0", + "reggol": "^1.3.4", + "superagent": "^8.0.6", + "superagent-proxy": "^3.0.0" + }, + "devDependencies": { + "@types/fs-extra": "^11.0.1", + "@types/js-yaml": "^4.0.5", + "@types/jsdom": "^20.0.1", + "@types/node": "^18.11.18", + "@types/superagent": "^4.1.16", + "@types/superagent-proxy": "^3.0.0", + "js-yaml": "^4.1.0", + "typescript": "^4.9.4" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, + "node_modules/@types/fs-extra": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", + "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", + "dev": true, + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/jsonfile": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", + "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "node_modules/@types/superagent": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.16.tgz", + "integrity": "sha512-tLfnlJf6A5mB6ddqF159GqcDizfzbMUB1/DeT59/wBNqzRTNNKsaw79A/1TZ84X+f/EwWH8FeuSkjlCLyqS/zQ==", + "dev": true, + "dependencies": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "node_modules/@types/superagent-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/superagent-proxy/-/superagent-proxy-3.0.0.tgz", + "integrity": "sha512-gLNLONQrB8p9WQh4NFzeITZxDqv086q4xwCYFCaoQdH+4WQXmrGrXCJDn4cO3wNh+Sgsjb78lSVnjTDuST2Yxg==", + "dev": true, + "dependencies": { + "@types/superagent": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cosmokit": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cosmokit/-/cosmokit-1.4.0.tgz", + "integrity": "sha512-9Y5epwkPxnWDSjweuWoFATY8GKg9N1/r/3wL32Cjs7FIvo0S9syyY39xmNKq7+SZjbw+9bZUSbeQSbJaqufV3Q==" + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, + "node_modules/data-uri-to-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", + "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/degenerator": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-3.0.2.tgz", + "integrity": "sha512-c0mef3SNQo56t6urUU6tdQAs+ThoD0o9B9MJ8HEt7NQcGEILCRFqQb7ZbP9JAv+QF1Ky5plydhMR/IrqWDm+TQ==", + "dependencies": { + "ast-types": "^0.13.2", + "escodegen": "^1.8.1", + "esprima": "^4.0.0", + "vm2": "^3.9.8" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/degenerator/node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/degenerator/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/file-uri-to-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", + "integrity": "sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/fs-extra": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/ftp": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", + "integrity": "sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==", + "dependencies": { + "readable-stream": "1.1.x", + "xregexp": "2.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-uri": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz", + "integrity": "sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==", + "dependencies": { + "@tootallnate/once": "1", + "data-uri-to-buffer": "3", + "debug": "4", + "file-uri-to-path": "2", + "fs-extra": "^8.1.0", + "ftp": "^0.3.10" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/get-uri/node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/get-uri/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/get-uri/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/get-uri/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.0.0.tgz", + "integrity": "sha512-AIw+3ZakSUtDYvhwPwWHiZsUi3zHugpMEKlNPaurviseYoBqo0zBd3zqoUi3LPCNtPFlEP8FiW9MqCZdjb2IYA==", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/math-sum/-/math-sum-2.0.0.tgz", + "integrity": "sha512-pt3L7X8npPNZzYCCOZSxgmRBc091gZ8aPAxqJwq4c7Zf2Gw8K+fjF6gWepZZEkY6fDadlTxYdltDuT/dq894Hw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pac-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-5.0.0.tgz", + "integrity": "sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ==", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4", + "get-uri": "3", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "5", + "pac-resolver": "^5.0.0", + "raw-body": "^2.2.0", + "socks-proxy-agent": "5" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/pac-proxy-agent/node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pac-resolver": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-5.0.1.tgz", + "integrity": "sha512-cy7u00ko2KVgBAjuhevqpPeHIkCIqPe1v24cydhWjmeuzaBfmUWFCZJ1iAh5TuVzVZoUzXIW7K8sMYOZ84uZ9Q==", + "dependencies": { + "degenerator": "^3.0.2", + "ip": "^1.1.5", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-5.0.0.tgz", + "integrity": "sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g==", + "dependencies": { + "agent-base": "^6.0.0", + "debug": "4", + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "lru-cache": "^5.1.1", + "pac-proxy-agent": "^5.0.0", + "proxy-from-env": "^1.0.0", + "socks-proxy-agent": "^5.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/proxy-agent/node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/proxy-agent/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.2.0.tgz", + "integrity": "sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/reggol": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/reggol/-/reggol-1.3.4.tgz", + "integrity": "sha512-E/sL4WovP0kR5EHQx3YF4ZDsI5D5baTROXaELCN1mOCB8tEOVOR3PHr807yVcGit2BfmXMSW5KyRaUAV5K8Vzw==", + "dependencies": { + "cosmokit": "^1.3.6", + "object-inspect": "^1.12.2", + "supports-color": "^8.1.1" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz", + "integrity": "sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "4", + "socks": "^2.3.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/socks/node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/superagent": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.6.tgz", + "integrity": "sha512-HqSe6DSIh3hEn6cJvCkaM1BLi466f1LHi4yubR0tpewlMpk4RUFFy35bKz8SsPBwYfIIJy5eclp+3tCYAuX0bw==", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.3", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "node_modules/superagent-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-3.0.0.tgz", + "integrity": "sha512-wAlRInOeDFyd9pyonrkJspdRAxdLrcsZ6aSnS+8+nu4x1aXbz6FWSTT9M6Ibze+eG60szlL7JA8wEIV7bPWuyQ==", + "dependencies": { + "debug": "^4.3.2", + "proxy-agent": "^5.0.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "superagent": ">= 0.15.4 || 1 || 2 || 3" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "4.9.4", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/vm2": { + "version": "3.9.13", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.13.tgz", + "integrity": "sha512-0rvxpB8P8Shm4wX2EKOiMp7H2zq+HUE/UwodY0pCZXs9IffIKZq6vUti5OgkVCTakKo9e/fgO4X1fkwfjWxE3Q==", + "dependencies": { + "acorn": "^8.7.0", + "acorn-walk": "^8.2.0" + }, + "bin": { + "vm2": "bin/vm2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/xregexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", + "integrity": "sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==", + "engines": { + "node": "*" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + }, + "dependencies": { + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" + }, + "@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, + "@types/fs-extra": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", + "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", + "dev": true, + "requires": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, + "@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "@types/jsonfile": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", + "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "@types/superagent": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.16.tgz", + "integrity": "sha512-tLfnlJf6A5mB6ddqF159GqcDizfzbMUB1/DeT59/wBNqzRTNNKsaw79A/1TZ84X+f/EwWH8FeuSkjlCLyqS/zQ==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "@types/superagent-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/superagent-proxy/-/superagent-proxy-3.0.0.tgz", + "integrity": "sha512-gLNLONQrB8p9WQh4NFzeITZxDqv086q4xwCYFCaoQdH+4WQXmrGrXCJDn4cO3wNh+Sgsjb78lSVnjTDuST2Yxg==", + "dev": true, + "requires": { + "@types/superagent": "*" + } + }, + "@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + }, + "acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "requires": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "requires": { + "tslib": "^2.0.1" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cosmokit": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cosmokit/-/cosmokit-1.4.0.tgz", + "integrity": "sha512-9Y5epwkPxnWDSjweuWoFATY8GKg9N1/r/3wL32Cjs7FIvo0S9syyY39xmNKq7+SZjbw+9bZUSbeQSbJaqufV3Q==" + }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + } + } + }, + "data-uri-to-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", + "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==" + }, + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "degenerator": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-3.0.2.tgz", + "integrity": "sha512-c0mef3SNQo56t6urUU6tdQAs+ThoD0o9B9MJ8HEt7NQcGEILCRFqQb7ZbP9JAv+QF1Ky5plydhMR/IrqWDm+TQ==", + "requires": { + "ast-types": "^0.13.2", + "escodegen": "^1.8.1", + "esprima": "^4.0.0", + "vm2": "^3.9.8" + }, + "dependencies": { + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "requires": { + "webidl-conversions": "^7.0.0" + } + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "file-uri-to-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", + "integrity": "sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", + "requires": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + } + }, + "fs-extra": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "ftp": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", + "integrity": "sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==", + "requires": { + "readable-stream": "1.1.x", + "xregexp": "2.0.0" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-uri": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz", + "integrity": "sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==", + "requires": { + "@tootallnate/once": "1", + "data-uri-to-buffer": "3", + "debug": "4", + "file-uri-to-path": "2", + "fs-extra": "^8.1.0", + "ftp": "^0.3.10" + }, + "dependencies": { + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + } + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==" + }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsdom": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.0.0.tgz", + "integrity": "sha512-AIw+3ZakSUtDYvhwPwWHiZsUi3zHugpMEKlNPaurviseYoBqo0zBd3zqoUi3LPCNtPFlEP8FiW9MqCZdjb2IYA==", + "requires": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "math-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/math-sum/-/math-sum-2.0.0.tgz", + "integrity": "sha512-pt3L7X8npPNZzYCCOZSxgmRBc091gZ8aPAxqJwq4c7Zf2Gw8K+fjF6gWepZZEkY6fDadlTxYdltDuT/dq894Hw==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" + }, + "nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "pac-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-5.0.0.tgz", + "integrity": "sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ==", + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4", + "get-uri": "3", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "5", + "pac-resolver": "^5.0.0", + "raw-body": "^2.2.0", + "socks-proxy-agent": "5" + }, + "dependencies": { + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + } + } + }, + "pac-resolver": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-5.0.1.tgz", + "integrity": "sha512-cy7u00ko2KVgBAjuhevqpPeHIkCIqPe1v24cydhWjmeuzaBfmUWFCZJ1iAh5TuVzVZoUzXIW7K8sMYOZ84uZ9Q==", + "requires": { + "degenerator": "^3.0.2", + "ip": "^1.1.5", + "netmask": "^2.0.2" + } + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "requires": { + "entities": "^4.4.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" + }, + "proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-5.0.0.tgz", + "integrity": "sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g==", + "requires": { + "agent-base": "^6.0.0", + "debug": "4", + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "lru-cache": "^5.1.1", + "pac-proxy-agent": "^5.0.0", + "proxy-from-env": "^1.0.0", + "socks-proxy-agent": "^5.0.0" + }, + "dependencies": { + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "punycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.2.0.tgz", + "integrity": "sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==" + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "reggol": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/reggol/-/reggol-1.3.4.tgz", + "integrity": "sha512-E/sL4WovP0kR5EHQx3YF4ZDsI5D5baTROXaELCN1mOCB8tEOVOR3PHr807yVcGit2BfmXMSW5KyRaUAV5K8Vzw==", + "requires": { + "cosmokit": "^1.3.6", + "object-inspect": "^1.12.2", + "supports-color": "^8.1.1" + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "dependencies": { + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + } + } + }, + "socks-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz", + "integrity": "sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==", + "requires": { + "agent-base": "^6.0.2", + "debug": "4", + "socks": "^2.3.3" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "superagent": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.6.tgz", + "integrity": "sha512-HqSe6DSIh3hEn6cJvCkaM1BLi466f1LHi4yubR0tpewlMpk4RUFFy35bKz8SsPBwYfIIJy5eclp+3tCYAuX0bw==", + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.3", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + } + }, + "superagent-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-3.0.0.tgz", + "integrity": "sha512-wAlRInOeDFyd9pyonrkJspdRAxdLrcsZ6aSnS+8+nu4x1aXbz6FWSTT9M6Ibze+eG60szlL7JA8wEIV7bPWuyQ==", + "requires": { + "debug": "^4.3.2", + "proxy-agent": "^5.0.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" + } + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + } + }, + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "typescript": { + "version": "4.9.4", + "dev": true + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "vm2": { + "version": "3.9.13", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.13.tgz", + "integrity": "sha512-0rvxpB8P8Shm4wX2EKOiMp7H2zq+HUE/UwodY0pCZXs9IffIKZq6vUti5OgkVCTakKo9e/fgO4X1fkwfjWxE3Q==", + "requires": { + "acorn": "^8.7.0", + "acorn-walk": "^8.2.0" + } + }, + "w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "requires": { + "xml-name-validator": "^4.0.0" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "requires": { + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "ws": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", + "requires": {} + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==" + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "xregexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", + "integrity": "sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/remote_judger/package.json b/remote_judger/package.json new file mode 100644 index 0000000..efde41f --- /dev/null +++ b/remote_judger/package.json @@ -0,0 +1,32 @@ +{ + "name": "s2oj-remote-judger", + "version": "0.0.0", + "description": "Remote judger of S2OJ.", + "scripts": { + "build": "tsc -p .", + "start": "node dist/entrypoint.js" + }, + "type": "module", + "repository": "https://github.com/renbaoshuo/S2OJ", + "author": "Baoshuo ", + "license": "AGPL-3.0", + "private": true, + "dependencies": { + "fs-extra": "^11.1.0", + "jsdom": "^21.0.0", + "math-sum": "^2.0.0", + "reggol": "^1.3.4", + "superagent": "^8.0.6", + "superagent-proxy": "^3.0.0" + }, + "devDependencies": { + "@types/fs-extra": "^11.0.1", + "@types/js-yaml": "^4.0.5", + "@types/jsdom": "^20.0.1", + "@types/node": "^18.11.18", + "@types/superagent": "^4.1.16", + "@types/superagent-proxy": "^3.0.0", + "js-yaml": "^4.1.0", + "typescript": "^4.9.4" + } +} diff --git a/remote_judger/src/daemon.ts b/remote_judger/src/daemon.ts new file mode 100644 index 0000000..c38e01e --- /dev/null +++ b/remote_judger/src/daemon.ts @@ -0,0 +1,194 @@ +import fs from 'fs-extra'; +import superagent from 'superagent'; +import proxy from 'superagent-proxy'; +import Logger from './utils/logger'; +import sleep from './utils/sleep'; +import * as TIME from './utils/time'; +import htmlspecialchars from './utils/htmlspecialchars'; +import { apply } from './vjudge'; +import path from 'path'; +import child from 'child_process'; + +proxy(superagent); + +const logger = new Logger('daemon'); + +interface UOJConfig { + server_url: string; + judger_name: string; + password: string; +} + +interface UOJSubmission { + id: number; + problem_id: number; + problem_mtime: number; + content: any; + status: string; + judge_time: string; +} + +export default async function daemon(config: UOJConfig) { + const request = (url: string, data = {}) => + superagent + .post(`${config.server_url}/judge${url}`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send( + Object.entries({ + judger_name: config.judger_name, + password: config.password, + ...data, + }) + .map( + ([k, v]) => + `${k}=${encodeURIComponent( + typeof v === 'string' ? v : JSON.stringify(v) + )}` + ) + .join('&') + ); + const vjudge = await apply(request); + + while (true) { + try { + const { text, error } = await request('/submit'); + + if (error) { + logger.error('/submit', error.message); + + await sleep(3 * TIME.second); + } else if (text.startsWith('Nothing to judge')) { + await sleep(3 * TIME.second); + } else { + const data: UOJSubmission = JSON.parse(text); + const { id, content, judge_time } = data; + const config = Object.fromEntries(content.config); + const tmpdir = `/tmp/s2oj_rmj/${id}/`; + + if (config.test_sample_only === 'on') { + await request('/submit', { + submit: true, + fetch_new: false, + id, + result: JSON.stringify({ + status: 'Judged', + score: 100, + time: 0, + memory: 0, + details: 'Sample test is not available.', + }), + judge_time, + }); + + continue; + } + + fs.ensureDirSync(tmpdir); + + const reportError = async (error: string, details: string) => { + await request('/submit', { + submit: true, + fetch_new: false, + id, + result: JSON.stringify({ + status: 'Judged', + score: 0, + error, + details: `${htmlspecialchars(details)}`, + }), + judge_time, + }); + }; + + // Download source code + logger.debug('Downloading source code.', id); + const zipFilePath = path.resolve(tmpdir, 'all.zip'); + const res = request(`/download${content.file_name}`); + const stream = fs.createWriteStream(zipFilePath); + res.pipe(stream); + + try { + await new Promise((resolve, reject) => { + stream.on('finish', resolve); + stream.on('error', reject); + }); + } catch (e) { + await reportError( + 'Judgment Failed', + `Failed to download source code.` + ); + logger.error('Failed to download source code.', id, e.message); + + fs.removeSync(tmpdir); + + continue; + } + + // Unzip source code + logger.debug('Unzipping source code.', id); + const extractedPath = path.resolve(tmpdir, 'all'); + + try { + await new Promise((resolve, reject) => { + child.exec(`unzip ${zipFilePath} -d ${extractedPath}`, e => { + if (e) reject(e); + else resolve(true); + }); + }); + } catch (e) { + await reportError('Judgment Failed', `Failed to unzip source code.`); + logger.error('Failed to unzip source code.', id, e.message); + + fs.removeSync(tmpdir); + + continue; + } + + // Read source code + logger.debug('Reading source code.', id); + const sourceCodePath = path.resolve(extractedPath, 'answer.code'); + let code = ''; + + try { + code = fs.readFileSync(sourceCodePath, 'utf-8'); + } catch (e) { + await reportError('Judgment Failed', `Failed to read source code.`); + logger.error('Failed to read source code.', id, e.message); + + fs.removeSync(tmpdir); + + continue; + } + + // Start judging + logger.info('Start judging', id, `(problem ${data.problem_id})`); + try { + await vjudge.judge( + id, + config.remote_online_judge, + config.remote_problem_id, + config.answer_language, + code, + judge_time + ); + } catch (err) { + await reportError( + 'Judgment Failed', + 'No details, please contact admin!' + ); + logger.error('Judgment Failed.', id, err.message); + + fs.removeSync(tmpdir); + + continue; + } + + fs.removeSync(tmpdir); + } + } catch (err) { + logger.error(err.message); + + await sleep(3 * TIME.second); + } + } +} diff --git a/remote_judger/src/entrypoint.ts b/remote_judger/src/entrypoint.ts new file mode 100644 index 0000000..97bf1d6 --- /dev/null +++ b/remote_judger/src/entrypoint.ts @@ -0,0 +1,15 @@ +import daemon from './daemon'; + +const { + UOJ_PROTOCOL = 'http', + UOJ_HOST = 'uoj-web', + UOJ_JUDGER_NAME = 'remote_judger', + UOJ_JUDGER_PASSWORD = '', +} = process.env; +const UOJ_BASEURL = `${UOJ_PROTOCOL}://${UOJ_HOST}`; + +daemon({ + server_url: UOJ_BASEURL, + judger_name: UOJ_JUDGER_NAME, + password: UOJ_JUDGER_PASSWORD, +}); diff --git a/remote_judger/src/interface.ts b/remote_judger/src/interface.ts new file mode 100644 index 0000000..77af4c5 --- /dev/null +++ b/remote_judger/src/interface.ts @@ -0,0 +1,35 @@ +export interface RemoteAccount { + type: string; + cookie?: string[]; + handle: string; + password: string; + endpoint?: string; + proxy?: string; +} + +export type NextFunction = (body: Partial) => void; + +export interface IBasicProvider { + ensureLogin(): Promise; + submitProblem( + id: string, + lang: string, + code: string, + submissionId: number, + next: NextFunction, + end: NextFunction + ): Promise; + waitForSubmission( + problem_id: string, + id: string, + next: NextFunction, + end: NextFunction + ): Promise; +} + +export interface BasicProvider { + new (account: RemoteAccount): IBasicProvider; +} + +export const USER_AGENT = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36 S2OJ/3.1.0'; diff --git a/remote_judger/src/providers/atcoder.ts b/remote_judger/src/providers/atcoder.ts new file mode 100644 index 0000000..97d05ad --- /dev/null +++ b/remote_judger/src/providers/atcoder.ts @@ -0,0 +1,326 @@ +import { JSDOM } from 'jsdom'; +import superagent from 'superagent'; +import proxy from 'superagent-proxy'; +import sleep from '../utils/sleep'; +import { IBasicProvider, RemoteAccount, USER_AGENT } from '../interface'; +import Logger from '../utils/logger'; + +proxy(superagent); +const logger = new Logger('remote/atcoder'); + +const langs_map = { + C: { + name: 'C (GCC 9.2.1)', + id: 4001, + comment: '//', + }, + 'C++': { + name: 'C++ (GCC 9.2.1)', + id: 4003, + comment: '//', + }, + Pascal: { + name: 'Pascal (FPC 3.0.4)', + id: 4041, + comment: '//', + }, + Python3: { + name: 'Python (3.8.2)', + id: 4006, + comment: '#', + }, +}; + +export function getAccountInfoFromEnv(): RemoteAccount | null { + const { + ATCODER_HANDLE, + ATCODER_PASSWORD, + ATCODER_ENDPOINT = 'https://atcoder.jp', + ATCODER_PROXY, + } = process.env; + + if (!ATCODER_HANDLE || !ATCODER_PASSWORD) return null; + + const account: RemoteAccount = { + type: 'atcoder', + handle: ATCODER_HANDLE, + password: ATCODER_PASSWORD, + endpoint: ATCODER_ENDPOINT, + }; + + if (ATCODER_PROXY) account.proxy = ATCODER_PROXY; + + return account; +} + +function parseProblemId(id: string) { + let [, contestId, problemId] = /^(\w+)([a-z][1-9]?)$/.exec(id); + + if (contestId.endsWith('_')) { + problemId = `${contestId}${problemId}`; + } else { + problemId = `${contestId}_${problemId}`; + } + + contestId = contestId.replace(/_/g, ''); + + return [contestId, problemId]; +} + +export default class AtcoderProvider implements IBasicProvider { + constructor(public account: RemoteAccount) { + if (account.cookie) this.cookie = account.cookie; + this.account.endpoint ||= 'https://atcoder.jp'; + } + + cookie: string[] = ['language=en']; + csrf: string; + + get(url: string) { + logger.debug('get', url); + if (!url.includes('//')) url = `${this.account.endpoint}${url}`; + const req = superagent + .get(url) + .redirects(0) + .ok(res => res.status < 400) + .set('Cookie', this.cookie) + .set('User-Agent', USER_AGENT); + if (this.account.proxy) return req.proxy(this.account.proxy); + return req; + } + + post(url: string) { + logger.debug('post', url, this.cookie); + if (!url.includes('//')) url = `${this.account.endpoint}${url}`; + const req = superagent + .post(url) + .type('form') + .redirects(0) + .ok(res => res.status < 400) + .set('Cookie', this.cookie) + .set('User-Agent', USER_AGENT); + if (this.account.proxy) return req.proxy(this.account.proxy); + return req; + } + + getCookie(target: string) { + return this.cookie + .find(i => i.startsWith(`${target}=`)) + ?.split('=')[1] + ?.split(';')[0]; + } + + setCookie(target: string, value: string) { + this.cookie = this.cookie.filter(i => !i.startsWith(`${target}=`)); + this.cookie.push(`${target}=${value}`); + } + + async getCsrfToken(url: string) { + const { text: html, header } = await this.get(url); + const { + window: { document }, + } = new JSDOM(html); + + if (header['set-cookie']) { + this.cookie = header['set-cookie']; + } + + if (document.body.children.length < 2 && html.length < 512) { + throw new Error(document.body.textContent!); + } + + return document + .querySelector('input[name="csrf_token"]') + ?.getAttribute('value'); + } + + get loggedIn() { + return this.get('/login').then(res => { + const html = res.text; + + if (res.header['set-cookie']) { + this.cookie = res.header['set-cookie']; + } + + if (html.includes('Sign In')) return false; + return true; + }); + } + + async ensureLogin() { + if (await this.loggedIn) return true; + logger.info('retry normal login'); + const csrf = await this.getCsrfToken('/login'); + const res = await this.post('/login').send({ + csrf_token: csrf, + username: this.account.handle, + password: this.account.password, + }); + const cookie = res.header['set-cookie']; + if (cookie) { + this.cookie = cookie; + } + if (await this.loggedIn) { + logger.success('Logged in'); + return true; + } + return false; + } + + async submitProblem( + id: string, + lang: string, + code: string, + submissionId: number, + next, + end + ) { + const programType = langs_map[lang] || langs_map['C++']; + const comment = programType.comment; + + if (comment) { + const msg = `S2OJ Submission #${submissionId} @ ${new Date().getTime()}`; + if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`; + else if (comment instanceof Array) + code = `${comment[0]} ${msg} ${comment[1]}\n${code}`; + } + + const [contestId, problemId] = parseProblemId(id); + const csrf = await this.getCsrfToken( + `/contests/${contestId}/tasks/${problemId}` + ); + + logger.debug( + 'Submitting', + id, + programType, + lang, + `(S2OJ Submission #${submissionId})` + ); + + // TODO: check submit time to ensure submission + const res = await this.post(`/contests/${contestId}/submit`).send({ + csrf_token: csrf, + 'data.TaskScreenName': problemId, + 'data.LanguageId': programType.id, + sourceCode: code, + }); + + if (res.error) { + await end({ + error: true, + status: 'Judgment Failed', + message: 'Failed to submit code.', + }); + + return null; + } + + if (res.header['set-cookie']) { + this.cookie = res.header['set-cookie']; + } + + const { text: status, header: status_header } = await this.get( + `/contests/${contestId}/submissions/me` + ).retry(3); + + if (status_header['set-cookie']) { + this.cookie = status_header['set-cookie']; + } + + const { + window: { document }, + } = new JSDOM(status); + + return document + .querySelector('.submission-score[data-id]') + .getAttribute('data-id'); + } + + async waitForSubmission(problem_id: string, id: string, next, end) { + let i = 0; + + const [contestId] = parseProblemId(problem_id); + const status_url = `/contests/${contestId}/submissions/me/status/json?reload=true&sids[]=${id}`; + + while (true) { + if (++i > 60) { + return await end({ + id, + error: true, + status: 'Judgment Failed', + message: 'Failed to fetch submission details.', + }); + } + + await sleep(2000); + const { body, error, header } = await this.get(status_url).retry(3); + + if (header['set-cookie']) { + this.cookie = header['set-cookie']; + } + + if (error) continue; + + const result = body.Result[id]; + const { + window: { document }, + } = new JSDOM(`${result.Html}
`); + + const elements = document.querySelectorAll('td'); + const statusTd = elements[0]; + const statusElem = statusTd.querySelector('span'); + + if ( + statusElem.title === 'Waiting for Judging' || + statusElem.title === 'Waiting for Re-judging' || + ['WJ', 'WR'].includes(statusElem.innerHTML.trim()) + ) { + await next({ test_id: 0 }); + + continue; + } + + if ( + statusElem.title === 'Judging' || + (statusTd.colSpan == 3 && statusTd.className.includes('waiting-judge')) + ) { + await next({ test_id: /(\d+)/.exec(statusElem.innerHTML)[1] || 0 }); + + continue; + } + + if (statusElem.title === 'Compilation Error') { + return await end({ + id, + error: true, + status: 'Compile Error', + message: '', + }); + } + + if (statusElem.title === 'Internal Error') { + return await end({ + error: true, + status: 'Judgment Failed', + message: 'AtCoder Internal Error.', + }); + } + + const time = parseInt(elements[1].innerHTML.trim()); + const memory = parseInt(elements[2].innerHTML.trim()); + + return await end({ + id, + status: statusElem.title || 'None', + score: + statusElem.title === 'Accepted' || + statusElem.innerHTML.trim() === 'AC' + ? 100 + : 0, + time, + memory, + }); + } + } +} diff --git a/remote_judger/src/providers/codeforces.ts b/remote_judger/src/providers/codeforces.ts new file mode 100644 index 0000000..c50b4a6 --- /dev/null +++ b/remote_judger/src/providers/codeforces.ts @@ -0,0 +1,347 @@ +import { JSDOM } from 'jsdom'; +import superagent from 'superagent'; +import proxy from 'superagent-proxy'; +import sleep from '../utils/sleep'; +import mathSum from 'math-sum'; +import { IBasicProvider, RemoteAccount, USER_AGENT } from '../interface'; +import { normalize, VERDICT } from '../verdict'; +import Logger from '../utils/logger'; + +proxy(superagent); +const logger = new Logger('remote/codeforces'); + +const langs_map = { + C: { + name: 'GNU GCC C11 5.1.0', + id: 43, + comment: '//', + }, + 'C++': { + name: 'GNU G++14 6.4.0', + id: 50, + comment: '//', + }, + 'C++17': { + name: 'GNU G++17 7.3.0', + id: 54, + comment: '//', + }, + 'C++20': { + name: 'GNU G++20 11.2.0 (64 bit, winlibs)', + id: 73, + comment: '//', + }, + Pascal: { + name: 'Free Pascal 3.0.2', + id: 4, + comment: '//', + }, + 'Python2.7': { + name: 'Python 2.7.18', + id: 7, + comment: '#', + }, + Python3: { + name: 'Python 3.9.1', + id: 31, + comment: '#', + }, +}; + +export function getAccountInfoFromEnv(): RemoteAccount | null { + const { + CODEFORCES_HANDLE, + CODEFORCES_PASSWORD, + CODEFORCES_ENDPOINT = 'https://codeforces.com', + CODEFORCES_PROXY, + } = process.env; + + if (!CODEFORCES_HANDLE || !CODEFORCES_PASSWORD) return null; + + const account: RemoteAccount = { + type: 'codeforces', + handle: CODEFORCES_HANDLE, + password: CODEFORCES_PASSWORD, + endpoint: CODEFORCES_ENDPOINT, + }; + + if (CODEFORCES_PROXY) account.proxy = CODEFORCES_PROXY; + + return account; +} + +function parseProblemId(id: string) { + const [, type, contestId, problemId] = id.startsWith('921') + ? ['', '921', '01'] + : /^(|GYM)(\d+)([A-Z]+[0-9]*)$/.exec(id); + if (type === 'GYM' && +contestId < 100000) { + return [type, (+contestId + 100000).toString(), problemId]; + } + return [type, contestId, problemId]; +} + +export default class CodeforcesProvider implements IBasicProvider { + constructor(public account: RemoteAccount) { + if (account.cookie) this.cookie = account.cookie; + this.account.endpoint ||= 'https://codeforces.com'; + } + + cookie: string[] = []; + csrf: string; + + get(url: string) { + logger.debug('get', url); + if (!url.includes('//')) url = `${this.account.endpoint}${url}`; + const req = superagent + .get(url) + .set('Cookie', this.cookie) + .set('User-Agent', USER_AGENT); + if (this.account.proxy) return req.proxy(this.account.proxy); + return req; + } + + post(url: string) { + logger.debug('post', url, this.cookie); + if (!url.includes('//')) url = `${this.account.endpoint}${url}`; + const req = superagent + .post(url) + .type('form') + .set('Cookie', this.cookie) + .set('User-Agent', USER_AGENT); + if (this.account.proxy) return req.proxy(this.account.proxy); + return req; + } + + getCookie(target: string) { + return this.cookie + .find(i => i.startsWith(`${target}=`)) + ?.split('=')[1] + ?.split(';')[0]; + } + + setCookie(target: string, value: string) { + this.cookie = this.cookie.filter(i => !i.startsWith(`${target}=`)); + this.cookie.push(`${target}=${value}`); + } + + tta(_39ce7: string) { + let _tta = 0; + for (let c = 0; c < _39ce7.length; c++) { + _tta = (_tta + (c + 1) * (c + 2) * _39ce7.charCodeAt(c)) % 1009; + if (c % 3 === 0) _tta++; + if (c % 2 === 0) _tta *= 2; + if (c > 0) + _tta -= + Math.floor(_39ce7.charCodeAt(Math.floor(c / 2)) / 2) * (_tta % 5); + _tta = ((_tta % 1009) + 1009) % 1009; + } + return _tta; + } + + async getCsrfToken(url: string) { + const { text: html } = await this.get(url); + const { + window: { document }, + } = new JSDOM(html); + if (document.body.children.length < 2 && html.length < 512) { + throw new Error(document.body.textContent!); + } + const ftaa = this.getCookie('70a7c28f3de') || 'n/a'; + const bfaa = this.getCookie('raa') || this.getCookie('bfaa') || 'n/a'; + return [ + ( + document.querySelector('meta[name="X-Csrf-Token"]') || + document.querySelector('input[name="csrf_token"]') + )?.getAttribute('content'), + ftaa, + bfaa, + ]; + } + + get loggedIn() { + return this.get('/enter').then(res => { + const html = res.text; + if (html.includes('Login into Codeforces')) return false; + if (html.length < 1000 && html.includes('Redirecting...')) { + logger.debug('Got a redirect', html); + return false; + } + return true; + }); + } + + async ensureLogin() { + if (await this.loggedIn) return true; + logger.info('retry normal login'); + const [csrf, ftaa, bfaa] = await this.getCsrfToken('/enter'); + const { header } = await this.get('/enter'); + if (header['set-cookie']) { + this.cookie = header['set-cookie']; + } + const res = await this.post('/enter').send({ + csrf_token: csrf, + action: 'enter', + ftaa, + bfaa, + handleOrEmail: this.account.handle, + password: this.account.password, + remember: 'on', + _tta: this.tta(this.getCookie('39ce7')), + }); + const cookie = res.header['set-cookie']; + if (cookie) { + this.cookie = cookie; + } + if (await this.loggedIn) { + logger.success('Logged in'); + return true; + } + return false; + } + + async submitProblem( + id: string, + lang: string, + code: string, + submissionId: number, + next, + end + ) { + const programType = langs_map[lang] || langs_map['C++']; + const comment = programType.comment; + if (comment) { + const msg = `S2OJ Submission #${submissionId} @ ${new Date().getTime()}`; + if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`; + else if (comment instanceof Array) + code = `${comment[0]} ${msg} ${comment[1]}\n${code}`; + } + const [type, contestId, problemId] = parseProblemId(id); + const [csrf, ftaa, bfaa] = await this.getCsrfToken( + type !== 'GYM' ? '/problemset/submit' : `/gym/${contestId}/submit` + ); + logger.debug( + 'Submitting', + id, + programType, + lang, + `(S2OJ Submission #${submissionId})` + ); + // TODO: check submit time to ensure submission + const { text: submit, error } = await this.post( + `/${ + type !== 'GYM' ? 'problemset' : `gym/${contestId}` + }/submit?csrf_token=${csrf}` + ).send({ + csrf_token: csrf, + action: 'submitSolutionFormSubmitted', + programTypeId: programType.id, + source: code, + tabsize: 4, + sourceFile: '', + ftaa, + bfaa, + _tta: this.tta(this.getCookie('39ce7')), + ...(type !== 'GYM' + ? { + submittedProblemCode: contestId + problemId, + sourceCodeConfirmed: true, + } + : { + submittedProblemIndex: problemId, + }), + }); + + if (error) { + end({ + error: true, + status: 'Judgment Failed', + message: 'Failed to submit code.', + }); + + return null; + } + + const { + window: { document: statusDocument }, + } = new JSDOM(submit); + const message = Array.from(statusDocument.querySelectorAll('.error')) + .map(i => i.textContent) + .join('') + .replace(/ /g, ' ') + .trim(); + + if (message) { + end({ error: true, status: 'Compile Error', message }); + return null; + } + + const { text: status } = await this.get( + type !== 'GYM' ? '/problemset/status?my=on' : `/gym/${contestId}/my` + ).retry(3); + const { + window: { document }, + } = new JSDOM(status); + this.csrf = document + .querySelector('meta[name="X-Csrf-Token"]') + .getAttribute('content'); + return document + .querySelector('[data-submission-id]') + .getAttribute('data-submission-id'); + } + + async waitForSubmission(problem_id: string, id: string, next, end) { + let i = 0; + + while (true) { + if (++i > 60) { + return await end({ + id, + error: true, + status: 'Judgment Failed', + message: 'Failed to fetch submission details.', + }); + } + + await sleep(3000); + const { body, error } = await this.post('/data/submitSource') + .send({ + csrf_token: this.csrf, + submissionId: id, + }) + .retry(3); + if (error) continue; + if (body.compilationError === 'true') { + return await end({ + id, + error: 1, + status: 'Compile Error', + message: body['checkerStdoutAndStderr#1'], + }); + } + const time = mathSum( + Object.keys(body) + .filter(k => k.startsWith('timeConsumed#')) + .map(k => +body[k]) + ); + const memory = + Math.max( + ...Object.keys(body) + .filter(k => k.startsWith('memoryConsumed#')) + .map(k => +body[k]) + ) / 1024; + await next({ test_id: body.testCount }); + if (body.waiting === 'true') continue; + const status = + VERDICT[ + Object.keys(VERDICT).find(k => normalize(body.verdict).includes(k)) + ]; + return await end({ + id, + status, + score: status === 'Accepted' ? 100 : 0, + time, + memory, + }); + } + } +} diff --git a/remote_judger/src/providers/loj.ts b/remote_judger/src/providers/loj.ts new file mode 100644 index 0000000..9b3ba20 --- /dev/null +++ b/remote_judger/src/providers/loj.ts @@ -0,0 +1,308 @@ +import superagent from 'superagent'; +import proxy from 'superagent-proxy'; +import sleep from '../utils/sleep'; +import { IBasicProvider, RemoteAccount, USER_AGENT } from '../interface'; +import Logger from '../utils/logger'; + +proxy(superagent); +const logger = new Logger('remote/loj'); + +const langs_map = { + C: { + name: 'C (gcc, c11, O2, m64)', + info: { + language: 'c', + compileAndRunOptions: { + compiler: 'gcc', + O: '2', + m: '64', + std: 'c11', + }, + }, + comment: '//', + }, + 'C++03': { + name: 'C++ (g++, c++03, O2, m64)', + info: { + language: 'cpp', + compileAndRunOptions: { + compiler: 'g++', + std: 'c++03', + O: '2', + m: '64', + }, + }, + comment: '//', + }, + 'C++11': { + name: 'C++ (g++, c++11, O2, m64)', + info: { + language: 'cpp', + compileAndRunOptions: { + compiler: 'g++', + std: 'c++11', + O: '2', + m: '64', + }, + }, + comment: '//', + }, + 'C++': { + name: 'C++ (g++, c++14, O2, m64)', + info: { + language: 'cpp', + compileAndRunOptions: { + compiler: 'g++', + std: 'c++14', + O: '2', + m: '64', + }, + }, + comment: '//', + }, + 'C++17': { + name: 'C++ (g++, c++17, O2, m64)', + info: { + language: 'cpp', + compileAndRunOptions: { + compiler: 'g++', + std: 'c++17', + O: '2', + m: '64', + }, + }, + comment: '//', + }, + 'C++20': { + name: 'C++ (g++, c++20, O2, m64)', + info: { + language: 'cpp', + compileAndRunOptions: { + compiler: 'g++', + std: 'c++20', + O: '2', + m: '64', + }, + }, + comment: '//', + }, + 'Python2.7': { + name: 'Python (2.7)', + info: { + language: 'python', + compileAndRunOptions: { + version: '2.7', + }, + }, + comment: '#', + }, + Python3: { + name: 'Python (3.10)', + info: { + language: 'python', + compileAndRunOptions: { + version: '3.10', + }, + }, + comment: '#', + }, + Java17: { + name: 'Java', + info: { + language: 'java', + compileAndRunOptions: {}, + }, + comment: '//', + }, + Pascal: { + name: 'Pascal', + info: { + language: 'pascal', + compileAndRunOptions: { + O: '2', + }, + }, + comment: '//', + }, +}; + +export function getAccountInfoFromEnv(): RemoteAccount | null { + const { + LOJ_HANDLE, + LOJ_TOKEN, + LOJ_ENDPOINT = 'https://api.loj.ac.cn/api', + LOJ_PROXY, + } = process.env; + + if (!LOJ_TOKEN) return null; + + const account: RemoteAccount = { + type: 'loj', + handle: LOJ_HANDLE, + password: LOJ_TOKEN, + endpoint: LOJ_ENDPOINT, + }; + + if (LOJ_PROXY) account.proxy = LOJ_PROXY; + + return account; +} + +export default class LibreojProvider implements IBasicProvider { + constructor(public account: RemoteAccount) { + this.account.endpoint ||= 'https://api.loj.ac.cn/api'; + } + + get(url: string) { + logger.debug('get', url); + if (!url.includes('//')) url = `${this.account.endpoint}${url}`; + const req = superagent + .get(url) + .auth(this.account.password, { type: 'bearer' }) + .set('User-Agent', USER_AGENT); + if (this.account.proxy) return req.proxy(this.account.proxy); + return req; + } + + post(url: string) { + logger.debug('post', url); + if (!url.includes('//')) url = `${this.account.endpoint}${url}`; + const req = superagent + .post(url) + .type('json') + .auth(this.account.password, { type: 'bearer' }) + .set('User-Agent', USER_AGENT) + .set('x-recaptcha-token', 'skip'); + if (this.account.proxy) return req.proxy(this.account.proxy); + return req; + } + + get loggedIn() { + return this.get('/auth/getSessionInfo?token=' + this.account.password).then( + res => + res.body.userMeta && res.body.userMeta.username === this.account.handle + ); + } + + async ensureLogin() { + if (await this.loggedIn) return true; + logger.info('retry login'); + // TODO: login + return false; + } + + async getProblemId(displayId: number) { + const { body } = await this.post('/problem/getProblem').send({ displayId }); + + return body.meta.id; + } + + async submitProblem( + displayId: string, + lang: string, + code: string, + submissionId: number, + next, + end + ) { + const programType = langs_map[lang] || langs_map['C++']; + const comment = programType.comment; + + if (comment) { + const msg = `S2OJ Submission #${submissionId} @ ${new Date().getTime()}`; + if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`; + else if (comment instanceof Array) + code = `${comment[0]} ${msg} ${comment[1]}\n${code}`; + } + + const id = await this.getProblemId(parseInt(displayId)); + + logger.debug( + 'Submitting', + id, + `(displayId: ${displayId})`, + programType, + lang, + `(S2OJ Submission #${submissionId})` + ); + + const { body, error } = await this.post('/submission/submit').send({ + problemId: id, + content: { + code, + ...programType.info, + }, + uploadInfo: null, + }); + + if (error) { + await end({ + error: true, + status: 'Judgment Failed', + message: 'Failed to submit code.', + }); + + return null; + } + + return body.submissionId; + } + + async waitForSubmission(problem_id: string, id: string, next, end) { + let i = 0; + + while (true) { + if (++i > 60) { + return await end({ + id, + error: true, + status: 'Judgment Failed', + message: 'Failed to fetch submission details.', + }); + } + + await sleep(2000); + const { body, error } = await this.post('/submission/getSubmissionDetail') + .send({ submissionId: String(id), locale: 'zh_CN' }) + .retry(3); + + if (error) continue; + + if (body.progress.progressType !== 'Finished') { + await next({ + status: `${body.progress.progressType}: ${body.progress.status}`, + }); + + continue; + } + + if (body.meta.status === 'CompilationError') { + await end({ + error: true, + id, + status: 'Compile Error', + }); + } + + if ( + ['SystemError', 'JudgementFailed', 'ConfigurationError'].includes( + body.meta.status + ) + ) { + await end({ + error: true, + id, + status: 'Judgment Failed', + }); + } + + return await end({ + id, + status: body.meta.status, + score: body.meta.score, + time: body.meta.timeUsed, + memory: body.meta.memoryUsed, + }); + } + } +} diff --git a/remote_judger/src/providers/uoj.ts b/remote_judger/src/providers/uoj.ts new file mode 100644 index 0000000..9268df9 --- /dev/null +++ b/remote_judger/src/providers/uoj.ts @@ -0,0 +1,266 @@ +import { JSDOM } from 'jsdom'; +import superagent from 'superagent'; +import proxy from 'superagent-proxy'; +import Logger from '../utils/logger'; +import { IBasicProvider, RemoteAccount, USER_AGENT } from '../interface'; +import { parseTimeMS, parseMemoryMB } from '../utils/parse'; +import sleep from '../utils/sleep'; + +proxy(superagent); +const logger = new Logger('remote/uoj'); + +const langs_map = { + C: { + name: 'C', + id: 'C', + comment: '//', + }, + 'C++03': { + name: 'C++ 03', + id: 'C++', + comment: '//', + }, + 'C++11': { + name: 'C++ 11', + id: 'C++11', + comment: '//', + }, + 'C++': { + name: 'C++ 14', + id: 'C++14', + comment: '//', + }, + 'C++17': { + name: 'C++ 17', + id: 'C++17', + comment: '//', + }, + 'C++20': { + name: 'C++ 20', + id: 'C++20', + comment: '//', + }, + 'Python2.7': { + name: 'Python 2.7', + id: 'Python2.7', + comment: '#', + }, + Python3: { + name: 'Python 3', + id: 'Python3', + comment: '#', + }, + Java8: { + name: 'Java 8', + id: 'Java8', + comment: '//', + }, + Java11: { + name: 'Java 11', + id: 'Java11', + comment: '//', + }, + Java17: { + name: 'Java 17', + id: 'Java17', + comment: '//', + }, + Pascal: { + name: 'Pascal', + id: 'Pascal', + comment: '//', + }, +}; + +export function getAccountInfoFromEnv(): RemoteAccount | null { + const { + UOJ_HANDLE, + UOJ_PASSWORD, + UOJ_ENDPOINT = 'https://uoj.ac', + UOJ_PROXY, + } = process.env; + + if (!UOJ_HANDLE || !UOJ_PASSWORD) return null; + + const account: RemoteAccount = { + type: 'uoj', + handle: UOJ_HANDLE, + password: UOJ_PASSWORD, + endpoint: UOJ_ENDPOINT, + }; + + if (UOJ_PROXY) account.proxy = UOJ_PROXY; + + return account; +} + +export default class UOJProvider implements IBasicProvider { + constructor(public account: RemoteAccount) { + if (account.cookie) this.cookie = account.cookie; + } + + cookie: string[] = []; + csrf: string; + + get(url: string) { + logger.debug('get', url); + if (!url.includes('//')) + url = `${this.account.endpoint || 'https://uoj.ac'}${url}`; + const req = superagent + .get(url) + .set('Cookie', this.cookie) + .set('User-Agent', USER_AGENT); + if (this.account.proxy) return req.proxy(this.account.proxy); + return req; + } + + post(url: string) { + logger.debug('post', url, this.cookie); + if (!url.includes('//')) + url = `${this.account.endpoint || 'https://uoj.ac'}${url}`; + const req = superagent + .post(url) + .set('Cookie', this.cookie) + .set('User-Agent', USER_AGENT) + .type('form'); + if (this.account.proxy) return req.proxy(this.account.proxy); + return req; + } + + async getCsrfToken(url: string) { + const { text: html, header } = await this.get(url); + if (header['set-cookie']) { + this.cookie = header['set-cookie']; + } + let value = /_token *: *"(.+?)"/g.exec(html); + if (value) return value[1]; + value = /_token" value="(.+?)"/g.exec(html); + return value?.[1]; + } + + get loggedIn() { + return this.get('/login').then( + ({ text: html }) => !html.includes('登录') + ); + } + + async ensureLogin() { + if (await this.loggedIn) return true; + logger.info('retry login'); + const _token = await this.getCsrfToken('/login'); + const { header, text } = await this.post('/login').send({ + _token, + login: '', + username: this.account.handle, + // NOTE: you should pass a pre-hashed key! + password: this.account.password, + }); + if (header['set-cookie'] && this.cookie.length === 1) { + header['set-cookie'].push(...this.cookie); + this.cookie = header['set-cookie']; + } + if (text === 'ok') return true; + return text; + } + + async submitProblem( + id: string, + lang: string, + code: string, + submissionId: number, + next, + end + ) { + const programType = langs_map[lang] || langs_map['C++']; + const comment = programType.comment; + + if (comment) { + const msg = `S2OJ Submission #${submissionId} @ ${new Date().getTime()}`; + if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`; + else if (comment instanceof Array) + code = `${comment[0]} ${msg} ${comment[1]}\n${code}`; + } + + const _token = await this.getCsrfToken(`/problem/${id}`); + const { text } = await this.post(`/problem/${id}`).send({ + _token, + answer_answer_language: programType.id, + answer_answer_upload_type: 'editor', + answer_answer_editor: code, + 'submit-answer': 'answer', + }); + + if (!text.includes('我的提交记录')) throw new Error('Submit failed'); + + const { text: status } = await this.get( + `/submissions?problem_id=${id}&submitter=${this.account.handle}` + ); + + const $dom = new JSDOM(status); + return $dom.window.document + .querySelector('tbody>tr>td>a') + .innerHTML.split('#')[1]; + } + + async waitForSubmission(problem_id: string, id: string, next, end) { + let i = 0; + + while (true) { + if (++i > 60) { + return await end({ + id, + error: true, + status: 'Judgment Failed', + message: 'Failed to fetch submission details.', + }); + } + + await sleep(2000); + const { text } = await this.get(`/submission/${id}`); + const { + window: { document }, + } = new JSDOM(text); + const find = (content: string) => + Array.from( + document.querySelectorAll('.panel-heading>.panel-title') + ).find(n => n.innerHTML === content).parentElement.parentElement + .children[1]; + if (text.includes('Compile Error')) { + return await end({ + error: true, + id, + status: 'Compile Error', + message: find('详细').children[0].innerHTML, + }); + } + + await next({}); + + const summary = document.querySelector('tbody>tr'); + if (!summary) continue; + const time = parseTimeMS(summary.children[4].innerHTML); + const memory = parseMemoryMB(summary.children[5].innerHTML) * 1024; + let panel = document.getElementById( + 'details_details_accordion_collapse_subtask_1' + ); + if (!panel) { + panel = document.getElementById('details_details_accordion'); + if (!panel) continue; + } + + if (document.querySelector('tbody').innerHTML.includes('Judging')) + continue; + + const score = +summary.children[3]?.children[0]?.innerHTML || 0; + const status = score === 100 ? 'Accepted' : 'Unaccepted'; + + return await end({ + id, + status, + score, + time, + memory, + }); + } + } +} diff --git a/remote_judger/src/proxy.ts b/remote_judger/src/proxy.ts new file mode 100644 index 0000000..aeb9f60 --- /dev/null +++ b/remote_judger/src/proxy.ts @@ -0,0 +1,7 @@ +declare module 'superagent' { + interface Request { + proxy(url: string): this; + } +} + +export default {}; diff --git a/remote_judger/src/utils/htmlspecialchars.ts b/remote_judger/src/utils/htmlspecialchars.ts new file mode 100644 index 0000000..d300a3e --- /dev/null +++ b/remote_judger/src/utils/htmlspecialchars.ts @@ -0,0 +1,11 @@ +export default function htmlspecialchars(text: string) { + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + + return text.replace(/[&<>"']/g, m => map[m]); +} diff --git a/remote_judger/src/utils/logger.ts b/remote_judger/src/utils/logger.ts new file mode 100644 index 0000000..a72027f --- /dev/null +++ b/remote_judger/src/utils/logger.ts @@ -0,0 +1,11 @@ +import Logger from 'reggol'; + +Logger.levels.base = process.env.DEV ? 3 : 2; +Logger.targets[0].showTime = 'dd hh:mm:ss'; +Logger.targets[0].label = { + align: 'right', + width: 9, + margin: 1, +}; + +export default Logger; diff --git a/remote_judger/src/utils/parse.ts b/remote_judger/src/utils/parse.ts new file mode 100644 index 0000000..e79e3c9 --- /dev/null +++ b/remote_judger/src/utils/parse.ts @@ -0,0 +1,20 @@ +const TIME_RE = /^([0-9]+(?:\.[0-9]*)?)([mu]?)s?$/i; +const TIME_UNITS = { '': 1000, m: 1, u: 0.001 }; +const MEMORY_RE = /^([0-9]+(?:\.[0-9]*)?)([kmg])b?$/i; +const MEMORY_UNITS = { k: 1 / 1024, m: 1, g: 1024 }; + +export function parseTimeMS(str: string | number, throwOnError = true) { + if (typeof str === 'number' || Number.isSafeInteger(+str)) return +str; + const match = TIME_RE.exec(str); + if (!match && throwOnError) throw new Error(`${str} error parsing time`); + if (!match) return 1000; + return Math.floor(parseFloat(match[1]) * TIME_UNITS[match[2].toLowerCase()]); +} + +export function parseMemoryMB(str: string | number, throwOnError = true) { + if (typeof str === 'number' || Number.isSafeInteger(+str)) return +str; + const match = MEMORY_RE.exec(str); + if (!match && throwOnError) throw new Error(`${str} error parsing memory`); + if (!match) return 256; + return Math.ceil(parseFloat(match[1]) * MEMORY_UNITS[match[2].toLowerCase()]); +} diff --git a/remote_judger/src/utils/sleep.ts b/remote_judger/src/utils/sleep.ts new file mode 100644 index 0000000..96d32bb --- /dev/null +++ b/remote_judger/src/utils/sleep.ts @@ -0,0 +1,5 @@ +export default function sleep(timeout: number) { + return new Promise(resolve => { + setTimeout(() => resolve(true), timeout); + }); +} diff --git a/remote_judger/src/utils/time.ts b/remote_judger/src/utils/time.ts new file mode 100644 index 0000000..2a4fcd7 --- /dev/null +++ b/remote_judger/src/utils/time.ts @@ -0,0 +1,5 @@ +export const second = 1000; +export const minute = second * 60; +export const hour = minute * 60; +export const day = hour * 24; +export const week = day * 7; diff --git a/remote_judger/src/verdict.ts b/remote_judger/src/verdict.ts new file mode 100644 index 0000000..90527fc --- /dev/null +++ b/remote_judger/src/verdict.ts @@ -0,0 +1,32 @@ +export function normalize(key: string) { + return key.toUpperCase().replace(/ /g, '_'); +} + +export const VERDICT = new Proxy<Record<string, string>>( + { + RUNTIME_ERROR: 'Runtime Error', + WRONG_ANSWER: 'Wrong Answer', + OK: 'Accepted', + COMPILING: 'Compiling', + TIME_LIMIT_EXCEEDED: 'Time Limit Exceeded', + MEMORY_LIMIT_EXCEEDED: 'Memory Limit Exceeded', + IDLENESS_LIMIT_EXCEEDED: 'Idleness Limit Exceeded', + ACCEPTED: 'Accepted', + PRESENTATION_ERROR: 'Wrong Answer', + OUTPUT_LIMIT_EXCEEDED: 'Output Limit Exceeded', + EXTRA_TEST_PASSED: 'Accepted', + COMPILE_ERROR: 'Compile Error', + 'RUNNING_&_JUDGING': 'Judging', + + // Codeforces + 'HAPPY_NEW_YEAR!': 'Accepted', + }, + { + get(self, key) { + if (typeof key === 'symbol') return null; + key = normalize(key); + if (self[key]) return self[key]; + return null; + }, + } +); diff --git a/remote_judger/src/vjudge.ts b/remote_judger/src/vjudge.ts new file mode 100644 index 0000000..387053c --- /dev/null +++ b/remote_judger/src/vjudge.ts @@ -0,0 +1,160 @@ +import type { BasicProvider, IBasicProvider, RemoteAccount } from './interface'; +import * as Time from './utils/time'; +import Logger from './utils/logger'; +import htmlspecialchars from './utils/htmlspecialchars'; + +const logger = new Logger('vjudge'); + +class AccountService { + api: IBasicProvider; + + constructor( + public Provider: BasicProvider, + public account: RemoteAccount, + private request: any + ) { + this.api = new Provider(account); + this.main().catch(e => + logger.error(`Error occured in ${account.type}/${account.handle}`, e) + ); + } + + async judge( + id: number, + problem_id: string, + language: string, + code: string, + judge_time: string + ) { + const next = async payload => { + return await this.request('/submit', { + 'update-status': true, + fetch_new: false, + id, + status: + payload.status || + (payload.test_id ? `Judging Test #${payload.test_id}` : 'Judging'), + }); + }; + + const end = async payload => { + if (payload.error) { + return await this.request('/submit', { + submit: true, + fetch_new: false, + id, + result: JSON.stringify({ + status: 'Judged', + score: 0, + error: payload.status, + details: + '<div>' + + `<info-block>ID = ${payload.id || 'None'}</info-block>` + + `<error>${htmlspecialchars(payload.message)}</error>` + + '</div>', + }), + judge_time, + }); + } + + return await this.request('/submit', { + submit: true, + fetch_new: false, + id, + result: JSON.stringify({ + status: 'Judged', + score: payload.score, + time: payload.time, + memory: payload.memory, + details: + '<div>' + + `<info-block>ID = ${payload.id || 'None'}</info-block>` + + `<info-block>VERDICT = ${payload.status}</info-block>` + + '</div>', + }), + judge_time, + }); + }; + + try { + const rid = await this.api.submitProblem( + problem_id, + language, + code, + id, + next, + end + ); + + if (!rid) return; + + await this.api.waitForSubmission(problem_id, rid, next, end); + } catch (e) { + logger.error(e); + await end({ error: true, message: e.message }); + } + } + + async login() { + const login = await this.api.ensureLogin(); + if (login === true) { + logger.info(`${this.account.type}/${this.account.handle}: logged in`); + return true; + } + logger.warn( + `${this.account.type}/${this.account.handle}: login fail`, + login || '' + ); + return false; + } + + async main() { + const res = await this.login(); + if (!res) return; + setInterval(() => this.login(), Time.hour); + } +} + +class VJudge { + private providers: Record<string, AccountService> = {}; + + constructor(private request: any) {} + + async addProvider(type: string) { + if (this.providers[type]) throw new Error(`duplicate provider ${type}`); + const provider = await import(`./providers/${type}`); + const account = provider.getAccountInfoFromEnv(); + + if (!account) throw new Error(`no account info for ${type}`); + + this.providers[type] = new AccountService( + provider.default, + account, + this.request + ); + } + + async judge( + id: number, + type: string, + problem_id: string, + language: string, + code: string, + judge_time: string + ) { + if (!this.providers[type]) throw new Error(`no provider ${type}`); + + this.providers[type].judge(id, problem_id, language, code, judge_time); + } +} + +export async function apply(request: any) { + const vjudge = new VJudge(request); + + await vjudge.addProvider('codeforces'); + await vjudge.addProvider('atcoder'); + await vjudge.addProvider('uoj'); + await vjudge.addProvider('loj'); + + return vjudge; +} diff --git a/remote_judger/tsconfig.json b/remote_judger/tsconfig.json new file mode 100644 index 0000000..48b6c8c --- /dev/null +++ b/remote_judger/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["esnext"], + "allowJs": true, + "skipLibCheck": true, + // "strict": true, + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "incremental": true, + "baseUrl": ".", + "outDir": "./dist" + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/web/Dockerfile b/web/Dockerfile index 94d5602..e9f118a 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -6,7 +6,7 @@ ENV USE_MIRROR $USE_MIRROR SHELL ["/bin/bash", "-c"] ENV DEBIAN_FRONTEND=noninteractive -ENV PKGS="php7.4 php7.4-yaml php7.4-xml php7.4-dev php7.4-zip php7.4-mysql php7.4-mbstring php7.4-gd php7.4-imagick libseccomp-dev git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile php-pear mysql-client build-essential fp-compiler re2c libseccomp-dev libyaml-dev python2.7 python3.10 python3-requests openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk" +ENV PKGS="php7.4 php7.4-yaml php7.4-xml php7.4-dev php7.4-zip php7.4-mysql php7.4-mbstring php7.4-gd php7.4-curl php7.4-imagick libseccomp-dev git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile php-pear mysql-client build-essential fp-compiler re2c libseccomp-dev libyaml-dev python2.7 python3.10 python3-requests openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk" RUN if [[ "$USE_MIRROR" == "1" ]]; then\ sed -i "s@http://.*archive.ubuntu.com@https://mirrors.aliyun.com@g" /etc/apt/sources.list &&\ sed -i "s@http://.*security.ubuntu.com@https://mirrors.aliyun.com@g" /etc/apt/sources.list ;\ diff --git a/web/app/composer.json b/web/app/composer.json index 8ef8663..7ebf2b0 100644 --- a/web/app/composer.json +++ b/web/app/composer.json @@ -3,7 +3,10 @@ "gregwar/captcha": "^1.1", "phpmailer/phpmailer": "^6.6", "ezyang/htmlpurifier": "^4.16", - "erusev/parsedown": "^1.7" + "erusev/parsedown": "^1.7", + "php-curl-class/php-curl-class": "^2.0", + "ext-dom": "20031129", + "ivopetkov/html5-dom-document-php": "2.*" }, "autoload": { "classmap": [ diff --git a/web/app/controllers/judge/submit.php b/web/app/controllers/judge/submit.php index d6c7913..59e25c0 100644 --- a/web/app/controllers/judge/submit.php +++ b/web/app/controllers/judge/submit.php @@ -130,17 +130,39 @@ if (isset($_POST['update-status'])) { die(); } -$problem_ban_list = DB::selectAll([ +$assignCond = []; + +$problem_ban_list = array_map(fn ($x) => $x['id'], DB::selectAll([ "select id from problems", "where", [ ["assigned_to_judger", "!=", "any"], ["assigned_to_judger", "!=", $_POST['judger_name']] ] -]); -foreach ($problem_ban_list as &$val) { - $val = $val['id']; +])); + +if ($problem_ban_list) { + $assignCond[] = ["problem_id", "not in", DB::rawtuple($problem_ban_list)]; +} + +if ($_POST['judger_name'] == "remote_judger") { + $problem_ban_list = array_map(fn ($x) => $x['id'], DB::selectAll([ + "select id from problems", + "where", [ + ["type", "!=", "remote"], + ], + ])); +} else { + $problem_ban_list = array_map(fn ($x) => $x['id'], DB::selectAll([ + "select id from problems", + "where", [ + ["type", "!=", "local"], + ], + ])); +} + +if ($problem_ban_list) { + $assignCond[] = ["problem_id", "not in", DB::rawtuple($problem_ban_list)]; } -$assignCond = $problem_ban_list ? [["problem_id", "not in", DB::rawtuple($problem_ban_list)]] : []; $submission = null; $hack = null; diff --git a/web/app/controllers/list.php b/web/app/controllers/list.php index ad1fcf7..5a24d80 100644 --- a/web/app/controllers/list.php +++ b/web/app/controllers/list.php @@ -20,6 +20,9 @@ function getProblemTR($info) { if ($problem->isUserOwnProblem(Auth::user())) { $html .= ' <span class="badge text-white bg-info">' . UOJLocale::get('problems::my problem') . '</span> '; } + if ($info['type'] == 'remote') { + $html .= ' ' . HTML::tag('span', ['class' => 'badge text-bg-success'], '远端评测题'); + } if ($info['is_hidden']) { $html .= ' <span class="badge text-bg-danger"><i class="bi bi-eye-slash-fill"></i> ' . UOJLocale::get('hidden') . '</span> '; } diff --git a/web/app/controllers/new_remote_problem.php b/web/app/controllers/new_remote_problem.php new file mode 100644 index 0000000..02992bc --- /dev/null +++ b/web/app/controllers/new_remote_problem.php @@ -0,0 +1,153 @@ +<?php +requireLib('bootstrap5'); +requirePHPLib('form'); +requirePHPLib('data'); + +Auth::check() || redirectToLogin(); +UOJProblem::userCanCreateProblem(Auth::user()) || UOJResponse::page403(); + +$new_remote_problem_form = new UOJForm('new_remote_problem'); +$new_remote_problem_form->addSelect('remote_online_judge', [ + 'label' => '远程 OJ', + 'options' => array_map(fn ($provider) => $provider['name'], UOJRemoteProblem::$providers), +]); +$new_remote_problem_form->addInput('remote_problem_id', [ + 'div_class' => 'mt-3', + 'label' => '远程 OJ 上的题目 ID', + 'validator_php' => function ($id, &$vdata) { + $remote_oj = $_POST['remote_online_judge']; + if ($remote_oj === 'codeforces') { + $id = trim(strtoupper($id)); + + if (!validateCodeforcesProblemId($id)) { + return '不合法的题目 ID'; + } + + $vdata['remote_problem_id'] = $id; + + return ''; + } else if ($remote_oj === 'atcoder') { + $id = trim(strtolower($id)); + + if (!validateString($id)) { + return '不合法的题目 ID'; + } + + $vdata['remote_problem_id'] = $id; + + return ''; + } else if ($remote_oj === 'uoj') { + if (!validateUInt($id)) { + return '不合法的题目 ID'; + } + + $vdata['remote_problem_id'] = $id; + + return ''; + } else if ($remote_oj === 'loj') { + if (!validateUInt($id)) { + return '不合法的题目 ID'; + } + + $vdata['remote_problem_id'] = $id; + + return ''; + } + + return '不合法的远程 OJ 类型'; + }, +]); +$new_remote_problem_form->handle = function (&$vdata) { + $remote_online_judge = $_POST['remote_online_judge']; + $remote_problem_id = $vdata['remote_problem_id']; + $remote_provider = UOJRemoteProblem::$providers[$remote_online_judge]; + + try { + $data = UOJRemoteProblem::getProblemBasicInfo($remote_online_judge, $remote_problem_id); + } catch (Exception $e) { + $data = null; + UOJLog::error($e->getMessage()); + } + + if ($data === null) { + UOJResponse::page500('题目抓取失败,可能是题目不存在或者没有题面!如果题目没有问题,请稍后再试。<a href="">返回</a>'); + } + + $submission_requirement = [ + [ + "name" => "answer", + "type" => "source code", + "file_name" => "answer.code", + "languages" => $remote_provider['languages'], + ] + ]; + $enc_submission_requirement = json_encode($submission_requirement); + + $extra_config = [ + 'remote_online_judge' => $remote_online_judge, + 'remote_problem_id' => $remote_problem_id, + 'time_limit' => $data['time_limit'], + 'memory_limit' => $data['memory_limit'], + ]; + $enc_extra_config = json_encode($extra_config); + + DB::insert([ + "insert into problems", + "(title, uploader, is_hidden, submission_requirement, extra_config, difficulty, type)", + "values", DB::tuple([$data['title'], Auth::id(), 1, $enc_submission_requirement, $enc_extra_config, $data['difficulty'] ?: -1, "remote"]) + ]); + + $id = DB::insert_id(); + + DB::insert([ + "insert into problems_contents", + "(id, remote_content, statement, statement_md)", + "values", + DB::tuple([$id, HTML::purifier(['a' => ['target' => 'Enum#_blank']])->purify($data['statement']), '', '']) + ]); + dataNewProblem($id); + + redirectTo("/problem/{$id}"); + die(); +}; +$new_remote_problem_form->runAtServer(); +?> + +<?php echoUOJPageHeader('导入远程题库') ?> + +<h1>导入远程题库</h1> + +<div class="row"> + <div class="col-md-9"> + <div class="card"> + <div class="card-body"> + <div class="row"> + <div class="col-md-6"> + <?php $new_remote_problem_form->printHTML() ?> + </div> + <div class="col-md-6 mt-3 mt-md-0"> + <h4>使用帮助</h4> + <ul> + <li> + <p>目前支持导入以下题库的题目作为远端评测题:</p> + <ul class="mb-3"> + <li><a href="https://codeforces.com/problemset">Codeforces</a></li> + <li><a href="https://codeforces.com/gyms">Codeforces::Gym</a>(题号前加 <code>GYM</code>)</li> + <li><a href="https://atcoder.jp/contests/archive">AtCoder</a></li> + <li><a href="https://uoj.ac/problems">UniversalOJ</a></li> + <li><a href="https://loj.ac/p">LibreOJ</a></li> + </ul> + </li> + <li>在导入题目前请先搜索题库中是否已经存在相应题目,避免重复添加。</li> + </ul> + </div> + </div> + </div> + </div> + </div> + <div class="col-md-3"> + <?php uojIncludeView('sidebar') ?> + </div> +</div> + +<?php echoUOJPageFooter() ?> diff --git a/web/app/controllers/problem.php b/web/app/controllers/problem.php index 40757b3..3998d72 100644 --- a/web/app/controllers/problem.php +++ b/web/app/controllers/problem.php @@ -223,7 +223,6 @@ if (UOJContest::cur()) { <div class="row"> <!-- Left col --> <div class="col-lg-9"> - <?php if (isset($tabs_info)) : ?> <!-- 比赛导航 --> <div class="mb-2"> @@ -233,7 +232,6 @@ if (UOJContest::cur()) { <div class="card card-default mb-2"> <div class="card-body"> - <h1 class="card-title text-center"> <?php if (UOJContest::cur()) : ?> <?= UOJProblem::cur()->getTitle(['with' => 'letter', 'simplify' => true]) ?> @@ -243,8 +241,13 @@ if (UOJContest::cur()) { </h1> <?php - $time_limit = $conf instanceof UOJProblemConf ? $conf->getVal('time_limit', 1) : null; - $memory_limit = $conf instanceof UOJProblemConf ? $conf->getVal('memory_limit', 256) : null; + if (UOJProblem::info('type') == 'local') { + $time_limit = $conf instanceof UOJProblemConf ? $conf->getVal('time_limit', 1) : null; + $memory_limit = $conf instanceof UOJProblemConf ? $conf->getVal('memory_limit', 256) : null; + } else if (UOJProblem::info('type') == 'remote') { + $time_limit = UOJProblem::cur()->getExtraConfig('time_limit'); + $memory_limit = UOJProblem::cur()->getExtraConfig('memory_limit'); + } ?> <div class="text-center small"> 时间限制: <?= $time_limit ? "$time_limit s" : "N/A" ?> @@ -259,6 +262,14 @@ if (UOJContest::cur()) { <article class="mt-3 markdown-body"> <?= $problem_content['statement'] ?> </article> + + <hr> + + <?php if (UOJProblem::info('type') == 'remote') : ?> + <article class="mt-3 markdown-body remote-content"> + <?= UOJProblem::cur()->queryContent()['remote_content'] ?> + </article> + <?php endif ?> </div> <div class="tab-pane" id="submit"> <?php if ($pre_submit_check_ret !== true) : ?> @@ -392,6 +403,14 @@ if (UOJContest::cur()) { <?= UOJProblem::cur()->getUploaderLink() ?> </span> </li> + <li class="list-group-item d-flex justify-content-between align-items-center"> + <span class="flex-shrink-0"> + 题目来源 + </span> + <span> + <?= UOJProblem::cur()->getProviderLink() ?> + </span> + </li> <?php if (!UOJContest::cur() || UOJContest::cur()->progress() >= CONTEST_FINISHED) : ?> <li class="list-group-item d-flex justify-content-between align-items-center"> <span class="flex-shrink-0"> diff --git a/web/app/controllers/problem_data_manage.php b/web/app/controllers/problem_data_manage.php index 6197d18..91112c7 100644 --- a/web/app/controllers/problem_data_manage.php +++ b/web/app/controllers/problem_data_manage.php @@ -10,6 +10,7 @@ requirePHPLib('data'); UOJProblem::init(UOJRequest::get('id')) || UOJResponse::page404(); UOJProblem::cur()->userCanManage(Auth::user()) || UOJResponse::page403(); +UOJProblem::info('type') === 'local' || UOJResponse::page404(); $problem = UOJProblem::info(); $problem_extra_config = UOJProblem::cur()->getExtraConfig(); diff --git a/web/app/controllers/problem_managers_manage.php b/web/app/controllers/problem_managers_manage.php index a93b4a6..9e8ca75 100644 --- a/web/app/controllers/problem_managers_manage.php +++ b/web/app/controllers/problem_managers_manage.php @@ -82,11 +82,13 @@ if (isSuperUser(Auth::user())) { 管理者 </a> </li> - <li class="nav-item"> - <a class="nav-link" href="/problem/<?= UOJProblem::info('id') ?>/manage/data" role="tab"> - 数据 - </a> - </li> + <?php if (UOJProblem::info('type') === 'local') : ?> + <li class="nav-item"> + <a class="nav-link" href="/problem/<?= UOJProblem::info('id') ?>/manage/data" role="tab"> + 数据 + </a> + </li> + <?php endif ?> </ul> <div class="card card-default"> diff --git a/web/app/controllers/problem_set.php b/web/app/controllers/problem_set.php index 47623eb..581860d 100644 --- a/web/app/controllers/problem_set.php +++ b/web/app/controllers/problem_set.php @@ -55,7 +55,7 @@ if (UOJProblem::userCanCreateProblem(Auth::user())) { 如有,在此处填写其他于题意或数据相关的说明。 EOD; - $new_problem_form = new UOJBs4Form('new_problem'); + $new_problem_form = new UOJForm('new_problem'); $new_problem_form->handle = function () use ($default_statement) { DB::insert([ "insert into problems", @@ -77,11 +77,10 @@ EOD; redirectTo("/problem/{$id}/manage/statement"); die(); }; - $new_problem_form->submit_button_config['align'] = 'right'; - $new_problem_form->submit_button_config['class_str'] = 'btn btn-primary'; - $new_problem_form->submit_button_config['text'] = UOJLocale::get('problems::add new'); - $new_problem_form->submit_button_config['smart_confirm'] = ''; - + $new_problem_form->config['submit_container']['class'] = ''; + $new_problem_form->config['submit_button']['class'] = 'bg-transparent border-0 d-block w-100 px-3 py-2 text-start'; + $new_problem_form->config['submit_button']['text'] = '<i class="bi bi-plus-lg"></i> 新建本地题目'; + $new_problem_form->config['confirm']['text'] = '添加新题'; $new_problem_form->runAtServer(); } @@ -95,6 +94,9 @@ function getProblemTR($info) { if ($problem->isUserOwnProblem(Auth::user())) { $html .= ' <a href="/problems?my=on"><span class="badge text-white bg-info">' . UOJLocale::get('problems::my problem') . '</span></a> '; } + if ($info['type'] == 'remote') { + $html .= ' ' . HTML::tag('span', ['class' => 'badge text-bg-success'], '远端评测题'); + } if ($info['is_hidden']) { $html .= ' <a href="/problems?is_hidden=on"><span class="badge text-bg-danger"><i class="bi bi-eye-slash-fill"></i> ' . UOJLocale::get('hidden') . '</span></a> '; } @@ -254,43 +256,35 @@ $pag = new Paginator([ <!-- left col --> <div class="col-md-9"> <!-- title --> - <div class="d-flex justify-content-between"> + <div class="d-flex justify-content-between flex-wrap"> <h1> <?= UOJLocale::get('problems') ?> </h1> - <?php if (isset($new_problem_form)) : ?> - <div class="text-end"> - <?php $new_problem_form->printHTML(); ?> - </div> - <?php endif ?> - + <div> + <?= HTML::tablist($tabs_info, $cur_tab, 'nav-pills') ?> + </div> </div> <!-- end title --> - <div class="row"> - <div class="col-sm-4 col-12"> - <?= HTML::tablist($tabs_info, $cur_tab, 'nav-pills') ?> - </div> - <div class="text-end p-2 col-12 col-sm-8"> - <div class="form-check d-inline-block me-2"> - <input type="checkbox" id="input-show_tags_mode" class="form-check-input" <?= isset($_COOKIE['show_tags_mode']) ? 'checked="checked" ' : '' ?> /> - <label class="form-check-label" for="input-show_tags_mode"> - <?= UOJLocale::get('problems::show tags') ?> - </label> - </div> + <?= $pag->pagination() ?> - <div class="form-check d-inline-block"> - <input type="checkbox" id="input-show_submit_mode" class="form-check-input" <?= isset($_COOKIE['show_submit_mode']) ? 'checked="checked" ' : '' ?> /> - <label class="form-check-label" for="input-show_submit_mode"> - <?= UOJLocale::get('problems::show statistics') ?> - </label> - </div> + <div class="text-end"> + <div class="form-check d-inline-block me-2"> + <input type="checkbox" id="input-show_tags_mode" class="form-check-input" <?= isset($_COOKIE['show_tags_mode']) ? 'checked="checked" ' : '' ?> /> + <label class="form-check-label" for="input-show_tags_mode"> + <?= UOJLocale::get('problems::show tags') ?> + </label> + </div> + + <div class="form-check d-inline-block"> + <input type="checkbox" id="input-show_submit_mode" class="form-check-input" <?= isset($_COOKIE['show_submit_mode']) ? 'checked="checked" ' : '' ?> /> + <label class="form-check-label" for="input-show_submit_mode"> + <?= UOJLocale::get('problems::show statistics') ?> + </label> </div> </div> - <?= $pag->pagination() ?> - <script type="text/javascript"> $('#input-show_tags_mode').click(function() { if (this.checked) { @@ -390,6 +384,23 @@ $pag = new Paginator([ }); </script> + <?php if (UOJProblem::userCanCreateProblem(Auth::user())) : ?> + <div class="card mb-3"> + <div class="card-header fw-bold"> + 新建题目 + </div> + <div class="list-group list-group-flush"> + <div class="list-group-item list-group-item-action p-0"> + <?php $new_problem_form->printHTML() ?> + </div> + <a class="list-group-item list-group-item-action" href="/problems/new/remote"> + <i class="bi bi-cloud-plus"></i> + 新建远端评测题目 + </a> + </div> + </div> + <?php endif ?> + <!-- sidebar --> <?php uojIncludeView('sidebar') ?> </aside> diff --git a/web/app/controllers/problem_statement_manage.php b/web/app/controllers/problem_statement_manage.php index eabaac1..723fe58 100644 --- a/web/app/controllers/problem_statement_manage.php +++ b/web/app/controllers/problem_statement_manage.php @@ -84,6 +84,81 @@ $difficulty_form->handle = function () { }; $difficulty_form->runAtServer(); +if (UOJProblem::info('type') == 'remote') { + $remote_online_judge = UOJProblem::cur()->getExtraConfig('remote_online_judge'); + $remote_problem_id = UOJProblem::cur()->getExtraConfig('remote_problem_id'); + $remote_provider = UOJRemoteProblem::$providers[$remote_online_judge]; + + $re_crawl_form = new UOJForm('re_crawl'); + $re_crawl_form->appendHTML(<<<EOD + <ul> + <li>远程题库:{$remote_provider['name']}</li> + <li>远程题号:{$remote_problem_id}</li> + </ul> + EOD); + $re_crawl_form->config['submit_button']['text'] = '重新爬取'; + $re_crawl_form->handle = function () use ($remote_online_judge, $remote_problem_id, $remote_provider) { + try { + $data = UOJRemoteProblem::getProblemBasicInfo($remote_online_judge, $remote_problem_id); + } catch (Exception $e) { + $data = null; + UOJLog::error($e->getMessage()); + } + + if ($data === null) { + UOJResponse::page500('题目抓取失败,可能是题目不存在或者没有题面!如果题目没有问题,请稍后再试。<a href="">返回</a>'); + } + + if ($data['difficulty'] == -1) { + $data['difficulty'] = UOJProblem::info('difficulty'); + } + + $submission_requirement = [ + [ + "name" => "answer", + "type" => "source code", + "file_name" => "answer.code", + "languages" => $remote_provider['languages'], + ] + ]; + $enc_submission_requirement = json_encode($submission_requirement); + + $extra_config = [ + 'remote_online_judge' => $remote_online_judge, + 'remote_problem_id' => $remote_problem_id, + 'time_limit' => $data['time_limit'], + 'memory_limit' => $data['memory_limit'], + ]; + $enc_extra_config = json_encode($extra_config); + + DB::update([ + "update problems", + "set", [ + "title" => $data['title'], + "submission_requirement" => $enc_submission_requirement, + "extra_config" => $enc_extra_config, + "difficulty" => $data['difficulty'] ?: -1, + ], + "where", [ + "id" => UOJProblem::info('id'), + ], + ]); + + DB::update([ + "update problems_contents", + "set", [ + "remote_content" => HTML::purifier()->purify($data['statement']), + ], + "where", [ + "id" => UOJProblem::info('id'), + ], + ]); + + redirectTo(UOJProblem::cur()->getUri()); + }; + $re_crawl_form->runAtServer(); +} + $view_type_form = new UOJForm('view_type'); $view_type_form->addSelect('view_content_type', [ 'div_class' => 'row align-items-center g-0', @@ -198,11 +273,13 @@ $solution_view_type_form->runAtServer(); 管理者 </a> </li> - <li class="nav-item"> - <a class="nav-link" href="/problem/<?= UOJProblem::info('id') ?>/manage/data" role="tab"> - 数据 - </a> - </li> + <?php if (UOJProblem::info('type') == 'local') : ?> + <li class="nav-item"> + <a class="nav-link" href="/problem/<?= UOJProblem::info('id') ?>/manage/data" role="tab"> + 数据 + </a> + </li> + <?php endif ?> </ul> <div class="card card-default"> @@ -358,6 +435,17 @@ $solution_view_type_form->runAtServer(); </div> </div> + <?php if (UOJProblem::info('type') == 'remote') : ?> + <div class="card mt-3"> + <div class="card-header fw-bold"> + 重新爬取题目信息 + </div> + <div class="card-body"> + <?php $re_crawl_form->printHTML() ?> + </div> + </div> + <?php endif ?> + <div class="card mt-3"> <div class="card-header fw-bold"> 提交记录可视权限 diff --git a/web/app/libs/uoj-html-lib.php b/web/app/libs/uoj-html-lib.php index 02242c0..3de8d1e 100644 --- a/web/app/libs/uoj-html-lib.php +++ b/web/app/libs/uoj-html-lib.php @@ -687,7 +687,7 @@ class JudgmentDetailsPrinter { } echo '<h4 class="mb-2">', $node->getAttribute("title"), ":</h4>"; } - echo '<pre>', "\n"; + echo '<pre class="bg-light p-3 rounded">', "\n"; $this->_print_c($node); echo "\n</pre>"; echo '</div>'; diff --git a/web/app/libs/uoj-utility-lib.php b/web/app/libs/uoj-utility-lib.php index ad016ff..5a9f027 100644 --- a/web/app/libs/uoj-utility-lib.php +++ b/web/app/libs/uoj-utility-lib.php @@ -202,10 +202,10 @@ function getProblemCustomTestRequirement($problem) { function sendSystemMsg($username, $title, $content) { DB::insert([ - "insert into user_system_msg", - "(receiver, title, content, send_time)", - "values", DB::tuple([$username, $title, $content, DB::now()]) - ]); + "insert into user_system_msg", + "(receiver, title, content, send_time)", + "values", DB::tuple([$username, $title, $content, DB::now()]) + ]); } function retry_loop(callable $f, $retry = 5, $ms = 10) { @@ -218,3 +218,66 @@ function retry_loop(callable $f, $retry = 5, $ms = 10) { } return $ret; } + +function getAbsoluteUrl($relativeUrl, $baseUrl) { + // if already absolute URL + if (parse_url($relativeUrl, PHP_URL_SCHEME) !== null) { + return $relativeUrl; + } + + // queries and anchors + if ($relativeUrl[0] === '#' || $relativeUrl[0] === '?') { + return $baseUrl . $relativeUrl; + } + + // parse base URL and convert to: $scheme, $host, $path, $query, $port, $user, $pass + extract(parse_url($baseUrl)); + + // if base URL contains a path remove non-directory elements from $path + if (isset($path) === true) { + $path = preg_replace('#/[^/]*$#', '', $path); + } else { + $path = ''; + } + + // if realtive URL starts with // + if (substr($relativeUrl, 0, 2) === '//') { + return $scheme . ':' . $relativeUrl; + } + + // if realtive URL starts with / + if ($relativeUrl[0] === '/') { + $path = null; + } + + $abs = null; + + // if realtive URL contains a user + if (isset($user) === true) { + $abs .= $user; + + // if realtive URL contains a password + if (isset($pass) === true) { + $abs .= ':' . $pass; + } + + $abs .= '@'; + } + + $abs .= $host; + + // if realtive URL contains a port + if (isset($port) === true) { + $abs .= ':' . $port; + } + + $abs .= $path . '/' . $relativeUrl . (isset($query) === true ? '?' . $query : null); + + // replace // or /./ or /foo/../ with / + $re = ['#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#']; + for ($n = 1; $n > 0; $abs = preg_replace($re, '/', $abs, -1, $n)) { + } + + // return absolute URL + return $scheme . '://' . $abs; +} diff --git a/web/app/libs/uoj-validate-lib.php b/web/app/libs/uoj-validate-lib.php index 4ba22e5..426cc92 100644 --- a/web/app/libs/uoj-validate-lib.php +++ b/web/app/libs/uoj-validate-lib.php @@ -75,3 +75,7 @@ function validateUserAndStoreByUsername($username, &$vdata) { function is_short_string($str) { return is_string($str) && strlen($str) <= 256; } + +function validateCodeforcesProblemId($str) { + return preg_match('/(|GYM)[1-9][0-9]{0,5}[A-Z][1-9]?/', $str) !== true; +} diff --git a/web/app/models/HTML.php b/web/app/models/HTML.php index df75d89..7ac1312 100644 --- a/web/app/models/HTML.php +++ b/web/app/models/HTML.php @@ -423,7 +423,7 @@ class HTML { return implode("&", $r); } - public static function purifier() { + public static function purifier($extra_allowed_html = []) { $config = HTMLPurifier_Config::createDefault(); $config->set('Output.Newline', true); $def = $config->getHTMLDefinition(true); @@ -435,14 +435,14 @@ class HTML { $def->addElement('header', 'Block', 'Flow', 'Common'); $def->addElement('footer', 'Block', 'Flow', 'Common'); - $extra_allowed_html = [ + mergeConfig($extra_allowed_html, [ 'span' => [ 'class' => 'Enum#uoj-username', 'data-realname' => 'Text', 'data-color' => 'Color', ], 'img' => ['width' => 'Text'], - ]; + ]); foreach ($extra_allowed_html as $element => $attributes) { foreach ($attributes as $attribute => $type) { diff --git a/web/app/models/UOJForm.php b/web/app/models/UOJForm.php index 7923268..cd3ad47 100644 --- a/web/app/models/UOJForm.php +++ b/web/app/models/UOJForm.php @@ -120,6 +120,53 @@ class UOJForm { $this->add($name, $html, $validator_php, $validator_js); } + public function addInput($name, $config) { + $config += [ + 'type' => 'text', + 'div_class' => '', + 'input_class' => 'form-control', + 'default_value' => '', + 'label' => '', + 'label_class' => 'form-label', + 'placeholder' => '', + 'help' => '', + 'help_class' => 'form-text', + 'validator_php' => function ($x) { + return ''; + }, + 'validator_js' => null, + ]; + + $html = ''; + $html .= HTML::tag_begin('div', ['class' => $config['div_class'], 'id' => "div-$name"]); + + if ($config['label']) { + $html .= HTML::tag('label', [ + 'class' => $config['label_class'], + 'for' => "input-$name", + 'id' => "label-$name" + ], $config['label']); + } + + $html .= HTML::empty_tag('input', [ + 'class' => $config['input_class'], + 'type' => $config['type'], + 'name' => $name, + 'id' => "input-$name", + 'value' => $config['default_value'], + 'placeholder' => $config['placeholder'], + ]); + $html .= HTML::tag('div', ['class' => 'invalid-feedback', 'id' => "help-$name"], ''); + + if ($config['help']) { + $html .= HTML::tag('div', ['class' => $config['help_class']], $config['help']); + } + + $html .= HTML::tag_end('div'); + + $this->add($name, $html, $config['validator_php'], $config['validator_js']); + } + public function addCheckbox($name, $config) { $config += [ 'checked' => false, diff --git a/web/app/models/UOJLang.php b/web/app/models/UOJLang.php index 996fe4b..e18342f 100644 --- a/web/app/models/UOJLang.php +++ b/web/app/models/UOJLang.php @@ -64,21 +64,21 @@ class UOJLang { return []; } $is_avail = []; - $dep_list = [ - ['C++', 'C++11', 'C++14', 'C++17', 'C++20'], - ['Java8', 'Java11', 'Java17'] - ]; + // $dep_list = [ + // ['C++98', 'C++03', 'C++11', 'C++', 'C++17', 'C++20'], + // ['Java8', 'Java11', 'Java17'] + // ]; foreach ($list as $lang) { $lang = static::getUpgradedLangCode($lang); - foreach ($dep_list as $dep) { - $ok = false; - foreach ($dep as $d) { - if ($ok || $d == $lang) { - $is_avail[$d] = true; - $ok = true; - } - } - } + // foreach ($dep_list as $dep) { + // $ok = false; + // foreach ($dep as $d) { + // if ($ok || $d == $lang) { + // $is_avail[$d] = true; + // $ok = true; + // } + // } + // } $is_avail[$lang] = true; } diff --git a/web/app/models/UOJProblem.php b/web/app/models/UOJProblem.php index c0bd929..8e4bbce 100644 --- a/web/app/models/UOJProblem.php +++ b/web/app/models/UOJProblem.php @@ -382,6 +382,26 @@ class UOJProblem { return UOJUser::getLink($this->info['uploader'] ?: "root"); } + public function getProviderLink() { + if ($this->info['type'] == 'local') { + return HTML::tag('a', ['href' => HTML::url('/')], UOJConfig::$data['profile']['oj-name-short']); + } + + $remote_oj = $this->getExtraConfig('remote_online_judge'); + $remote_id = $this->getExtraConfig('remote_problem_id'); + + if (!$remote_oj || !array_key_exists($remote_oj, UOJRemoteProblem::$providers)) { + return 'Error'; + } + + $provider = UOJRemoteProblem::$providers[$remote_oj]; + + return HTML::tag('a', [ + 'href' => UOJRemoteProblem::getProblemRemoteUrl($remote_oj, $remote_id), + 'target' => '_blank' + ], $provider['name']); + } + public function getDifficultyHTML() { $difficulty = (int)$this->info['difficulty']; $difficulty_text = in_array($difficulty, static::$difficulty) ? $difficulty : '?'; @@ -440,28 +460,33 @@ class UOJProblem { return $key === null ? $extra_config : $extra_config[$key]; } public function getCustomTestRequirement() { + if ($this->info['type'] == 'remote') { + return []; + } + $extra_config = json_decode($this->info['extra_config'], true); + if (isset($extra_config['custom_test_requirement'])) { return $extra_config['custom_test_requirement']; - } else { - $answer = [ - 'name' => 'answer', - 'type' => 'source code', - 'file_name' => 'answer.code' - ]; - foreach ($this->getSubmissionRequirement() as $req) { - if ($req['name'] == 'answer' && $req['type'] == 'source code' && isset($req['languages'])) { - $answer['languages'] = $req['languages']; - } - } - return [ - $answer, [ - 'name' => 'input', - 'type' => 'text', - 'file_name' => 'input.txt' - ] - ]; } + + $answer = [ + 'name' => 'answer', + 'type' => 'source code', + 'file_name' => 'answer.code' + ]; + foreach ($this->getSubmissionRequirement() as $req) { + if ($req['name'] == 'answer' && $req['type'] == 'source code' && isset($req['languages'])) { + $answer['languages'] = $req['languages']; + } + } + return [ + $answer, [ + 'name' => 'input', + 'type' => 'text', + 'file_name' => 'input.txt' + ] + ]; } public function userCanView(array $user = null, array $cfg = []) { diff --git a/web/app/models/UOJRemoteProblem.php b/web/app/models/UOJRemoteProblem.php new file mode 100644 index 0000000..b5f0ac9 --- /dev/null +++ b/web/app/models/UOJRemoteProblem.php @@ -0,0 +1,439 @@ +<?php + +class UOJRemoteProblem { + static $providers = [ + 'codeforces' => [ + 'name' => 'Codeforces', + 'short_name' => 'CF', + 'url' => 'https://codeforces.com', + 'not_exists_texts' => [ + '<th>Actions</th>', + 'Statement is not available on English language', + 'ограничение по времени на тест', + ], + 'languages' => ['C', 'C++', 'C++17', 'C++20', 'Java17', 'Pascal', 'Python2', 'Python3'], + ], + 'atcoder' => [ + 'name' => 'AtCoder', + 'short_name' => 'AT', + 'url' => 'https://atcoder.jp', + 'not_exists_texts' => [ + 'Task not found', + '指定されたタスクが見つかりません', + ], + 'languages' => ['C', 'C++', 'Java11', 'Python3', 'Pascal'], + ], + 'uoj' => [ + 'name' => 'UniversalOJ', + 'short_name' => 'UOJ', + 'url' => 'https://uoj.ac', + 'not_exist_texts' => [ + '未找到该页面', + ], + 'languages' => ['C', 'C++03', 'C++11', 'C++', 'C++17', 'C++20', 'Python3', 'Python2.7', 'Java8', 'Java11', 'Java17', 'Pascal'], + ], + 'loj' => [ + 'name' => 'LibreOJ', + 'short_name' => 'LOJ', + 'url' => 'https://loj.ac', + 'languages' => ['C', 'C++03', 'C++11', 'C++', 'C++17', 'C++20', 'Python3', 'Python2.7', 'Java17', 'Pascal'], + ], + ]; + + static function getCodeforcesProblemUrl($id) { + if (str_starts_with($id, 'GYM')) { + return static::$providers['codeforces']['url'] . '/gym/' . preg_replace_callback('/GYM([1-9][0-9]{0,5})([A-Z][1-9]?)/', fn ($matches) => $matches[1] . '/problem/' . $matches[2], $id); + } + + return static::$providers['codeforces']['url'] . '/problemset/problem/' . preg_replace_callback('/([1-9][0-9]{0,5})([A-Z][1-9]?)/', fn ($matches) => $matches[1] . '/' . $matches[2], $id); + } + + static function getAtcoderProblemUrl($id) { + return static::$providers['atcoder']['url'] . '/contests/' . preg_replace_callback('/(\w+)([a-z][1-9]?)/', function ($matches) { + $contest = str_replace('_', '', $matches[1]); + + if (str_ends_with($matches[1], '_')) { + return "{$contest}/tasks/{$matches[1]}{$matches[2]}"; + } + + return "{$contest}/tasks/{$matches[1]}_{$matches[2]}"; + }, $id); + } + + static function getUojProblemUrl($id) { + return static::$providers['uoj']['url'] . '/problem/' . $id; + } + + static function getLojProblemUrl($id) { + return static::$providers['loj']['url'] . '/p/' . $id; + } + + static function getCodeforcesProblemBasicInfoFromHtml($id, $html) { + $remote_provider = static::$providers['codeforces']; + + $html = preg_replace('/\$\$\$/', '$', $html); + $dom = new \IvoPetkov\HTML5DOMDocument(); + $dom->loadHTML($html); + + $judgestatement = $dom->querySelector('html')->innerHTML; + + foreach ($remote_provider['not_exists_texts'] as $text) { + if (str_contains($judgestatement, $text)) { + return null; + } + } + + $statement_dom = $dom->querySelector('.problem-statement'); + $title_prefix = str_starts_with($id, 'GYM') ? 'Gym' : 'CF'; + $title = explode('. ', trim($statement_dom->querySelector('.title')->innerHTML))[1]; + $title_id = str_starts_with($id, 'GYM') ? substr($id, 3) : $id; + $title = "【{$title_prefix}{$title_id}】{$title}"; + $time_limit = intval(substr($statement_dom->querySelector('.time-limit')->innerHTML, 53)); + $memory_limit = intval(substr($statement_dom->querySelector('.memory-limit')->innerHTML, 55)); + $difficulty = -1; + + foreach ($dom->querySelectorAll('.tag-box') as &$elem) { + $matches = []; + + if (preg_match('/\*([0-9]{3,4})/', trim($elem->innerHTML), $matches)) { + $difficulty = intval($matches[1]); + + break; + } + } + + if ($difficulty != -1) { + $closest = null; + + foreach (UOJProblem::$difficulty as $val) { + if ($closest === null || abs($val - $difficulty) < abs($closest - $difficulty)) { + $closest = $val; + } + } + + $difficulty = $closest; + } + + $statement_dom->removeChild($statement_dom->querySelector('.header')); + $statement_dom->childNodes->item(0)->insertBefore($dom->createElement('h3', 'Description'), $statement_dom->childNodes->item(0)->childNodes->item(0)); + + foreach ($statement_dom->querySelectorAll('.section-title') as &$elem) { + $elem->outerHTML = '<h3>' . $elem->innerHTML . '</h3>'; + } + + $sample_input_cnt = 0; + $sample_output_cnt = 0; + + foreach ($statement_dom->querySelectorAll('.input') as &$input_dom) { + $sample_input_cnt++; + $input_text = ''; + + if ($input_dom->querySelector('.test-example-line')) { + foreach ($input_dom->querySelectorAll('.test-example-line') as &$line) { + $input_text .= HTML::stripTags($line->innerHTML) . "\n"; + } + } else { + $input_text = HTML::stripTags($input_dom->querySelector('pre')->innerHTML); + } + + $input_dom->outerHTML = HTML::tag('h4', [], "Input #{$sample_input_cnt}") . HTML::tag('pre', [], HTML::tag('code', [], $input_text)); + } + + foreach ($statement_dom->querySelectorAll('.output') as &$output_dom) { + $sample_output_cnt++; + $output_text = ''; + + if ($output_dom->querySelector('.test-example-line')) { + foreach ($output_dom->querySelectorAll('.test-example-line') as &$line) { + $output_text .= HTML::stripTags($line->innerHTML) . "\n"; + } + } else { + $output_text = HTML::stripTags($output_dom->querySelector('pre')->innerHTML); + } + + $output_dom->outerHTML = HTML::tag('h4', [], "Output #{$sample_output_cnt}") . HTML::tag('pre', [], HTML::tag('code', [], $output_text)); + } + + return [ + 'type' => 'html', + 'title' => $title, + 'time_limit' => $time_limit, + 'memory_limit' => $memory_limit, + 'difficulty' => $difficulty, + 'statement' => $statement_dom->innerHTML, + ]; + } + + static function getCodeforcesProblemBasicInfo($id) { + $curl = new Curl(); + $curl->setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36 S2OJ/3.1.0'); + + $res = retry_loop(function () use (&$curl, $id) { + $curl->get(static::getCodeforcesProblemUrl($id)); + + if ($curl->error) { + return false; + } + + return [ + 'content-type' => $curl->response_headers['Content-Type'], + 'response' => $curl->response, + ]; + }); + + if (!$res) return null; + + if (str_starts_with($res['content-type'], 'text/html')) { + return static::getCodeforcesProblemBasicInfoFromHtml($id, $res['response']); + } else if (str_starts_with($res['content-type'], 'application/pdf')) { + $title_prefix = str_starts_with($id, 'GYM') ? 'Gym' : 'CF'; + $title_id = str_starts_with($id, 'GYM') ? substr($id, 3) : $id; + $title = "【{$title_prefix}{$title_id}】{$title_prefix}{$title_id}"; + + return [ + 'type' => 'pdf', + 'title' => $title, + 'time_limit' => null, + 'memory_limit' => null, + 'difficulty' => -1, + 'statement' => HTML::tag('h3', [], '提示') . + HTML::tag( + 'p', + [], + '本题题面为 PDF 题面,请' . + HTML::tag('a', ['href' => static::getCodeforcesProblemUrl($id), 'target' => '_blank'], '点此') . + '以查看题面。' + ), + ]; + } else { + return null; + } + } + + static function getAtcoderProblemBasicInfo($id) { + $curl = new Curl(); + $curl->setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36 S2OJ/3.1.0'); + $curl->setCookie('language', 'en'); + + $res = retry_loop(function () use (&$curl, $id) { + $curl->get(static::getAtcoderProblemUrl($id)); + + if ($curl->error) { + return false; + } + + return $curl->response; + }); + + if (!$res) return null; + + $dom = new \IvoPetkov\HTML5DOMDocument(); + $dom->loadHTML($res); + $container_dom = $dom->querySelectorAll('#main-container > div.row > div.col-sm-12')->item(1); + + if (!$container_dom) return null; + + $title_dom = $container_dom->querySelector('span.h2'); + $title = '【' . strtoupper($id) . '】' . preg_replace('/([A-Z][1-9]?) - (.*)/', '$2', explode("\n", trim($title_dom->textContent))[0]); + + $limit_dom = $container_dom->querySelector('p'); + + $time_limit_matches = []; + preg_match('/Time Limit: (\d+)/', $limit_dom->textContent, $time_limit_matches); + $time_limit = intval($time_limit_matches[1]); + + $memory_limit_matches = []; + preg_match('/Memory Limit: (\d+)/', $limit_dom->textContent, $memory_limit_matches); + $memory_limit = intval($memory_limit_matches[1]); + + $statement_container_dom = $container_dom->querySelector('#task-statement'); + $statement_dom = $statement_container_dom->querySelector('.lang-en'); + + if (!$statement_dom) { + $statement_dom = $statement_container_dom->querySelector('.lang-ja'); + } + + $statement_first_child = $statement_dom->querySelector('p'); + $first_child_content = trim($statement_first_child->textContent); + + if (str_starts_with($first_child_content, 'Score :') || str_starts_with($first_child_content, '配点 :')) { + $statement_dom->removeChild($statement_first_child); + } + + foreach ($statement_dom->querySelectorAll('var') as &$elem) { + $html = $elem->innerHTML; + + // <sub> => _{ + $html = str_replace('<sub>', '_{', $html); + + // </sub> => } + $html = str_replace('</sub>', '}', $html); + + // <sup> => ^{ + $html = str_replace('<sup>', '^{', $html); + + // </sup> => } + $html = str_replace('</sup>', '}', $html); + + $elem->innerHTML = $html; + } + + $statement = $statement_dom->innerHTML; + + // <var> => $ + $statement = str_replace('<var>', '\\(', $statement); + + // </var> => $ + $statement = str_replace('</var>', '\\)', $statement); + + return [ + 'type' => 'html', + 'title' => $title, + 'time_limit' => $time_limit, + 'memory_limit' => $memory_limit, + 'difficulty' => -1, + 'statement' => $statement, + ]; + } + + static function getUojProblemBasicInfo($id) { + $remote_provider = static::$providers['uoj']; + $curl = new Curl(); + $curl->setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36 S2OJ/3.1.0'); + + $res = retry_loop(function () use (&$curl, $id) { + $curl->get(static::getUojProblemUrl($id)); + + if ($curl->error) { + return false; + } + + return $curl->response; + }); + + if (!$res) return null; + + $dom = new \IvoPetkov\HTML5DOMDocument(); + $dom->loadHTML($res); + + $title_dom = $dom->querySelector('.page-header'); + $title_matches = []; + preg_match('/^#[1-9][0-9]*\. (.*)$/', trim($title_dom->textContent), $title_matches); + $title = "【{$remote_provider['short_name']}{$id}】{$title_matches[1]}"; + + $statement_dom = $dom->querySelector('.uoj-article'); + $statement = HTML::tag('h3', [], '题目描述'); + + foreach ($statement_dom->querySelectorAll('a') as &$elem) { + $href = $elem->getAttribute('href'); + $href = getAbsoluteUrl($href, $remote_provider['url']); + $elem->setAttribute('href', $href); + } + + $statement .= $statement_dom->innerHTML; + + return [ + 'type' => 'html', + 'title' => $title, + 'time_limit' => null, + 'memory_limit' => null, + 'difficulty' => -1, + 'statement' => $statement, + ]; + } + + static function getLojProblemBasicInfo($id) { + $remote_provider = static::$providers['loj']; + $curl = new Curl(); + $curl->setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36 S2OJ/3.1.0'); + $curl->setHeader('Content-Type', 'application/json'); + + $res = retry_loop(function () use (&$curl, $id) { + $curl->post('https://api.loj.ac.cn/api/problem/getProblem', json_encode([ + 'displayId' => (int)$id, + 'localizedContentsOfLocale' => 'zh_CN', + 'samples' => true, + 'judgeInfo' => true, + ])); + + if ($curl->error) { + return false; + } + + return $curl->response; + }); + + if (!$res) return null; + + // Convert stdClass to array + $res = json_decode(json_encode($res), true); + + if (isset($res['error'])) return null; + + $localized_contents = $res['localizedContentsOfLocale']; + $statement = ''; + + foreach ($localized_contents['contentSections'] as $section) { + $statement .= "\n###" . $section['sectionTitle'] . "\n\n"; + + if ($section['type'] === 'Text') { + $statement .= $section['text'] . "\n"; + } else if ($section['type'] === 'Sample') { + // assert($res['samples'][$section['sampleId']]); + $display_sample_id = $section['sampleId'] + 1; + $sample = $res['samples'][$section['sampleId']]; + + $statement .= "\n#### 样例输入 #{$display_sample_id}\n\n"; + $statement .= "\n```text\n{$sample['inputData']}\n```\n\n"; + + $statement .= "\n#### 样例输出 #{$display_sample_id}\n\n"; + $statement .= "\n```text\n{$sample['outputData']}\n```\n\n"; + + if (trim($section['text'])) { + $statement .= "\n#### 样例解释 #{$display_sample_id}\n\n"; + $statement .= $section['text'] . "\n"; + } + } else { + // do nothing... + } + } + + return [ + 'type' => 'html', + 'title' => "【{$remote_provider['short_name']}{$id}】{$localized_contents['title']}", + 'time_limit' => (float)$res['judgeInfo']['timeLimit'] / 1000.0, + 'memory_limit' => $res['judgeInfo']['memoryLimit'], + 'difficulty' => -1, + 'statement' => HTML::purifier()->purify(HTML::parsedown()->text($statement)), + ]; + } + + public static function getProblemRemoteUrl($oj, $id) { + if ($oj === 'codeforces') { + return static::getCodeforcesProblemUrl($id); + } else if ($oj === 'atcoder') { + return static::getAtcoderProblemUrl($id); + } else if ($oj === 'uoj') { + return static::getUojProblemUrl($id); + } else if ($oj === 'loj') { + return static::getLojProblemUrl($id); + } + + return null; + } + + // 传入 ID 需确保有效 + public static function getProblemBasicInfo($oj, $id) { + if ($oj === 'codeforces') { + return static::getCodeforcesProblemBasicInfo($id); + } else if ($oj === 'atcoder') { + return static::getAtcoderProblemBasicInfo($id); + } else if ($oj === 'uoj') { + return static::getUojProblemBasicInfo($id); + } else if ($oj === 'loj') { + return static::getLojProblemBasicInfo($id); + } + + return null; + } +} diff --git a/web/app/models/UOJSubmission.php b/web/app/models/UOJSubmission.php index 34570ee..8f5e341 100644 --- a/web/app/models/UOJSubmission.php +++ b/web/app/models/UOJSubmission.php @@ -87,11 +87,18 @@ class UOJSubmission { $judge_reason = ''; $content['config'][] = ['problem_id', UOJProblem::info('id')]; + + if (UOJProblem::info('type') == 'remote') { + $content['config'][] = ['remote_online_judge', UOJProblem::cur()->getExtraConfig('remote_online_judge')]; + $content['config'][] = ['remote_problem_id', UOJProblem::cur()->getExtraConfig('remote_problem_id')]; + } + if ($is_contest_submission && UOJContestProblem::cur()->getJudgeTypeInContest() == 'sample') { $content['final_test_config'] = $content['config']; $content['config'][] = ['test_sample_only', 'on']; $judge_reason = json_encode(['text' => '样例测评']); } + $content_json = json_encode($content); $language = static::getAndRememberSubmissionLanguage($content); @@ -400,6 +407,10 @@ class UOJSubmission { } public function userCanSeeMinorVersions(array $user = null) { + if ($this->problem->info['type'] == 'remote') { + return false; + } + if (isSuperUser($user)) { return true; } @@ -410,6 +421,7 @@ class UOJSubmission { if (isSuperUser($user)) { return true; } + return $this->userCanManageProblemOrContest($user) && $this->hasFullyJudged(); } diff --git a/web/app/route.php b/web/app/route.php index 1f11278a..4916464 100644 --- a/web/app/route.php +++ b/web/app/route.php @@ -17,6 +17,7 @@ Route::group( Route::any('/', '/index.php'); Route::any('/problems', '/problem_set.php'); Route::any('/problems/template', '/problem_set.php?tab=template'); + Route::any('/problems/new/remote', '/new_remote_problem.php'); Route::any('/problem/{id}', '/problem.php'); Route::any('/problem/{id}/solutions', '/problem_solutions.php'); Route::any('/problem/{id}/statistics', '/problem_statistics.php'); @@ -85,9 +86,6 @@ Route::group( Route::any('/super_manage(?:/{tab})?', '/super_manage.php'); - Route::any('/download/problem/{id}/data.zip', '/download.php?type=problem'); - Route::any('/download/problem/{id}/attachment.zip', '/download.php?type=attachment'); - Route::any('/check-notice', '/check_notice.php'); Route::any('/click-zan', '/click_zan.php'); diff --git a/web/app/upgrade/28_remote_judge/README.md b/web/app/upgrade/28_remote_judge/README.md new file mode 100644 index 0000000..2a005c3 --- /dev/null +++ b/web/app/upgrade/28_remote_judge/README.md @@ -0,0 +1 @@ +https://github.com/renbaoshuo/S2OJ/pull/28 diff --git a/web/app/upgrade/28_remote_judge/up.sql b/web/app/upgrade/28_remote_judge/up.sql new file mode 100644 index 0000000..d9ee623 --- /dev/null +++ b/web/app/upgrade/28_remote_judge/up.sql @@ -0,0 +1,6 @@ +ALTER TABLE `problems` ADD `type` varchar(20) NOT NULL DEFAULT 'local' AFTER `difficulty`; +ALTER TABLE `problems` ADD KEY `type` (`type`); + +ALTER TABLE `problems_contents` ADD `remote_content` longtext COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' AFTER `id`; + +insert into judger_info (judger_name, password, ip, display_name, description) values ('remote_judger', '_judger_password_', 'uoj-remote-judger', '远端评测机', '用于桥接远端 OJ 评测机的虚拟评测机。'); diff --git a/web/app/vendor/composer/ClassLoader.php b/web/app/vendor/composer/ClassLoader.php index fce8549..afef3fa 100644 --- a/web/app/vendor/composer/ClassLoader.php +++ b/web/app/vendor/composer/ClassLoader.php @@ -37,57 +37,130 @@ namespace Composer\Autoload; * * @author Fabien Potencier <fabien@symfony.com> * @author Jordi Boggiano <j.boggiano@seld.be> - * @see http://www.php-fig.org/psr/psr-0/ - * @see http://www.php-fig.org/psr/psr-4/ + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { + /** @var ?string */ + private $vendorDir; + // PSR-4 + /** + * @var array[] + * @psalm-var array<string, array<string, int>> + */ private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array<string, array<int, string>> + */ private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array<string, string> + */ private $fallbackDirsPsr4 = array(); // PSR-0 + /** + * @var array[] + * @psalm-var array<string, array<string, string[]>> + */ private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array<string, string> + */ private $fallbackDirsPsr0 = array(); + /** @var bool */ private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array<string, string> + */ private $classMap = array(); + + /** @var bool */ private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array<string, bool> + */ private $missingClasses = array(); + + /** @var ?string */ private $apcuPrefix; + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + } + + /** + * @return string[] + */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', $this->prefixesPsr0); + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } + /** + * @return array[] + * @psalm-return array<string, array<int, string>> + */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } + /** + * @return array[] + * @psalm-return array<string, string> + */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } + /** + * @return array[] + * @psalm-return array<string, string> + */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } + /** + * @return string[] Array of classname => path + * @psalm-return array<string, string> + */ public function getClassMap() { return $this->classMap; } /** - * @param array $classMap Class to filename map + * @param string[] $classMap Class to filename map + * @psalm-param array<string, string> $classMap + * + * @return void */ public function addClassMap(array $classMap) { @@ -102,9 +175,11 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void */ public function add($prefix, $paths, $prepend = false) { @@ -147,11 +222,13 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException + * + * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { @@ -195,8 +272,10 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 base directories + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void */ public function set($prefix, $paths) { @@ -211,10 +290,12 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException + * + * @return void */ public function setPsr4($prefix, $paths) { @@ -234,6 +315,8 @@ class ClassLoader * Turns on searching the include path for class files. * * @param bool $useIncludePath + * + * @return void */ public function setUseIncludePath($useIncludePath) { @@ -256,6 +339,8 @@ class ClassLoader * that have not been registered with the class map. * * @param bool $classMapAuthoritative + * + * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { @@ -276,6 +361,8 @@ class ClassLoader * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix + * + * @return void */ public function setApcuPrefix($apcuPrefix) { @@ -296,25 +383,44 @@ class ClassLoader * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } } /** * Unregisters this instance as an autoloader. + * + * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } } /** * Loads the given class or interface. * * @param string $class The name of the class - * @return bool|null True if loaded, null otherwise + * @return true|null True if loaded, null otherwise */ public function loadClass($class) { @@ -323,6 +429,8 @@ class ClassLoader return true; } + + return null; } /** @@ -367,6 +475,21 @@ class ClassLoader return $file; } + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup @@ -438,6 +561,10 @@ class ClassLoader * Scope isolated include. * * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + * @private */ function includeFile($file) { diff --git a/web/app/vendor/composer/InstalledVersions.php b/web/app/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..d50e0c9 --- /dev/null +++ b/web/app/vendor/composer/InstalledVersions.php @@ -0,0 +1,350 @@ +<?php + +/* + * This file is part of Composer. + * + * (c) Nils Adermann <naderman@naderman.de> + * Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list<string> + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list<string> + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints($constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = require __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + $installed[] = self::$installed; + + return $installed; + } +} diff --git a/web/app/vendor/composer/autoload_classmap.php b/web/app/vendor/composer/autoload_classmap.php index cdc6ae7..e047a6e 100644 --- a/web/app/vendor/composer/autoload_classmap.php +++ b/web/app/vendor/composer/autoload_classmap.php @@ -6,5 +6,13 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CaseInsensitiveArray' => $vendorDir . '/php-curl-class/php-curl-class/src/Curl.class.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'Curl' => $vendorDir . '/php-curl-class/php-curl-class/src/Curl.class.php', 'ParsedownMath' => $vendorDir . '/parsedown-math/ParsedownMath.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); diff --git a/web/app/vendor/composer/autoload_files.php b/web/app/vendor/composer/autoload_files.php index c25686b..e4823db 100644 --- a/web/app/vendor/composer/autoload_files.php +++ b/web/app/vendor/composer/autoload_files.php @@ -6,5 +6,8 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', + '16eed290c5592c18dc3f16802ad3d0e4' => $vendorDir . '/ivopetkov/html5-dom-document-php/autoload.php', ); diff --git a/web/app/vendor/composer/autoload_psr4.php b/web/app/vendor/composer/autoload_psr4.php index bd5bb53..d7892aa 100644 --- a/web/app/vendor/composer/autoload_psr4.php +++ b/web/app/vendor/composer/autoload_psr4.php @@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), 'Gregwar\\' => array($vendorDir . '/gregwar/captcha/src/Gregwar'), diff --git a/web/app/vendor/composer/autoload_real.php b/web/app/vendor/composer/autoload_real.php index a6fbe24..8c78c5f 100644 --- a/web/app/vendor/composer/autoload_real.php +++ b/web/app/vendor/composer/autoload_real.php @@ -22,13 +22,15 @@ class ComposerAutoloaderInit0d7c2cd5c2dbf2120e4372996869e900 return self::$loader; } + require __DIR__ . '/platform_check.php'; + spl_autoload_register(array('ComposerAutoloaderInit0d7c2cd5c2dbf2120e4372996869e900', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInit0d7c2cd5c2dbf2120e4372996869e900', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { - require_once __DIR__ . '/autoload_static.php'; + require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit0d7c2cd5c2dbf2120e4372996869e900::getInitializer($loader)); } else { @@ -63,11 +65,16 @@ class ComposerAutoloaderInit0d7c2cd5c2dbf2120e4372996869e900 } } +/** + * @param string $fileIdentifier + * @param string $file + * @return void + */ function composerRequire0d7c2cd5c2dbf2120e4372996869e900($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - require $file; - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; } } diff --git a/web/app/vendor/composer/autoload_static.php b/web/app/vendor/composer/autoload_static.php index 3fc152b..506d13a 100644 --- a/web/app/vendor/composer/autoload_static.php +++ b/web/app/vendor/composer/autoload_static.php @@ -7,12 +7,16 @@ namespace Composer\Autoload; class ComposerStaticInit0d7c2cd5c2dbf2120e4372996869e900 { public static $files = array ( + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', + '16eed290c5592c18dc3f16802ad3d0e4' => __DIR__ . '/..' . '/ivopetkov/html5-dom-document-php/autoload.php', ); public static $prefixLengthsPsr4 = array ( 'S' => array ( + 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Component\\Finder\\' => 25, ), 'P' => @@ -26,6 +30,10 @@ class ComposerStaticInit0d7c2cd5c2dbf2120e4372996869e900 ); public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), 'Symfony\\Component\\Finder\\' => array ( 0 => __DIR__ . '/..' . '/symfony/finder', @@ -58,7 +66,15 @@ class ComposerStaticInit0d7c2cd5c2dbf2120e4372996869e900 ); public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CaseInsensitiveArray' => __DIR__ . '/..' . '/php-curl-class/php-curl-class/src/Curl.class.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Curl' => __DIR__ . '/..' . '/php-curl-class/php-curl-class/src/Curl.class.php', 'ParsedownMath' => __DIR__ . '/..' . '/parsedown-math/ParsedownMath.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/web/app/vendor/composer/installed.json b/web/app/vendor/composer/installed.json index f5ef1af..4b3dccd 100644 --- a/web/app/vendor/composer/installed.json +++ b/web/app/vendor/composer/installed.json @@ -1,303 +1,540 @@ -[ - { - "name": "erusev/parsedown", - "version": "1.7.4", - "version_normalized": "1.7.4.0", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "time": "2019-12-30T22:54:17+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ] - }, - { - "name": "ezyang/htmlpurifier", - "version": "v4.16.0", - "version_normalized": "4.16.0.0", - "source": { - "type": "git", - "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", - "shasum": "" - }, - "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" - }, - "require-dev": { - "cerdic/css-tidy": "^1.7 || ^2.0", - "simpletest/simpletest": "dev-master" - }, - "suggest": { - "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", - "ext-bcmath": "Used for unit conversion and imagecrash protection", - "ext-iconv": "Converts text to and from non-UTF-8 encodings", - "ext-tidy": "Used for pretty-printing HTML" - }, - "time": "2022-09-18T07:06:19+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "files": [ - "library/HTMLPurifier.composer.php" +{ + "packages": [ + { + "name": "erusev/parsedown", + "version": "1.7.4", + "version_normalized": "1.7.4.0", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "time": "2019-12-30T22:54:17+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], - "psr-0": { - "HTMLPurifier": "library/" + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "install-path": "../erusev/parsedown" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.16.0", + "version_normalized": "4.16.0.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" }, - "exclude-from-classmap": [ - "/library/HTMLPurifier/Language/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-or-later" - ], - "authors": [ - { - "name": "Edward Z. Yang", - "email": "admin@htmlpurifier.org", - "homepage": "http://ezyang.com" - } - ], - "description": "Standards compliant HTML filter written in PHP", - "homepage": "http://htmlpurifier.org/", - "keywords": [ - "html" - ] - }, - { - "name": "gregwar/captcha", - "version": "v1.1.9", - "version_normalized": "1.1.9.0", - "source": { - "type": "git", - "url": "https://github.com/Gregwar/Captcha.git", - "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", - "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", - "shasum": "" - }, - "require": { - "ext-gd": "*", - "ext-mbstring": "*", - "php": ">=5.3.0", - "symfony/finder": "*" - }, - "require-dev": { - "phpunit/phpunit": "^6.4" - }, - "time": "2020-03-24T14:39:05+00:00", - "type": "captcha", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Gregwar\\": "src/Gregwar" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Grégoire Passault", - "email": "g.passault@gmail.com", - "homepage": "http://www.gregwar.com/" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "shasum": "" }, - { - "name": "Jeremy Livingston", - "email": "jeremy.j.livingston@gmail.com" - } - ], - "description": "Captcha generator", - "homepage": "https://github.com/Gregwar/Captcha", - "keywords": [ - "bot", - "captcha", - "spam" - ] - }, - { - "name": "phpmailer/phpmailer", - "version": "v6.6.5", - "version_normalized": "6.6.5.0", - "source": { - "type": "git", - "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "8b6386d7417526d1ea4da9edb70b8352f7543627" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/8b6386d7417526d1ea4da9edb70b8352f7543627", - "reference": "8b6386d7417526d1ea4da9edb70b8352f7543627", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-filter": "*", - "ext-hash": "*", - "php": ">=5.5.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.2", - "php-parallel-lint/php-console-highlighter": "^1.0.0", - "php-parallel-lint/php-parallel-lint": "^1.3.2", - "phpcompatibility/php-compatibility": "^9.3.5", - "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6.2", - "yoast/phpunit-polyfills": "^1.0.0" - }, - "suggest": { - "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", - "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", - "league/oauth2-google": "Needed for Google XOAUTH2 authentication", - "psr/log": "For optional PSR-3 debug logging", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", - "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" - }, - "time": "2022-10-07T12:23:10+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "PHPMailer\\PHPMailer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-only" - ], - "authors": [ - { - "name": "Marcus Bointon", - "email": "phpmailer@synchromedia.co.uk" + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" }, - { - "name": "Jim Jagielski", - "email": "jimjag@gmail.com" + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" }, - { - "name": "Andy Prevost", - "email": "codeworxtech@users.sourceforge.net" + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" }, - { - "name": "Brent R. Matzelle" - } - ], - "description": "PHPMailer is a full-featured email creation and transfer class for PHP", - "funding": [ - { - "url": "https://github.com/Synchro", - "type": "github" - } - ] - }, - { - "name": "symfony/finder", - "version": "v6.1.3", - "version_normalized": "6.1.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709" + "time": "2022-09-18T07:06:19+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "install-path": "../ezyang/htmlpurifier" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/39696bff2c2970b3779a5cac7bf9f0b88fc2b709", - "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "symfony/filesystem": "^6.0" - }, - "time": "2022-07-29T07:42:06+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" + { + "name": "gregwar/captcha", + "version": "v1.1.9", + "version_normalized": "1.1.9.0", + "source": { + "type": "git", + "url": "https://github.com/Gregwar/Captcha.git", + "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5" }, - "exclude-from-classmap": [ - "/Tests/" - ] + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", + "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "php": ">=5.3.0", + "symfony/finder": "*" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "time": "2020-03-24T14:39:05+00:00", + "type": "captcha", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Gregwar\\": "src/Gregwar" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Passault", + "email": "g.passault@gmail.com", + "homepage": "http://www.gregwar.com/" + }, + { + "name": "Jeremy Livingston", + "email": "jeremy.j.livingston@gmail.com" + } + ], + "description": "Captcha generator", + "homepage": "https://github.com/Gregwar/Captcha", + "keywords": [ + "bot", + "captcha", + "spam" + ], + "install-path": "../gregwar/captcha" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + { + "name": "ivopetkov/html5-dom-document-php", + "version": "v2.4.0", + "version_normalized": "2.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/ivopetkov/html5-dom-document-php.git", + "reference": "32c5ba748d661a9654c190bf70ce2854eaf5ad22" }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ivopetkov/html5-dom-document-php/zipball/32c5ba748d661a9654c190bf70ce2854eaf5ad22", + "reference": "32c5ba748d661a9654c190bf70ce2854eaf5ad22", + "shasum": "" }, - { - "url": "https://github.com/fabpot", - "type": "github" + "require": { + "ext-dom": "*", + "php": "7.0.*|7.1.*|7.2.*|7.3.*|7.4.*|8.0.*|8.1.*|8.2.*" }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ] - } -] + "require-dev": { + "ivopetkov/docs-generator": "1.*" + }, + "time": "2022-12-17T00:20:55+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivo Petkov", + "email": "ivo@ivopetkov.com", + "homepage": "http://ivopetkov.com" + } + ], + "description": "HTML5 DOMDocument PHP library (extends DOMDocument)", + "support": { + "issues": "https://github.com/ivopetkov/html5-dom-document-php/issues", + "source": "https://github.com/ivopetkov/html5-dom-document-php/tree/v2.4.0" + }, + "install-path": "../ivopetkov/html5-dom-document-php" + }, + { + "name": "php-curl-class/php-curl-class", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-curl-class/php-curl-class.git", + "reference": "24a93bdc51058ad50d219842b63f7f2e0cb350ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-curl-class/php-curl-class/zipball/24a93bdc51058ad50d219842b63f7f2e0cb350ac", + "reference": "24a93bdc51058ad50d219842b63f7f2e0cb350ac", + "shasum": "" + }, + "time": "2014-04-12T09:46:33+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "description": "PHP Curl Class is an object-oriented wrapper of the PHP cURL extension.", + "support": { + "issues": "https://github.com/php-curl-class/php-curl-class/issues", + "source": "https://github.com/php-curl-class/php-curl-class/tree/2.0.0" + }, + "install-path": "../php-curl-class/php-curl-class" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.6.5", + "version_normalized": "6.6.5.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "8b6386d7417526d1ea4da9edb70b8352f7543627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/8b6386d7417526d1ea4da9edb70b8352f7543627", + "reference": "8b6386d7417526d1ea4da9edb70b8352f7543627", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.2", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.6.2", + "yoast/phpunit-polyfills": "^1.0.0" + }, + "suggest": { + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "time": "2022-10-07T12:23:10+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "install-path": "../phpmailer/phpmailer" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-01-02T09:53:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/finder", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "time": "2022-07-29T07:37:50+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/finder" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-05-10T07:21:04+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/web/app/vendor/composer/installed.php b/web/app/vendor/composer/installed.php new file mode 100644 index 0000000..32b055e --- /dev/null +++ b/web/app/vendor/composer/installed.php @@ -0,0 +1,104 @@ +<?php return array( + 'root' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'reference' => 'd6997b84758fbe52d9d90a2d5fe2f2e06806b176', + 'name' => '__root__', + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'reference' => 'd6997b84758fbe52d9d90a2d5fe2f2e06806b176', + 'dev_requirement' => false, + ), + 'erusev/parsedown' => array( + 'pretty_version' => '1.7.4', + 'version' => '1.7.4.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../erusev/parsedown', + 'aliases' => array(), + 'reference' => 'cb17b6477dfff935958ba01325f2e8a2bfa6dab3', + 'dev_requirement' => false, + ), + 'ezyang/htmlpurifier' => array( + 'pretty_version' => 'v4.16.0', + 'version' => '4.16.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ezyang/htmlpurifier', + 'aliases' => array(), + 'reference' => '523407fb06eb9e5f3d59889b3978d5bfe94299c8', + 'dev_requirement' => false, + ), + 'gregwar/captcha' => array( + 'pretty_version' => 'v1.1.9', + 'version' => '1.1.9.0', + 'type' => 'captcha', + 'install_path' => __DIR__ . '/../gregwar/captcha', + 'aliases' => array(), + 'reference' => '4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5', + 'dev_requirement' => false, + ), + 'ivopetkov/html5-dom-document-php' => array( + 'pretty_version' => 'v2.4.0', + 'version' => '2.4.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ivopetkov/html5-dom-document-php', + 'aliases' => array(), + 'reference' => '32c5ba748d661a9654c190bf70ce2854eaf5ad22', + 'dev_requirement' => false, + ), + 'php-curl-class/php-curl-class' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-curl-class/php-curl-class', + 'aliases' => array(), + 'reference' => '24a93bdc51058ad50d219842b63f7f2e0cb350ac', + 'dev_requirement' => false, + ), + 'phpmailer/phpmailer' => array( + 'pretty_version' => 'v6.6.5', + 'version' => '6.6.5.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpmailer/phpmailer', + 'aliases' => array(), + 'reference' => '8b6386d7417526d1ea4da9edb70b8352f7543627', + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66', + 'dev_requirement' => false, + ), + 'symfony/finder' => array( + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/finder', + 'aliases' => array(), + 'reference' => '7872a66f57caffa2916a584db1aa7f12adc76f8c', + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'reference' => 'cfa0ae98841b9e461207c13ab093d76b0fa7bace', + 'dev_requirement' => false, + ), + ), +); diff --git a/web/app/vendor/composer/platform_check.php b/web/app/vendor/composer/platform_check.php new file mode 100644 index 0000000..a8b98d5 --- /dev/null +++ b/web/app/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ +<?php + +// platform_check.php @generated by Composer + +$issues = array(); + +if (!(PHP_VERSION_ID >= 70205)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/LICENSE b/web/app/vendor/ivopetkov/html5-dom-document-php/LICENSE new file mode 100644 index 0000000..c640f81 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) Ivo Petkov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/autoload.php b/web/app/vendor/ivopetkov/html5-dom-document-php/autoload.php new file mode 100644 index 0000000..276ed48 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/autoload.php @@ -0,0 +1,22 @@ +<?php + +/* + * HTML5 DOMDocument PHP library (extends DOMDocument) + * https://github.com/ivopetkov/html5-dom-document-php + * Copyright (c) Ivo Petkov + * Free to use under the MIT license. + */ + +$classes = array( + 'IvoPetkov\HTML5DOMDocument' => __DIR__ . '/src/HTML5DOMDocument.php', + 'IvoPetkov\HTML5DOMDocument\Internal\QuerySelectors' => __DIR__ . '/src/HTML5DOMDocument/Internal/QuerySelectors.php', + 'IvoPetkov\HTML5DOMElement' => __DIR__ . '/src/HTML5DOMElement.php', + 'IvoPetkov\HTML5DOMNodeList' => __DIR__ . '/src/HTML5DOMNodeList.php', + 'IvoPetkov\HTML5DOMTokenList' => __DIR__ . '/src/HTML5DOMTokenList.php' +); + +spl_autoload_register(function ($class) use ($classes) { + if (isset($classes[$class])) { + require $classes[$class]; + } +}); diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/composer.json b/web/app/vendor/ivopetkov/html5-dom-document-php/composer.json new file mode 100644 index 0000000..11aa395 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/composer.json @@ -0,0 +1,24 @@ +{ + "name": "ivopetkov/html5-dom-document-php", + "description": "HTML5 DOMDocument PHP library (extends DOMDocument)", + "license": "MIT", + "authors": [ + { + "name": "Ivo Petkov", + "email": "ivo@ivopetkov.com", + "homepage": "http://ivopetkov.com" + } + ], + "require": { + "php": "7.0.*|7.1.*|7.2.*|7.3.*|7.4.*|8.0.*|8.1.*|8.2.*", + "ext-dom": "*" + }, + "require-dev": { + "ivopetkov/docs-generator": "1.*" + }, + "autoload": { + "files": [ + "autoload.php" + ] + } +} \ No newline at end of file diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument.php b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument.php new file mode 100644 index 0000000..b561f93 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument.php @@ -0,0 +1,747 @@ +<?php + +/* + * HTML5 DOMDocument PHP library (extends DOMDocument) + * https://github.com/ivopetkov/html5-dom-document-php + * Copyright (c) Ivo Petkov + * Free to use under the MIT license. + */ + +namespace IvoPetkov; + +use IvoPetkov\HTML5DOMDocument\Internal\QuerySelectors; + +/** + * Represents a live (can be manipulated) representation of a HTML5 document. + * + * @method \IvoPetkov\HTML5DOMElement|false createElement(string $localName, string $value = '') Create new element node. + * @method \IvoPetkov\HTML5DOMElement|false createElementNS(?string $namespace, string $qualifiedName, string $value = '') Create new element node with an associated namespace. + * @method ?\IvoPetkov\HTML5DOMElement getElementById(string $elementId) Searches for an element with a certain id. + */ +class HTML5DOMDocument extends \DOMDocument +{ + + use QuerySelectors; + + /** + * An option passed to loadHTML() and loadHTMLFile() to disable duplicate element IDs exception. + */ + const ALLOW_DUPLICATE_IDS = 67108864; + + /** + * A modification (passed to modify()) that removes all but the last title elements. + */ + const FIX_MULTIPLE_TITLES = 2; + + /** + * A modification (passed to modify()) that removes all but the last metatags with matching name or property attributes. + */ + const FIX_DUPLICATE_METATAGS = 4; + + /** + * A modification (passed to modify()) that merges multiple head elements. + */ + const FIX_MULTIPLE_HEADS = 8; + + /** + * A modification (passed to modify()) that merges multiple body elements. + */ + const FIX_MULTIPLE_BODIES = 16; + + /** + * A modification (passed to modify()) that moves charset metatag and title elements first. + */ + const OPTIMIZE_HEAD = 32; + + /** + * + * @var array + */ + static private $newObjectsCache = []; + + /** + * Indicates whether an HTML code is loaded. + * + * @var boolean + */ + private $loaded = false; + + /** + * Creates a new HTML5DOMDocument object. + * + * @param string $version The version number of the document as part of the XML declaration. + * @param string $encoding The encoding of the document as part of the XML declaration. + */ + public function __construct(string $version = '1.0', string $encoding = '') + { + parent::__construct($version, $encoding); + $this->registerNodeClass('DOMElement', '\IvoPetkov\HTML5DOMElement'); + } + + /** + * Load HTML from a string. + * + * @param string $source The HTML code. + * @param int $options Additional Libxml parameters. + * @return boolean TRUE on success or FALSE on failure. + */ + public function loadHTML($source, $options = 0) + { + // Enables libxml errors handling + $internalErrorsOptionValue = libxml_use_internal_errors(); + if ($internalErrorsOptionValue === false) { + libxml_use_internal_errors(true); + } + + $source = trim($source); + + // Add CDATA around script tags content + $matches = null; + preg_match_all('/<script(.*?)>/', $source, $matches); + if (isset($matches[0])) { + $matches[0] = array_unique($matches[0]); + foreach ($matches[0] as $match) { + if (substr($match, -2, 1) !== '/') { // check if ends with /> + $source = str_replace($match, $match . '<![CDATA[-html5-dom-document-internal-cdata', $source); // Add CDATA after the open tag + } + } + } + $source = str_replace('</script>', '-html5-dom-document-internal-cdata]]></script>', $source); // Add CDATA before the end tag + $source = str_replace('<![CDATA[-html5-dom-document-internal-cdata-html5-dom-document-internal-cdata]]>', '', $source); // Clean empty script tags + $matches = null; + preg_match_all('/\<!\[CDATA\[-html5-dom-document-internal-cdata.*?-html5-dom-document-internal-cdata\]\]>/s', $source, $matches); + if (isset($matches[0])) { + $matches[0] = array_unique($matches[0]); + foreach ($matches[0] as $match) { + if (strpos($match, '</') !== false) { // check if contains </ + $source = str_replace($match, str_replace('</', '<-html5-dom-document-internal-cdata-endtagfix/', $match), $source); + } + } + } + + $autoAddHtmlAndBodyTags = !defined('LIBXML_HTML_NOIMPLIED') || ($options & LIBXML_HTML_NOIMPLIED) === 0; + $autoAddDoctype = !defined('LIBXML_HTML_NODEFDTD') || ($options & LIBXML_HTML_NODEFDTD) === 0; + + $allowDuplicateIDs = ($options & self::ALLOW_DUPLICATE_IDS) !== 0; + + // Add body tag if missing + if ($autoAddHtmlAndBodyTags && $source !== '' && preg_match('/\<!DOCTYPE.*?\>/', $source) === 0 && preg_match('/\<html.*?\>/', $source) === 0 && preg_match('/\<body.*?\>/', $source) === 0 && preg_match('/\<head.*?\>/', $source) === 0) { + $source = '<body>' . $source . '</body>'; + } + + // Add DOCTYPE if missing + if ($autoAddDoctype && strtoupper(substr($source, 0, 9)) !== '<!DOCTYPE') { + $source = "<!DOCTYPE html>\n" . $source; + } + + // Adds temporary head tag + $charsetTag = '<meta data-html5-dom-document-internal-attribute="charset-meta" http-equiv="content-type" content="text/html; charset=utf-8" />'; + $matches = []; + preg_match('/\<head.*?\>/', $source, $matches); + $removeHeadTag = false; + $removeHtmlTag = false; + if (isset($matches[0])) { // has head tag + $insertPosition = strpos($source, $matches[0]) + strlen($matches[0]); + $source = substr($source, 0, $insertPosition) . $charsetTag . substr($source, $insertPosition); + } else { + $matches = []; + preg_match('/\<html.*?\>/', $source, $matches); + if (isset($matches[0])) { // has html tag + $source = str_replace($matches[0], $matches[0] . '<head>' . $charsetTag . '</head>', $source); + } else { + $source = '<head>' . $charsetTag . '</head>' . $source; + $removeHtmlTag = true; + } + $removeHeadTag = true; + } + + // Preserve html entities + $source = preg_replace('/&([a-zA-Z]*);/', 'html5-dom-document-internal-entity1-$1-end', $source); + $source = preg_replace('/&#([0-9]*);/', 'html5-dom-document-internal-entity2-$1-end', $source); + + $result = parent::loadHTML('<?xml encoding="utf-8" ?>' . $source, $options); + if ($internalErrorsOptionValue === false) { + libxml_use_internal_errors(false); + } + if ($result === false) { + return false; + } + $this->encoding = 'utf-8'; + foreach ($this->childNodes as $item) { + if ($item->nodeType === XML_PI_NODE) { + $this->removeChild($item); + break; + } + } + /** @var HTML5DOMElement|null */ + $metaTagElement = $this->getElementsByTagName('meta')->item(0); + if ($metaTagElement !== null) { + if ($metaTagElement->getAttribute('data-html5-dom-document-internal-attribute') === 'charset-meta') { + $headElement = $metaTagElement->parentNode; + $htmlElement = $headElement->parentNode; + $metaTagElement->parentNode->removeChild($metaTagElement); + if ($removeHeadTag && $headElement !== null && $headElement->parentNode !== null && ($headElement->firstChild === null || ($headElement->childNodes->length === 1 && $headElement->firstChild instanceof \DOMText))) { + $headElement->parentNode->removeChild($headElement); + } + if ($removeHtmlTag && $htmlElement !== null && $htmlElement->parentNode !== null && $htmlElement->firstChild === null) { + $htmlElement->parentNode->removeChild($htmlElement); + } + } + } + + if (!$allowDuplicateIDs) { + $matches = []; + preg_match_all('/\sid[\s]*=[\s]*(["\'])(.*?)\1/', $source, $matches); + if (!empty($matches[2]) && max(array_count_values($matches[2])) > 1) { + $elementIDs = []; + $walkChildren = function ($element) use (&$walkChildren, &$elementIDs) { + foreach ($element->childNodes as $child) { + if ($child instanceof \DOMElement) { + if ($child->attributes->length > 0) { // Performance optimization + $id = $child->getAttribute('id'); + if ($id !== '') { + if (isset($elementIDs[$id])) { + throw new \Exception('A DOM node with an ID value "' . $id . '" already exists! Pass the HTML5DOMDocument::ALLOW_DUPLICATE_IDS option to disable this check.'); + } else { + $elementIDs[$id] = true; + } + } + } + $walkChildren($child); + } + } + }; + $walkChildren($this); + } + } + + $this->loaded = true; + return true; + } + + /** + * Load HTML from a file. + * + * @param string $filename The path to the HTML file. + * @param int $options Additional Libxml parameters. + */ + public function loadHTMLFile($filename, $options = 0) + { + return $this->loadHTML(file_get_contents($filename), $options); + } + + /** + * Adds the HTML tag to the document if missing. + * + * @return boolean TRUE on success, FALSE otherwise. + */ + private function addHtmlElementIfMissing(): bool + { + if ($this->getElementsByTagName('html')->length === 0) { + if (!isset(self::$newObjectsCache['htmlelement'])) { + self::$newObjectsCache['htmlelement'] = new \DOMElement('html'); + } + $this->appendChild(clone (self::$newObjectsCache['htmlelement'])); + return true; + } + return false; + } + + /** + * Adds the HEAD tag to the document if missing. + * + * @return boolean TRUE on success, FALSE otherwise. + */ + private function addHeadElementIfMissing(): bool + { + if ($this->getElementsByTagName('head')->length === 0) { + $htmlElement = $this->getElementsByTagName('html')->item(0); + if (!isset(self::$newObjectsCache['headelement'])) { + self::$newObjectsCache['headelement'] = new \DOMElement('head'); + } + $headElement = clone (self::$newObjectsCache['headelement']); + if ($htmlElement->firstChild === null) { + $htmlElement->appendChild($headElement); + } else { + $htmlElement->insertBefore($headElement, $htmlElement->firstChild); + } + return true; + } + return false; + } + + /** + * Adds the BODY tag to the document if missing. + * + * @return boolean TRUE on success, FALSE otherwise. + */ + private function addBodyElementIfMissing(): bool + { + if ($this->getElementsByTagName('body')->length === 0) { + if (!isset(self::$newObjectsCache['bodyelement'])) { + self::$newObjectsCache['bodyelement'] = new \DOMElement('body'); + } + $this->getElementsByTagName('html')->item(0)->appendChild(clone (self::$newObjectsCache['bodyelement'])); + return true; + } + return false; + } + + /** + * Dumps the internal document into a string using HTML formatting. + * + * @param \DOMNode $node Optional parameter to output a subset of the document. + * @return string The document (or node) HTML code as string. + */ + public function saveHTML(\DOMNode $node = null): string + { + $nodeMode = $node !== null; + if ($nodeMode && $node instanceof \DOMDocument) { + $nodeMode = false; + } + + if ($nodeMode) { + if (!isset(self::$newObjectsCache['html5domdocument'])) { + self::$newObjectsCache['html5domdocument'] = new HTML5DOMDocument(); + } + $tempDomDocument = clone (self::$newObjectsCache['html5domdocument']); + if ($node->nodeName === 'html') { + $tempDomDocument->loadHTML('<!DOCTYPE html>'); + $tempDomDocument->appendChild($tempDomDocument->importNode(clone ($node), true)); + $html = $tempDomDocument->saveHTML(); + $html = substr($html, 16); // remove the DOCTYPE + the new line after + } elseif ($node->nodeName === 'head' || $node->nodeName === 'body') { + $tempDomDocument->loadHTML("<!DOCTYPE html>\n<html></html>"); + $tempDomDocument->childNodes[1]->appendChild($tempDomDocument->importNode(clone ($node), true)); + $html = $tempDomDocument->saveHTML(); + $html = substr($html, 22, -7); // remove the DOCTYPE + the new line after + html tag + } else { + $isInHead = false; + $parentNode = $node; + for ($i = 0; $i < 1000; $i++) { + $parentNode = $parentNode->parentNode; + if ($parentNode === null) { + break; + } + if ($parentNode->nodeName === 'body') { + break; + } elseif ($parentNode->nodeName === 'head') { + $isInHead = true; + break; + } + } + $tempDomDocument->loadHTML("<!DOCTYPE html>\n<html>" . ($isInHead ? '<head></head>' : '<body></body>') . '</html>'); + $tempDomDocument->childNodes[1]->childNodes[0]->appendChild($tempDomDocument->importNode(clone ($node), true)); + $html = $tempDomDocument->saveHTML(); + $html = substr($html, 28, -14); // remove the DOCTYPE + the new line + html + body or head tags + } + $html = trim($html); + } else { + $removeHtmlElement = false; + $removeHeadElement = false; + $headElement = $this->getElementsByTagName('head')->item(0); + if ($headElement === null) { + if ($this->addHtmlElementIfMissing()) { + $removeHtmlElement = true; + } + if ($this->addHeadElementIfMissing()) { + $removeHeadElement = true; + } + $headElement = $this->getElementsByTagName('head')->item(0); + } + $meta = $this->createElement('meta'); + $meta->setAttribute('data-html5-dom-document-internal-attribute', 'charset-meta'); + $meta->setAttribute('http-equiv', 'content-type'); + $meta->setAttribute('content', 'text/html; charset=utf-8'); + if ($headElement->firstChild !== null) { + $headElement->insertBefore($meta, $headElement->firstChild); + } else { + $headElement->appendChild($meta); + } + $html = parent::saveHTML(); + $html = rtrim($html, "\n"); + + if ($removeHeadElement) { + $headElement->parentNode->removeChild($headElement); + } else { + $meta->parentNode->removeChild($meta); + } + + if (strpos($html, 'html5-dom-document-internal-entity') !== false) { + $html = preg_replace('/html5-dom-document-internal-entity1-(.*?)-end/', '&$1;', $html); + $html = preg_replace('/html5-dom-document-internal-entity2-(.*?)-end/', '&#$1;', $html); + } + + $codeToRemove = [ + 'html5-dom-document-internal-content', + '<meta data-html5-dom-document-internal-attribute="charset-meta" http-equiv="content-type" content="text/html; charset=utf-8">', + '</area>', '</base>', '</br>', '</col>', '</command>', '</embed>', '</hr>', '</img>', '</input>', '</keygen>', '</link>', '</meta>', '</param>', '</source>', '</track>', '</wbr>', + '<![CDATA[-html5-dom-document-internal-cdata', '-html5-dom-document-internal-cdata]]>', '-html5-dom-document-internal-cdata-endtagfix' + ]; + if ($removeHeadElement) { + $codeToRemove[] = '<head></head>'; + } + if ($removeHtmlElement) { + $codeToRemove[] = '<html></html>'; + } + + $html = str_replace($codeToRemove, '', $html); + } + return $html; + } + + /** + * Dumps the internal document into a file using HTML formatting. + * + * @param string $filename The path to the saved HTML document. + * @return int|false the number of bytes written or FALSE if an error occurred. + */ + #[\ReturnTypeWillChange] // Return type "int|false" is invalid in older supported versions. + public function saveHTMLFile($filename) + { + if (!is_writable($filename)) { + return false; + } + $result = $this->saveHTML(); + file_put_contents($filename, $result); + $bytesWritten = filesize($filename); + if ($bytesWritten === strlen($result)) { + return $bytesWritten; + } + return false; + } + + /** + * Returns the first document element matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname.classname.classname2, .classname.classname2, tagname[attribute-selector], [attribute-selector], "div, p", div p, div > p, div + p and p ~ ul. + * @return HTML5DOMElement|null The result DOMElement or null if not found. + * @throws \InvalidArgumentException + */ + public function querySelector(string $selector) + { + return $this->internalQuerySelector($selector); + } + + /** + * Returns a list of document elements matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname.classname.classname2, .classname.classname2, tagname[attribute-selector], [attribute-selector], "div, p", div p, div > p, div + p and p ~ ul. + * @return HTML5DOMNodeList Returns a list of DOMElements matching the criteria. + * @throws \InvalidArgumentException + */ + public function querySelectorAll(string $selector) + { + return $this->internalQuerySelectorAll($selector); + } + + /** + * Creates an element that will be replaced by the new body in insertHTML. + * + * @param string $name The name of the insert target. + * @return HTML5DOMElement A new DOMElement that must be set in the place where the new body will be inserted. + */ + public function createInsertTarget(string $name) + { + if (!$this->loaded) { + $this->loadHTML(''); + } + $element = $this->createElement('html5-dom-document-insert-target'); + $element->setAttribute('name', $name); + return $element; + } + + /** + * Inserts a HTML document into the current document. The elements from the head and the body will be moved to their proper locations. + * + * @param string $source The HTML code to be inserted. + * @param string $target Body target position. Available values: afterBodyBegin, beforeBodyEnd or insertTarget name. + */ + public function insertHTML(string $source, string $target = 'beforeBodyEnd') + { + $this->insertHTMLMulti([['source' => $source, 'target' => $target]]); + } + + /** + * Inserts multiple HTML documents into the current document. The elements from the head and the body will be moved to their proper locations. + * + * @param array $sources An array containing the source of the document to be inserted in the following format: [ ['source'=>'', 'target'=>''], ['source'=>'', 'target'=>''], ... ] + * @throws \Exception + */ + public function insertHTMLMulti(array $sources) + { + if (!$this->loaded) { + $this->loadHTML(''); + } + + if (!isset(self::$newObjectsCache['html5domdocument'])) { + self::$newObjectsCache['html5domdocument'] = new HTML5DOMDocument(); + } + + $currentDomDocument = &$this; + + $copyAttributes = function ($sourceNode, $targetNode) { + foreach ($sourceNode->attributes as $attributeName => $attribute) { + $targetNode->setAttribute($attributeName, $attribute->value); + } + }; + + $currentDomHTMLElement = null; + $currentDomHeadElement = null; + $currentDomBodyElement = null; + + $insertTargetsList = null; + $prepareInsertTargetsList = function () use (&$insertTargetsList) { + if ($insertTargetsList === null) { + $insertTargetsList = []; + $targetElements = $this->getElementsByTagName('html5-dom-document-insert-target'); + foreach ($targetElements as $targetElement) { + $insertTargetsList[$targetElement->getAttribute('name')] = $targetElement; + } + } + }; + + foreach ($sources as $sourceData) { + if (!isset($sourceData['source'])) { + throw new \Exception('Missing source key'); + } + $source = $sourceData['source']; + $target = isset($sourceData['target']) ? $sourceData['target'] : 'beforeBodyEnd'; + + $domDocument = clone (self::$newObjectsCache['html5domdocument']); + $domDocument->loadHTML($source, self::ALLOW_DUPLICATE_IDS); + + $htmlElement = $domDocument->getElementsByTagName('html')->item(0); + if ($htmlElement !== null) { + if ($htmlElement->attributes->length > 0) { + if ($currentDomHTMLElement === null) { + $currentDomHTMLElement = $this->getElementsByTagName('html')->item(0); + if ($currentDomHTMLElement === null) { + $this->addHtmlElementIfMissing(); + $currentDomHTMLElement = $this->getElementsByTagName('html')->item(0); + } + } + $copyAttributes($htmlElement, $currentDomHTMLElement); + } + } + + $headElement = $domDocument->getElementsByTagName('head')->item(0); + if ($headElement !== null) { + if ($currentDomHeadElement === null) { + $currentDomHeadElement = $this->getElementsByTagName('head')->item(0); + if ($currentDomHeadElement === null) { + $this->addHtmlElementIfMissing(); + $this->addHeadElementIfMissing(); + $currentDomHeadElement = $this->getElementsByTagName('head')->item(0); + } + } + foreach ($headElement->childNodes as $headElementChild) { + $newNode = $currentDomDocument->importNode($headElementChild, true); + if ($newNode !== null) { + $currentDomHeadElement->appendChild($newNode); + } + } + if ($headElement->attributes->length > 0) { + $copyAttributes($headElement, $currentDomHeadElement); + } + } + + $bodyElement = $domDocument->getElementsByTagName('body')->item(0); + if ($bodyElement !== null) { + if ($currentDomBodyElement === null) { + $currentDomBodyElement = $this->getElementsByTagName('body')->item(0); + if ($currentDomBodyElement === null) { + $this->addHtmlElementIfMissing(); + $this->addBodyElementIfMissing(); + $currentDomBodyElement = $this->getElementsByTagName('body')->item(0); + } + } + $bodyElementChildren = $bodyElement->childNodes; + if ($target === 'afterBodyBegin') { + $bodyElementChildrenCount = $bodyElementChildren->length; + for ($i = $bodyElementChildrenCount - 1; $i >= 0; $i--) { + $newNode = $currentDomDocument->importNode($bodyElementChildren->item($i), true); + if ($newNode !== null) { + if ($currentDomBodyElement->firstChild === null) { + $currentDomBodyElement->appendChild($newNode); + } else { + $currentDomBodyElement->insertBefore($newNode, $currentDomBodyElement->firstChild); + } + } + } + } elseif ($target === 'beforeBodyEnd') { + foreach ($bodyElementChildren as $bodyElementChild) { + $newNode = $currentDomDocument->importNode($bodyElementChild, true); + if ($newNode !== null) { + $currentDomBodyElement->appendChild($newNode); + } + } + } else { + $prepareInsertTargetsList(); + if (isset($insertTargetsList[$target])) { + $targetElement = $insertTargetsList[$target]; + $targetElementParent = $targetElement->parentNode; + foreach ($bodyElementChildren as $bodyElementChild) { + $newNode = $currentDomDocument->importNode($bodyElementChild, true); + if ($newNode !== null) { + $targetElementParent->insertBefore($newNode, $targetElement); + } + } + $targetElementParent->removeChild($targetElement); + } + } + if ($bodyElement->attributes->length > 0) { + $copyAttributes($bodyElement, $currentDomBodyElement); + } + } else { // clear the insert target when there is no body element + $prepareInsertTargetsList(); + if (isset($insertTargetsList[$target])) { + $targetElement = $insertTargetsList[$target]; + $targetElement->parentNode->removeChild($targetElement); + } + } + } + } + + /** + * Applies the modifications specified to the DOM document. + * + * @param int $modifications The modifications to apply. Available values: + * - HTML5DOMDocument::FIX_MULTIPLE_TITLES - removes all but the last title elements. + * - HTML5DOMDocument::FIX_DUPLICATE_METATAGS - removes all but the last metatags with matching name or property attributes. + * - HTML5DOMDocument::FIX_MULTIPLE_HEADS - merges multiple head elements. + * - HTML5DOMDocument::FIX_MULTIPLE_BODIES - merges multiple body elements. + * - HTML5DOMDocument::OPTIMIZE_HEAD - moves charset metatag and title elements first. + */ + public function modify($modifications = 0) + { + + $fixMultipleTitles = ($modifications & self::FIX_MULTIPLE_TITLES) !== 0; + $fixDuplicateMetatags = ($modifications & self::FIX_DUPLICATE_METATAGS) !== 0; + $fixMultipleHeads = ($modifications & self::FIX_MULTIPLE_HEADS) !== 0; + $fixMultipleBodies = ($modifications & self::FIX_MULTIPLE_BODIES) !== 0; + $optimizeHead = ($modifications & self::OPTIMIZE_HEAD) !== 0; + + /** @var \DOMNodeList<HTML5DOMElement> */ + $headElements = $this->getElementsByTagName('head'); + + if ($fixMultipleHeads) { // Merges multiple head elements. + if ($headElements->length > 1) { + $firstHeadElement = $headElements->item(0); + while ($headElements->length > 1) { + $nextHeadElement = $headElements->item(1); + $nextHeadElementChildren = $nextHeadElement->childNodes; + $nextHeadElementChildrenCount = $nextHeadElementChildren->length; + for ($i = 0; $i < $nextHeadElementChildrenCount; $i++) { + $firstHeadElement->appendChild($nextHeadElementChildren->item(0)); + } + $nextHeadElement->parentNode->removeChild($nextHeadElement); + } + $headElements = [$firstHeadElement]; + } + } + + foreach ($headElements as $headElement) { + + if ($fixMultipleTitles) { // Remove all title elements except the last one. + $titleTags = $headElement->getElementsByTagName('title'); + $titleTagsCount = $titleTags->length; + for ($i = 0; $i < $titleTagsCount - 1; $i++) { + $node = $titleTags->item($i); + $node->parentNode->removeChild($node); + } + } + + if ($fixDuplicateMetatags) { // Remove all meta tags that has matching name or property attributes. + $metaTags = $headElement->getElementsByTagName('meta'); + if ($metaTags->length > 0) { + $list = []; + $idsList = []; + foreach ($metaTags as $metaTag) { + $id = $metaTag->getAttribute('name'); + if ($id !== '') { + $id = 'name:' . $id; + } else { + $id = $metaTag->getAttribute('property'); + if ($id !== '') { + $id = 'property:' . $id; + } else { + $id = $metaTag->getAttribute('charset'); + if ($id !== '') { + $id = 'charset'; + } + } + } + if (!isset($idsList[$id])) { + $idsList[$id] = 0; + } + $idsList[$id]++; + $list[] = [$metaTag, $id]; + } + foreach ($idsList as $id => $count) { + if ($count > 1 && $id !== '') { + foreach ($list as $i => $item) { + if ($item[1] === $id) { + $node = $item[0]; + $node->parentNode->removeChild($node); + unset($list[$i]); + $count--; + } + if ($count === 1) { + break; + } + } + } + } + } + } + + if ($optimizeHead) { // Moves charset metatag and title elements first. + $titleElement = $headElement->getElementsByTagName('title')->item(0); + $hasTitleElement = false; + if ($titleElement !== null && $titleElement->previousSibling !== null) { + $headElement->insertBefore($titleElement, $headElement->firstChild); + $hasTitleElement = true; + } + $metaTags = $headElement->getElementsByTagName('meta'); + $metaTagsLength = $metaTags->length; + if ($metaTagsLength > 0) { + $charsetMetaTag = null; + $nodesToMove = []; + for ($i = $metaTagsLength - 1; $i >= 0; $i--) { + $nodesToMove[$i] = $metaTags->item($i); + } + for ($i = $metaTagsLength - 1; $i >= 0; $i--) { + $nodeToMove = $nodesToMove[$i]; + if ($charsetMetaTag === null && $nodeToMove->getAttribute('charset') !== '') { + $charsetMetaTag = $nodeToMove; + } + $referenceNode = $headElement->childNodes->item($hasTitleElement ? 1 : 0); + if ($nodeToMove !== $referenceNode) { + $headElement->insertBefore($nodeToMove, $referenceNode); + } + } + if ($charsetMetaTag !== null && $charsetMetaTag->previousSibling !== null) { + $headElement->insertBefore($charsetMetaTag, $headElement->firstChild); + } + } + } + } + + if ($fixMultipleBodies) { // Merges multiple body elements. + $bodyElements = $this->getElementsByTagName('body'); + if ($bodyElements->length > 1) { + $firstBodyElement = $bodyElements->item(0); + while ($bodyElements->length > 1) { + $nextBodyElement = $bodyElements->item(1); + $nextBodyElementChildren = $nextBodyElement->childNodes; + $nextBodyElementChildrenCount = $nextBodyElementChildren->length; + for ($i = 0; $i < $nextBodyElementChildrenCount; $i++) { + $firstBodyElement->appendChild($nextBodyElementChildren->item(0)); + } + $nextBodyElement->parentNode->removeChild($nextBodyElement); + } + } + } + } +} diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument/Internal/QuerySelectors.php b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument/Internal/QuerySelectors.php new file mode 100644 index 0000000..b99ffd0 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument/Internal/QuerySelectors.php @@ -0,0 +1,514 @@ +<?php + +namespace IvoPetkov\HTML5DOMDocument\Internal; + +use IvoPetkov\HTML5DOMElement; + +trait QuerySelectors +{ + + /** + * Returns the first element matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname[attribute-selector] and [attribute-selector]. + * @return HTML5DOMElement|null The result DOMElement or null if not found + */ + private function internalQuerySelector(string $selector) + { + $result = $this->internalQuerySelectorAll($selector, 1); + return $result->item(0); + } + + /** + * Returns a list of document elements matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname[attribute-selector] and [attribute-selector]. + * @param int|null $preferredLimit Preferred maximum number of elements to return. + * @return DOMNodeList Returns a list of DOMElements matching the criteria. + * @throws \InvalidArgumentException + */ + private function internalQuerySelectorAll(string $selector, $preferredLimit = null) + { + $selector = trim($selector); + + $cache = []; + $walkChildren = function (\DOMNode $context, $tagNames, callable $callback) use (&$cache) { + if (!empty($tagNames)) { + $children = []; + foreach ($tagNames as $tagName) { + $elements = $context->getElementsByTagName($tagName); + foreach ($elements as $element) { + $children[] = $element; + } + } + } else { + $getChildren = function () use ($context) { + $result = []; + $process = function (\DOMNode $node) use (&$process, &$result) { + foreach ($node->childNodes as $child) { + if ($child instanceof \DOMElement) { + $result[] = $child; + $process($child); + } + } + }; + $process($context); + return $result; + }; + if ($this === $context) { + $cacheKey = 'walk_children'; + if (!isset($cache[$cacheKey])) { + $cache[$cacheKey] = $getChildren(); + } + $children = $cache[$cacheKey]; + } else { + $children = $getChildren(); + } + } + foreach ($children as $child) { + if ($callback($child) === true) { + return true; + } + } + }; + + $getElementById = function (\DOMNode $context, $id, $tagName) use (&$walkChildren) { + if ($context instanceof \DOMDocument) { + $element = $context->getElementById($id); + if ($element && ($tagName === null || $element->tagName === $tagName)) { + return $element; + } + } else { + $foundElement = null; + $walkChildren($context, $tagName !== null ? [$tagName] : null, function ($element) use ($id, &$foundElement) { + if ($element->attributes->length > 0 && $element->getAttribute('id') === $id) { + $foundElement = $element; + return true; + } + }); + return $foundElement; + } + return null; + }; + + $simpleSelectors = []; + + // all + $simpleSelectors['\*'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + if ($mode === 'validate') { + return true; + } else { + $walkChildren($context, [], function ($element) use ($add) { + if ($add($element)) { + return true; + } + }); + } + }; + + // tagname + $simpleSelectors['[a-zA-Z0-9\-]+'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + $tagNames = []; + foreach ($matches as $match) { + $tagNames[] = strtolower($match[0]); + } + if ($mode === 'validate') { + return array_search($context->tagName, $tagNames) !== false; + } + $walkChildren($context, $tagNames, function ($element) use ($add) { + if ($add($element)) { + return true; + } + }); + }; + + // tagname[target] or [target] // Available values for targets: attr, attr="value", attr~="value", attr|="value", attr^="value", attr$="value", attr*="value" + $simpleSelectors['(?:[a-zA-Z0-9\-]*)(?:\[.+?\])'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + $run = function ($match) use ($mode, $context, $add, $walkChildren) { + $attributeSelectors = explode('][', substr($match[2], 1, -1)); + foreach ($attributeSelectors as $i => $attributeSelector) { + $attributeSelectorMatches = null; + if (preg_match('/^(.+?)(=|~=|\|=|\^=|\$=|\*=)\"(.+?)\"$/', $attributeSelector, $attributeSelectorMatches) === 1) { + $attributeSelectors[$i] = [ + 'name' => strtolower($attributeSelectorMatches[1]), + 'value' => $attributeSelectorMatches[3], + 'operator' => $attributeSelectorMatches[2] + ]; + } else { + $attributeSelectors[$i] = [ + 'name' => $attributeSelector + ]; + } + } + $tagName = strlen($match[1]) > 0 ? strtolower($match[1]) : null; + $check = function ($element) use ($attributeSelectors) { + if ($element->attributes->length > 0) { + foreach ($attributeSelectors as $attributeSelector) { + $isMatch = false; + $attributeValue = $element->getAttribute($attributeSelector['name']); + if (isset($attributeSelector['value'])) { + $valueToMatch = $attributeSelector['value']; + switch ($attributeSelector['operator']) { + case '=': + if ($attributeValue === $valueToMatch) { + $isMatch = true; + } + break; + case '~=': + $words = preg_split("/[\s]+/", $attributeValue); + if (array_search($valueToMatch, $words) !== false) { + $isMatch = true; + } + break; + + case '|=': + if ($attributeValue === $valueToMatch || strpos($attributeValue, $valueToMatch . '-') === 0) { + $isMatch = true; + } + break; + + case '^=': + if (strpos($attributeValue, $valueToMatch) === 0) { + $isMatch = true; + } + break; + + case '$=': + if (substr($attributeValue, -strlen($valueToMatch)) === $valueToMatch) { + $isMatch = true; + } + break; + + case '*=': + if (strpos($attributeValue, $valueToMatch) !== false) { + $isMatch = true; + } + break; + } + } else { + if ($attributeValue !== '') { + $isMatch = true; + } + } + if (!$isMatch) { + return false; + } + } + return true; + } + return false; + }; + if ($mode === 'validate') { + return ($tagName === null ? true : $context->tagName === $tagName) && $check($context); + } else { + $walkChildren($context, $tagName !== null ? [$tagName] : null, function ($element) use ($check, $add) { + if ($check($element)) { + if ($add($element)) { + return true; + } + } + }); + } + }; + // todo optimize + foreach ($matches as $match) { + if ($mode === 'validate') { + if ($run($match)) { + return true; + } + } else { + $run($match); + } + } + if ($mode === 'validate') { + return false; + } + }; + + // tagname#id or #id + $simpleSelectors['(?:[a-zA-Z0-9\-]*)#(?:[a-zA-Z0-9\-\_]+?)'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($getElementById) { + $run = function ($match) use ($mode, $context, $add, $getElementById) { + $tagName = strlen($match[1]) > 0 ? strtolower($match[1]) : null; + $id = $match[2]; + if ($mode === 'validate') { + return ($tagName === null ? true : $context->tagName === $tagName) && $context->getAttribute('id') === $id; + } else { + $element = $getElementById($context, $id, $tagName); + if ($element) { + $add($element); + } + } + }; + // todo optimize + foreach ($matches as $match) { + if ($mode === 'validate') { + if ($run($match)) { + return true; + } + } else { + $run($match); + } + } + if ($mode === 'validate') { + return false; + } + }; + + // tagname.classname, .classname, tagname.classname.classname2, .classname.classname2 + $simpleSelectors['(?:[a-zA-Z0-9\-]*)\.(?:[a-zA-Z0-9\-\_\.]+?)'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + $rawData = []; // Array containing [tag, classnames] + $tagNames = []; + foreach ($matches as $match) { + $tagName = strlen($match[1]) > 0 ? $match[1] : null; + $classes = explode('.', $match[2]); + if (empty($classes)) { + continue; + } + $rawData[] = [$tagName, $classes]; + if ($tagName !== null) { + $tagNames[] = $tagName; + } + } + $check = function ($element) use ($rawData) { + if ($element->attributes->length > 0) { + $classAttribute = ' ' . $element->getAttribute('class') . ' '; + $tagName = $element->tagName; + foreach ($rawData as $rawMatch) { + if ($rawMatch[0] !== null && $tagName !== $rawMatch[0]) { + continue; + } + $allClassesFound = true; + foreach ($rawMatch[1] as $class) { + if (strpos($classAttribute, ' ' . $class . ' ') === false) { + $allClassesFound = false; + break; + } + } + if ($allClassesFound) { + return true; + } + } + } + return false; + }; + if ($mode === 'validate') { + return $check($context); + } + $walkChildren($context, $tagNames, function ($element) use ($check, $add) { + if ($check($element)) { + if ($add($element)) { + return true; + } + } + }); + }; + + $isMatchingElement = function (\DOMNode $context, string $selector) use ($simpleSelectors) { + foreach ($simpleSelectors as $simpleSelector => $callback) { + $match = null; + if (preg_match('/^' . (str_replace('?:', '', $simpleSelector)) . '$/', $selector, $match) === 1) { + return call_user_func($callback, 'validate', [$match], $context); + } + } + }; + + $complexSelectors = []; + + $getMatchingElements = function (\DOMNode $context, string $selector, $preferredLimit = null) use (&$simpleSelectors, &$complexSelectors) { + + $processSelector = function (string $mode, string $selector, $operator = null) use (&$processSelector, $simpleSelectors, $complexSelectors, $context, $preferredLimit) { + $supportedSimpleSelectors = array_keys($simpleSelectors); + $supportedSimpleSelectorsExpression = '(?:(?:' . implode(')|(?:', $supportedSimpleSelectors) . '))'; + $supportedSelectors = $supportedSimpleSelectors; + $supportedComplexOperators = array_keys($complexSelectors); + if ($operator === null) { + $operator = ','; + foreach ($supportedComplexOperators as $complexOperator) { + array_unshift($supportedSelectors, '(?:(?:(?:' . $supportedSimpleSelectorsExpression . '\s*\\' . $complexOperator . '\s*))+' . $supportedSimpleSelectorsExpression . ')'); + } + } + $supportedSelectorsExpression = '(?:(?:' . implode(')|(?:', $supportedSelectors) . '))'; + + $vallidationExpression = '/^(?:(?:' . $supportedSelectorsExpression . '\s*\\' . $operator . '\s*))*' . $supportedSelectorsExpression . '$/'; + if (preg_match($vallidationExpression, $selector) !== 1) { + return false; + } + $selector .= $operator; // append the seprator at the back for easier matching below + + $result = []; + if ($mode === 'execute') { + $add = function ($element) use ($preferredLimit, &$result) { + $found = false; + foreach ($result as $addedElement) { + if ($addedElement === $element) { + $found = true; + break; + } + } + if (!$found) { + $result[] = $element; + if ($preferredLimit !== null && sizeof($result) >= $preferredLimit) { + return true; + } + } + return false; + }; + } + + $selectorsToCall = []; + $addSelectorToCall = function ($type, $selector, $argument) use (&$selectorsToCall) { + $previousIndex = sizeof($selectorsToCall) - 1; + // todo optimize complex too + if ($type === 1 && isset($selectorsToCall[$previousIndex]) && $selectorsToCall[$previousIndex][0] === $type && $selectorsToCall[$previousIndex][1] === $selector) { + $selectorsToCall[$previousIndex][2][] = $argument; + } else { + $selectorsToCall[] = [$type, $selector, [$argument]]; + } + }; + for ($i = 0; $i < 100000; $i++) { + $matches = null; + preg_match('/^(?<subselector>' . $supportedSelectorsExpression . ')\s*\\' . $operator . '\s*/', $selector, $matches); // getting the next subselector + if (isset($matches['subselector'])) { + $subSelector = $matches['subselector']; + $selectorFound = false; + foreach ($simpleSelectors as $simpleSelector => $callback) { + $match = null; + if (preg_match('/^' . (str_replace('?:', '', $simpleSelector)) . '$/', $subSelector, $match) === 1) { // if simple selector + if ($mode === 'parse') { + $result[] = $match[0]; + } else { + $addSelectorToCall(1, $simpleSelector, $match); + //call_user_func($callback, 'execute', $match, $context, $add); + } + $selectorFound = true; + break; + } + } + if (!$selectorFound) { + foreach ($complexSelectors as $complexOperator => $callback) { + $subSelectorParts = $processSelector('parse', $subSelector, $complexOperator); + if ($subSelectorParts !== false) { + $addSelectorToCall(2, $complexOperator, $subSelectorParts); + //call_user_func($callback, $subSelectorParts, $context, $add); + $selectorFound = true; + break; + } + } + } + if (!$selectorFound) { + throw new \Exception('Internal error for selector "' . $selector . '"!'); + } + $selector = substr($selector, strlen($matches[0])); // remove the matched subselector and continue parsing + if (strlen($selector) === 0) { + break; + } + } + } + foreach ($selectorsToCall as $selectorToCall) { + if ($selectorToCall[0] === 1) { // is simple selector + call_user_func($simpleSelectors[$selectorToCall[1]], 'execute', $selectorToCall[2], $context, $add); + } else { // is complex selector + call_user_func($complexSelectors[$selectorToCall[1]], $selectorToCall[2][0], $context, $add); // todo optimize and send all arguments + } + } + return $result; + }; + + return $processSelector('execute', $selector); + }; + + // div p (space between) - all <p> elements inside <div> elements + $complexSelectors[' '] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + $temp = array_merge($temp, $getMatchingElements($element, $part)); + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + // div > p - all <p> elements where the parent is a <div> element + $complexSelectors['>'] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements, &$isMatchingElement) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + foreach ($element->childNodes as $child) { + if ($child instanceof \DOMElement && $isMatchingElement($child, $part)) { + $temp[] = $child; + } + } + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + // div + p - all <p> elements that are placed immediately after <div> elements + $complexSelectors['+'] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements, &$isMatchingElement) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + if ($element->nextSibling !== null && $isMatchingElement($element->nextSibling, $part)) { + $temp[] = $element->nextSibling; + } + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + // p ~ ul - all <ul> elements that are preceded by a <p> element + $complexSelectors['~'] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements, &$isMatchingElement) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + $nextSibling = $element->nextSibling; + while ($nextSibling !== null) { + if ($isMatchingElement($nextSibling, $part)) { + $temp[] = $nextSibling; + } + $nextSibling = $nextSibling->nextSibling; + } + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + $result = $getMatchingElements($this, $selector, $preferredLimit); + if ($result === false) { + throw new \InvalidArgumentException('Unsupported selector (' . $selector . ')'); + } + return new \IvoPetkov\HTML5DOMNodeList($result); + } +} diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMElement.php b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMElement.php new file mode 100644 index 0000000..6647c8c --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMElement.php @@ -0,0 +1,240 @@ +<?php + +/* + * HTML5 DOMDocument PHP library (extends DOMDocument) + * https://github.com/ivopetkov/html5-dom-document-php + * Copyright (c) Ivo Petkov + * Free to use under the MIT license. + */ + +namespace IvoPetkov; + +use IvoPetkov\HTML5DOMDocument\Internal\QuerySelectors; + +/** + * Represents a live (can be manipulated) representation of an element in a HTML5 document. + * + * @property string $innerHTML The HTML code inside the element. + * @property string $outerHTML The HTML code for the element including the code inside. + * @property \IvoPetkov\HTML5DOMTokenList $classList A collection of the class attributes of the element. + */ +class HTML5DOMElement extends \DOMElement +{ + + use QuerySelectors; + + /** + * + * @var array + */ + static private $foundEntitiesCache = [[], []]; + + /** + * + * @var array + */ + static private $newObjectsCache = []; + + /* + * + * @var HTML5DOMTokenList + */ + private $classList = null; + + /** + * Returns the value for the property specified. + * + * @param string $name + * @return string + * @throws \Exception + */ + public function __get(string $name) + { + if ($name === 'innerHTML') { + if ($this->firstChild === null) { + return ''; + } + $html = $this->ownerDocument->saveHTML($this); + $nodeName = $this->nodeName; + return preg_replace('@^<' . $nodeName . '[^>]*>|</' . $nodeName . '>$@', '', $html); + } elseif ($name === 'outerHTML') { + if ($this->firstChild === null) { + $nodeName = $this->nodeName; + $attributes = $this->getAttributes(); + $result = '<' . $nodeName . ''; + foreach ($attributes as $name => $value) { + $result .= ' ' . $name . '="' . htmlentities($value) . '"'; + } + if (array_search($nodeName, ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']) === false) { + $result .= '></' . $nodeName . '>'; + } else { + $result .= '/>'; + } + return $result; + } + return $this->ownerDocument->saveHTML($this); + } elseif ($name === 'classList') { + if ($this->classList === null) { + $this->classList = new HTML5DOMTokenList($this, 'class'); + } + return $this->classList; + } + throw new \Exception('Undefined property: HTML5DOMElement::$' . $name); + } + + /** + * Sets the value for the property specified. + * + * @param string $name + * @param string $value + * @throws \Exception + */ + public function __set(string $name, $value) + { + if ($name === 'innerHTML') { + while ($this->hasChildNodes()) { + $this->removeChild($this->firstChild); + } + if (!isset(self::$newObjectsCache['html5domdocument'])) { + self::$newObjectsCache['html5domdocument'] = new \IvoPetkov\HTML5DOMDocument(); + } + $tmpDoc = clone (self::$newObjectsCache['html5domdocument']); + $tmpDoc->loadHTML('<body>' . $value . '</body>', HTML5DOMDocument::ALLOW_DUPLICATE_IDS); + foreach ($tmpDoc->getElementsByTagName('body')->item(0)->childNodes as $node) { + $node = $this->ownerDocument->importNode($node, true); + $this->appendChild($node); + } + return; + } elseif ($name === 'outerHTML') { + if (!isset(self::$newObjectsCache['html5domdocument'])) { + self::$newObjectsCache['html5domdocument'] = new \IvoPetkov\HTML5DOMDocument(); + } + $tmpDoc = clone (self::$newObjectsCache['html5domdocument']); + $tmpDoc->loadHTML('<body>' . $value . '</body>', HTML5DOMDocument::ALLOW_DUPLICATE_IDS); + foreach ($tmpDoc->getElementsByTagName('body')->item(0)->childNodes as $node) { + $node = $this->ownerDocument->importNode($node, true); + $this->parentNode->insertBefore($node, $this); + } + $this->parentNode->removeChild($this); + return; + } elseif ($name === 'classList') { + $this->setAttribute('class', $value); + return; + } + throw new \Exception('Undefined property: HTML5DOMElement::$' . $name); + } + + /** + * Updates the result value before returning it. + * + * @param string $value + * @return string The updated value + */ + private function updateResult(string $value): string + { + $value = str_replace(self::$foundEntitiesCache[0], self::$foundEntitiesCache[1], $value); + if (strstr($value, 'html5-dom-document-internal-entity') !== false) { + $search = []; + $replace = []; + $matches = []; + preg_match_all('/html5-dom-document-internal-entity([12])-(.*?)-end/', $value, $matches); + $matches[0] = array_unique($matches[0]); + foreach ($matches[0] as $i => $match) { + $search[] = $match; + $replace[] = html_entity_decode(($matches[1][$i] === '1' ? '&' : '&#') . $matches[2][$i] . ';'); + } + $value = str_replace($search, $replace, $value); + self::$foundEntitiesCache[0] = array_merge(self::$foundEntitiesCache[0], $search); + self::$foundEntitiesCache[1] = array_merge(self::$foundEntitiesCache[1], $replace); + unset($search); + unset($replace); + unset($matches); + } + return $value; + } + + /** + * Returns the updated nodeValue Property + * + * @return string The updated $nodeValue + */ + public function getNodeValue(): string + { + return $this->updateResult($this->nodeValue); + } + + /** + * Returns the updated $textContent Property + * + * @return string The updated $textContent + */ + public function getTextContent(): string + { + return $this->updateResult($this->textContent); + } + + /** + * Returns the value for the attribute name specified. + * + * @param string $name The attribute name. + * @return string The attribute value. + * @throws \InvalidArgumentException + */ + public function getAttribute($name): string + { + if ($this->attributes->length === 0) { // Performance optimization + return ''; + } + $value = parent::getAttribute($name); + return $value !== '' ? (strstr($value, 'html5-dom-document-internal-entity') !== false ? $this->updateResult($value) : $value) : ''; + } + + /** + * Returns an array containing all attributes. + * + * @return array An associative array containing all attributes. + */ + public function getAttributes(): array + { + $attributes = []; + foreach ($this->attributes as $attributeName => $attribute) { + $value = $attribute->value; + $attributes[$attributeName] = $value !== '' ? (strstr($value, 'html5-dom-document-internal-entity') !== false ? $this->updateResult($value) : $value) : ''; + } + return $attributes; + } + + /** + * Returns the element outerHTML. + * + * @return string The element outerHTML. + */ + public function __toString(): string + { + return $this->outerHTML; + } + + /** + * Returns the first child element matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname.classname.classname2, .classname.classname2, tagname[attribute-selector], [attribute-selector], "div, p", div p, div > p, div + p and p ~ ul. + * @return HTML5DOMElement|null The result DOMElement or null if not found. + * @throws \InvalidArgumentException + */ + public function querySelector(string $selector) + { + return $this->internalQuerySelector($selector); + } + + /** + * Returns a list of children elements matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname.classname.classname2, .classname.classname2, tagname[attribute-selector], [attribute-selector], "div, p", div p, div > p, div + p and p ~ ul. + * @return HTML5DOMNodeList Returns a list of DOMElements matching the criteria. + * @throws \InvalidArgumentException + */ + public function querySelectorAll(string $selector) + { + return $this->internalQuerySelectorAll($selector); + } +} diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMNodeList.php b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMNodeList.php new file mode 100644 index 0000000..dc07236 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMNodeList.php @@ -0,0 +1,45 @@ +<?php + +/* + * HTML5 DOMDocument PHP library (extends DOMDocument) + * https://github.com/ivopetkov/html5-dom-document-php + * Copyright (c) Ivo Petkov + * Free to use under the MIT license. + */ + +namespace IvoPetkov; + +/** + * Represents a list of DOM nodes. + * + * @property-read int $length The list items count + */ +class HTML5DOMNodeList extends \ArrayObject +{ + + /** + * Returns the item at the specified index. + * + * @param int $index The item index. + * @return \IvoPetkov\HTML5DOMElement|null The item at the specified index or null if not existent. + */ + public function item(int $index) + { + return $this->offsetExists($index) ? $this->offsetGet($index) : null; + } + + /** + * Returns the value for the property specified. + * + * @param string $name The name of the property. + * @return mixed + * @throws \Exception + */ + public function __get(string $name) + { + if ($name === 'length') { + return sizeof($this); + } + throw new \Exception('Undefined property: \IvoPetkov\HTML5DOMNodeList::$' . $name); + } +} diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMTokenList.php b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMTokenList.php new file mode 100644 index 0000000..e917ab4 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMTokenList.php @@ -0,0 +1,266 @@ +<?php + +/* + * HTML5 DOMDocument PHP library (extends DOMDocument) + * https://github.com/ivopetkov/html5-dom-document-php + * Copyright (c) Ivo Petkov + * Free to use under the MIT license. + */ + +namespace IvoPetkov; + +use ArrayIterator; +use DOMElement; + +/** + * Represents a set of space-separated tokens of an element attribute. + * + * @property-read int $length The number of tokens. + * @property-read string $value A space-separated list of the tokens. + */ +class HTML5DOMTokenList +{ + + /** + * @var string + */ + private $attributeName; + + /** + * @var DOMElement + */ + private $element; + + /** + * @var string[] + */ + private $tokens; + + /** + * @var string + */ + private $previousValue; + + /** + * Creates a list of space-separated tokens based on the attribute value of an element. + * + * @param DOMElement $element The DOM element. + * @param string $attributeName The name of the attribute. + */ + public function __construct(DOMElement $element, string $attributeName) + { + $this->element = $element; + $this->attributeName = $attributeName; + $this->previousValue = null; + $this->tokenize(); + } + + /** + * Adds the given tokens to the list. + * + * @param string[] $tokens The tokens you want to add to the list. + * @return void + */ + public function add(string ...$tokens) + { + if (count($tokens) === 0) { + return; + } + foreach ($tokens as $t) { + if (in_array($t, $this->tokens)) { + continue; + } + $this->tokens[] = $t; + } + $this->setAttributeValue(); + } + + /** + * Removes the specified tokens from the list. If the string does not exist in the list, no error is thrown. + * + * @param string[] $tokens The token you want to remove from the list. + * @return void + */ + public function remove(string ...$tokens) + { + if (count($tokens) === 0) { + return; + } + if (count($this->tokens) === 0) { + return; + } + foreach ($tokens as $t) { + $i = array_search($t, $this->tokens); + if ($i === false) { + continue; + } + array_splice($this->tokens, $i, 1); + } + $this->setAttributeValue(); + } + + /** + * Returns an item in the list by its index (returns null if the number is greater than or equal to the length of the list). + * + * @param int $index The zero-based index of the item you want to return. + * @return null|string + */ + public function item(int $index) + { + $this->tokenize(); + if ($index >= count($this->tokens)) { + return null; + } + return $this->tokens[$index]; + } + + /** + * Removes a given token from the list and returns false. If token doesn't exist it's added and the function returns true. + * + * @param string $token The token you want to toggle. + * @param bool $force A Boolean that, if included, turns the toggle into a one way-only operation. If set to false, the token will only be removed but not added again. If set to true, the token will only be added but not removed again. + * @return bool false if the token is not in the list after the call, or true if the token is in the list after the call. + */ + public function toggle(string $token, bool $force = null): bool + { + $this->tokenize(); + $isThereAfter = false; + $i = array_search($token, $this->tokens); + if (is_null($force)) { + if ($i === false) { + $this->tokens[] = $token; + $isThereAfter = true; + } else { + array_splice($this->tokens, $i, 1); + } + } else { + if ($force) { + if ($i === false) { + $this->tokens[] = $token; + } + $isThereAfter = true; + } else { + if ($i !== false) { + array_splice($this->tokens, $i, 1); + } + } + } + $this->setAttributeValue(); + return $isThereAfter; + } + + /** + * Returns true if the list contains the given token, otherwise false. + * + * @param string $token The token you want to check for the existence of in the list. + * @return bool true if the list contains the given token, otherwise false. + */ + public function contains(string $token): bool + { + $this->tokenize(); + return in_array($token, $this->tokens); + } + + /** + * Replaces an existing token with a new token. + * + * @param string $old The token you want to replace. + * @param string $new The token you want to replace $old with. + * @return void + */ + public function replace(string $old, string $new) + { + if ($old === $new) { + return; + } + $this->tokenize(); + $i = array_search($old, $this->tokens); + if ($i !== false) { + $j = array_search($new, $this->tokens); + if ($j === false) { + $this->tokens[$i] = $new; + } else { + array_splice($this->tokens, $i, 1); + } + $this->setAttributeValue(); + } + } + + /** + * + * @return string + */ + public function __toString(): string + { + $this->tokenize(); + return implode(' ', $this->tokens); + } + + /** + * Returns an iterator allowing you to go through all tokens contained in the list. + * + * @return ArrayIterator + */ + public function entries(): ArrayIterator + { + $this->tokenize(); + return new ArrayIterator($this->tokens); + } + + /** + * Returns the value for the property specified + * + * @param string $name The name of the property + * @return string The value of the property specified + * @throws \Exception + */ + public function __get(string $name) + { + if ($name === 'length') { + $this->tokenize(); + return count($this->tokens); + } elseif ($name === 'value') { + return $this->__toString(); + } + throw new \Exception('Undefined property: HTML5DOMTokenList::$' . $name); + } + + /** + * + * @return void + */ + private function tokenize() + { + $current = $this->element->getAttribute($this->attributeName); + if ($this->previousValue === $current) { + return; + } + $this->previousValue = $current; + $tokens = explode(' ', $current); + $finals = []; + foreach ($tokens as $token) { + if ($token === '') { + continue; + } + if (in_array($token, $finals)) { + continue; + } + $finals[] = $token; + } + $this->tokens = $finals; + } + + /** + * + * @return void + */ + private function setAttributeValue() + { + $value = implode(' ', $this->tokens); + if ($this->previousValue === $value) { + return; + } + $this->previousValue = $value; + $this->element->setAttribute($this->attributeName, $value); + } +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/.gitignore b/web/app/vendor/php-curl-class/php-curl-class/.gitignore new file mode 100644 index 0000000..d1502b0 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/.gitignore @@ -0,0 +1,2 @@ +vendor/ +composer.lock diff --git a/web/app/vendor/php-curl-class/php-curl-class/.travis.yml b/web/app/vendor/php-curl-class/php-curl-class/.travis.yml new file mode 100644 index 0000000..a915771 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/.travis.yml @@ -0,0 +1,19 @@ +language: php +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm +matrix: + allow_failures: + - php: 5.6 + - php: hhvm +before_script: + - if [[ "$TRAVIS_PHP_VERSION" == "5.4" ]]; then sh -c "php -S 127.0.0.1:8000 -t tests/PHPCurlClass/ &"; fi + - if [[ "$TRAVIS_PHP_VERSION" == "5.5" ]]; then sh -c "php -S 127.0.0.1:8000 -t tests/PHPCurlClass/ &"; fi + - if [[ "$TRAVIS_PHP_VERSION" == "5.6" ]]; then sh -c "php -S 127.0.0.1:8000 -t tests/PHPCurlClass/ &"; fi + - if [[ "$TRAVIS_PHP_VERSION" == "hhvm" ]]; then sh -c "cd tests && hhvm --mode server --port 8000 --config PHPCurlClass/server.hdf &"; fi +script: + - php -l src/* + - if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then sh -c "cd tests && phpunit --configuration phpunit.xml"; fi diff --git a/web/app/vendor/php-curl-class/php-curl-class/LICENSE b/web/app/vendor/php-curl-class/php-curl-class/LICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <http://unlicense.org/> diff --git a/web/app/vendor/php-curl-class/php-curl-class/README.md b/web/app/vendor/php-curl-class/php-curl-class/README.md new file mode 100644 index 0000000..5934471 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/README.md @@ -0,0 +1,136 @@ +# php-curl-class + +[![Build Status](https://travis-ci.org/php-curl-class/php-curl-class.png?branch=master)](https://travis-ci.org/php-curl-class/php-curl-class) + +PHP Curl Class is an object-oriented wrapper of the PHP cURL extension. + +### Composer + + $ composer require php-curl-class/php-curl-class + +### Quick Start and Examples + +```php +require 'Curl.class.php'; + +$curl = new Curl(); +$curl->get('http://www.example.com/'); +``` + +```php +$curl = new Curl(); +$curl->get('http://www.example.com/search', array( + 'q' => 'keyword', +)); +``` + +```php +$curl = new Curl(); +$curl->post('http://www.example.com/login/', array( + 'username' => 'myusername', + 'password' => 'mypassword', +)); +``` + +```php +$curl = new Curl(); +$curl->setBasicAuthentication('username', 'password'); +$curl->setUserAgent(''); +$curl->setReferrer(''); +$curl->setHeader('X-Requested-With', 'XMLHttpRequest'); +$curl->setCookie('key', 'value'); +$curl->get('http://www.example.com/'); + +if ($curl->error) { + echo $curl->error_code; +} +else { + echo $curl->response; +} + +var_dump($curl->request_headers); +var_dump($curl->response_headers); +``` + +```php +$curl = new Curl(); +$curl->setOpt(CURLOPT_SSL_VERIFYPEER, false); +$curl->get('https://encrypted.example.com/'); +``` + +```php +$curl = new Curl(); +$curl->put('http://api.example.com/user/', array( + 'first_name' => 'Zach', + 'last_name' => 'Borboa', +)); +``` + +```php +$curl = new Curl(); +$curl->patch('http://api.example.com/profile/', array( + 'image' => '@path/to/file.jpg', +)); +``` + +```php +$curl = new Curl(); +$curl->delete('http://api.example.com/user/', array( + 'id' => '1234', +)); +``` + +```php +// Enable gzip compression. +$curl = new Curl(); +$curl->setOpt(CURLOPT_ENCODING , 'gzip'); +$curl->get('https://www.example.com/image.png'); +``` + +```php +// Case-insensitive access to headers. +$curl = new Curl(); +$curl->get('https://www.example.com/image.png'); +echo $curl->response_headers['Content-Type'] . "\n"; // image/png +echo $curl->response_headers['CoNTeNT-TyPE'] . "\n"; // image/png +``` + +```php +$curl->close(); +``` + +```php +// Example access to curl object. +curl_set_opt($curl->curl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1'); +curl_close($curl->curl); +``` + +```php +// Requests in parallel with callback functions. +$curl = new Curl(); +$curl->setOpt(CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1'); + +$curl->success(function($instance) { + echo 'call was successful. response was' . "\n"; + echo $instance->response . "\n"; +}); +$curl->error(function($instance) { + echo 'call was unsuccessful.' . "\n"; + echo 'error code:' . $instance->error_code . "\n"; + echo 'error message:' . $instance->error_message . "\n"; +}); +$curl->complete(function($instance) { + echo 'call completed' . "\n"; +}); + +$curl->get(array( + 'https://duckduckgo.com/', + 'https://search.yahoo.com/search', + 'https://www.bing.com/search', + 'http://www.dogpile.com/search/web', + 'https://www.google.com/search', + 'https://www.wolframalpha.com/input/', +), array( + 'q' => 'hello world', +)); +``` diff --git a/web/app/vendor/php-curl-class/php-curl-class/composer.json b/web/app/vendor/php-curl-class/php-curl-class/composer.json new file mode 100644 index 0000000..007da18 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/composer.json @@ -0,0 +1,7 @@ +{ + "name": "php-curl-class/php-curl-class", + "description": "PHP Curl Class is an object-oriented wrapper of the PHP cURL extension.", + "autoload": { + "classmap": ["src/"] + } +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_account_balance.php b/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_account_balance.php new file mode 100644 index 0000000..b45add8 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_account_balance.php @@ -0,0 +1,22 @@ +<?php +require '../src/Curl.class.php'; + + +define('API_KEY', ''); +define('API_SECRET', ''); + +$url = 'https://coinbase.com/api/v1/account/balance'; + +$nonce = (int)(microtime(true) * 1e6); +$message = $nonce . $url; +$signature = hash_hmac('sha256', $message, API_SECRET); + +$curl = new Curl(); +$curl->setHeader('ACCESS_KEY', API_KEY); +$curl->setHeader('ACCESS_SIGNATURE', $signature); +$curl->setHeader('ACCESS_NONCE', $nonce); +$curl->get($url); + +echo + 'My current account balance at Coinbase is ' . + $curl->response->amount . ' ' . $curl->response->currency . '.' . "\n"; diff --git a/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_spot_rate.php b/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_spot_rate.php new file mode 100644 index 0000000..f84e450 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_spot_rate.php @@ -0,0 +1,10 @@ +<?php +require '../src/Curl.class.php'; + + +$curl = new Curl(); +$curl->get('https://coinbase.com/api/v1/prices/spot_rate'); + +echo + 'The current price of bitcoin at Coinbase is ' . + '$' . $curl->response->amount . ' ' . $curl->response->currency . '.' . "\n"; diff --git a/web/app/vendor/php-curl-class/php-curl-class/examples/instagram_search_photos.php b/web/app/vendor/php-curl-class/php-curl-class/examples/instagram_search_photos.php new file mode 100644 index 0000000..dad8d96 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/examples/instagram_search_photos.php @@ -0,0 +1,17 @@ +<?php +require '../src/Curl.class.php'; + + +define('INSTAGRAM_CLIENT_ID', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'); + +$curl = new Curl(); +$curl->get('https://api.instagram.com/v1/media/search', array( + 'client_id' => INSTAGRAM_CLIENT_ID, + 'lat' => '37.8296', + 'lng' => '-122.4832', +)); + +foreach ($curl->response->data as $media) { + $image = $media->images->low_resolution; + echo '<img alt="" src="' . $image->url . '" width="' . $image->width . '" height="' . $image->height . '" />'; +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/examples/put.php b/web/app/vendor/php-curl-class/php-curl-class/examples/put.php new file mode 100644 index 0000000..f06e14c --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/examples/put.php @@ -0,0 +1,14 @@ +<?php +require '../src/Curl.class.php'; + + +// curl -X PUT -d "id=1&first_name=Zach&last_name=Borboa" "http://httpbin.org/put" +$curl = new Curl(); +$curl->put('http://httpbin.org/put', array( + 'id' => 1, + 'first_name' => 'Zach', + 'last_name' => 'Borboa', +)); + +echo 'Data server received via PUT:' . "\n"; +var_dump($curl->response->form); diff --git a/web/app/vendor/php-curl-class/php-curl-class/examples/twitter_post_tweet.php b/web/app/vendor/php-curl-class/php-curl-class/examples/twitter_post_tweet.php new file mode 100644 index 0000000..18b7671 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/examples/twitter_post_tweet.php @@ -0,0 +1,35 @@ +<?php +require '../src/Curl.class.php'; + + +define('API_KEY', 'XXXXXXXXXXXXXXXXXXXXXXXXX'); +define('API_SECRET', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'); +define('OAUTH_ACCESS_TOKEN', 'XXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'); +define('OAUTH_TOKEN_SECRET', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'); + +$status = 'I love php curl class. https://github.com/php-curl-class/php-curl-class'; + +$oauth_data = array( + 'oauth_consumer_key' => API_KEY, + 'oauth_nonce' => md5(microtime() . mt_rand()), + 'oauth_signature_method' => 'HMAC-SHA1', + 'oauth_timestamp' => time(), + 'oauth_token' => OAUTH_ACCESS_TOKEN, + 'oauth_version' => '1.0', + 'status' => $status, +); + +$url = 'https://api.twitter.com/1.1/statuses/update.json'; +$request = implode('&', array( + 'POST', + rawurlencode($url), + rawurlencode(http_build_query($oauth_data, '', '&', PHP_QUERY_RFC3986)), +)); +$key = implode('&', array(API_SECRET, OAUTH_TOKEN_SECRET)); +$oauth_data['oauth_signature'] = base64_encode(hash_hmac('sha1', $request, $key, true)); +$data = http_build_query($oauth_data, '', '&'); + +$curl = new Curl(); +$curl->post($url, $data); + +echo 'Posted "' . $curl->response->text . '" at ' . $curl->response->created_at . '.' . "\n"; diff --git a/web/app/vendor/php-curl-class/php-curl-class/src/Curl.class.php b/web/app/vendor/php-curl-class/php-curl-class/src/Curl.class.php new file mode 100644 index 0000000..26209ba --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/src/Curl.class.php @@ -0,0 +1,496 @@ +<?php + +class Curl +{ + const USER_AGENT = 'PHP-Curl-Class/2.0 (+https://github.com/php-curl-class/php-curl-class)'; + + private $cookies = array(); + private $headers = array(); + private $options = array(); + + private $multi_parent = false; + private $multi_child = false; + private $before_send_function = null; + private $success_function = null; + private $error_function = null; + private $complete_function = null; + + public $curl; + public $curls; + + public $error = false; + public $error_code = 0; + public $error_message = null; + + public $curl_error = false; + public $curl_error_code = 0; + public $curl_error_message = null; + + public $http_error = false; + public $http_status_code = 0; + public $http_error_message = null; + + public $request_headers = null; + public $response_headers = null; + public $response = null; + + public function __construct() + { + if (!extension_loaded('curl')) { + throw new \ErrorException('cURL library is not loaded'); + } + + $this->curl = curl_init(); + $this->setUserAgent(self::USER_AGENT); + $this->setOpt(CURLINFO_HEADER_OUT, true); + $this->setOpt(CURLOPT_HEADER, true); + $this->setOpt(CURLOPT_RETURNTRANSFER, true); + } + + public function get($url_mixed, $data = array()) + { + if (is_array($url_mixed)) { + $curl_multi = curl_multi_init(); + $this->multi_parent = true; + + $this->curls = array(); + + foreach ($url_mixed as $url) { + $curl = new Curl(); + $curl->multi_child = true; + $curl->setOpt(CURLOPT_URL, $this->buildURL($url, $data), $curl->curl); + $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); + $curl->setOpt(CURLOPT_HTTPGET, true); + $this->call($this->before_send_function, $curl); + $this->curls[] = $curl; + + $curlm_error_code = curl_multi_add_handle($curl_multi, $curl->curl); + if (!($curlm_error_code === CURLM_OK)) { + throw new \ErrorException('cURL multi add handle error: ' . curl_multi_strerror($curlm_error_code)); + } + } + + foreach ($this->curls as $ch) { + foreach ($this->options as $key => $value) { + $ch->setOpt($key, $value); + } + } + + do { + $status = curl_multi_exec($curl_multi, $active); + } while ($status === CURLM_CALL_MULTI_PERFORM || $active); + + foreach ($this->curls as $ch) { + $this->exec($ch); + } + } else { + $this->setopt(CURLOPT_URL, $this->buildURL($url_mixed, $data)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); + $this->setopt(CURLOPT_HTTPGET, true); + return $this->exec(); + } + } + + public function post($url, $data = array()) + { + if (is_array($data) && empty($data)) { + $this->setHeader('Content-Length'); + } + + $this->setOpt(CURLOPT_URL, $this->buildURL($url)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST'); + $this->setOpt(CURLOPT_POST, true); + $this->setOpt(CURLOPT_POSTFIELDS, $this->postfields($data)); + return $this->exec(); + } + + public function put($url, $data = array()) + { + $this->setOpt(CURLOPT_URL, $url); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); + $put_data = http_build_query($data); + if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) { + $this->setHeader('Content-Length', strlen($put_data)); + } + $this->setOpt(CURLOPT_POSTFIELDS, $put_data); + return $this->exec(); + } + + public function patch($url, $data = array()) + { + $this->setHeader('Content-Length'); + $this->setOpt(CURLOPT_URL, $this->buildURL($url)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); + $this->setOpt(CURLOPT_POSTFIELDS, $data); + return $this->exec(); + } + + public function delete($url, $data = array()) + { + $this->setHeader('Content-Length'); + $this->setOpt(CURLOPT_URL, $this->buildURL($url, $data)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE'); + return $this->exec(); + } + + public function head($url, $data = array()) + { + $this->setOpt(CURLOPT_URL, $this->buildURL($url, $data)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD'); + $this->setOpt(CURLOPT_NOBODY, true); + return $this->exec(); + } + + public function options($url, $data = array()) + { + $this->setHeader('Content-Length'); + $this->setOpt(CURLOPT_URL, $this->buildURL($url, $data)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS'); + return $this->exec(); + } + + public function setBasicAuthentication($username, $password) + { + $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); + } + + public function setHeader($key, $value = '') + { + $this->headers[$key] = $key . ': ' . $value; + $this->setOpt(CURLOPT_HTTPHEADER, array_values($this->headers)); + } + + public function setUserAgent($user_agent) + { + $this->setOpt(CURLOPT_USERAGENT, $user_agent); + } + + public function setReferrer($referrer) + { + $this->setOpt(CURLOPT_REFERER, $referrer); + } + + public function setCookie($key, $value) + { + $this->cookies[$key] = $value; + $this->setOpt(CURLOPT_COOKIE, http_build_query($this->cookies, '', '; ')); + } + + public function setCookieFile($cookie_file) + { + $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file); + } + + public function setCookieJar($cookie_jar) + { + $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar); + } + + public function setOpt($option, $value, $_ch = null) + { + $ch = is_null($_ch) ? $this->curl : $_ch; + + $required_options = array( + CURLINFO_HEADER_OUT => 'CURLINFO_HEADER_OUT', + CURLOPT_HEADER => 'CURLOPT_HEADER', + CURLOPT_RETURNTRANSFER => 'CURLOPT_RETURNTRANSFER', + ); + + if (in_array($option, array_keys($required_options), true) && !($value === true)) { + trigger_error($required_options[$option] . ' is a required option', E_USER_WARNING); + } + + $this->options[$option] = $value; + return curl_setopt($ch, $option, $value); + } + + public function verbose($on = true) + { + $this->setOpt(CURLOPT_VERBOSE, $on); + } + + public function close() + { + if ($this->multi_parent) { + foreach ($this->curls as $curl) { + $curl->close(); + } + } + + if (is_resource($this->curl)) { + curl_close($this->curl); + } + } + + public function beforeSend($function) + { + $this->before_send_function = $function; + } + + public function success($callback) + { + $this->success_function = $callback; + } + + public function error($callback) + { + $this->error_function = $callback; + } + + public function complete($callback) + { + $this->complete_function = $callback; + } + + private function buildURL($url, $data = array()) + { + return $url . (empty($data) ? '' : '?' . http_build_query($data)); + } + + private function parseHeaders($raw_headers) + { + $raw_headers = preg_split('/\r\n/', $raw_headers, null, PREG_SPLIT_NO_EMPTY); + $http_headers = new CaseInsensitiveArray(); + + for ($i = 1; $i < count($raw_headers); $i++) { + list($key, $value) = explode(':', $raw_headers[$i], 2); + $key = trim($key); + $value = trim($value); + // Use isset() as array_key_exists() and ArrayAccess are not compatible. + if (isset($http_headers[$key])) { + $http_headers[$key] .= ',' . $value; + } else { + $http_headers[$key] = $value; + } + } + + return array(isset($raw_headers['0']) ? $raw_headers['0'] : '', $http_headers); + } + + private function parseRequestHeaders($raw_headers) + { + $request_headers = new CaseInsensitiveArray(); + list($first_line, $headers) = $this->parseHeaders($raw_headers); + $request_headers['Request-Line'] = $first_line; + foreach ($headers as $key => $value) { + $request_headers[$key] = $value; + } + return $request_headers; + } + + private function parseResponseHeaders($raw_headers) + { + $response_headers = new CaseInsensitiveArray(); + list($first_line, $headers) = $this->parseHeaders($raw_headers); + $response_headers['Status-Line'] = $first_line; + foreach ($headers as $key => $value) { + $response_headers[$key] = $value; + } + return $response_headers; + } + + private function postfields($data) + { + if (is_array($data)) { + if (is_array_multidim($data)) { + $data = http_build_multi_query($data); + } else { + foreach ($data as $key => $value) { + // Fix "Notice: Array to string conversion" when $value in + // curl_setopt($ch, CURLOPT_POSTFIELDS, $value) is an array + // that contains an empty array. + if (is_array($value) && empty($value)) { + $data[$key] = ''; + // Fix "curl_setopt(): The usage of the @filename API for + // file uploading is deprecated. Please use the CURLFile + // class instead". + } elseif (is_string($value) && strpos($value, '@') === 0) { + if (class_exists('CURLFile')) { + $data[$key] = new CURLFile(substr($value, 1)); + } + } + } + } + } + + return $data; + } + + protected function exec($_ch = null) + { + $ch = is_null($_ch) ? $this : $_ch; + + if ($ch->multi_child) { + $ch->response = curl_multi_getcontent($ch->curl); + } else { + $ch->response = curl_exec($ch->curl); + } + + $ch->curl_error_code = curl_errno($ch->curl); + $ch->curl_error_message = curl_error($ch->curl); + $ch->curl_error = !($ch->curl_error_code === 0); + $ch->http_status_code = curl_getinfo($ch->curl, CURLINFO_HTTP_CODE); + $ch->http_error = in_array(floor($ch->http_status_code / 100), array(4, 5)); + $ch->error = $ch->curl_error || $ch->http_error; + $ch->error_code = $ch->error ? ($ch->curl_error ? $ch->curl_error_code : $ch->http_status_code) : 0; + + $ch->request_headers = $this->parseRequestHeaders(curl_getinfo($ch->curl, CURLINFO_HEADER_OUT)); + $ch->response_headers = ''; + if (!(strpos($ch->response, "\r\n\r\n") === false)) { + list($response_header, $ch->response) = explode("\r\n\r\n", $ch->response, 2); + if ($response_header === 'HTTP/1.1 100 Continue') { + list($response_header, $ch->response) = explode("\r\n\r\n", $ch->response, 2); + } + $ch->response_headers = $this->parseResponseHeaders($response_header); + + if (isset($ch->response_headers['Content-Type'])) { + if (preg_match('/^application\/json/i', $ch->response_headers['Content-Type'])) { + $json_obj = json_decode($ch->response, false); + if (!is_null($json_obj)) { + $ch->response = $json_obj; + } + } + } + } + + $ch->http_error_message = ''; + if ($ch->error) { + if (isset($ch->response_headers['Status-Line'])) { + $ch->http_error_message = $ch->response_headers['Status-Line']; + } + } + $ch->error_message = $ch->curl_error ? $ch->curl_error_message : $ch->http_error_message; + + if (!$ch->error) { + $ch->call($this->success_function, $ch); + } else { + $ch->call($this->error_function, $ch); + } + + $ch->call($this->complete_function, $ch); + + return $ch->error_code; + } + + private function call($function) + { + if (is_callable($function)) { + $args = func_get_args(); + array_shift($args); + call_user_func_array($function, $args); + } + } + + public function __destruct() + { + $this->close(); + } +} + +class CaseInsensitiveArray implements ArrayAccess, Countable, Iterator +{ + private $container = array(); + + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $index = array_search(strtolower($offset), array_keys(array_change_key_case($this->container, CASE_LOWER))); + if (!($index === false)) { + $keys = array_keys($this->container); + unset($this->container[$keys[$index]]); + } + $this->container[$offset] = $value; + } + } + + public function offsetExists($offset) + { + return array_key_exists(strtolower($offset), array_change_key_case($this->container, CASE_LOWER)); + } + + public function offsetUnset($offset) + { + unset($this->container[$offset]); + } + + public function offsetGet($offset) + { + $index = array_search(strtolower($offset), array_keys(array_change_key_case($this->container, CASE_LOWER))); + if ($index === false) { + return null; + } + + $values = array_values($this->container); + return $values[$index]; + } + + public function count() + { + return count($this->container); + } + + public function current() + { + return current($this->container); + } + + public function next() + { + return next($this->container); + } + + public function key() + { + return key($this->container); + } + + public function valid() + { + return !($this->current() === false); + } + + public function rewind() + { + reset($this->container); + } +} + +function is_array_assoc($array) +{ + return (bool)count(array_filter(array_keys($array), 'is_string')); +} + +function is_array_multidim($array) +{ + if (!is_array($array)) { + return false; + } + + return !(count($array) === count($array, COUNT_RECURSIVE)); +} + +function http_build_multi_query($data, $key = null) +{ + $query = array(); + + if (empty($data)) { + return $key . '='; + } + + $is_array_assoc = is_array_assoc($data); + + foreach ($data as $k => $value) { + if (is_string($value) || is_numeric($value)) { + $brackets = $is_array_assoc ? '[' . $k . ']' : '[]'; + $query[] = urlencode(is_null($key) ? $k : $key . $brackets) . '=' . rawurlencode($value); + } elseif (is_array($value)) { + $nested = is_null($key) ? $k : $key . '[' . $k . ']'; + $query[] = http_build_multi_query($value, $nested); + } + } + + return implode('&', $query); +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/PHPCurlClassTest.php b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/PHPCurlClassTest.php new file mode 100644 index 0000000..c42bea0 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/PHPCurlClassTest.php @@ -0,0 +1,740 @@ +<?php +// Usage: phpunit --verbose run.php + +require '../src/Curl.class.php'; +require 'helper.inc.php'; + + +class CurlTest extends PHPUnit_Framework_TestCase { + public function testExtensionLoaded() { + $this->assertTrue(extension_loaded('curl')); + } + + public function testArrayAssociative() { + $this->assertTrue(is_array_assoc(array( + 'foo' => 'wibble', + 'bar' => 'wubble', + 'baz' => 'wobble', + ))); + } + + public function testArrayIndexed() { + $this->assertFalse(is_array_assoc(array( + 'wibble', + 'wubble', + 'wobble', + ))); + } + + public function testCaseInsensitiveArrayGet() { + $array = new CaseInsensitiveArray(); + $this->assertTrue(is_object($array)); + $this->assertCount(0, $array); + $this->assertNull($array[(string)rand()]); + + $array['foo'] = 'bar'; + $this->assertNotEmpty($array); + $this->assertCount(1, $array); + } + + public function testCaseInsensitiveArraySet() { + function assertions($array, $count=1) { + PHPUnit_Framework_Assert::assertCount($count, $array); + PHPUnit_Framework_Assert::assertTrue($array['foo'] === 'bar'); + PHPUnit_Framework_Assert::assertTrue($array['Foo'] === 'bar'); + PHPUnit_Framework_Assert::assertTrue($array['FOo'] === 'bar'); + PHPUnit_Framework_Assert::assertTrue($array['FOO'] === 'bar'); + } + + $array = new CaseInsensitiveArray(); + $array['foo'] = 'bar'; + assertions($array); + + $array['Foo'] = 'bar'; + assertions($array); + + $array['FOo'] = 'bar'; + assertions($array); + + $array['FOO'] = 'bar'; + assertions($array); + + $array['baz'] = 'qux'; + assertions($array, 2); + } + + public function testUserAgent() { + $test = new Test(); + $test->curl->setUserAgent(Curl::USER_AGENT); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'HTTP_USER_AGENT', + )) === Curl::USER_AGENT); + } + + public function testGet() { + $test = new Test(); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'REQUEST_METHOD', + )) === 'GET'); + } + + public function testPostRequestMethod() { + $test = new Test(); + $this->assertTrue($test->server('server', 'POST', array( + 'key' => 'REQUEST_METHOD', + )) === 'POST'); + } + + public function testPostData() { + $test = new Test(); + $this->assertTrue($test->server('post', 'POST', array( + 'key' => 'value', + )) === 'key=value'); + } + + public function testPostAssociativeArrayData() { + $test = new Test(); + $this->assertTrue($test->server('post_multidimensional', 'POST', array( + 'username' => 'myusername', + 'password' => 'mypassword', + 'more_data' => array( + 'param1' => 'something', + 'param2' => 'other thing', + 'param3' => 123, + 'param4' => 3.14, + ), + )) === 'username=myusername&password=mypassword&more_data%5Bparam1%5D=something&more_data%5Bparam2%5D=other%20thing&more_data%5Bparam3%5D=123&more_data%5Bparam4%5D=3.14'); + } + + public function testPostMultidimensionalData() { + $test = new Test(); + $this->assertTrue($test->server('post_multidimensional', 'POST', array( + 'key' => 'file', + 'file' => array( + 'wibble', + 'wubble', + 'wobble', + ), + )) === 'key=file&file%5B%5D=wibble&file%5B%5D=wubble&file%5B%5D=wobble'); + } + + public function testPostFilePathUpload() { + $file_path = get_png(); + + $test = new Test(); + $this->assertTrue($test->server('post_file_path_upload', 'POST', array( + 'key' => 'image', + 'image' => '@' . $file_path, + )) === 'image/png'); + + unlink($file_path); + $this->assertFalse(file_exists($file_path)); + } + + public function testPostCurlFileUpload() { + if (class_exists('CURLFile')) { + $file_path = get_png(); + + $test = new Test(); + $this->assertTrue($test->server('post_file_path_upload', 'POST', array( + 'key' => 'image', + 'image' => new CURLFile($file_path), + )) === 'image/png'); + + unlink($file_path); + $this->assertFalse(file_exists($file_path)); + } + } + + public function testPutRequestMethod() { + $test = new Test(); + $this->assertTrue($test->server('request_method', 'PUT') === 'PUT'); + } + + public function testPutData() { + $test = new Test(); + $this->assertTrue($test->server('put', 'PUT', array( + 'key' => 'value', + )) === 'key=value'); + } + + public function testPutFileHandle() { + $png = create_png(); + $tmp_file = create_tmp_file($png); + + $test = new Test(); + $test->curl->setHeader('X-DEBUG-TEST', 'put_file_handle'); + $test->curl->setOpt(CURLOPT_PUT, true); + $test->curl->setOpt(CURLOPT_INFILE, $tmp_file); + $test->curl->setOpt(CURLOPT_INFILESIZE, strlen($png)); + $test->curl->put(Test::TEST_URL); + + fclose($tmp_file); + + $this->assertTrue($test->curl->response === 'image/png'); + } + + public function testPatchRequestMethod() { + $test = new Test(); + $this->assertTrue($test->server('request_method', 'PATCH') === 'PATCH'); + } + + public function testDelete() { + $test = new Test(); + $this->assertTrue($test->server('server', 'DELETE', array( + 'key' => 'REQUEST_METHOD', + )) === 'DELETE'); + + $test = new Test(); + $this->assertTrue($test->server('delete', 'DELETE', array( + 'test' => 'delete', + 'key' => 'test', + )) === 'delete'); + } + + public function testHeadRequestMethod() { + $test = new Test(); + $test->server('request_method', 'HEAD', array( + 'key' => 'REQUEST_METHOD', + )); + $this->assertEquals($test->curl->response_headers['X-REQUEST-METHOD'], 'HEAD'); + $this->assertEmpty($test->curl->response); + } + + public function testOptionsRequestMethod() { + $test = new Test(); + $test->server('request_method', 'OPTIONS', array( + 'key' => 'REQUEST_METHOD', + )); + $this->assertEquals($test->curl->response_headers['X-REQUEST-METHOD'], 'OPTIONS'); + } + + public function testBasicHttpAuth401Unauthorized() { + $test = new Test(); + $this->assertTrue($test->server('http_basic_auth', 'GET') === 'canceled'); + } + + public function testBasicHttpAuthSuccess() { + $username = 'myusername'; + $password = 'mypassword'; + $test = new Test(); + $test->curl->setBasicAuthentication($username, $password); + $test->server('http_basic_auth', 'GET'); + $json = $test->curl->response; + $this->assertTrue($json->username === $username); + $this->assertTrue($json->password === $password); + } + + public function testReferrer() { + $test = new Test(); + $test->curl->setReferrer('myreferrer'); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'HTTP_REFERER', + )) === 'myreferrer'); + } + + public function testCookies() { + $test = new Test(); + $test->curl->setCookie('mycookie', 'yum'); + $this->assertTrue($test->server('cookie', 'GET', array( + 'key' => 'mycookie', + )) === 'yum'); + } + + public function testCookieFile() { + $cookie_file = dirname(__FILE__) . '/cookies.txt'; + $cookie_data = implode("\t", array( + '127.0.0.1', // domain + 'FALSE', // tailmatch + '/', // path + 'FALSE', // secure + '0', // expires + 'mycookie', // name + 'yum', // value + )); + file_put_contents($cookie_file, $cookie_data); + + $test = new Test(); + $test->curl->setCookieFile($cookie_file); + $this->assertTrue($test->server('cookie', 'GET', array( + 'key' => 'mycookie', + )) === 'yum'); + + unlink($cookie_file); + $this->assertFalse(file_exists($cookie_file)); + } + + public function testCookieJar() { + $cookie_file = dirname(__FILE__) . '/cookies.txt'; + + $test = new Test(); + $test->curl->setCookieJar($cookie_file); + $test->server('cookiejar', 'GET'); + $test->curl->close(); + + $this->assertTrue(!(strpos(file_get_contents($cookie_file), "\t" . 'mycookie' . "\t" . 'yum') === false)); + unlink($cookie_file); + $this->assertFalse(file_exists($cookie_file)); + } + + public function testMultipleCookieResponse() { + $expected_response = 'cookie1=scrumptious,cookie2=mouthwatering'; + + // github.com/facebook/hhvm/issues/2345 + if (defined('HHVM_VERSION')) { + $expected_response = 'cookie2=mouthwatering,cookie1=scrumptious'; + } + + $test = new Test(); + $test->server('multiple_cookie', 'GET'); + $this->assertEquals($test->curl->response_headers['Set-Cookie'], $expected_response); + } + + public function testError() { + $test = new Test(); + $test->curl->setOpt(CURLOPT_CONNECTTIMEOUT_MS, 4000); + $test->curl->get(Test::ERROR_URL); + $this->assertTrue($test->curl->error); + $this->assertTrue($test->curl->curl_error); + $this->assertTrue($test->curl->curl_error_code === CURLE_OPERATION_TIMEOUTED); + } + + public function testErrorMessage() { + $test = new Test(); + $test->server('error_message', 'GET'); + + $expected_response = 'HTTP/1.1 401 Unauthorized'; + if (defined('HHVM_VERSION')) { + $expected_response = 'HTTP/1.1 401'; + } + + $this->assertEquals($test->curl->error_message, $expected_response); + } + + public function testHeaders() { + $test = new Test(); + $test->curl->setHeader('Content-Type', 'application/json'); + $test->curl->setHeader('X-Requested-With', 'XMLHttpRequest'); + $test->curl->setHeader('Accept', 'application/json'); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'HTTP_CONTENT_TYPE', // OR "CONTENT_TYPE". + )) === 'application/json'); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'HTTP_X_REQUESTED_WITH', + )) === 'XMLHttpRequest'); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'HTTP_ACCEPT', + )) === 'application/json'); + } + + public function testHeaderCaseSensitivity() { + $content_type = 'application/json'; + $test = new Test(); + $test->curl->setHeader('Content-Type', $content_type); + $test->server('response_header', 'GET'); + + $request_headers = $test->curl->request_headers; + $response_headers = $test->curl->response_headers; + + $this->assertEquals($request_headers['Content-Type'], $content_type); + $this->assertEquals($request_headers['content-type'], $content_type); + $this->assertEquals($request_headers['CONTENT-TYPE'], $content_type); + $this->assertEquals($request_headers['cOnTeNt-TyPe'], $content_type); + + $etag = $response_headers['ETag']; + $this->assertEquals($response_headers['ETAG'], $etag); + $this->assertEquals($response_headers['etag'], $etag); + $this->assertEquals($response_headers['eTAG'], $etag); + $this->assertEquals($response_headers['eTaG'], $etag); + } + + public function testRequestURL() { + $test = new Test(); + $this->assertFalse(substr($test->server('request_uri', 'GET'), -1) === '?'); + $test = new Test(); + $this->assertFalse(substr($test->server('request_uri', 'POST'), -1) === '?'); + $test = new Test(); + $this->assertFalse(substr($test->server('request_uri', 'PUT'), -1) === '?'); + $test = new Test(); + $this->assertFalse(substr($test->server('request_uri', 'PATCH'), -1) === '?'); + $test = new Test(); + $this->assertFalse(substr($test->server('request_uri', 'DELETE'), -1) === '?'); + } + + public function testNestedData() { + $test = new Test(); + $data = array( + 'username' => 'myusername', + 'password' => 'mypassword', + 'more_data' => array( + 'param1' => 'something', + 'param2' => 'other thing', + 'another' => array( + 'extra' => 'level', + 'because' => 'I need it', + ), + ), + ); + $this->assertTrue( + $test->server('post', 'POST', $data) === http_build_query($data) + ); + } + + public function testPostContentTypes() { + $test = new Test(); + $test->server('server', 'POST', 'foo=bar'); + $this->assertEquals($test->curl->request_headers['Content-Type'], 'application/x-www-form-urlencoded'); + + $test = new Test(); + $test->server('server', 'POST', array( + 'foo' => 'bar', + )); + $this->assertEquals($test->curl->request_headers['Expect'], '100-continue'); + preg_match('/^multipart\/form-data; boundary=/', $test->curl->request_headers['Content-Type'], $content_type); + $this->assertTrue(!empty($content_type)); + } + + public function testJSONResponse() { + function assertion($key, $value) { + $test = new Test(); + $test->server('json_response', 'POST', array( + 'key' => $key, + 'value' => $value, + )); + + $response = $test->curl->response; + PHPUnit_Framework_Assert::assertNotNull($response); + PHPUnit_Framework_Assert::assertNull($response->null); + PHPUnit_Framework_Assert::assertTrue($response->true); + PHPUnit_Framework_Assert::assertFalse($response->false); + PHPUnit_Framework_Assert::assertTrue(is_int($response->integer)); + PHPUnit_Framework_Assert::assertTrue(is_float($response->float)); + PHPUnit_Framework_Assert::assertEmpty($response->empty); + PHPUnit_Framework_Assert::assertTrue(is_string($response->string)); + } + + assertion('Content-Type', 'application/json; charset=utf-8'); + assertion('content-type', 'application/json; charset=utf-8'); + assertion('Content-Type', 'application/json'); + assertion('content-type', 'application/json'); + assertion('CONTENT-TYPE', 'application/json'); + assertion('CONTENT-TYPE', 'APPLICATION/JSON'); + } + + public function testArrayToStringConversion() { + $test = new Test(); + $test->server('post', 'POST', array( + 'foo' => 'bar', + 'baz' => array( + ), + )); + $this->assertTrue($test->curl->response === 'foo=bar&baz='); + + $test = new Test(); + $test->server('post', 'POST', array( + 'foo' => 'bar', + 'baz' => array( + 'qux' => array( + ), + ), + )); + $this->assertTrue(urldecode($test->curl->response) === + 'foo=bar&baz[qux]=' + ); + + $test = new Test(); + $test->server('post', 'POST', array( + 'foo' => 'bar', + 'baz' => array( + 'qux' => array( + ), + 'wibble' => 'wobble', + ), + )); + $this->assertTrue(urldecode($test->curl->response) === + 'foo=bar&baz[qux]=&baz[wibble]=wobble' + ); + } + + public function testParallelRequests() { + $test = new Test(); + $curl = $test->curl; + $curl->beforeSend(function($instance) { + $instance->setHeader('X-DEBUG-TEST', 'request_uri'); + }); + $curl->get(array( + Test::TEST_URL . 'a/', + Test::TEST_URL . 'b/', + Test::TEST_URL . 'c/', + ), array( + 'foo' => 'bar', + )); + + $len = strlen('/a/?foo=bar'); + $this->assertTrue(substr($curl->curls['0']->response, - $len) === '/a/?foo=bar'); + $this->assertTrue(substr($curl->curls['1']->response, - $len) === '/b/?foo=bar'); + $this->assertTrue(substr($curl->curls['2']->response, - $len) === '/c/?foo=bar'); + } + + public function testParallelSetOptions() { + $test = new Test(); + $curl = $test->curl; + $curl->setHeader('X-DEBUG-TEST', 'server'); + $curl->setOpt(CURLOPT_USERAGENT, 'useragent'); + $curl->complete(function($instance) { + PHPUnit_Framework_Assert::assertTrue($instance->response === 'useragent'); + }); + $curl->get(array( + Test::TEST_URL, + ), array( + 'key' => 'HTTP_USER_AGENT', + )); + } + + public function testSuccessCallback() { + $success_called = false; + $error_called = false; + $complete_called = false; + + $test = new Test(); + $curl = $test->curl; + $curl->setHeader('X-DEBUG-TEST', 'get'); + + $curl->success(function($instance) use (&$success_called, &$error_called, &$complete_called) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $success_called = true; + }); + $curl->error(function($instance) use (&$success_called, &$error_called, &$complete_called, &$curl) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $error_called = true; + }); + $curl->complete(function($instance) use (&$success_called, &$error_called, &$complete_called) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertTrue($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $complete_called = true; + }); + + $curl->get(Test::TEST_URL); + + $this->assertTrue($success_called); + $this->assertFalse($error_called); + $this->assertTrue($complete_called); + } + + public function testParallelSuccessCallback() { + $success_called = false; + $error_called = false; + $complete_called = false; + + $success_called_once = false; + $error_called_once = false; + $complete_called_once = false; + + $test = new Test(); + $curl = $test->curl; + $curl->setHeader('X-DEBUG-TEST', 'get'); + + $curl->success(function($instance) use (&$success_called, + &$error_called, + &$complete_called, + &$success_called_once) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $success_called = true; + $success_called_once = true; + }); + $curl->error(function($instance) use (&$success_called, + &$error_called, + &$complete_called, + &$curl, + &$error_called_once) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $error_called = true; + $error_called_once = true; + }); + $curl->complete(function($instance) use (&$success_called, + &$error_called, + &$complete_called, + &$complete_called_once) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertTrue($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $complete_called = true; + $complete_called_once = true; + + PHPUnit_Framework_Assert::assertTrue($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertTrue($complete_called); + + $success_called = false; + $error_called = false; + $complete_called = false; + }); + + $curl->get(array( + Test::TEST_URL . 'a/', + Test::TEST_URL . 'b/', + Test::TEST_URL . 'c/', + )); + + PHPUnit_Framework_Assert::assertTrue($success_called_once || $error_called_once); + PHPUnit_Framework_Assert::assertTrue($complete_called_once); + } + + public function testErrorCallback() { + $success_called = false; + $error_called = false; + $complete_called = false; + + $test = new Test(); + $curl = $test->curl; + $curl->setHeader('X-DEBUG-TEST', 'get'); + $curl->setOpt(CURLOPT_CONNECTTIMEOUT_MS, 2000); + + $curl->success(function($instance) use (&$success_called, &$error_called, &$complete_called) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $success_called = true; + }); + $curl->error(function($instance) use (&$success_called, &$error_called, &$complete_called, &$curl) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $error_called = true; + }); + $curl->complete(function($instance) use (&$success_called, &$error_called, &$complete_called) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertTrue($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $complete_called = true; + }); + + $curl->get(Test::ERROR_URL); + + $this->assertFalse($success_called); + $this->assertTrue($error_called); + $this->assertTrue($complete_called); + } + + public function testClose() { + $test = new Test(); + $curl = $test->curl; + $curl->setHeader('X-DEBUG-TEST', 'post'); + $curl->post(Test::TEST_URL); + $this->assertTrue(is_resource($curl->curl)); + $curl->close(); + $this->assertFalse(is_resource($curl->curl)); + } + + /** + * @expectedException PHPUnit_Framework_Error_Warning + */ + public function testRequiredOptionCurlInfoHeaderOutEmitsWarning() { + $curl = new Curl(); + $curl->setOpt(CURLINFO_HEADER_OUT, false); + } + + /** + * @expectedException PHPUnit_Framework_Error_Warning + */ + public function testRequiredOptionCurlOptHeaderEmitsWarning() { + $curl = new Curl(); + $curl->setOpt(CURLOPT_HEADER, false); + } + + /** + * @expectedException PHPUnit_Framework_Error_Warning + */ + public function testRequiredOptionCurlOptReturnTransferEmitsWarning() { + $curl = new Curl(); + $curl->setOpt(CURLOPT_RETURNTRANSFER, false); + } + + public function testRequestMethodSuccessiveGetRequests() { + $test = new Test(); + test($test, 'GET', 'POST'); + test($test, 'GET', 'PUT'); + test($test, 'GET', 'PATCH'); + test($test, 'GET', 'DELETE'); + test($test, 'GET', 'HEAD'); + test($test, 'GET', 'OPTIONS'); + } + + public function testRequestMethodSuccessivePostRequests() { + $test = new Test(); + test($test, 'POST', 'GET'); + test($test, 'POST', 'PUT'); + test($test, 'POST', 'PATCH'); + test($test, 'POST', 'DELETE'); + test($test, 'POST', 'HEAD'); + test($test, 'POST', 'OPTIONS'); + } + + public function testRequestMethodSuccessivePutRequests() { + $test = new Test(); + test($test, 'PUT', 'GET'); + test($test, 'PUT', 'POST'); + test($test, 'PUT', 'PATCH'); + test($test, 'PUT', 'DELETE'); + test($test, 'PUT', 'HEAD'); + test($test, 'PUT', 'OPTIONS'); + } + + public function testRequestMethodSuccessivePatchRequests() { + $test = new Test(); + test($test, 'PATCH', 'GET'); + test($test, 'PATCH', 'POST'); + test($test, 'PATCH', 'PUT'); + test($test, 'PATCH', 'DELETE'); + test($test, 'PATCH', 'HEAD'); + test($test, 'PATCH', 'OPTIONS'); + } + + public function testRequestMethodSuccessiveDeleteRequests() { + $test = new Test(); + test($test, 'DELETE', 'GET'); + test($test, 'DELETE', 'POST'); + test($test, 'DELETE', 'PUT'); + test($test, 'DELETE', 'PATCH'); + test($test, 'DELETE', 'HEAD'); + test($test, 'DELETE', 'OPTIONS'); + } + + public function testRequestMethodSuccessiveHeadRequests() { + $test = new Test(); + test($test, 'HEAD', 'GET'); + test($test, 'HEAD', 'POST'); + test($test, 'HEAD', 'PUT'); + test($test, 'HEAD', 'PATCH'); + test($test, 'HEAD', 'DELETE'); + test($test, 'HEAD', 'OPTIONS'); + } + + public function testRequestMethodSuccessiveOptionsRequests() { + $test = new Test(); + test($test, 'OPTIONS', 'GET'); + test($test, 'OPTIONS', 'POST'); + test($test, 'OPTIONS', 'PUT'); + test($test, 'OPTIONS', 'PATCH'); + test($test, 'OPTIONS', 'DELETE'); + test($test, 'OPTIONS', 'HEAD'); + } +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/helper.inc.php b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/helper.inc.php new file mode 100644 index 0000000..406a8e1 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/helper.inc.php @@ -0,0 +1,47 @@ +<?php +class Test { + const TEST_URL = 'http://127.0.0.1:8000/'; + const ERROR_URL = 'https://1.2.3.4/'; + + function __construct() { + $this->curl = new Curl(); + $this->curl->setOpt(CURLOPT_SSL_VERIFYPEER, false); + $this->curl->setOpt(CURLOPT_SSL_VERIFYHOST, false); + } + + function server($test, $request_method, $data=array()) { + $this->curl->setHeader('X-DEBUG-TEST', $test); + $request_method = strtolower($request_method); + $this->curl->$request_method(self::TEST_URL, $data); + return $this->curl->response; + } +} + +function test($instance, $before, $after) { + $instance->server('request_method', $before); + PHPUnit_Framework_Assert::assertEquals($instance->curl->response_headers['X-REQUEST-METHOD'], $before); + $instance->server('request_method', $after); + PHPUnit_Framework_Assert::assertEquals($instance->curl->response_headers['X-REQUEST-METHOD'], $after); +} + +function create_png() { + // PNG image data, 1 x 1, 1-bit colormap, non-interlaced + ob_start(); + imagepng(imagecreatefromstring(base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'))); + $raw_image = ob_get_contents(); + ob_end_clean(); + return $raw_image; +} + +function create_tmp_file($data) { + $tmp_file = tmpfile(); + fwrite($tmp_file, $data); + rewind($tmp_file); + return $tmp_file; +} + +function get_png() { + $tmp_filename = tempnam('/tmp', 'php-curl-class.'); + file_put_contents($tmp_filename, create_png()); + return $tmp_filename; +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/index.php b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/index.php new file mode 120000 index 0000000..21a87f6 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/index.php @@ -0,0 +1 @@ +server.php \ No newline at end of file diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.hdf b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.hdf new file mode 100644 index 0000000..8e49d88 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.hdf @@ -0,0 +1,7 @@ +Log { + Level = Verbose +} + +Server { + DefaultDocument = index.php +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.php b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.php new file mode 100644 index 0000000..96e835b --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.php @@ -0,0 +1,132 @@ +<?php +$http_raw_post_data = file_get_contents('php://input'); +$_PUT = array(); +$_PATCH = array(); + +$request_method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : ''; +$content_type = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : ''; +$data_values = $_GET; +if ($request_method === 'POST') { + $data_values = $_POST; +} +else if ($request_method === 'PUT') { + if (strpos($content_type, 'application/x-www-form-urlencoded') === 0) { + parse_str($http_raw_post_data, $_PUT); + $data_values = $_PUT; + } +} +else if ($request_method === 'PATCH') { + if (strpos($content_type, 'application/x-www-form-urlencoded') === 0) { + parse_str($http_raw_post_data, $_PATCH); + $data_values = $_PATCH; + } +} + +$test = isset($_SERVER['HTTP_X_DEBUG_TEST']) ? $_SERVER['HTTP_X_DEBUG_TEST'] : ''; +$key = isset($data_values['key']) ? $data_values['key'] : ''; + +if ($test == 'http_basic_auth') { + if (!isset($_SERVER['PHP_AUTH_USER'])) { + header('WWW-Authenticate: Basic realm="My Realm"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'canceled'; + exit; + } + + header('Content-Type: application/json'); + echo json_encode(array( + 'username' => $_SERVER['PHP_AUTH_USER'], + 'password' => $_SERVER['PHP_AUTH_PW'], + )); + exit; +} +else if ($test === 'get') { + echo http_build_query($_GET); + exit; +} +else if ($test === 'post') { + echo http_build_query($_POST); + exit; +} +else if ($test === 'put') { + echo $http_raw_post_data; + exit; +} +else if ($test === 'post_multidimensional') { + echo $http_raw_post_data; + exit; +} +else if ($test === 'post_file_path_upload') { + echo mime_content_type($_FILES[$key]['tmp_name']); + exit; +} +else if ($test === 'put_file_handle') { + $tmp_filename = tempnam('/tmp', 'php-curl-class.'); + file_put_contents($tmp_filename, $http_raw_post_data); + echo mime_content_type($tmp_filename); + unlink($tmp_filename); + exit; +} +else if ($test === 'request_method') { + header('X-REQUEST-METHOD: ' . $request_method); + echo $request_method; + exit; +} +else if ($test === 'request_uri') { + echo $_SERVER['REQUEST_URI']; + exit; +} +else if ($test === 'cookiejar') { + setcookie('mycookie', 'yum'); + exit; +} +else if ($test === 'multiple_cookie') { + setcookie('cookie1', 'scrumptious'); + setcookie('cookie2', 'mouthwatering'); + exit; +} +else if ($test === 'response_header') { + header('Content-Type: application/json'); + header('ETag: ' . md5('worldpeace')); + exit; +} +else if ($test === 'json_response') { + $key = $_POST['key']; + $value = $_POST['value']; + header($key . ': ' . $value); + echo json_encode(array( + 'null' => null, + 'true' => true, + 'false' => false, + 'integer' => 1, + 'float' => 3.14, + 'empty' => '', + 'string' => 'string', + )); + exit; +} +else if ($test === 'error_message') { + if (function_exists('http_response_code')) { + http_response_code(401); + } + else { + header('HTTP/1.1 401 Unauthorized'); + } + exit; +} + +header('Content-Type: text/plain'); + +$data_mapping = array( + 'cookie' => '_COOKIE', + 'delete' => '_GET', + 'get' => '_GET', + 'patch' => '_PATCH', + 'post' => '_POST', + 'put' => '_PUT', + 'server' => '_SERVER', +); + +$data = $$data_mapping[$test]; +$value = isset($data[$key]) ? $data[$key] : ''; +echo $value; diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/phpunit.xml b/web/app/vendor/php-curl-class/php-curl-class/tests/phpunit.xml new file mode 100644 index 0000000..8eccd99 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/phpunit.xml @@ -0,0 +1,8 @@ +<phpunit> + <testsuite name="PHPCurlClass"> + <directory>.</directory> + </testsuite> + <logging> + <log type="coverage-text" target="php://stdout" showUncoveredFiles="false" /> + </logging> +</phpunit> diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/run.sh b/web/app/vendor/php-curl-class/php-curl-class/tests/run.sh new file mode 100644 index 0000000..54330ba --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/run.sh @@ -0,0 +1,4 @@ +php -S 127.0.0.1:8000 -t PHPCurlClass/ & +pid=$! +phpunit --configuration phpunit.xml +kill $pid diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/syntax.sh b/web/app/vendor/php-curl-class/php-curl-class/tests/syntax.sh new file mode 100644 index 0000000..a5a32f1 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/syntax.sh @@ -0,0 +1 @@ +phpcs --standard=PSR2 ../src/Curl.class.php diff --git a/web/app/vendor/symfony/deprecation-contracts/.gitignore b/web/app/vendor/symfony/deprecation-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/web/app/vendor/symfony/deprecation-contracts/CHANGELOG.md b/web/app/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/web/app/vendor/symfony/deprecation-contracts/LICENSE b/web/app/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000..406242f --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/vendor/symfony/deprecation-contracts/README.md b/web/app/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000..4957933 --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/web/app/vendor/symfony/deprecation-contracts/composer.json b/web/app/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000..cc7cc12 --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/web/app/vendor/symfony/deprecation-contracts/function.php b/web/app/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000..d437150 --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas <p@tchwork.com> + */ + function trigger_deprecation(string $package, string $version, string $message, ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/web/app/vendor/symfony/finder/CHANGELOG.md b/web/app/vendor/symfony/finder/CHANGELOG.md index 9e2fc5a..6a44e87 100644 --- a/web/app/vendor/symfony/finder/CHANGELOG.md +++ b/web/app/vendor/symfony/finder/CHANGELOG.md @@ -1,11 +1,6 @@ CHANGELOG ========= -6.0 ---- - - * Remove `Comparator::setTarget()` and `Comparator::setOperator()` - 5.4.0 ----- diff --git a/web/app/vendor/symfony/finder/Comparator/Comparator.php b/web/app/vendor/symfony/finder/Comparator/Comparator.php index bd68583..3af551f 100644 --- a/web/app/vendor/symfony/finder/Comparator/Comparator.php +++ b/web/app/vendor/symfony/finder/Comparator/Comparator.php @@ -16,47 +16,102 @@ namespace Symfony\Component\Finder\Comparator; */ class Comparator { - private string $target; - private string $operator; + private $target; + private $operator = '=='; - public function __construct(string $target, string $operator = '==') + public function __construct(string $target = null, string $operator = '==') { - if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { - throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + if (null === $target) { + trigger_deprecation('symfony/finder', '5.4', 'Constructing a "%s" without setting "$target" is deprecated.', __CLASS__); } $this->target = $target; - $this->operator = $operator; + $this->doSetOperator($operator); } /** * Gets the target value. + * + * @return string */ - public function getTarget(): string + public function getTarget() { + if (null === $this->target) { + trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); + } + return $this->target; } /** - * Gets the comparison operator. + * @deprecated set the target via the constructor instead */ - public function getOperator(): string + public function setTarget(string $target) + { + trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the target via the constructor instead.', __METHOD__); + + $this->target = $target; + } + + /** + * Gets the comparison operator. + * + * @return string + */ + public function getOperator() { return $this->operator; } /** - * Tests against the target. + * Sets the comparison operator. + * + * @throws \InvalidArgumentException + * + * @deprecated set the operator via the constructor instead */ - public function test(mixed $test): bool + public function setOperator(string $operator) { - return match ($this->operator) { - '>' => $test > $this->target, - '>=' => $test >= $this->target, - '<' => $test < $this->target, - '<=' => $test <= $this->target, - '!=' => $test != $this->target, - default => $test == $this->target, - }; + trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the operator via the constructor instead.', __METHOD__); + + $this->doSetOperator('' === $operator ? '==' : $operator); + } + + /** + * Tests against the target. + * + * @param mixed $test A test value + * + * @return bool + */ + public function test($test) + { + if (null === $this->target) { + trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); + } + + switch ($this->operator) { + case '>': + return $test > $this->target; + case '>=': + return $test >= $this->target; + case '<': + return $test < $this->target; + case '<=': + return $test <= $this->target; + case '!=': + return $test != $this->target; + } + + return $test == $this->target; + } + + private function doSetOperator(string $operator): void + { + if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->operator = $operator; } } diff --git a/web/app/vendor/symfony/finder/Comparator/DateComparator.php b/web/app/vendor/symfony/finder/Comparator/DateComparator.php index 159964d..8f651e1 100644 --- a/web/app/vendor/symfony/finder/Comparator/DateComparator.php +++ b/web/app/vendor/symfony/finder/Comparator/DateComparator.php @@ -32,7 +32,7 @@ class DateComparator extends Comparator try { $date = new \DateTime($matches[2]); $target = $date->format('U'); - } catch (\Exception) { + } catch (\Exception $e) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); } diff --git a/web/app/vendor/symfony/finder/Finder.php b/web/app/vendor/symfony/finder/Finder.php index 4636f05..8cc564c 100644 --- a/web/app/vendor/symfony/finder/Finder.php +++ b/web/app/vendor/symfony/finder/Finder.php @@ -45,27 +45,27 @@ class Finder implements \IteratorAggregate, \Countable public const IGNORE_DOT_FILES = 2; public const IGNORE_VCS_IGNORED_FILES = 4; - private int $mode = 0; - private array $names = []; - private array $notNames = []; - private array $exclude = []; - private array $filters = []; - private array $depths = []; - private array $sizes = []; - private bool $followLinks = false; - private bool $reverseSorting = false; - private \Closure|int|false $sort = false; - private int $ignore = 0; - private array $dirs = []; - private array $dates = []; - private array $iterators = []; - private array $contains = []; - private array $notContains = []; - private array $paths = []; - private array $notPaths = []; - private bool $ignoreUnreadableDirs = false; + private $mode = 0; + private $names = []; + private $notNames = []; + private $exclude = []; + private $filters = []; + private $depths = []; + private $sizes = []; + private $followLinks = false; + private $reverseSorting = false; + private $sort = false; + private $ignore = 0; + private $dirs = []; + private $dates = []; + private $iterators = []; + private $contains = []; + private $notContains = []; + private $paths = []; + private $notPaths = []; + private $ignoreUnreadableDirs = false; - private static array $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; + private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; public function __construct() { @@ -74,8 +74,10 @@ class Finder implements \IteratorAggregate, \Countable /** * Creates a new Finder. + * + * @return static */ - public static function create(): static + public static function create() { return new static(); } @@ -85,7 +87,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function directories(): static + public function directories() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; @@ -97,7 +99,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function files(): static + public function files() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; @@ -120,7 +122,7 @@ class Finder implements \IteratorAggregate, \Countable * @see DepthRangeFilterIterator * @see NumberComparator */ - public function depth(string|int|array $levels): static + public function depth($levels) { foreach ((array) $levels as $level) { $this->depths[] = new Comparator\NumberComparator($level); @@ -148,7 +150,7 @@ class Finder implements \IteratorAggregate, \Countable * @see DateRangeFilterIterator * @see DateComparator */ - public function date(string|array $dates): static + public function date($dates) { foreach ((array) $dates as $date) { $this->dates[] = new Comparator\DateComparator($date); @@ -173,7 +175,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function name(string|array $patterns): static + public function name($patterns) { $this->names = array_merge($this->names, (array) $patterns); @@ -189,7 +191,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function notName(string|array $patterns): static + public function notName($patterns) { $this->notNames = array_merge($this->notNames, (array) $patterns); @@ -211,7 +213,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilecontentFilterIterator */ - public function contains(string|array $patterns): static + public function contains($patterns) { $this->contains = array_merge($this->contains, (array) $patterns); @@ -233,7 +235,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilecontentFilterIterator */ - public function notContains(string|array $patterns): static + public function notContains($patterns) { $this->notContains = array_merge($this->notContains, (array) $patterns); @@ -257,7 +259,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function path(string|array $patterns): static + public function path($patterns) { $this->paths = array_merge($this->paths, (array) $patterns); @@ -281,7 +283,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function notPath(string|array $patterns): static + public function notPath($patterns) { $this->notPaths = array_merge($this->notPaths, (array) $patterns); @@ -303,7 +305,7 @@ class Finder implements \IteratorAggregate, \Countable * @see SizeRangeFilterIterator * @see NumberComparator */ - public function size(string|int|array $sizes): static + public function size($sizes) { foreach ((array) $sizes as $size) { $this->sizes[] = new Comparator\NumberComparator($size); @@ -325,7 +327,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see ExcludeDirectoryFilterIterator */ - public function exclude(string|array $dirs): static + public function exclude($dirs) { $this->exclude = array_merge($this->exclude, (array) $dirs); @@ -341,7 +343,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see ExcludeDirectoryFilterIterator */ - public function ignoreDotFiles(bool $ignoreDotFiles): static + public function ignoreDotFiles(bool $ignoreDotFiles) { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; @@ -361,7 +363,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see ExcludeDirectoryFilterIterator */ - public function ignoreVCS(bool $ignoreVCS): static + public function ignoreVCS(bool $ignoreVCS) { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; @@ -379,7 +381,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function ignoreVCSIgnored(bool $ignoreVCSIgnored): static + public function ignoreVCSIgnored(bool $ignoreVCSIgnored) { if ($ignoreVCSIgnored) { $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; @@ -397,7 +399,7 @@ class Finder implements \IteratorAggregate, \Countable * * @param string|string[] $pattern VCS patterns to ignore */ - public static function addVCSPattern(string|array $pattern) + public static function addVCSPattern($pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; @@ -417,7 +419,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sort(\Closure $closure): static + public function sort(\Closure $closure) { $this->sort = $closure; @@ -433,7 +435,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByName(bool $useNaturalSort = false): static + public function sortByName(bool $useNaturalSort = false) { $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; @@ -449,7 +451,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByType(): static + public function sortByType() { $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; @@ -467,7 +469,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByAccessedTime(): static + public function sortByAccessedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; @@ -479,7 +481,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function reverseSorting(): static + public function reverseSorting() { $this->reverseSorting = true; @@ -499,7 +501,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByChangedTime(): static + public function sortByChangedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; @@ -517,7 +519,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByModifiedTime(): static + public function sortByModifiedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; @@ -534,7 +536,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see CustomFilterIterator */ - public function filter(\Closure $closure): static + public function filter(\Closure $closure) { $this->filters[] = $closure; @@ -546,7 +548,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function followLinks(): static + public function followLinks() { $this->followLinks = true; @@ -560,7 +562,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function ignoreUnreadableDirs(bool $ignore = true): static + public function ignoreUnreadableDirs(bool $ignore = true) { $this->ignoreUnreadableDirs = $ignore; @@ -576,7 +578,7 @@ class Finder implements \IteratorAggregate, \Countable * * @throws DirectoryNotFoundException if one of the directories does not exist */ - public function in(string|array $dirs): static + public function in($dirs) { $resolvedDirs = []; @@ -585,7 +587,7 @@ class Finder implements \IteratorAggregate, \Countable $resolvedDirs[] = [$this->normalizeDir($dir)]; } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { sort($glob); - $resolvedDirs[] = array_map($this->normalizeDir(...), $glob); + $resolvedDirs[] = array_map([$this, 'normalizeDir'], $glob); } else { throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); } @@ -605,7 +607,8 @@ class Finder implements \IteratorAggregate, \Countable * * @throws \LogicException if the in() method has not been called */ - public function getIterator(): \Iterator + #[\ReturnTypeWillChange] + public function getIterator() { if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); @@ -648,7 +651,7 @@ class Finder implements \IteratorAggregate, \Countable * * @throws \InvalidArgumentException when the given argument is not iterable */ - public function append(iterable $iterator): static + public function append(iterable $iterator) { if ($iterator instanceof \IteratorAggregate) { $this->iterators[] = $iterator->getIterator(); @@ -670,8 +673,10 @@ class Finder implements \IteratorAggregate, \Countable /** * Check if any results were found. + * + * @return bool */ - public function hasResults(): bool + public function hasResults() { foreach ($this->getIterator() as $_) { return true; @@ -682,8 +687,11 @@ class Finder implements \IteratorAggregate, \Countable /** * Counts all the results collected by the iterators. + * + * @return int */ - public function count(): int + #[\ReturnTypeWillChange] + public function count() { return iterator_count($this->getIterator()); } diff --git a/web/app/vendor/symfony/finder/Gitignore.php b/web/app/vendor/symfony/finder/Gitignore.php index 070074b..d42cca1 100644 --- a/web/app/vendor/symfony/finder/Gitignore.php +++ b/web/app/vendor/symfony/finder/Gitignore.php @@ -43,7 +43,7 @@ class Gitignore foreach ($gitignoreLines as $line) { $line = preg_replace('~(?<!\\\\)[ \t]+$~', '', $line); - if (str_starts_with($line, '!')) { + if ('!' === substr($line, 0, 1)) { $line = substr($line, 1); $isNegative = true; } else { diff --git a/web/app/vendor/symfony/finder/Glob.php b/web/app/vendor/symfony/finder/Glob.php index 7fe8b1a..8447932 100644 --- a/web/app/vendor/symfony/finder/Glob.php +++ b/web/app/vendor/symfony/finder/Glob.php @@ -37,8 +37,10 @@ class Glob { /** * Returns a regexp which is the equivalent of the glob pattern. + * + * @return string */ - public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#'): string + public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#') { $firstByte = true; $escaping = false; diff --git a/web/app/vendor/symfony/finder/Iterator/CustomFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/CustomFilterIterator.php index 82ee81d..f7bf19b 100644 --- a/web/app/vendor/symfony/finder/Iterator/CustomFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/CustomFilterIterator.php @@ -23,7 +23,7 @@ namespace Symfony\Component\Finder\Iterator; */ class CustomFilterIterator extends \FilterIterator { - private array $filters = []; + private $filters = []; /** * @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter @@ -45,8 +45,11 @@ class CustomFilterIterator extends \FilterIterator /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { $fileinfo = $this->current(); diff --git a/web/app/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php index 718d42b..f592e19 100644 --- a/web/app/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -22,7 +22,7 @@ use Symfony\Component\Finder\Comparator\DateComparator; */ class DateRangeFilterIterator extends \FilterIterator { - private array $comparators = []; + private $comparators = []; /** * @param \Iterator<string, \SplFileInfo> $iterator @@ -37,8 +37,11 @@ class DateRangeFilterIterator extends \FilterIterator /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { $fileinfo = $this->current(); diff --git a/web/app/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php index 1cddb5f..f593a3f 100644 --- a/web/app/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -23,7 +23,7 @@ namespace Symfony\Component\Finder\Iterator; */ class DepthRangeFilterIterator extends \FilterIterator { - private int $minDepth = 0; + private $minDepth = 0; /** * @param \RecursiveIteratorIterator<\RecursiveIterator<TKey, TValue>> $iterator The Iterator to filter @@ -40,8 +40,11 @@ class DepthRangeFilterIterator extends \FilterIterator /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; } diff --git a/web/app/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php index efe9364..d9e182c 100644 --- a/web/app/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -11,27 +11,24 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\SplFileInfo; - /** * ExcludeDirectoryFilterIterator filters out directories. * * @author Fabien Potencier <fabien@symfony.com> * - * @extends \FilterIterator<string, SplFileInfo> - * @implements \RecursiveIterator<string, SplFileInfo> + * @extends \FilterIterator<string, \SplFileInfo> + * @implements \RecursiveIterator<string, \SplFileInfo> */ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator { - /** @var \Iterator<string, SplFileInfo> */ - private \Iterator $iterator; - private bool $isRecursive; - private array $excludedDirs = []; - private ?string $excludedPattern = null; + private $iterator; + private $isRecursive; + private $excludedDirs = []; + private $excludedPattern; /** - * @param \Iterator<string, SplFileInfo> $iterator The Iterator to filter - * @param string[] $directories An array of directories to exclude + * @param \Iterator $iterator The Iterator to filter + * @param string[] $directories An array of directories to exclude */ public function __construct(\Iterator $iterator, array $directories) { @@ -55,8 +52,11 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { return false; @@ -72,12 +72,20 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi return true; } - public function hasChildren(): bool + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function hasChildren() { return $this->isRecursive && $this->iterator->hasChildren(); } - public function getChildren(): self + /** + * @return self + */ + #[\ReturnTypeWillChange] + public function getChildren() { $children = new self($this->iterator->getChildren(), []); $children->excludedDirs = $this->excludedDirs; diff --git a/web/app/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php index 2130378..793ae35 100644 --- a/web/app/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -23,11 +23,11 @@ class FileTypeFilterIterator extends \FilterIterator public const ONLY_FILES = 1; public const ONLY_DIRECTORIES = 2; - private int $mode; + private $mode; /** - * @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter - * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) */ public function __construct(\Iterator $iterator, int $mode) { @@ -38,8 +38,11 @@ class FileTypeFilterIterator extends \FilterIterator /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { $fileinfo = $this->current(); if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { diff --git a/web/app/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php index bdc71ff..79f8c29 100644 --- a/web/app/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -11,22 +11,23 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\SplFileInfo; - /** * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). * * @author Fabien Potencier <fabien@symfony.com> * @author Włodzimierz Gajda <gajdaw@gajdaw.pl> * - * @extends MultiplePcreFilterIterator<string, SplFileInfo> + * @extends MultiplePcreFilterIterator<string, \SplFileInfo> */ class FilecontentFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { if (!$this->matchRegexps && !$this->noMatchRegexps) { return true; @@ -50,8 +51,10 @@ class FilecontentFilterIterator extends MultiplePcreFilterIterator * Converts string to regexp if necessary. * * @param string $str Pattern: string or regexp + * + * @return string */ - protected function toRegex(string $str): string + protected function toRegex(string $str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } diff --git a/web/app/vendor/symfony/finder/Iterator/FilenameFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/FilenameFilterIterator.php index 05d9535..77b3b24 100644 --- a/web/app/vendor/symfony/finder/Iterator/FilenameFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/FilenameFilterIterator.php @@ -24,8 +24,11 @@ class FilenameFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { return $this->isAccepted($this->current()->getFilename()); } @@ -37,8 +40,10 @@ class FilenameFilterIterator extends MultiplePcreFilterIterator * Glob strings are transformed with Glob::toRegex(). * * @param string $str Pattern: glob or regexp + * + * @return string */ - protected function toRegex(string $str): string + protected function toRegex(string $str) { return $this->isRegex($str) ? $str : Glob::toRegex($str); } diff --git a/web/app/vendor/symfony/finder/Iterator/LazyIterator.php b/web/app/vendor/symfony/finder/Iterator/LazyIterator.php index 5b5806b..32cc37f 100644 --- a/web/app/vendor/symfony/finder/Iterator/LazyIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/LazyIterator.php @@ -18,11 +18,11 @@ namespace Symfony\Component\Finder\Iterator; */ class LazyIterator implements \IteratorAggregate { - private \Closure $iteratorFactory; + private $iteratorFactory; public function __construct(callable $iteratorFactory) { - $this->iteratorFactory = $iteratorFactory(...); + $this->iteratorFactory = $iteratorFactory; } public function getIterator(): \Traversable diff --git a/web/app/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php index 82a9df3..564765d 100644 --- a/web/app/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -27,9 +27,9 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator protected $noMatchRegexps = []; /** - * @param \Iterator<TKey, TValue> $iterator The Iterator to filter - * @param string[] $matchPatterns An array of patterns that need to match - * @param string[] $noMatchPatterns An array of patterns that need to not match + * @param \Iterator $iterator The Iterator to filter + * @param string[] $matchPatterns An array of patterns that need to match + * @param string[] $noMatchPatterns An array of patterns that need to not match */ public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) { @@ -50,8 +50,10 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator * If there is no regexps defined in the class, this method will accept the string. * Such case can be handled by child classes before calling the method if they want to * apply a different behavior. + * + * @return bool */ - protected function isAccepted(string $string): bool + protected function isAccepted(string $string) { // should at least not match one rule to exclude foreach ($this->noMatchRegexps as $regex) { @@ -77,8 +79,10 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator /** * Checks whether the string is a regex. + * + * @return bool */ - protected function isRegex(string $str): bool + protected function isRegex(string $str) { $availableModifiers = 'imsxuADU'; @@ -106,6 +110,8 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator /** * Converts string into regexp. + * + * @return string */ - abstract protected function toRegex(string $str): string; + abstract protected function toRegex(string $str); } diff --git a/web/app/vendor/symfony/finder/Iterator/PathFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/PathFilterIterator.php index c6d5813..7974c4e 100644 --- a/web/app/vendor/symfony/finder/Iterator/PathFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/PathFilterIterator.php @@ -11,22 +11,23 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\SplFileInfo; - /** * PathFilterIterator filters files by path patterns (e.g. some/special/dir). * * @author Fabien Potencier <fabien@symfony.com> * @author Włodzimierz Gajda <gajdaw@gajdaw.pl> * - * @extends MultiplePcreFilterIterator<string, SplFileInfo> + * @extends MultiplePcreFilterIterator<string, \SplFileInfo> */ class PathFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { $filename = $this->current()->getRelativePathname(); @@ -48,8 +49,10 @@ class PathFilterIterator extends MultiplePcreFilterIterator * Use only / as directory separator (on Windows also). * * @param string $str Pattern: regexp or dirname + * + * @return string */ - protected function toRegex(string $str): string + protected function toRegex(string $str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } diff --git a/web/app/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/web/app/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php index c321aee..27589cd 100644 --- a/web/app/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -18,17 +18,23 @@ use Symfony\Component\Finder\SplFileInfo; * Extends the \RecursiveDirectoryIterator to support relative paths. * * @author Victor Berchet <victor@suumit.com> - * @extends \RecursiveDirectoryIterator<string, SplFileInfo> */ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { - private bool $ignoreUnreadableDirs; - private ?bool $rewindable = null; + /** + * @var bool + */ + private $ignoreUnreadableDirs; + + /** + * @var bool + */ + private $rewindable; // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations - private string $rootPath; - private string $subPath; - private string $directorySeparator = '/'; + private $rootPath; + private $subPath; + private $directorySeparator = '/'; /** * @throws \RuntimeException @@ -49,15 +55,17 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator /** * Return an instance of SplFileInfo with support for relative paths. + * + * @return SplFileInfo */ - public function current(): SplFileInfo + #[\ReturnTypeWillChange] + public function current() { // the logic here avoids redoing the same work in all iterations - if (!isset($this->subPath)) { - $this->subPath = $this->getSubPath(); + if (null === $subPathname = $this->subPath) { + $subPathname = $this->subPath = $this->getSubPath(); } - $subPathname = $this->subPath; if ('' !== $subPathname) { $subPathname .= $this->directorySeparator; } @@ -70,7 +78,13 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname); } - public function hasChildren(bool $allowLinks = false): bool + /** + * @param bool $allowLinks + * + * @return bool + */ + #[\ReturnTypeWillChange] + public function hasChildren($allowLinks = false) { $hasChildren = parent::hasChildren($allowLinks); @@ -82,16 +96,19 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator parent::getChildren(); return true; - } catch (\UnexpectedValueException) { + } catch (\UnexpectedValueException $e) { // If directory is unreadable and finder is set to ignore it, skip children return false; } } /** + * @return \RecursiveDirectoryIterator + * * @throws AccessDeniedException */ - public function getChildren(): \RecursiveDirectoryIterator + #[\ReturnTypeWillChange] + public function getChildren() { try { $children = parent::getChildren(); @@ -113,8 +130,11 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator /** * Do nothing for non rewindable stream. + * + * @return void */ - public function rewind(): void + #[\ReturnTypeWillChange] + public function rewind() { if (false === $this->isRewindable()) { return; @@ -125,8 +145,10 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator /** * Checks if the stream is rewindable. + * + * @return bool */ - public function isRewindable(): bool + public function isRewindable() { if (null !== $this->rewindable) { return $this->rewindable; diff --git a/web/app/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php index 925830a..575bf29 100644 --- a/web/app/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php @@ -22,7 +22,7 @@ use Symfony\Component\Finder\Comparator\NumberComparator; */ class SizeRangeFilterIterator extends \FilterIterator { - private array $comparators = []; + private $comparators = []; /** * @param \Iterator<string, \SplFileInfo> $iterator @@ -37,8 +37,11 @@ class SizeRangeFilterIterator extends \FilterIterator /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { $fileinfo = $this->current(); if (!$fileinfo->isFile()) { diff --git a/web/app/vendor/symfony/finder/Iterator/SortableIterator.php b/web/app/vendor/symfony/finder/Iterator/SortableIterator.php index cc4dbd6..9afde5c 100644 --- a/web/app/vendor/symfony/finder/Iterator/SortableIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/SortableIterator.php @@ -28,9 +28,8 @@ class SortableIterator implements \IteratorAggregate public const SORT_BY_MODIFIED_TIME = 5; public const SORT_BY_NAME_NATURAL = 6; - /** @var \Traversable<string, \SplFileInfo> */ - private \Traversable $iterator; - private \Closure|int $sort; + private $iterator; + private $sort; /** * @param \Traversable<string, \SplFileInfo> $iterator @@ -38,7 +37,7 @@ class SortableIterator implements \IteratorAggregate * * @throws \InvalidArgumentException */ - public function __construct(\Traversable $iterator, int|callable $sort, bool $reverseOrder = false) + public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false) { $this->iterator = $iterator; $order = $reverseOrder ? -1 : 1; @@ -76,13 +75,17 @@ class SortableIterator implements \IteratorAggregate } elseif (self::SORT_BY_NONE === $sort) { $this->sort = $order; } elseif (\is_callable($sort)) { - $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort(...); + $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort; } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } } - public function getIterator(): \Traversable + /** + * @return \Traversable<string, \SplFileInfo> + */ + #[\ReturnTypeWillChange] + public function getIterator() { if (1 === $this->sort) { return $this->iterator; diff --git a/web/app/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php index 29fc2d9..e27158c 100644 --- a/web/app/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php @@ -13,9 +13,6 @@ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Gitignore; -/** - * @extends \FilterIterator<string, \SplFileInfo> - */ final class VcsIgnoredFilterIterator extends \FilterIterator { /** @@ -33,20 +30,10 @@ final class VcsIgnoredFilterIterator extends \FilterIterator */ private $ignoredPathsCache = []; - /** - * @param \Iterator<string, \SplFileInfo> $iterator - */ public function __construct(\Iterator $iterator, string $baseDir) { $this->baseDir = $this->normalizePath($baseDir); - foreach ($this->parentDirectoriesUpwards($this->baseDir) as $parentDirectory) { - if (@is_dir("{$parentDirectory}/.git")) { - $this->baseDir = $parentDirectory; - break; - } - } - parent::__construct($iterator); } @@ -71,7 +58,7 @@ final class VcsIgnoredFilterIterator extends \FilterIterator $ignored = false; - foreach ($this->parentDirectoriesDownwards($fileRealPath) as $parentDirectory) { + foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) { if ($this->isIgnored($parentDirectory)) { // rules in ignored directories are ignored, no need to check further. break; @@ -102,11 +89,11 @@ final class VcsIgnoredFilterIterator extends \FilterIterator /** * @return list<string> */ - private function parentDirectoriesUpwards(string $from): array + private function parentsDirectoryDownward(string $fileRealPath): array { $parentDirectories = []; - $parentDirectory = $from; + $parentDirectory = $fileRealPath; while (true) { $newParentDirectory = \dirname($parentDirectory); @@ -116,30 +103,16 @@ final class VcsIgnoredFilterIterator extends \FilterIterator break; } - $parentDirectories[] = $parentDirectory = $newParentDirectory; + $parentDirectory = $newParentDirectory; + + if (0 !== strpos($parentDirectory, $this->baseDir)) { + break; + } + + $parentDirectories[] = $parentDirectory; } - return $parentDirectories; - } - - private function parentDirectoriesUpTo(string $from, string $upTo): array - { - return array_filter( - $this->parentDirectoriesUpwards($from), - static function (string $directory) use ($upTo): bool { - return str_starts_with($directory, $upTo); - } - ); - } - - /** - * @return list<string> - */ - private function parentDirectoriesDownwards(string $fileRealPath): array - { - return array_reverse( - $this->parentDirectoriesUpTo($fileRealPath, $this->baseDir) - ); + return array_reverse($parentDirectories); } /** diff --git a/web/app/vendor/symfony/finder/SplFileInfo.php b/web/app/vendor/symfony/finder/SplFileInfo.php index 867e8e8..11604a2 100644 --- a/web/app/vendor/symfony/finder/SplFileInfo.php +++ b/web/app/vendor/symfony/finder/SplFileInfo.php @@ -18,8 +18,8 @@ namespace Symfony\Component\Finder; */ class SplFileInfo extends \SplFileInfo { - private string $relativePath; - private string $relativePathname; + private $relativePath; + private $relativePathname; /** * @param string $file The file name @@ -37,8 +37,10 @@ class SplFileInfo extends \SplFileInfo * Returns the relative path. * * This path does not contain the file name. + * + * @return string */ - public function getRelativePath(): string + public function getRelativePath() { return $this->relativePath; } @@ -47,8 +49,10 @@ class SplFileInfo extends \SplFileInfo * Returns the relative path name. * * This path contains the file name. + * + * @return string */ - public function getRelativePathname(): string + public function getRelativePathname() { return $this->relativePathname; } @@ -63,9 +67,11 @@ class SplFileInfo extends \SplFileInfo /** * Returns the contents of the file. * + * @return string + * * @throws \RuntimeException */ - public function getContents(): string + public function getContents() { set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); try { diff --git a/web/app/vendor/symfony/finder/composer.json b/web/app/vendor/symfony/finder/composer.json index 06d129c..ef19911 100644 --- a/web/app/vendor/symfony/finder/composer.json +++ b/web/app/vendor/symfony/finder/composer.json @@ -16,10 +16,9 @@ } ], "require": { - "php": ">=8.1" - }, - "require-dev": { - "symfony/filesystem": "^6.0" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" }, diff --git a/web/app/vendor/symfony/polyfill-php80/LICENSE b/web/app/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000..5593b1d --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/vendor/symfony/polyfill-php80/Php80.php b/web/app/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000..362dd1a --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan <ion.bazan@gmail.com> + * @author Nico Oelgart <nicoswd@gmail.com> + * @author Nicolas Grekas <p@tchwork.com> + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/web/app/vendor/symfony/polyfill-php80/PhpToken.php b/web/app/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 0000000..fe6e691 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton <info@ensostudio.ru> + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/web/app/vendor/symfony/polyfill-php80/README.md b/web/app/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000..3816c55 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/web/app/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000..7ea6d27 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,22 @@ +<?php + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/web/app/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 0000000..72f1081 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,7 @@ +<?php + +if (\PHP_VERSION_ID < 80000 && \extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/web/app/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000..77e037c --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,11 @@ +<?php + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/web/app/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 0000000..37937cb --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,7 @@ +<?php + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/web/app/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 0000000..a3a9b88 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,7 @@ +<?php + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/web/app/vendor/symfony/polyfill-php80/bootstrap.php b/web/app/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 0000000..e5f7dbc --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/web/app/vendor/symfony/polyfill-php80/composer.json b/web/app/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000..cd3e9b6 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/web/app/views/page-header.php b/web/app/views/page-header.php index 460e2b6..8f67a2c 100644 --- a/web/app/views/page-header.php +++ b/web/app/views/page-header.php @@ -175,6 +175,9 @@ if (!isset($ShowPageHeader)) { ['\\(', '\\)'] ], processEscapes: true + }, + options: { + skipHtmlTags: { '[-]': ['pre'] }, } }; </script> diff --git a/web/css/uoj-bs5.css b/web/css/uoj-bs5.css index 1019f61..80ac420 100644 --- a/web/css/uoj-bs5.css +++ b/web/css/uoj-bs5.css @@ -463,3 +463,8 @@ form.uoj-bs4-form-compressed button { --bs-btn-hover-bg: #d3d4d570; --bs-btn-hover-border-color: transparent; } + +.remote-content center > img + span { + display: block; + font-size: 90%; +}