diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 179d83d837..c7bec59af9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,4 +7,4 @@ Checklist: Tracker ticket: -https://tracker.phpbb.com/browse/PHPBB3-12345 +https://tracker.phpbb.com/browse/PHPBB-12345 diff --git a/.github/workflows/check_merge_to_master.yml b/.github/workflows/check_merge_to_master.yml new file mode 100644 index 0000000000..45288a072a --- /dev/null +++ b/.github/workflows/check_merge_to_master.yml @@ -0,0 +1,70 @@ +name: Check merge to master + +on: + pull_request_target: + types: [ opened, synchronize, reopened ] + branches: + - 3.3.x + +jobs: + merge-check: + if: github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == '3.3.x' + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Ensure full history is fetched + + - name: Set up Git user + run: | + git config --global user.name "github-actions" + git config --global user.email "github-actions@github.com" + + - name: Fetch all branches + run: git fetch origin + + - name: Simulate merging PR into 3.3.x + id: simulate_merge + run: | + git checkout 3.3.x + git fetch origin pull/${{ github.event.pull_request.number }}/head + git merge --no-ff FETCH_HEAD || exit 1 + + - name: Attempt to merge updated 3.3.x into master + id: merge_master + run: | + git checkout master + if git merge --no-ff 3.3.x --no-commit; then + echo "mergeable=true" >> $GITHUB_OUTPUT + else + echo "mergeable=false" >> $GITHUB_OUTPUT + git merge --abort + fi + + - name: Find Comment + uses: peter-evans/find-comment@v3 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: The attempt to merge branch `3.3.x` into `master` has completed + + - name: Post comment on PR + if: always() # Ensure this step always runs, regardless of merge result + uses: peter-evans/create-or-update-comment@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + comment-id: ${{ steps.fc.outputs.comment-id }} + edit-mode: replace + body: | + The attempt to merge branch `3.3.x` into `master` has completed after considering the changes in this PR. + + - Merge result: ${{ steps.merge_master.outputs.mergeable == 'true' && 'Success ✅' || 'Conflict ❌' }} + + ${{ steps.merge_master.outputs.mergeable == 'true' && 'This PR is ready to be merged.' || 'A separate PR will be needed to merge `3.3.x` into `master`.' }} + + - name: Mark job as succeeded + if: always() + run: echo "Merge check completed. Ignoring the result to avoid failed status." diff --git a/.github/workflows/merge_3.3.x_to_master.yml b/.github/workflows/merge_3.3.x_to_master.yml new file mode 100644 index 0000000000..9aea009c3a --- /dev/null +++ b/.github/workflows/merge_3.3.x_to_master.yml @@ -0,0 +1,60 @@ +name: Merge 3.3.x into master + +on: + push: + branches: + - 3.3.x + +jobs: + merge-branch: + runs-on: ubuntu-latest + + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.MERGE_MASTER_APP_ID }} + private-key: ${{ secrets.MERGE_MASTER_SECRET }} + + - name: Checkout the repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch full history for proper merging + ref: 3.3.x # Checkout the 3.3.x branch + token: ${{ steps.app-token.outputs.token }} + + - name: Fetch the latest commit information + id: get-commit-info + run: | + # Get the latest commit SHA and its author details + COMMIT_SHA=$(git rev-parse HEAD) + COMMIT_AUTHOR_NAME=$(git log -1 --pretty=format:'%an' $COMMIT_SHA) + COMMIT_AUTHOR_EMAIL=$(git log -1 --pretty=format:'%ae' $COMMIT_SHA) + + # Save them as output for later steps + echo "commit_sha=$COMMIT_SHA" >> $GITHUB_ENV + echo "commit_author_name=$COMMIT_AUTHOR_NAME" >> $GITHUB_ENV + echo "commit_author_email=$COMMIT_AUTHOR_EMAIL" >> $GITHUB_ENV + + - name: Set up Git with the pull request author's info + run: | + git config --global user.name "${{ env.commit_author_name }}" + git config --global user.email "${{ env.commit_author_email }}" + + - name: Fetch all branches + run: git fetch --all + + - name: Merge 3.3.x into master + run: | + git checkout master + if git merge --no-ff 3.3.x; then + echo "merge_failed=false" >> $GITHUB_ENV + else + echo "merge_failed=true" >> $GITHUB_ENV + fi + + - name: Push changes to master if merge was successful + if: env.merge_failed == 'false' + run: git push origin master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cbf31fc288..3795b00a7e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -137,6 +137,10 @@ jobs: db: "mysql:5.7" - php: '8.3' db: "mariadb:10.2" + - php: '8.4' + db: "mysql:8.0" + - php: '8.4' + db: "mariadb:10.3" name: PHP ${{ matrix.php }} - ${{ matrix.db_alias != '' && matrix.db_alias || matrix.db }} @@ -272,6 +276,8 @@ jobs: db: "postgres:9.5" - php: '8.3' db: "postgres:9.5" + - php: '8.4' + db: "postgres:9.5" name: PHP ${{ matrix.php }} - ${{ matrix.db }} @@ -364,7 +370,7 @@ jobs: # Other database types, namely sqlite3 and mssql other-tests: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: include: @@ -374,17 +380,17 @@ jobs: db: "mcr.microsoft.com/mssql/server:2017-latest" db_alias: 'MSSQL 2017' - php: '8.1' - db: "mcr.microsoft.com/mssql/server:2019-latest" + db: "mcr.microsoft.com/mssql/server:2019-CU27-ubuntu-20.04" db_alias: 'MSSQL 2019' - php: '8.1' - db: "mcr.microsoft.com/mssql/server:2022-latest" + db: "mcr.microsoft.com/mssql/server:2022-CU13-ubuntu-22.04" db_alias: 'MSSQL 2022' name: PHP ${{ matrix.php }} - ${{ matrix.db_alias != '' && matrix.db_alias || matrix.db }} services: mssql: - image: ${{ matrix.db != 'mcr.microsoft.com/mssql/server:2017-latest' && matrix.db != 'mcr.microsoft.com/mssql/server:2019-latest' && matrix.db != 'mcr.microsoft.com/mssql/server:2022-latest' && 'mcr.microsoft.com/mssql/server:2017-latest' || matrix.db }} + image: ${{ matrix.db != 'mcr.microsoft.com/mssql/server:2017-latest' && matrix.db != 'mcr.microsoft.com/mssql/server:2019-CU27-ubuntu-20.04' && matrix.db != 'mcr.microsoft.com/mssql/server:2022-CU13-ubuntu-22.04' && 'mcr.microsoft.com/mssql/server:2017-latest' || matrix.db }} env: SA_PASSWORD: "Pssw0rd_12" ACCEPT_EULA: "y" @@ -416,7 +422,7 @@ jobs: env: MATRIX_DB: ${{ matrix.db }} run: | - if [ $MATRIX_DB == 'mcr.microsoft.com/mssql/server:2017-latest' ] || [ $MATRIX_DB == 'mcr.microsoft.com/mssql/server:2019-latest' ] || [ $MATRIX_DB == 'mcr.microsoft.com/mssql/server:2022-latest' ] + if [ $MATRIX_DB == 'mcr.microsoft.com/mssql/server:2017-latest' ] || [ $MATRIX_DB == 'mcr.microsoft.com/mssql/server:2019-CU27-ubuntu-20.04' ] || [ $MATRIX_DB == 'mcr.microsoft.com/mssql/server:2022-CU13-ubuntu-22.04' ] then db='mssql' else @@ -480,7 +486,7 @@ jobs: strategy: matrix: type: ['unit', 'functional'] - php: ['8.1', '8.2'] + php: ['8.1', '8.2', '8.3'] db: ['postgres'] name: Windows - ${{ matrix.type }} - PHP ${{ matrix.php }} - ${{ matrix.db }} diff --git a/.gitignore b/.gitignore index ed9a602d26..a4985d7bfc 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ # Excludes test / dev files /phpunit.xml +/.phpunit.result.cache /phpBB/composer.phar /tests/phpbb_unit_tests.sqlite* /tests/test_config*.php diff --git a/README.md b/README.md index af3fb91de7..228d62047f 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,8 @@ We have unit and functional tests in order to prevent regressions. You can view Branch | Description | GitHub Actions | ------- | ----------- | -------------- | -**master** | Latest development version | ![Tests](https://github.com/phpbb/phpbb/workflows/Tests/badge.svg?branch=master) | -**3.3.x** | Development of version 3.3.x | ![Tests](https://github.com/phpbb/phpbb/workflows/Tests/badge.svg?branch=3.3.x) | +**master** | Latest development version | ![Tests](https://github.com/phpbb/phpbb/actions/workflows/tests.yml/badge.svg?branch=master) | +**3.3.x** | Development of version 3.3.x | ![Tests](https://github.com/phpbb/phpbb/actions/workflows/tests.yml/badge.svg?branch=3.3.x) | ## 📜 License diff --git a/build/build.xml b/build/build.xml index 65b99b14c2..b6391b011d 100644 --- a/build/build.xml +++ b/build/build.xml @@ -3,8 +3,8 @@ - - + + @@ -181,6 +181,7 @@ + diff --git a/composer.phar b/composer.phar index bd5bab8c1f..15c4a2081c 100755 Binary files a/composer.phar and b/composer.phar differ diff --git a/git-tools/hooks/commit-msg b/git-tools/hooks/commit-msg index 5f5e9c29ce..6405d5b7c8 100755 --- a/git-tools/hooks/commit-msg +++ b/git-tools/hooks/commit-msg @@ -224,7 +224,7 @@ do "footer") err=$ERR_FOOTER; # Each ticket is on its own line - echo "$line" | grep -Eq "^PHPBB3-[0-9]+$"; + echo "$line" | grep -Eq "^PHPBB3?-[0-9]+$"; ;; "eof") err=$ERR_EOF; @@ -356,7 +356,7 @@ echo "$expecting" | grep -q "eof" || ( # Check the branch ticket is mentioned, doesn't make sense otherwise if [ $ticket -gt 0 ] then - echo "$tickets" | grep -Eq "\bPHPBB3-$ticket\b" || ( + echo "$tickets" | grep -Eq "\bPHPBB3?-$ticket\b" || ( complain "Ticket ID [$ticket] of branch missing from list of tickets:" >&2; complain "$tickets" | sed 's/ /\n/g;s/^/* /g' >&2; quit $ERR_FOOTER; diff --git a/git-tools/hooks/prepare-commit-msg b/git-tools/hooks/prepare-commit-msg index 83db1f9ba1..6e5b701fde 100755 --- a/git-tools/hooks/prepare-commit-msg +++ b/git-tools/hooks/prepare-commit-msg @@ -47,7 +47,7 @@ then # Branch is prefixed with 'ticket/', append ticket ID to message if [ "$branch" != "${branch##ticket/}" ]; then - tail="$(printf '\n\nPHPBB3-%s' "$ticket_id")"; + tail="$(printf '\n\nPHPBB-%s' "$ticket_id")"; fi fi diff --git a/package-lock.json b/package-lock.json index d21a035c1f..102d21cd0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -595,10 +595,11 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -608,7 +609,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -623,6 +624,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -631,7 +633,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/boolbase": { "version": "1.0.0", @@ -650,12 +653,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -725,6 +729,7 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -734,6 +739,7 @@ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -1027,15 +1033,17 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1316,6 +1324,7 @@ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -1333,6 +1342,7 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1342,6 +1352,7 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -1461,7 +1472,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.4.111", @@ -1476,10 +1488,11 @@ "dev": true }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1516,6 +1529,7 @@ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", "dev": true, + "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -1528,6 +1542,7 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -1545,7 +1560,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -1739,6 +1755,7 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1768,37 +1785,38 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -1958,10 +1976,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1970,13 +1989,14 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, + "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -1992,6 +2012,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -2000,7 +2021,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/find-up": { "version": "4.1.0", @@ -2122,6 +2144,7 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2188,6 +2211,7 @@ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -2338,6 +2362,7 @@ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, + "license": "MIT", "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -2509,6 +2534,7 @@ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -2521,6 +2547,7 @@ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2533,6 +2560,7 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2545,6 +2573,7 @@ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -2605,6 +2634,7 @@ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -2621,6 +2651,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -2851,6 +2882,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -3226,6 +3258,7 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3269,10 +3302,14 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge2": { "version": "1.4.1", @@ -3293,12 +3330,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -3310,6 +3348,7 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -3587,10 +3626,14 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3627,6 +3670,7 @@ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -3763,6 +3807,7 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -3822,10 +3867,11 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "dev": true, + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", @@ -4443,12 +4489,13 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -4497,6 +4544,7 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4506,6 +4554,7 @@ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -4854,10 +4903,11 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -4882,6 +4932,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -4890,24 +4941,37 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, + "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -4918,6 +4982,7 @@ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -4934,7 +4999,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -4962,6 +5028,7 @@ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -5086,6 +5153,7 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -5519,6 +5587,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -5543,6 +5612,7 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6" } @@ -5585,6 +5655,7 @@ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -5640,6 +5711,7 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6389,9 +6461,9 @@ } }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "requires": { "bytes": "3.1.2", @@ -6402,7 +6474,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -6442,12 +6514,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browserslist": { @@ -6709,9 +6781,9 @@ "dev": true }, "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true }, "cookie-signature": { @@ -7033,9 +7105,9 @@ "dev": true }, "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true }, "end-of-stream": { @@ -7246,37 +7318,37 @@ } }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -7408,22 +7480,22 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8402,9 +8474,9 @@ } }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "dev": true }, "merge2": { @@ -8420,12 +8492,12 @@ "dev": true }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -8637,9 +8709,9 @@ } }, "object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "dev": true }, "object.defaults": { @@ -8809,9 +8881,9 @@ "dev": true }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "dev": true }, "path-type": { @@ -9201,12 +9273,12 @@ "dev": true }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "queue-microtask": { @@ -9493,9 +9565,9 @@ } }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "requires": { "debug": "2.6.9", @@ -9530,6 +9602,12 @@ } } }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9539,15 +9617,15 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" } }, "set-function-length": { diff --git a/phpBB/adm/style/acp_bots.html b/phpBB/adm/style/acp_bots.html index b4f8ea5072..8332df7e2b 100644 --- a/phpBB/adm/style/acp_bots.html +++ b/phpBB/adm/style/acp_bots.html @@ -27,7 +27,9 @@

{L_WARNING}


{L_BOT_STYLE_EXPLAIN}
-
+
+ {{ FormsSelect(S_STYLE_OPTIONS) }} +

{L_BOT_LANG_EXPLAIN}
@@ -37,7 +39,9 @@

{L_WARNING}

-
+
+ {{ FormsSelect(S_ACTIVE_OPTIONS) }} +

{L_BOT_AGENT_EXPLAIN}
diff --git a/phpBB/adm/style/acp_captcha.html b/phpBB/adm/style/acp_captcha.html index 4353becd2f..0c9d3bf0af 100644 --- a/phpBB/adm/style/acp_captcha.html +++ b/phpBB/adm/style/acp_captcha.html @@ -1,79 +1,85 @@ - +{% include 'overall_header.html' %} -

{L_ACP_VC_SETTINGS}

+

{{ lang('ACP_VC_SETTINGS') }}

-

{L_ACP_VC_SETTINGS_EXPLAIN}

+

{{ lang('ACP_VC_SETTINGS_EXPLAIN') }}

-

{L_ACP_VC_EXT_GET_MORE}

+

{{ lang('ACP_VC_EXT_GET_MORE') }}

- +{% if ERRORS %}
-

{L_WARNING}

-

{ERROR_MSG}

+

{{ lang('WARNING') }}

+

{{ ERRORS|join('
') }}

- +{% endif %} -
+
-{L_GENERAL_OPTIONS} +{{ lang('GENERAL_OPTIONS') }}
-

{L_VISUAL_CONFIRM_REG_EXPLAIN}
-
-
+

{{ lang('VISUAL_CONFIRM_REG_EXPLAIN') }}
+
+ + +
-

{L_REG_LIMIT_EXPLAIN}
-
+

{{ lang('REG_LIMIT_EXPLAIN') }}
+
-

{L_MAX_LOGIN_ATTEMPTS_EXPLAIN}
-
+

{{ lang('MAX_LOGIN_ATTEMPTS_EXPLAIN') }}
+
-

{L_VISUAL_CONFIRM_POST_EXPLAIN}
-
-
+

{{ lang('VISUAL_CONFIRM_POST_EXPLAIN') }}
+
+ + +
-

{L_VISUAL_CONFIRM_REFRESH_EXPLAIN}
-
-
+

{{ lang('VISUAL_CONFIRM_REFRESH_EXPLAIN') }}
+
+ + +
-{L_AVAILABLE_CAPTCHAS} -
-

{L_CAPTCHA_SELECT_EXPLAIN}
-
-
- -
-

{L_CAPTCHA_CONFIGURE_EXPLAIN}
-
-
- +{{ lang('AVAILABLE_CAPTCHAS') }} +
+

{{ lang('CAPTCHA_SELECT_EXPLAIN') }}
+
{{ FormsSelect(CAPTCHA_SELECT | merge({id: 'captcha_select', onchange: "(document.getElementById('acp_captcha')).submit()"})) }}
+
+ {% if S_CAPTCHA_HAS_CONFIG %} +
+

{{ lang('CAPTCHA_CONFIGURE_EXPLAIN') }}
+
+
+ {% endif %}
- +{% if CAPTCHA_PREVIEW_TPL %}
- {L_PREVIEW} - + {{ lang('PREVIEW') }} + {% include CAPTCHA_PREVIEW_TPL %}
- +{% endif %}
- {L_ACP_SUBMIT_CHANGES} + {{ lang('ACP_SUBMIT_CHANGES') }}

-   -   +   +  

- {S_FORM_TOKEN} + {{ S_FORM_TOKEN }}
- +{% include 'overall_footer.html' %} diff --git a/phpBB/adm/style/acp_ext_list.html b/phpBB/adm/style/acp_ext_list.html index bb22ac861c..af21b4f6b1 100644 --- a/phpBB/adm/style/acp_ext_list.html +++ b/phpBB/adm/style/acp_ext_list.html @@ -42,82 +42,94 @@

{L_EXTENSIONS_ADMIN}

- - - {L_EXTENSIONS_ENABLED} - - - - {enabled.META_DISPLAY_NAME} - - - {enabled.META_VERSION} - {{ Icon('font', 'circle-exclamation', '', true, 'fas outdated-ext') }} - - {enabled.META_VERSION} - - - {L_DETAILS} - - - style="color: #bc2a4d;" title="{enabled.actions.L_ACTION_EXPLAIN}">{enabled.actions.L_ACTION} -  |  - - - - - - - - - {L_EXTENSIONS_DISABLED} - - - - {disabled.META_DISPLAY_NAME} - - - {disabled.META_VERSION} - {{ Icon('font', 'circle-exclamation', '', true, 'fas outdated-ext') }} - - {disabled.META_VERSION} - - - - {L_DETAILS} - - - - style="color: #bc2a4d;" title="{disabled.actions.L_ACTION_EXPLAIN}">{disabled.actions.L_ACTION} -  |  - - - - - + {% for list in ['enabled', 'disabled', 'not_installed'] %} + {% set blockname = attribute(loops, list) %} + {% if blockname|length %} + + {{ lang('EXTENSIONS_' ~ list|upper) }} + {% if list == 'enabled' %} + {% EVENT acp_ext_list_enabled_title_after %} + {% elseif list == 'disabled' %} + {% EVENT acp_ext_list_disabled_title_after %} + {% elseif list == 'not_installed' %} + {% EVENT acp_ext_list_not_installed_title_after %} + {% endif %} + + + {% for data in blockname %} + + {{ data.META_DISPLAY_NAME }} + {% if list == 'enabled' %} + {% EVENT acp_ext_list_enabled_name_after %} + {% elseif list == 'disabled' %} + {% EVENT acp_ext_list_disabled_name_after %} + {% elseif list == 'not_installed' %} + {% EVENT acp_ext_list_not_installed_name_after %} + {% endif %} + + + {% if data.S_VERSIONCHECK %} + {{ data.META_VERSION }} + {% if not data.S_UP_TO_DATE %}{{ Icon('font', 'circle-exclamation', '', true, 'fas outdated-ext') }}{% endif %} + {% else %} + {{ data.META_VERSION }} + {% endif %} + + + {% if data.U_DETAILS %}{{ lang ('DETAILS') }}{% endif %} + + + {% for actions in data.actions %} + {{ actions.L_ACTION }} + {% if not actions.S_LAST_ROW %} | {% endif %} + {% endfor %} + + + {% endfor %} + {% endif %} + {% endfor %} - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + +
{L_EXTENSION_INSTALL_HEADLINE}
{L_EXTENSION_INSTALL_EXPLAIN}
{L_EXTENSION_UPDATE_HEADLINE}
{L_EXTENSION_UPDATE_EXPLAIN}
{L_EXTENSION_REMOVE_HEADLINE}
{L_EXTENSION_REMOVE_EXPLAIN}
{L_EXTENSION_INSTALLING_HEADLINE}
+
    + {% for step in lang_raw('EXTENSION_INSTALLING_EXPLAIN') %} +
  1. {{ step }}
  2. + {% endfor %} +
+
{L_EXTENSION_UPDATING_HEADLINE}
+
    + {% for step in lang_raw('EXTENSION_UPDATING_EXPLAIN') %} +
  1. {{ step }}
  2. + {% endfor %} +
+
{L_EXTENSION_REMOVING_HEADLINE}
+
    + {% for step in lang_raw('EXTENSION_REMOVING_EXPLAIN') %} +
  1. {{ step }}
  2. + {% endfor %} +
+
diff --git a/phpBB/adm/style/acp_forums.html b/phpBB/adm/style/acp_forums.html index 613a054b32..d030d2e587 100644 --- a/phpBB/adm/style/acp_forums.html +++ b/phpBB/adm/style/acp_forums.html @@ -181,7 +181,14 @@

{L_WARNING}

-
+
+ +
diff --git a/phpBB/adm/style/acp_forums_copy_perm.html b/phpBB/adm/style/acp_forums_copy_perm.html deleted file mode 100644 index 1fcf2f62a4..0000000000 --- a/phpBB/adm/style/acp_forums_copy_perm.html +++ /dev/null @@ -1,22 +0,0 @@ - - -

{L_COPY_PERMISSIONS}

- -

{L_COPY_PERMISSIONS_EXPLAIN}

-

{L_ACL_LINK}

- -
- -
-
-

{L_COPY_PERMISSIONS_EXPLAIN}
-
-
-
{S_FORM_TOKEN}{S_HIDDEN_FIELDS} -   -
-
- -
- - diff --git a/phpBB/adm/style/acp_icons.html b/phpBB/adm/style/acp_icons.html index 25d5cb5207..9a5179419e 100644 --- a/phpBB/adm/style/acp_icons.html +++ b/phpBB/adm/style/acp_icons.html @@ -105,7 +105,7 @@

{L_TITLE}

- {items.TEXT_ALT} + {items.TEXT_ALT} [{items.IMG}] diff --git a/phpBB/adm/style/acp_posting_buttons.html b/phpBB/adm/style/acp_posting_buttons.html index e5f96b0c22..c0c5aacd74 100644 --- a/phpBB/adm/style/acp_posting_buttons.html +++ b/phpBB/adm/style/acp_posting_buttons.html @@ -28,7 +28,7 @@ - diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index 3886f006d1..86c9298828 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -16,31 +16,32 @@

{{ lang('STORAGE_TITLE') }}

- {% for storage in STORAGE_STATS %} - - {{ storage.name }} - {{ storage.files }} - {{ storage.size }} - {{ storage.free_space }} - - {% endfor %} + {% for storage in STORAGE_STATS %} + + {{ storage.name }} + {{ storage.files }} + {{ storage.size }} + {{ storage.free_space }} + + {% endfor %} -{% if S_ERROR %} +{% if ERROR_MESSAGES is not empty %}

{{ lang('WARNING') }}

-

{{ ERROR_MSG }}

+ {% for ERROR_MESSAGE in ERROR_MESSAGES %} +

{{ ERROR_MESSAGE }}

+ {% endfor %}
{% endif %}
- {% for storage in STORAGES %}
{{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }}
-

{{ lang('STORAGE_SELECT_DESC') }}
+

{{ lang('STORAGE_SELECT_DESC') }}
- {% elseif input_type == 'textarea' %} - - {% elseif input_type == 'radio' %} - {% for option_name, option_value in options['options'] %} - {{ lang(option_name) }} + {% set input_name = storage.get_name ~ '[' ~ name ~ ']' %} + {% set input_value = attribute(config, 'storage\\' ~ storage.get_name ~ '\\config\\' ~ name) %} + + {% if options.tag == 'input' %} + {{ FormsInput(options | merge({"name": input_name, "value": input_value})) }} + {% elseif options.tag == 'textarea' %} + {{ FormsTextarea(options | merge({"name": input_name, "content": input_value})) }} + {% elseif options.tag == 'radio' %} + {% set buttons = [] %} + + {% for button in options.buttons %} + {% set new_button = button | merge({"name": input_name, "checked": button.value == input_value}) %} + {% set buttons = buttons | merge([new_button]) %} + {% endfor %} + + {{ FormsRadioButtons(options | merge({"buttons": buttons})) }} + {% elseif options.tag == 'select' %} + {% set select_options = [] %} + + {% for option in options.options %} + {% set new_option = option | merge({"selected": option.value == input_value}) %} + {% set select_options = select_options | merge([new_option]) %} {% endfor %} - {% elseif input_type == 'select' %} - + + {{ FormsSelect(options | merge({"name": input_name, "options": select_options})) }} {% endif %}
@@ -97,6 +105,17 @@

{{ lang('WARNING') }}

{% endfor %} {% endfor %} +
+
+
+
+ + + +
+
+
+
{{ lang('SUBMIT') }}   diff --git a/phpBB/adm/style/acp_storage_update_inprogress.html b/phpBB/adm/style/acp_storage_update_inprogress.html new file mode 100644 index 0000000000..5f7614cb2c --- /dev/null +++ b/phpBB/adm/style/acp_storage_update_inprogress.html @@ -0,0 +1,32 @@ +{% include 'overall_header.html' %} + + + +

{{ lang('STORAGE_TITLE') }}

+ +

{{ lang('STORAGE_TITLE_EXPLAIN') }}

+ + +
+ {{ lang('SUBMIT') }} + + {% if CONTINUE_PROGRESS %} +
+
+
+ {{ CONTINUE_PROGRESS.PERCENTAGE|number_format(2) ~ ' %' }} +
+ {% endif %} + +

+   + +

+ {{ S_FORM_TOKEN }} +
+ + +{% include 'overall_footer.html' %} diff --git a/phpBB/adm/style/acp_storage_update_progress.html b/phpBB/adm/style/acp_storage_update_progress.html new file mode 100644 index 0000000000..17ba67a037 --- /dev/null +++ b/phpBB/adm/style/acp_storage_update_progress.html @@ -0,0 +1,20 @@ +{% include 'overall_header.html' %} + + + +
+

{{ INDEXING_TITLE }}

+

+ {{ INDEXING_EXPLAIN }} + {% if INDEXING_PROGRESS_BAR %} +
+
+ {{ INDEXING_PROGRESS_BAR.PERCENTAGE|number_format(2) ~ ' %' }} + {% endif %} +

+
+ +{% include 'overall_footer.html' %} diff --git a/phpBB/adm/style/acp_users_prefs.html b/phpBB/adm/style/acp_users_prefs.html index 68420389b4..d485d1e113 100644 --- a/phpBB/adm/style/acp_users_prefs.html +++ b/phpBB/adm/style/acp_users_prefs.html @@ -48,7 +48,9 @@
-
+
+ {{ FormsSelect(S_STYLE_OPTIONS) }} +
diff --git a/phpBB/adm/style/captcha_qa_acp.html b/phpBB/adm/style/captcha_qa_acp.html index 26598c7c2b..1118a1f32e 100644 --- a/phpBB/adm/style/captcha_qa_acp.html +++ b/phpBB/adm/style/captcha_qa_acp.html @@ -18,11 +18,13 @@

{L_QUESTIONS}

{L_QUESTIONS} + {% if questions %} {L_QUESTION_TEXT} {L_QUESTION_LANG} {L_ACTION} + {% endif %} @@ -33,6 +35,10 @@

{L_QUESTIONS}

{{ question.QUESTION_LANG }} {{ ICON_EDIT }} {{ ICON_DELETE }} + {% else %} + + {{ lang('QA_NO_QUESTIONS') }} + {% endfor %} diff --git a/phpBB/adm/style/captcha_turnstile_acp.html b/phpBB/adm/style/captcha_turnstile_acp.html new file mode 100644 index 0000000000..a187e1e41b --- /dev/null +++ b/phpBB/adm/style/captcha_turnstile_acp.html @@ -0,0 +1,64 @@ +{% include('overall_header.html') %} + + + +

{{ lang('ACP_VC_SETTINGS') }}

+ +

{{ lang('ACP_VC_SETTINGS_EXPLAIN') }}

+ +
+
+ {{ lang('GENERAL_OPTIONS') }} +
+
+
+ {{ lang('CAPTCHA_TURNSTILE_SITEKEY_EXPLAIN') }} +
+
+
+
+
+
+ {{ lang('CAPTCHA_TURNSTILE_SECRET_EXPLAIN') }} +
+
+
+
+
+ +
{{ lang('CAPTCHA_TURNSTILE_THEME_EXPLAIN') }} +
+
+ {% for theme in CAPTCHA_TURNSTILE_THEMES %} + + {% endfor %} +
+
+
+
+ {{ lang('PREVIEW') }} + {% if PREVIEW %} +
+

{{ lang('WARNING') }}

+

{{ lang('CAPTCHA_PREVIEW_MSG') }}

+
+ {% endif %} + {% include(CAPTCHA_PREVIEW) %} +
+ +
+ {{ lang('ACP_SUBMIT_CHANGES') }} +

+   +   +

+ + + {{ S_FORM_TOKEN }} +
+
+ +{% include('overall_footer.html') %} diff --git a/phpBB/adm/style/captcha_turnstile_acp_demo.html b/phpBB/adm/style/captcha_turnstile_acp_demo.html new file mode 100644 index 0000000000..757d0a4bcb --- /dev/null +++ b/phpBB/adm/style/captcha_turnstile_acp_demo.html @@ -0,0 +1,23 @@ +
+
+
+{% INCLUDEJS U_TURNSTILE_SCRIPT %} + diff --git a/phpBB/assets/javascript/installer.js b/phpBB/assets/javascript/installer.js index a11b76b863..cfe685f88b 100644 --- a/phpBB/assets/javascript/installer.js +++ b/phpBB/assets/javascript/installer.js @@ -605,6 +605,8 @@ function interceptFormSubmit($form) { if (!$form.length) { return; + } else if ($form.find('input[name="admin_name"]').length > 0) { + setAdminTimezone($form); } $form.find(':submit').bind('click', function (event) { @@ -612,4 +614,20 @@ submitForm($form, $(this)); }); } + + /** + * Set admin timezone in form + * + * @param $form + */ + function setAdminTimezone($form) { + // Set admin timezone if it does not exist yet + if ($form.find('input[name="admin_timezone"]').length === 0) { + const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + // Add timezone as form entry + const timezoneEntry = $(''); + $form.append(timezoneEntry); + } + } })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/assets/javascript/webpush.js b/phpBB/assets/javascript/webpush.js index c326fe1cff..4adb9ecf10 100644 --- a/phpBB/assets/javascript/webpush.js +++ b/phpBB/assets/javascript/webpush.js @@ -51,7 +51,7 @@ function PhpbbWebpush() { // Service workers are only supported in secure context if (window.isSecureContext !== true) { - subscribeButton.disabled = true; + setDisabledState(); return; } @@ -66,13 +66,33 @@ function PhpbbWebpush() { .catch(error => { console.info(error); // Service worker could not be registered - subscribeButton.disabled = true; + setDisabledState(); }); } else { - subscribeButton.disabled = true; + setDisabledState(); } }; + /** + * Disable subscribing buttons, update subscribe button text and hide dropdown toggle + * + * @return void + */ + function setDisabledState() { + subscribeButton.disabled = true; + + const notificationList = document.getElementById('notification-menu'); + const subscribeToggle = notificationList.querySelector('.webpush-subscribe'); + + if (subscribeToggle) { + subscribeToggle.style.display = 'none'; + } + + if (subscribeButton.type === 'submit' || subscribeButton.classList.contains('button')) { + subscribeButton.value = subscribeButton.getAttribute('data-disabled-msg'); + } + } + /** * Update button state depending on notifications state * @@ -150,6 +170,7 @@ function PhpbbWebpush() { // Prevent the user from clicking the subscribe button multiple times. const result = await Notification.requestPermission(); if (result === 'denied') { + phpbb.alert(subscribeButton.getAttribute('data-l-err'), subscribeButton.getAttribute('data-l-msg')); return; } diff --git a/phpBB/composer.json b/phpBB/composer.json index 6d59cefe26..2d75027ff0 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -58,7 +58,7 @@ "symfony/routing": "^6.3", "symfony/twig-bridge": "^6.3", "symfony/yaml": "^6.3", - "twig/twig": "^3.0" + "twig/twig": "^3.14" }, "require-dev": { "laravel/homestead": "~14.4", @@ -73,9 +73,6 @@ "vimeo/psalm": "^5.18.0", "psalm/plugin-symfony": "^v5.1.0" }, - "suggest": { - "ext-mbstring": "Better performance in search" - }, "extra": { "branch-alias": { "dev-master": "4.0.x-dev" diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 287f56b08b..8fe9b37b7e 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "26143c95732859f6f0849abd2fe74ae5", + "content-hash": "504e019821be33ba6ad322b37d698744", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -102,21 +102,24 @@ }, { "name": "carlos-mg89/oauth", - "version": "0.8.15", + "version": "0.8.16", "source": { "type": "git", "url": "https://github.com/carlos-mg89/PHPoAuthLib.git", - "reference": "c14c44e4f8993009a10d56a41e61c19291824a29" + "reference": "7c8aaace2004f0736c4598ad5d78307b43ccd858" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/carlos-mg89/PHPoAuthLib/zipball/c14c44e4f8993009a10d56a41e61c19291824a29", - "reference": "c14c44e4f8993009a10d56a41e61c19291824a29", + "url": "https://api.github.com/repos/carlos-mg89/PHPoAuthLib/zipball/7c8aaace2004f0736c4598ad5d78307b43ccd858", + "reference": "7c8aaace2004f0736c4598ad5d78307b43ccd858", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, + "replace": { + "lusitanian/oauth": "~0.3" + }, "require-dev": { "ext-curl": "*", "ext-dom": "*", @@ -164,9 +167,9 @@ ], "support": { "issues": "https://github.com/carlos-mg89/PHPoAuthLib/issues", - "source": "https://github.com/carlos-mg89/PHPoAuthLib/tree/0.8.15" + "source": "https://github.com/carlos-mg89/PHPoAuthLib/tree/0.8.16" }, - "time": "2022-07-15T08:38:14+00:00" + "time": "2024-08-09T10:00:52+00:00" }, { "name": "chita/topological_sort", @@ -213,28 +216,28 @@ }, { "name": "composer/ca-bundle", - "version": "1.4.1", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd" + "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3b1fc3f0be055baa7c6258b1467849c3e8204eb2", + "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -269,7 +272,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.4.1" + "source": "https://github.com/composer/ca-bundle/tree/1.5.3" }, "funding": [ { @@ -285,20 +288,20 @@ "type": "tidelift" } ], - "time": "2024-02-23T10:16:52+00:00" + "time": "2024-11-04T10:15:26+00:00" }, { "name": "composer/class-map-generator", - "version": "1.1.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9" + "reference": "98bbf6780e56e0fd2404fe4b82eb665a0f93b783" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/953cc4ea32e0c31f2185549c7d216d7921f03da9", - "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/98bbf6780e56e0fd2404fe4b82eb665a0f93b783", + "reference": "98bbf6780e56e0fd2404fe4b82eb665a0f93b783", "shasum": "" }, "require": { @@ -311,8 +314,8 @@ "phpstan/phpstan-deprecation-rules": "^1", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/filesystem": "^5.4 || ^6", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6" }, "type": "library", "extra": { @@ -342,7 +345,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.1.0" + "source": "https://github.com/composer/class-map-generator/tree/1.4.0" }, "funding": [ { @@ -358,52 +361,52 @@ "type": "tidelift" } ], - "time": "2023-06-30T13:58:57+00:00" + "time": "2024-10-03T18:14:00+00:00" }, { "name": "composer/composer", - "version": "2.7.1", + "version": "2.8.2", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc" + "reference": "6e543d03187c882ea1c6ba43add2467754427803" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc", - "reference": "aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc", + "url": "https://api.github.com/repos/composer/composer/zipball/6e543d03187c882ea1c6ba43add2467754427803", + "reference": "6e543d03187c882ea1c6ba43add2467754427803", "shasum": "" }, "require": { - "composer/ca-bundle": "^1.0", - "composer/class-map-generator": "^1.0", + "composer/ca-bundle": "^1.5", + "composer/class-map-generator": "^1.4.0", "composer/metadata-minifier": "^1.0", - "composer/pcre": "^2.1 || ^3.1", - "composer/semver": "^3.2.5", + "composer/pcre": "^2.2 || ^3.2", + "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", "composer/xdebug-handler": "^2.0.2 || ^3.0.3", - "justinrainbow/json-schema": "^5.2.11", + "justinrainbow/json-schema": "^5.3", "php": "^7.2.5 || ^8.0", "psr/log": "^1.0 || ^2.0 || ^3.0", - "react/promise": "^2.8 || ^3", + "react/promise": "^3.2", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.2", "seld/signal-handler": "^2.0", - "symfony/console": "^5.4.11 || ^6.0.11 || ^7", - "symfony/filesystem": "^5.4 || ^6.0 || ^7", - "symfony/finder": "^5.4 || ^6.0 || ^7", + "symfony/console": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3", "symfony/polyfill-php73": "^1.24", "symfony/polyfill-php80": "^1.24", "symfony/polyfill-php81": "^1.24", - "symfony/process": "^5.4 || ^6.0 || ^7" + "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3" }, "require-dev": { - "phpstan/phpstan": "^1.9.3", - "phpstan/phpstan-deprecation-rules": "^1", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1", - "phpstan/phpstan-symfony": "^1.2.10", - "symfony/phpunit-bridge": "^6.4.1 || ^7.0.1" + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0", + "symfony/phpunit-bridge": "^6.4.3 || ^7.0.1" }, "suggest": { "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", @@ -416,7 +419,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.7-dev" + "dev-main": "2.8-dev" }, "phpstan": { "includes": [ @@ -456,7 +459,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.7.1" + "source": "https://github.com/composer/composer/tree/2.8.2" }, "funding": [ { @@ -472,7 +475,7 @@ "type": "tidelift" } ], - "time": "2024-02-09T14:26:28+00:00" + "time": "2024-10-29T15:12:11+00:00" }, { "name": "composer/installers", @@ -769,30 +772,38 @@ }, { "name": "composer/pcre", - "version": "3.1.1", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4", + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, "require-dev": { - "phpstan/phpstan": "^1.3", + "phpstan/phpstan": "^1.11.10", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { "branch-alias": { "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { @@ -820,7 +831,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.1" + "source": "https://github.com/composer/pcre/tree/3.3.1" }, "funding": [ { @@ -836,28 +847,28 @@ "type": "tidelift" } ], - "time": "2023-10-11T07:11:09+00:00" + "time": "2024-08-27T18:44:43+00:00" }, { "name": "composer/semver", - "version": "3.4.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -901,7 +912,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "source": "https://github.com/composer/semver/tree/3.4.3" }, "funding": [ { @@ -917,7 +928,7 @@ "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { "name": "composer/spdx-licenses", @@ -1001,16 +1012,16 @@ }, { "name": "composer/xdebug-handler", - "version": "3.0.3", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { @@ -1021,7 +1032,7 @@ "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { @@ -1045,9 +1056,9 @@ "performance" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { @@ -1063,7 +1074,7 @@ "type": "tidelift" } ], - "time": "2022-02-25T21:32:43+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { "name": "doctrine/cache", @@ -1410,16 +1421,16 @@ }, { "name": "friendsofphp/proxy-manager-lts", - "version": "v1.0.16", + "version": "v1.0.18", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git", - "reference": "ecadbdc9052e4ad08c60c8a02268712e50427f7c" + "reference": "2c8a6cffc3220e99352ad958fe7cf06bf6f7690f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/ecadbdc9052e4ad08c60c8a02268712e50427f7c", - "reference": "ecadbdc9052e4ad08c60c8a02268712e50427f7c", + "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/2c8a6cffc3220e99352ad958fe7cf06bf6f7690f", + "reference": "2c8a6cffc3220e99352ad958fe7cf06bf6f7690f", "shasum": "" }, "require": { @@ -1476,7 +1487,7 @@ ], "support": { "issues": "https://github.com/FriendsOfPHP/proxy-manager-lts/issues", - "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.16" + "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.18" }, "funding": [ { @@ -1488,7 +1499,7 @@ "type": "tidelift" } ], - "time": "2023-05-24T07:17:17+00:00" + "time": "2024-03-20T12:50:41+00:00" }, { "name": "google/recaptcha", @@ -1843,20 +1854,20 @@ }, { "name": "justinrainbow/json-schema", - "version": "v5.2.13", + "version": "5.3.0", "source": { "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", @@ -1867,11 +1878,6 @@ "bin/validate-json" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" @@ -1906,35 +1912,35 @@ "schema" ], "support": { - "issues": "https://github.com/justinrainbow/json-schema/issues", - "source": "https://github.com/justinrainbow/json-schema/tree/v5.2.13" + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0" }, - "time": "2023-09-26T02:20:38+00:00" + "time": "2024-07-06T21:00:26+00:00" }, { "name": "laminas/laminas-code", - "version": "4.13.0", + "version": "4.15.1", "source": { "type": "git", "url": "https://github.com/laminas/laminas-code.git", - "reference": "7353d4099ad5388e84737dd16994316a04f48dbf" + "reference": "877ad42fe9c164785182fca8afa3f416a056884d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/7353d4099ad5388e84737dd16994316a04f48dbf", - "reference": "7353d4099ad5388e84737dd16994316a04f48dbf", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/877ad42fe9c164785182fca8afa3f416a056884d", + "reference": "877ad42fe9c164785182fca8afa3f416a056884d", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { "doctrine/annotations": "^2.0.1", "ext-phar": "*", - "laminas/laminas-coding-standard": "^2.5.0", - "laminas/laminas-stdlib": "^3.17.0", - "phpunit/phpunit": "^10.3.3", - "psalm/plugin-phpunit": "^0.18.4", + "laminas/laminas-coding-standard": "^3.0.0", + "laminas/laminas-stdlib": "^3.18.0", + "phpunit/phpunit": "^10.5.37", + "psalm/plugin-phpunit": "^0.19.0", "vimeo/psalm": "^5.15.0" }, "suggest": { @@ -1972,7 +1978,7 @@ "type": "community_bridge" } ], - "time": "2023-10-18T10:00:55+00:00" + "time": "2024-10-25T10:15:16+00:00" }, { "name": "marc1706/fast-image-size", @@ -2103,24 +2109,24 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.6.3", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "58c3f47f650c94ec05a151692652a868995d2938" + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", - "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", "shasum": "" }, "require": { - "php": "^7|^8" + "php": "^8" }, "require-dev": { - "phpunit/phpunit": "^6|^7|^8|^9", - "vimeo/psalm": "^1|^2|^3|^4" + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" }, "type": "library", "autoload": { @@ -2166,84 +2172,39 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2022-06-14T06:56:20+00:00" - }, - { - "name": "paragonie/random_compat", - "version": "v9.99.100", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", - "shasum": "" - }, - "require": { - "php": ">= 7" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/random_compat/issues", - "source": "https://github.com/paragonie/random_compat" - }, - "time": "2020-10-15T08:29:30+00:00" + "time": "2024-05-08T12:36:18+00:00" }, { "name": "paragonie/sodium_compat", - "version": "v1.20.0", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6" + "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/e592a3e06d1fa0d43988c7c7d9948ca836f644b6", - "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/a673d5f310477027cead2e2f2b6db5d8368157cb", + "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb", "shasum": "" }, "require": { - "paragonie/random_compat": ">=1", - "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" + "php": "^8.1", + "php-64bit": "*" }, "require-dev": { - "phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9" + "phpunit/phpunit": "^7|^8|^9", + "vimeo/psalm": "^4|^5" }, "suggest": { - "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", - "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "files": [ "autoload.php" @@ -2300,9 +2261,9 @@ ], "support": { "issues": "https://github.com/paragonie/sodium_compat/issues", - "source": "https://github.com/paragonie/sodium_compat/tree/v1.20.0" + "source": "https://github.com/paragonie/sodium_compat/tree/v2.1.0" }, - "time": "2023-04-30T00:54:53+00:00" + "time": "2024-09-04T12:51:01+00:00" }, { "name": "psr/cache", @@ -2558,20 +2519,20 @@ }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -2595,7 +2556,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -2607,9 +2568,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-04-10T20:10:41+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -2666,16 +2627,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -2710,9 +2671,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "ralouphie/getallheaders", @@ -2760,16 +2721,16 @@ }, { "name": "react/promise", - "version": "v3.1.0", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c" + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", - "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", "shasum": "" }, "require": { @@ -2821,7 +2782,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.1.0" + "source": "https://github.com/reactphp/promise/tree/v3.2.0" }, "funding": [ { @@ -2829,7 +2790,7 @@ "type": "open_collective" } ], - "time": "2023-11-16T16:21:57+00:00" + "time": "2024-05-24T10:39:05+00:00" }, { "name": "s9e/regexp-builder", @@ -2875,16 +2836,16 @@ }, { "name": "s9e/sweetdom", - "version": "3.4.0", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/s9e/SweetDOM.git", - "reference": "b07ae8f5fe4ac40a0b734e0e23dbadcc26a161e3" + "reference": "ef3a7d2745b30b4ad0d1d3d60be391a3604c69dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/SweetDOM/zipball/b07ae8f5fe4ac40a0b734e0e23dbadcc26a161e3", - "reference": "b07ae8f5fe4ac40a0b734e0e23dbadcc26a161e3", + "url": "https://api.github.com/repos/s9e/SweetDOM/zipball/ef3a7d2745b30b4ad0d1d3d60be391a3604c69dd", + "reference": "ef3a7d2745b30b4ad0d1d3d60be391a3604c69dd", "shasum": "" }, "require": { @@ -2892,6 +2853,7 @@ "php": "^8.1" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.52", "phpunit/phpunit": "^10.0", "s9e/repdoc": "dev-wip" }, @@ -2914,22 +2876,22 @@ ], "support": { "issues": "https://github.com/s9e/SweetDOM/issues", - "source": "https://github.com/s9e/SweetDOM/tree/3.4.0" + "source": "https://github.com/s9e/SweetDOM/tree/3.4.1" }, - "time": "2024-01-01T17:22:53+00:00" + "time": "2024-03-23T14:03:01+00:00" }, { "name": "s9e/text-formatter", - "version": "2.16.0", + "version": "2.18.0", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "a78b8f9bc169d0b6dd81ffff3c97479875bd673d" + "reference": "4970711f25d94306b4835b723b9cc5010170ea37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/a78b8f9bc169d0b6dd81ffff3c97479875bd673d", - "reference": "a78b8f9bc169d0b6dd81ffff3c97479875bd673d", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/4970711f25d94306b4835b723b9cc5010170ea37", + "reference": "4970711f25d94306b4835b723b9cc5010170ea37", "shasum": "" }, "require": { @@ -2942,6 +2904,7 @@ }, "require-dev": { "code-lts/doctum": "*", + "friendsofphp/php-cs-fixer": "^3.52", "matthiasmullie/minify": "*", "phpunit/phpunit": "^9.5" }, @@ -2956,7 +2919,7 @@ }, "type": "library", "extra": { - "version": "2.16.0" + "version": "2.18.0" }, "autoload": { "psr-4": { @@ -2988,29 +2951,29 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.16.0" + "source": "https://github.com/s9e/TextFormatter/tree/2.18.0" }, - "time": "2024-01-07T00:41:30+00:00" + "time": "2024-07-24T14:50:52+00:00" }, { "name": "seld/jsonlint", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259" + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9bb7db07b5d66d90f6ebf542f09fc67d800e5259", - "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", "shasum": "" }, "require": { "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.5", + "phpstan/phpstan": "^1.11", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" }, "bin": [ @@ -3042,7 +3005,7 @@ ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.10.2" + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" }, "funding": [ { @@ -3054,7 +3017,7 @@ "type": "tidelift" } ], - "time": "2024-02-07T12:57:50+00:00" + "time": "2024-07-11T14:55:45+00:00" }, { "name": "seld/phar-utils", @@ -3232,16 +3195,16 @@ }, { "name": "spomky-labs/pki-framework", - "version": "1.1.1", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "86102bdd19379b2c6e5b0feb94fd490d40e7d133" + "reference": "0b10c8b53366729417d6226ae89a665f9e2d61b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/86102bdd19379b2c6e5b0feb94fd490d40e7d133", - "reference": "86102bdd19379b2c6e5b0feb94fd490d40e7d133", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/0b10c8b53366729417d6226ae89a665f9e2d61b6", + "reference": "0b10c8b53366729417d6226ae89a665f9e2d61b6", "shasum": "" }, "require": { @@ -3253,7 +3216,7 @@ "ekino/phpstan-banned-code": "^1.0", "ext-gmp": "*", "ext-openssl": "*", - "infection/infection": "^0.27", + "infection/infection": "^0.28", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.3", "phpstan/phpstan": "^1.8", @@ -3261,8 +3224,8 @@ "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^10.1", - "rector/rector": "^0.19", + "phpunit/phpunit": "^10.1|^11.0", + "rector/rector": "^1.0", "roave/security-advisories": "dev-latest", "symfony/phpunit-bridge": "^6.4|^7.0", "symfony/string": "^6.4|^7.0", @@ -3327,7 +3290,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.1.1" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.2.1" }, "funding": [ { @@ -3339,20 +3302,20 @@ "type": "patreon" } ], - "time": "2024-02-05T20:37:46+00:00" + "time": "2024-03-30T18:03:49+00:00" }, { "name": "symfony/config", - "version": "v6.4.3", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "206482ff3ed450495b1d5b7bad1bc3a852def96f" + "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/206482ff3ed450495b1d5b7bad1bc3a852def96f", - "reference": "206482ff3ed450495b1d5b7bad1bc3a852def96f", + "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", + "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", "shasum": "" }, "require": { @@ -3398,7 +3361,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.4.3" + "source": "https://github.com/symfony/config/tree/v6.4.14" }, "funding": [ { @@ -3414,20 +3377,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T13:26:27+00:00" + "time": "2024-11-04T11:33:53+00:00" }, { "name": "symfony/console", - "version": "v6.4.3", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2aaf83b4de5b9d43b93e4aec6f2f8b676f7c567e" + "reference": "897c2441ed4eec8a8a2c37b943427d24dba3f26b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2aaf83b4de5b9d43b93e4aec6f2f8b676f7c567e", - "reference": "2aaf83b4de5b9d43b93e4aec6f2f8b676f7c567e", + "url": "https://api.github.com/repos/symfony/console/zipball/897c2441ed4eec8a8a2c37b943427d24dba3f26b", + "reference": "897c2441ed4eec8a8a2c37b943427d24dba3f26b", "shasum": "" }, "require": { @@ -3492,7 +3455,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.3" + "source": "https://github.com/symfony/console/tree/v6.4.14" }, "funding": [ { @@ -3508,20 +3471,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/dependency-injection", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "6871811c5a5c5e180244ddb689746446db02c05b" + "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6871811c5a5c5e180244ddb689746446db02c05b", - "reference": "6871811c5a5c5e180244ddb689746446db02c05b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", + "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", "shasum": "" }, "require": { @@ -3573,7 +3536,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.3" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.13" }, "funding": [ { @@ -3589,20 +3552,20 @@ "type": "tidelift" } ], - "time": "2024-01-30T08:32:12+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -3611,7 +3574,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -3640,7 +3603,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -3656,20 +3619,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/error-handler", - "version": "v6.4.3", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "6dc3c76a278b77f01d864a6005d640822c6f26a6" + "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/6dc3c76a278b77f01d864a6005d640822c6f26a6", - "reference": "6dc3c76a278b77f01d864a6005d640822c6f26a6", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/9e024324511eeb00983ee76b9aedc3e6ecd993d9", + "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9", "shasum": "" }, "require": { @@ -3715,7 +3678,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.3" + "source": "https://github.com/symfony/error-handler/tree/v6.4.14" }, "funding": [ { @@ -3731,20 +3694,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T15:40:36+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "ae9d3a6f3003a6caf56acd7466d8d52378d44fef" + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ae9d3a6f3003a6caf56acd7466d8d52378d44fef", - "reference": "ae9d3a6f3003a6caf56acd7466d8d52378d44fef", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", "shasum": "" }, "require": { @@ -3795,7 +3758,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.3" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" }, "funding": [ { @@ -3811,20 +3774,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", "shasum": "" }, "require": { @@ -3834,7 +3797,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -3871,7 +3834,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" }, "funding": [ { @@ -3887,20 +3850,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb" + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb", - "reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", "shasum": "" }, "require": { @@ -3908,6 +3871,9 @@ "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, + "require-dev": { + "symfony/process": "^5.4|^6.4|^7.0" + }, "type": "library", "autoload": { "psr-4": { @@ -3934,7 +3900,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.3" + "source": "https://github.com/symfony/filesystem/tree/v6.4.13" }, "funding": [ { @@ -3950,20 +3916,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/finder", - "version": "v6.4.0", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" + "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", - "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", + "url": "https://api.github.com/repos/symfony/finder/zipball/daea9eca0b08d0ed1dc9ab702a46128fd1be4958", + "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958", "shasum": "" }, "require": { @@ -3998,7 +3964,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.0" + "source": "https://github.com/symfony/finder/tree/v6.4.13" }, "funding": [ { @@ -4014,27 +3980,27 @@ "type": "tidelift" } ], - "time": "2023-10-31T17:30:12+00:00" + "time": "2024-10-01T08:30:56+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.3", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "a9034bc119fab8238f76cf49c770f3135f3ead86" + "reference": "05d88cbd816ad6e0202edd9a9963cb9d615b8826" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/a9034bc119fab8238f76cf49c770f3135f3ead86", - "reference": "a9034bc119fab8238f76cf49c770f3135f3ead86", + "url": "https://api.github.com/repos/symfony/http-client/zipball/05d88cbd816ad6e0202edd9a9963cb9d615b8826", + "reference": "05d88cbd816ad6e0202edd9a9963cb9d615b8826", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^3", + "symfony/http-client-contracts": "^3.4.1", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -4052,7 +4018,7 @@ "amphp/http-client": "^4.2.1", "amphp/http-tunnel": "^1.0", "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4", + "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", @@ -4091,7 +4057,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.3" + "source": "https://github.com/symfony/http-client/tree/v6.4.14" }, "funding": [ { @@ -4107,20 +4073,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T15:01:07+00:00" + "time": "2024-11-05T16:39:55+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "1ee70e699b41909c209a0c930f11034b93578654" + "reference": "20414d96f391677bf80078aa55baece78b82647d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", - "reference": "1ee70e699b41909c209a0c930f11034b93578654", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", + "reference": "20414d96f391677bf80078aa55baece78b82647d", "shasum": "" }, "require": { @@ -4129,7 +4095,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -4169,7 +4135,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" }, "funding": [ { @@ -4185,20 +4151,20 @@ "type": "tidelift" } ], - "time": "2023-07-30T20:28:31+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.3", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "5677bdf7cade4619cb17fc9e1e7b31ec392244a9" + "reference": "ba020a321a95519303a3f09ec2824d34d601c388" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5677bdf7cade4619cb17fc9e1e7b31ec392244a9", - "reference": "5677bdf7cade4619cb17fc9e1e7b31ec392244a9", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ba020a321a95519303a3f09ec2824d34d601c388", + "reference": "ba020a321a95519303a3f09ec2824d34d601c388", "shasum": "" }, "require": { @@ -4246,7 +4212,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.3" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.14" }, "funding": [ { @@ -4262,20 +4228,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-11-05T16:39:55+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.3", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "9c6ec4e543044f7568a53a76ab1484ecd30637a2" + "reference": "8278a947d0369754a47b758a9e17b72cab970951" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9c6ec4e543044f7568a53a76ab1484ecd30637a2", - "reference": "9c6ec4e543044f7568a53a76ab1484ecd30637a2", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8278a947d0369754a47b758a9e17b72cab970951", + "reference": "8278a947d0369754a47b758a9e17b72cab970951", "shasum": "" }, "require": { @@ -4324,12 +4290,13 @@ "symfony/process": "^5.4|^6.0|^7.0", "symfony/property-access": "^5.4.5|^6.0.5|^7.0", "symfony/routing": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.3|^7.0", + "symfony/serializer": "^6.4.4|^7.0.4", "symfony/stopwatch": "^5.4|^6.0|^7.0", "symfony/translation": "^5.4|^6.0|^7.0", "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^5.4|^6.0|^7.0", "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.4|^7.0", "symfony/var-exporter": "^6.2|^7.0", "twig/twig": "^2.13|^3.0.4" }, @@ -4359,7 +4326,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.3" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.14" }, "funding": [ { @@ -4375,20 +4342,20 @@ "type": "tidelift" } ], - "time": "2024-01-31T07:21:29+00:00" + "time": "2024-11-06T09:45:21+00:00" }, { "name": "symfony/mime", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "5017e0a9398c77090b7694be46f20eb796262a34" + "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/5017e0a9398c77090b7694be46f20eb796262a34", - "reference": "5017e0a9398c77090b7694be46f20eb796262a34", + "url": "https://api.github.com/repos/symfony/mime/zipball/1de1cf14d99b12c7ebbb850491ec6ae3ed468855", + "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855", "shasum": "" }, "require": { @@ -4402,16 +4369,17 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/mailer": "<5.4", - "symfony/serializer": "<6.3.2" + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", "symfony/property-access": "^5.4|^6.0|^7.0", "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.3.2|^7.0" + "symfony/serializer": "^6.4.3|^7.0.3" }, "type": "library", "autoload": { @@ -4443,7 +4411,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.3" + "source": "https://github.com/symfony/mime/tree/v6.4.13" }, "funding": [ { @@ -4459,24 +4427,24 @@ "type": "tidelift" } ], - "time": "2024-01-30T08:32:12+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -4522,7 +4490,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -4538,24 +4506,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -4600,7 +4568,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -4616,26 +4584,25 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "a287ed7475f85bf6f61890146edbc932c0fff919" + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a287ed7475f85bf6f61890146edbc932c0fff919", - "reference": "a287ed7475f85bf6f61890146edbc932c0fff919", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, "suggest": { "ext-intl": "For best performance" @@ -4684,7 +4651,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" }, "funding": [ { @@ -4700,24 +4667,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -4765,7 +4732,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -4781,24 +4748,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -4845,7 +4812,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -4861,97 +4828,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.29.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/861391a8da9a04cbad2d232ddd9e4893220d6e25", - "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "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": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.29.0" - }, - "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" - } - ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -4994,7 +4888,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" }, "funding": [ { @@ -5010,24 +4904,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -5074,7 +4968,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -5090,24 +4984,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", - "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -5150,7 +5044,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" }, "funding": [ { @@ -5166,25 +5060,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "86fcae159633351e5fd145d1c47de6c528f8caff" + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/86fcae159633351e5fd145d1c47de6c528f8caff", - "reference": "86fcae159633351e5fd145d1c47de6c528f8caff", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-php80": "^1.14" + "php": ">=7.2" }, "type": "library", "extra": { @@ -5227,7 +5120,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -5243,20 +5136,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", - "version": "v6.4.3", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "31642b0818bfcff85930344ef93193f8c607e0a3" + "reference": "25214adbb0996d18112548de20c281be9f27279f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/31642b0818bfcff85930344ef93193f8c607e0a3", - "reference": "31642b0818bfcff85930344ef93193f8c607e0a3", + "url": "https://api.github.com/repos/symfony/process/zipball/25214adbb0996d18112548de20c281be9f27279f", + "reference": "25214adbb0996d18112548de20c281be9f27279f", "shasum": "" }, "require": { @@ -5288,7 +5181,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.3" + "source": "https://github.com/symfony/process/tree/v6.4.14" }, "funding": [ { @@ -5304,20 +5197,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-11-06T09:25:01+00:00" }, { "name": "symfony/proxy-manager-bridge", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/proxy-manager-bridge.git", - "reference": "c3f1b7d8f0b567eb960c540567f24219cb759e0a" + "reference": "8932b572e147e80fb498045c580eb14215197529" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/c3f1b7d8f0b567eb960c540567f24219cb759e0a", - "reference": "c3f1b7d8f0b567eb960c540567f24219cb759e0a", + "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/8932b572e147e80fb498045c580eb14215197529", + "reference": "8932b572e147e80fb498045c580eb14215197529", "shasum": "" }, "require": { @@ -5355,7 +5248,7 @@ "description": "Provides integration for ProxyManager with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.3" + "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.13" }, "funding": [ { @@ -5371,20 +5264,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/routing", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3b2957ad54902f0f544df83e3d58b38d7e8e5842" + "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3b2957ad54902f0f544df83e3d58b38d7e8e5842", - "reference": "3b2957ad54902f0f544df83e3d58b38d7e8e5842", + "url": "https://api.github.com/repos/symfony/routing/zipball/640a74250d13f9c30d5ca045b6aaaabcc8215278", + "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278", "shasum": "" }, "require": { @@ -5438,7 +5331,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.3" + "source": "https://github.com/symfony/routing/tree/v6.4.13" }, "funding": [ { @@ -5454,25 +5347,26 @@ "type": "tidelift" } ], - "time": "2024-01-30T13:55:02+00:00" + "time": "2024-10-01T08:30:56+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.4.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", - "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^1.1|^2.0" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -5480,7 +5374,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -5520,7 +5414,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.4.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" }, "funding": [ { @@ -5536,20 +5430,20 @@ "type": "tidelift" } ], - "time": "2023-12-26T14:02:43+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/string", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "7a14736fb179876575464e4658fce0c304e8c15b" + "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/7a14736fb179876575464e4658fce0c304e8c15b", - "reference": "7a14736fb179876575464e4658fce0c304e8c15b", + "url": "https://api.github.com/repos/symfony/string/zipball/38371c60c71c72b3d64d8d76f6b1bb81a2cc3627", + "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627", "shasum": "" }, "require": { @@ -5606,7 +5500,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.3" + "source": "https://github.com/symfony/string/tree/v6.4.13" }, "funding": [ { @@ -5622,20 +5516,20 @@ "type": "tidelift" } ], - "time": "2024-01-25T09:26:29+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.4.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "06450585bf65e978026bda220cdebca3f867fde7" + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/06450585bf65e978026bda220cdebca3f867fde7", - "reference": "06450585bf65e978026bda220cdebca3f867fde7", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", "shasum": "" }, "require": { @@ -5644,7 +5538,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -5684,7 +5578,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.4.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" }, "funding": [ { @@ -5700,20 +5594,20 @@ "type": "tidelift" } ], - "time": "2023-12-26T14:02:43+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/twig-bridge", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "bf6b411a5d9a0ce6ea43cca0fcf5f05f5196a957" + "reference": "ec3511eef0576f378b2758da9e1c157086babd59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/bf6b411a5d9a0ce6ea43cca0fcf5f05f5196a957", - "reference": "bf6b411a5d9a0ce6ea43cca0fcf5f05f5196a957", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/ec3511eef0576f378b2758da9e1c157086babd59", + "reference": "ec3511eef0576f378b2758da9e1c157086babd59", "shasum": "" }, "require": { @@ -5793,7 +5687,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v6.4.3" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.13" }, "funding": [ { @@ -5809,20 +5703,20 @@ "type": "tidelift" } ], - "time": "2024-01-30T08:32:12+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.3", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0435a08f69125535336177c29d56af3abc1f69da" + "reference": "93c09246038178717a9c14b809ea8151ffcf7091" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0435a08f69125535336177c29d56af3abc1f69da", - "reference": "0435a08f69125535336177c29d56af3abc1f69da", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/93c09246038178717a9c14b809ea8151ffcf7091", + "reference": "93c09246038178717a9c14b809ea8151ffcf7091", "shasum": "" }, "require": { @@ -5878,7 +5772,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.3" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.14" }, "funding": [ { @@ -5894,20 +5788,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:53:30+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "a8c12b5448a5ac685347f5eeb2abf6a571ec16b8" + "reference": "0f605f72a363f8743001038a176eeb2a11223b51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/a8c12b5448a5ac685347f5eeb2abf6a571ec16b8", - "reference": "a8c12b5448a5ac685347f5eeb2abf6a571ec16b8", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51", + "reference": "0f605f72a363f8743001038a176eeb2a11223b51", "shasum": "" }, "require": { @@ -5915,6 +5809,8 @@ "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", @@ -5953,7 +5849,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.3" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.13" }, "funding": [ { @@ -5969,20 +5865,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "d75715985f0f94f978e3a8fa42533e10db921b90" + "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d75715985f0f94f978e3a8fa42533e10db921b90", - "reference": "d75715985f0f94f978e3a8fa42533e10db921b90", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", + "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", "shasum": "" }, "require": { @@ -6025,7 +5921,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.3" + "source": "https://github.com/symfony/yaml/tree/v6.4.13" }, "funding": [ { @@ -6041,34 +5937,41 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "twig/twig", - "version": "v3.8.0", + "version": "v3.14.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" + "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", + "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php80": "^1.22" + "symfony/polyfill-php81": "^1.29" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "type": "library", "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -6101,7 +6004,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.8.0" + "source": "https://github.com/twigphp/Twig/tree/v3.14.2" }, "funding": [ { @@ -6113,11 +6016,11 @@ "type": "tidelift" } ], - "time": "2023-11-21T18:54:41+00:00" + "time": "2024-11-07T12:36:22+00:00" }, { "name": "web-token/jwt-key-mgmt", - "version": "3.3.0", + "version": "3.4.6", "source": { "type": "git", "url": "https://github.com/web-token/jwt-key-mgmt.git", @@ -6172,7 +6075,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-key-mgmt/tree/3.3.0" + "source": "https://github.com/web-token/jwt-key-mgmt/tree/3.4.6" }, "funding": [ { @@ -6185,29 +6088,30 @@ }, { "name": "web-token/jwt-library", - "version": "3.3.0", + "version": "3.4.6", "source": { "type": "git", "url": "https://github.com/web-token/jwt-library.git", - "reference": "5edf0f193425bb9c695a433180ddf9d263f55063" + "reference": "1a25c8ced3e2b3c31d32dcfad215cbd8cb812f28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-library/zipball/5edf0f193425bb9c695a433180ddf9d263f55063", - "reference": "5edf0f193425bb9c695a433180ddf9d263f55063", + "url": "https://api.github.com/repos/web-token/jwt-library/zipball/1a25c8ced3e2b3c31d32dcfad215cbd8cb812f28", + "reference": "1a25c8ced3e2b3c31d32dcfad215cbd8cb812f28", "shasum": "" }, "require": { "brick/math": "^0.9|^0.10|^0.11|^0.12", "ext-json": "*", "ext-mbstring": "*", - "paragonie/constant_time_encoding": "^2.6", - "paragonie/sodium_compat": "^1.20", + "paragonie/constant_time_encoding": "^2.6|^3.0", + "paragonie/sodium_compat": "^1.20|^2.0", "php": ">=8.1", + "psr/cache": "^3.0", "psr/clock": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "spomky-labs/pki-framework": "^1.0", + "spomky-labs/pki-framework": "^1.2.1", "symfony/console": "^5.4|^6.0|^7.0", "symfony/http-client": "^5.4|^6.0|^7.0", "symfony/polyfill-mbstring": "^1.12" @@ -6266,7 +6170,7 @@ ], "support": { "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/3.3.0" + "source": "https://github.com/web-token/jwt-library/tree/3.4.6" }, "funding": [ { @@ -6278,11 +6182,11 @@ "type": "patreon" } ], - "time": "2024-02-22T08:15:45+00:00" + "time": "2024-07-02T16:35:11+00:00" }, { "name": "web-token/jwt-signature", - "version": "3.3.0", + "version": "3.4.6", "source": { "type": "git", "url": "https://github.com/web-token/jwt-signature.git", @@ -6334,7 +6238,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-signature/tree/3.3.0" + "source": "https://github.com/web-token/jwt-signature/tree/3.4.6" }, "funding": [ { @@ -6347,7 +6251,7 @@ }, { "name": "web-token/jwt-signature-algorithm-ecdsa", - "version": "3.3.0", + "version": "3.4.6", "source": { "type": "git", "url": "https://github.com/web-token/jwt-signature-algorithm-ecdsa.git", @@ -6400,7 +6304,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/3.3.0" + "source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/3.4.6" }, "funding": [ { @@ -6413,7 +6317,7 @@ }, { "name": "web-token/jwt-util-ecc", - "version": "3.3.0", + "version": "3.4.6", "source": { "type": "git", "url": "https://github.com/web-token/jwt-util-ecc.git", @@ -6466,7 +6370,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-util-ecc/tree/3.3.0" + "source": "https://github.com/web-token/jwt-util-ecc/tree/3.4.6" }, "funding": [ { @@ -6481,16 +6385,16 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.2", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", "shasum": "" }, "require": { @@ -6502,8 +6406,8 @@ "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^7 | ^8 | ^9", - "psalm/phar": "^3.11@dev", - "react/promise": "^2" + "react/promise": "^2", + "vimeo/psalm": "^3.12" }, "type": "library", "extra": { @@ -6558,7 +6462,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.2" + "source": "https://github.com/amphp/amp/tree/v2.6.4" }, "funding": [ { @@ -6566,20 +6470,20 @@ "type": "github" } ], - "time": "2022-02-20T17:52:18+00:00" + "time": "2024-03-21T18:52:26+00:00" }, { "name": "amphp/byte-stream", - "version": "v1.8.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", "shasum": "" }, "require": { @@ -6595,11 +6499,6 @@ "psalm/phar": "^3.11.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "files": [ "lib/functions.php" @@ -6623,7 +6522,7 @@ } ], "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", + "homepage": "https://amphp.org/byte-stream", "keywords": [ "amp", "amphp", @@ -6633,9 +6532,8 @@ "stream" ], "support": { - "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" }, "funding": [ { @@ -6643,7 +6541,7 @@ "type": "github" } ], - "time": "2021-03-30T17:13:30+00:00" + "time": "2024-04-13T18:00:56+00:00" }, { "name": "dnoegel/php-xdg-base-dir", @@ -6799,16 +6697,16 @@ }, { "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", + "version": "v1.5.3", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", "shasum": "" }, "require": { @@ -6849,22 +6747,22 @@ ], "support": { "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" }, - "time": "2022-03-02T22:36:06+00:00" + "time": "2024-04-30T00:40:11+00:00" }, { "name": "fidry/cpu-core-counter", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + "reference": "8520451a140d3f46ac33042715115e290cf5785f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", "shasum": "" }, "require": { @@ -6904,7 +6802,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" }, "funding": [ { @@ -6912,7 +6810,7 @@ "type": "github" } ], - "time": "2024-02-07T09:43:46+00:00" + "time": "2024-08-06T10:04:20+00:00" }, { "name": "laravel/homestead", @@ -6970,16 +6868,16 @@ }, { "name": "masterminds/html5", - "version": "2.8.1", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf" + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf", - "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", "shasum": "" }, "require": { @@ -6987,7 +6885,7 @@ "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8" + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" }, "type": "library", "extra": { @@ -7031,9 +6929,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.8.1" + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" }, - "time": "2023-05-10T11:58:31+00:00" + "time": "2024-03-31T07:05:07+00:00" }, { "name": "misantron/dbunit", @@ -7092,16 +6990,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -7109,11 +7007,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -7139,7 +7038,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -7147,20 +7046,20 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "netresearch/jsonmapper", - "version": "v4.4.1", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", "shasum": "" }, "require": { @@ -7196,31 +7095,31 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" }, - "time": "2024-01-31T06:18:54+00:00" + "time": "2024-09-08T10:13:13+00:00" }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v4.19.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.1" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -7252,26 +7151,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2024-09-29T15:01:53+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -7312,9 +7212,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -7534,28 +7440,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", + "version": "5.5.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + "reference": "0c70d2c566e899666f367ab7b80986beb3581e6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/0c70d2c566e899666f367ab7b80986beb3581e6f", + "reference": "0c70d2c566e899666f367ab7b80986beb3581e6f", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.1", "ext-filter": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" }, "type": "library", "extra": { @@ -7579,35 +7492,35 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "email": "opensource@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.5.1" }, - "time": "2021-10-19T17:43:47+00:00" + "time": "2024-11-06T11:58:54+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.2", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "153ae662783729388a584b4361f2545e4d841e3c" + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", - "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", @@ -7643,22 +7556,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" }, - "time": "2024-02-23T11:10:43+00:00" + "time": "2024-11-09T15:12:26+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.26.0", + "version": "1.33.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "231e3186624c03d7e7c890ec662b81e6b0405227" + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/231e3186624c03d7e7c890ec662b81e6b0405227", - "reference": "231e3186624c03d7e7c890ec662b81e6b0405227", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", "shasum": "" }, "require": { @@ -7690,41 +7603,41 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.26.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" }, - "time": "2024-02-23T16:05:55+00:00" + "time": "2024-10-13T11:25:22+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.30", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -7733,7 +7646,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -7762,7 +7675,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -7770,7 +7683,7 @@ "type": "github" } ], - "time": "2023-12-22T06:47:57+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -8015,45 +7928,45 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.17", + "version": "9.6.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -8098,7 +8011,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.21" }, "funding": [ { @@ -8114,27 +8027,27 @@ "type": "tidelift" } ], - "time": "2024-02-23T13:14:51+00:00" + "time": "2024-09-19T10:50:18+00:00" }, { "name": "psalm/plugin-symfony", - "version": "v5.1.0", + "version": "v5.2.5", "source": { "type": "git", "url": "https://github.com/psalm/psalm-plugin-symfony.git", - "reference": "f23ec3439743fb24f5c1101e52d032f23d5befa6" + "reference": "fb801a9b3d12ace9fb619febfaa3ae0bc1dbb196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/f23ec3439743fb24f5c1101e52d032f23d5befa6", - "reference": "f23ec3439743fb24f5c1101e52d032f23d5befa6", + "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/fb801a9b3d12ace9fb619febfaa3ae0bc1dbb196", + "reference": "fb801a9b3d12ace9fb619febfaa3ae0bc1dbb196", "shasum": "" }, "require": { "ext-simplexml": "*", - "php": "^7.4 || ^8.0", + "php": "^8.1", "symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0", - "vimeo/psalm": "^5.1" + "vimeo/psalm": "^5.16" }, "require-dev": { "doctrine/annotations": "^1.8|^2", @@ -8144,7 +8057,7 @@ "symfony/console": "*", "symfony/form": "^5.0 || ^6.0 || ^7.0", "symfony/messenger": "^5.0 || ^6.0 || ^7.0", - "symfony/security-guard": "*", + "symfony/security-core": "*", "symfony/serializer": "^5.0 || ^6.0 || ^7.0", "symfony/validator": "*", "twig/twig": "^2.10 || ^3.0", @@ -8177,22 +8090,22 @@ "description": "Psalm Plugin for Symfony", "support": { "issues": "https://github.com/psalm/psalm-plugin-symfony/issues", - "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.1.0" + "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.2.5" }, - "time": "2023-11-12T10:04:27+00:00" + "time": "2024-07-03T11:57:02+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -8227,7 +8140,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -8235,7 +8148,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -8481,16 +8394,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -8535,7 +8448,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -8543,7 +8456,7 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", @@ -8610,16 +8523,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -8675,7 +8588,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -8683,20 +8596,20 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -8739,7 +8652,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -8747,7 +8660,7 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", @@ -8983,16 +8896,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -9004,7 +8917,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -9025,8 +8938,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -9034,7 +8946,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", @@ -9147,16 +9059,16 @@ }, { "name": "spatie/array-to-xml", - "version": "3.2.3", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "c95fd4db94ec199f798d4b5b4a81757bd20d88ab" + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/c95fd4db94ec199f798d4b5b4a81757bd20d88ab", - "reference": "c95fd4db94ec199f798d4b5b4a81757bd20d88ab", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f56b220fe2db1ade4c88098d83413ebdfc3bf876", + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876", "shasum": "" }, "require": { @@ -9169,6 +9081,11 @@ "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Spatie\\ArrayToXml\\": "src" @@ -9194,7 +9111,7 @@ "xml" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.2.3" + "source": "https://github.com/spatie/array-to-xml/tree/3.3.0" }, "funding": [ { @@ -9206,20 +9123,20 @@ "type": "github" } ], - "time": "2024-02-07T10:39:02+00:00" + "time": "2024-05-01T10:20:27+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.0", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", "shasum": "" }, "require": { @@ -9286,20 +9203,20 @@ "type": "open_collective" } ], - "time": "2024-02-16T15:06:51+00:00" + "time": "2024-09-18T10:38:58+00:00" }, { "name": "symfony/browser-kit", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "495ffa2e6d17e199213f93768efa01af32bbf70e" + "reference": "65d4b3fd9556e4b5b41287bef93c671f8f9f86ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/495ffa2e6d17e199213f93768efa01af32bbf70e", - "reference": "495ffa2e6d17e199213f93768efa01af32bbf70e", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/65d4b3fd9556e4b5b41287bef93c671f8f9f86ab", + "reference": "65d4b3fd9556e4b5b41287bef93c671f8f9f86ab", "shasum": "" }, "require": { @@ -9338,7 +9255,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v6.4.3" + "source": "https://github.com/symfony/browser-kit/tree/v6.4.13" }, "funding": [ { @@ -9354,20 +9271,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/cache", - "version": "v6.4.3", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "49f8cdee544a621a621cd21b6cda32a38926d310" + "reference": "36fb8aa88833708e9f29014b6f15fac051a8b613" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/49f8cdee544a621a621cd21b6cda32a38926d310", - "reference": "49f8cdee544a621a621cd21b6cda32a38926d310", + "url": "https://api.github.com/repos/symfony/cache/zipball/36fb8aa88833708e9f29014b6f15fac051a8b613", + "reference": "36fb8aa88833708e9f29014b6f15fac051a8b613", "shasum": "" }, "require": { @@ -9434,7 +9351,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.3" + "source": "https://github.com/symfony/cache/tree/v6.4.14" }, "funding": [ { @@ -9450,20 +9367,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "1d74b127da04ffa87aa940abe15446fa89653778" + "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/1d74b127da04ffa87aa940abe15446fa89653778", - "reference": "1d74b127da04ffa87aa940abe15446fa89653778", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/df6a1a44c890faded49a5fca33c2d5c5fd3c2197", + "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197", "shasum": "" }, "require": { @@ -9473,7 +9390,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -9510,7 +9427,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.0" }, "funding": [ { @@ -9526,20 +9443,20 @@ "type": "tidelift" } ], - "time": "2023-09-25T12:52:38+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/css-selector", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ee0f7ed5cf298cc019431bb3b3977ebc52b86229" + "reference": "cb23e97813c5837a041b73a6d63a9ddff0778f5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ee0f7ed5cf298cc019431bb3b3977ebc52b86229", - "reference": "ee0f7ed5cf298cc019431bb3b3977ebc52b86229", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/cb23e97813c5837a041b73a6d63a9ddff0778f5e", + "reference": "cb23e97813c5837a041b73a6d63a9ddff0778f5e", "shasum": "" }, "require": { @@ -9575,7 +9492,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.4.3" + "source": "https://github.com/symfony/css-selector/tree/v6.4.13" }, "funding": [ { @@ -9591,20 +9508,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/dom-crawler", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "6db31849011fefe091e94d0bb10cba26f7919894" + "reference": "ae074dffb018c37a57071990d16e6152728dd972" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/6db31849011fefe091e94d0bb10cba26f7919894", - "reference": "6db31849011fefe091e94d0bb10cba26f7919894", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/ae074dffb018c37a57071990d16e6152728dd972", + "reference": "ae074dffb018c37a57071990d16e6152728dd972", "shasum": "" }, "require": { @@ -9642,7 +9559,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.4.3" + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.13" }, "funding": [ { @@ -9658,20 +9575,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/framework-bundle", - "version": "v6.4.3", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "fb413ac4483803954411966a39f3a9204835848e" + "reference": "e8b0bd921f9bd35ea4d1508067c3f3f6e2036418" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/fb413ac4483803954411966a39f3a9204835848e", - "reference": "fb413ac4483803954411966a39f3a9204835848e", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/e8b0bd921f9bd35ea4d1508067c3f3f6e2036418", + "reference": "e8b0bd921f9bd35ea4d1508067c3f3f6e2036418", "shasum": "" }, "require": { @@ -9680,7 +9597,7 @@ "php": ">=8.1", "symfony/cache": "^5.4|^6.0|^7.0", "symfony/config": "^6.1|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4.12|^7.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.1|^7.0", "symfony/event-dispatcher": "^5.4|^6.0|^7.0", @@ -9710,7 +9627,8 @@ "symfony/mime": "<6.4", "symfony/property-access": "<5.4", "symfony/property-info": "<5.4", - "symfony/scheduler": "<6.4.3|>=7.0.0,<7.0.3", + "symfony/runtime": "<5.4.45|>=6.0,<6.4.13|>=7.0,<7.1.6", + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", "symfony/security-core": "<5.4", "symfony/security-csrf": "<5.4", "symfony/serializer": "<6.4", @@ -9749,7 +9667,7 @@ "symfony/process": "^5.4|^6.0|^7.0", "symfony/property-info": "^5.4|^6.0|^7.0", "symfony/rate-limiter": "^5.4|^6.0|^7.0", - "symfony/scheduler": "^6.4.3|^7.0.3", + "symfony/scheduler": "^6.4.4|^7.0.4", "symfony/security-bundle": "^5.4|^6.0|^7.0", "symfony/semaphore": "^5.4|^6.0|^7.0", "symfony/serializer": "^6.4|^7.0", @@ -9762,7 +9680,7 @@ "symfony/web-link": "^5.4|^6.0|^7.0", "symfony/workflow": "^6.4|^7.0", "symfony/yaml": "^5.4|^6.0|^7.0", - "twig/twig": "^2.10|^3.0" + "twig/twig": "^2.10|^3.0.4" }, "type": "symfony-bundle", "autoload": { @@ -9790,7 +9708,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v6.4.3" + "source": "https://github.com/symfony/framework-bundle/tree/v6.4.13" }, "funding": [ { @@ -9806,20 +9724,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T15:02:55+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -9848,7 +9766,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -9856,20 +9774,20 @@ "type": "github" } ], - "time": "2023-11-20T00:12:19+00:00" + "time": "2024-03-03T12:36:25+00:00" }, { "name": "vimeo/psalm", - "version": "5.22.2", + "version": "5.26.1", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "d768d914152dbbf3486c36398802f74e80cfde48" + "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/d768d914152dbbf3486c36398802f74e80cfde48", - "reference": "d768d914152dbbf3486c36398802f74e80cfde48", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d747f6500b38ac4f7dfc5edbcae6e4b637d7add0", + "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0", "shasum": "" }, "require": { @@ -9890,7 +9808,7 @@ "felixfbecker/language-server-protocol": "^1.5.2", "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.16", + "nikic/php-parser": "^4.17", "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", "sebastian/diff": "^4.0 || ^5.0 || ^6.0", "spatie/array-to-xml": "^2.17.0 || ^3.0", @@ -9966,7 +9884,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2024-02-22T23:39:07+00:00" + "time": "2024-09-08T18:53:08+00:00" }, { "name": "webmozart/assert", diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml index 14c0d40263..253a70342a 100644 --- a/phpBB/config/default/container/services.yml +++ b/phpBB/config/default/container/services.yml @@ -157,6 +157,15 @@ services: - '%core.php_ext%' - '%tables.log%' + manifest.controller: + class: phpbb\manifest + arguments: + - '@config' + - '@language' + - '@path_helper' + - '@event_dispatcher' + - '@user' + path_helper: class: phpbb\path_helper arguments: diff --git a/phpBB/config/default/container/services_avatar.yml b/phpBB/config/default/container/services_avatar.yml index e8048278c7..a7c93869cc 100644 --- a/phpBB/config/default/container/services_avatar.yml +++ b/phpBB/config/default/container/services_avatar.yml @@ -56,11 +56,11 @@ services: class: phpbb\avatar\driver\upload arguments: - '@config' - - '@controller.helper' - '%core.root_path%' - '%core.php_ext%' - '@storage.avatar' - '@path_helper' + - '@routing.helper' - '@event_dispatcher' - '@files.factory' - '@php_ini' diff --git a/phpBB/config/default/container/services_captcha.yml b/phpBB/config/default/container/services_captcha.yml index 8e9d829b47..ea41fefe80 100644 --- a/phpBB/config/default/container/services_captcha.yml +++ b/phpBB/config/default/container/services_captcha.yml @@ -19,7 +19,11 @@ services: shared: false arguments: - '@config' + - '@dbal.conn' + - '@language' + - '@request' - '@template' + - '@user' - '%core.root_path%' - '%core.php_ext%' calls: @@ -54,3 +58,19 @@ services: - ['set_name', ['core.captcha.plugins.recaptcha_v3']] tags: - { name: captcha.plugins } + + core.captcha.plugins.turnstile: + class: phpbb\captcha\plugins\turnstile + shared: false + arguments: + - '@config' + - '@dbal.conn' + - '@language' + - '@log' + - '@request' + - '@template' + - '@user' + calls: + - ['set_name', ['core.captcha.plugins.turnstile']] + tags: + - { name: captcha.plugins } diff --git a/phpBB/config/default/container/services_console.yml b/phpBB/config/default/container/services_console.yml index 5d089bef99..131a95376c 100644 --- a/phpBB/config/default/container/services_console.yml +++ b/phpBB/config/default/container/services_console.yml @@ -359,6 +359,22 @@ services: tags: - { name: console.command } + console.command.user.delete_id: + class: phpbb\console\command\user\delete_id + arguments: + - '@dbal.conn' + - '@language' + - '@log' + - '@user' + - '@user_loader' + - '%tables.bots%' + - '%tables.user_group%' + - '%tables.users%' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: console.command } + console.command.user.reclean: class: phpbb\console\command\user\reclean arguments: diff --git a/phpBB/config/default/container/services_storage.yml b/phpBB/config/default/container/services_storage.yml index 4ef28ccf3b..4ff6f96ae3 100644 --- a/phpBB/config/default/container/services_storage.yml +++ b/phpBB/config/default/container/services_storage.yml @@ -70,8 +70,6 @@ services: shared: false arguments: - '@filesystem' - - '@upload_imagesize' - - '@mimetype.guesser' - '%core.root_path%' tags: - { name: storage.adapter } @@ -90,6 +88,7 @@ services: - '@cache' - '@config' - '@dbal.conn' + - '@mimetype.extension_guesser' - '@storage.avatar' - '@symfony_request' @@ -102,8 +101,26 @@ services: - '@content.visibility' - '@dbal.conn' - '@event_dispatcher' + - '@mimetype.extension_guesser' - '@language' - '@request' - '@storage.attachment' - '@symfony_request' - '@user' + +# Helpers + storage.state_helper: + class: phpbb\storage\state_helper + arguments: + - '@config' + - '@config_text' + - '@storage.provider_collection' + + storage.helper: + class: phpbb\storage\helper + arguments: + - '@config' + - '@storage.adapter.factory' + - '@storage.state_helper' + - '@storage.provider_collection' + - '@storage.adapter_collection' diff --git a/phpBB/config/default/container/services_twig.yml b/phpBB/config/default/container/services_twig.yml index 17ca9c7503..e7a7155deb 100644 --- a/phpBB/config/default/container/services_twig.yml +++ b/phpBB/config/default/container/services_twig.yml @@ -45,6 +45,8 @@ services: template.twig.extensions.avatar: class: phpbb\template\twig\extension\avatar + arguments: + - '@avatar.helper' tags: - { name: twig.extension } diff --git a/phpBB/config/default/container/services_ucp.yml b/phpBB/config/default/container/services_ucp.yml index a950ef8e35..acd78f8440 100644 --- a/phpBB/config/default/container/services_ucp.yml +++ b/phpBB/config/default/container/services_ucp.yml @@ -1,4 +1,15 @@ services: + phpbb.ucp.controller.delete_cookies: + class: phpbb\ucp\controller\delete_cookies + arguments: + - '@config' + - '@dispatcher' + - '@language' + - '@request' + - '@user' + - '%core.root_path%' + - '%core.php_ext%' + phpbb.ucp.controller.reset_password: class: phpbb\ucp\controller\reset_password arguments: @@ -23,8 +34,11 @@ services: - '@controller.helper' - '@dbal.conn' - '@form_helper' + - '@language' + - '@notification_manager' - '@path_helper' - '@request' + - '@user_loader' - '@user' - '@template.twig.environment' - '%tables.notification_push%' diff --git a/phpBB/config/default/routing/routing.yml b/phpBB/config/default/routing/routing.yml index 7fff9204f0..46d20e4527 100644 --- a/phpBB/config/default/routing/routing.yml +++ b/phpBB/config/default/routing/routing.yml @@ -24,6 +24,10 @@ phpbb_help_routing: resource: help.yml prefix: /help +phpbb_manifest_controller: + path: /manifest + defaults: { _controller: manifest.controller:handle } + phpbb_mention_controller: path: /mention methods: [GET, POST] diff --git a/phpBB/config/default/routing/ucp.yml b/phpBB/config/default/routing/ucp.yml index 772910bfe1..0103dbd2bb 100644 --- a/phpBB/config/default/routing/ucp.yml +++ b/phpBB/config/default/routing/ucp.yml @@ -1,3 +1,7 @@ +phpbb_ucp_delete_cookies_controller: + path: /delete_cookies + defaults: { _controller: phpbb.ucp.controller.delete_cookies:handle } + phpbb_ucp_reset_password_controller: path: /reset_password defaults: { _controller: phpbb.ucp.controller.reset_password:reset } diff --git a/phpBB/config/installer/container/services.yml b/phpBB/config/installer/container/services.yml index b4e731306d..70fb0ee308 100644 --- a/phpBB/config/installer/container/services.yml +++ b/phpBB/config/installer/container/services.yml @@ -1,5 +1,6 @@ imports: - { resource: services_installer.yml } + - { resource: services_avatar.yml } - { resource: ../../default/container/services_event.yml } - { resource: ../../default/container/services_filesystem.yml } - { resource: ../../default/container/services_http.yml } diff --git a/phpBB/config/installer/container/services_avatar.yml b/phpBB/config/installer/container/services_avatar.yml new file mode 100644 index 0000000000..31d28e77fb --- /dev/null +++ b/phpBB/config/installer/container/services_avatar.yml @@ -0,0 +1,25 @@ +services: + avatar.manager: + class: phpbb\avatar\manager + arguments: + - '@config' + - '@dispatcher' + - '@avatar.driver_collection' + + avatar.helper: + class: phpbb\avatar\helper + arguments: + - '@config' + - '@dispatcher' + - '@language' + - '@avatar.manager' + - '@path_helper' + - '@user' + + # ----- Avatar drivers ----- + avatar.driver_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: avatar.driver } diff --git a/phpBB/develop/export_events_for_bbcode.php b/phpBB/develop/export_events_for_bbcode.php index e30027bf94..fc8c604f80 100644 --- a/phpBB/develop/export_events_for_bbcode.php +++ b/phpBB/develop/export_events_for_bbcode.php @@ -65,7 +65,8 @@ function validate_argument_count($arguments, $count) require __DIR__ . '/../phpbb/event/rst_exporter.' . $phpEx; require __DIR__ . '/../includes/functions.' . $phpEx; require __DIR__ . '/../phpbb/event/recursive_event_filter_iterator.' . $phpEx; -require __DIR__ . '/../phpbb/iterator/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_path_iterator.' . $phpEx; switch ($action) { diff --git a/phpBB/develop/export_events_for_rst.php b/phpBB/develop/export_events_for_rst.php index 908d4298d6..9ca58e6e69 100644 --- a/phpBB/develop/export_events_for_rst.php +++ b/phpBB/develop/export_events_for_rst.php @@ -68,7 +68,8 @@ function validate_argument_count($arguments, $count) require __DIR__ . '/../phpbb/event/rst_exporter.' . $phpEx; require __DIR__ . '/../includes/functions.' . $phpEx; require __DIR__ . '/../phpbb/event/recursive_event_filter_iterator.' . $phpEx; -require __DIR__ . '/../phpbb/iterator/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_path_iterator.' . $phpEx; switch ($action) { diff --git a/phpBB/develop/export_events_for_wiki.php b/phpBB/develop/export_events_for_wiki.php index 128d6f3c57..cf8bc3c61c 100644 --- a/phpBB/develop/export_events_for_wiki.php +++ b/phpBB/develop/export_events_for_wiki.php @@ -67,7 +67,8 @@ function validate_argument_count($arguments, $count) require __DIR__ . '/../phpbb/event/md_exporter.' . $phpEx; require __DIR__ . '/../includes/functions.' . $phpEx; require __DIR__ . '/../phpbb/event/recursive_event_filter_iterator.' . $phpEx; -require __DIR__ . '/../phpbb/iterator/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_path_iterator.' . $phpEx; switch ($action) { diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index 8b20e374b1..4674867ecd 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -50,6 +50,10 @@

Changelog

  1. Changelog
      +
    • Changes since 3.3.14-RC1
    • +
    • Changes since 3.3.13
    • +
    • Changes since 3.3.13-RC1
    • +
    • Changes since 3.3.12
    • Changes since 3.3.12-RC1
    • Changes since 3.3.11
    • Changes since 3.3.10
    • @@ -169,6 +173,97 @@

      Changelog

      +

      Changes since 3.3.14-RC1

      +

      Improvement

      +
        +
      • [PHPBB-17421] - Rename section for not installed extensions to not installed
      • +
      + +

      Changes since 3.3.13

      +

      Bug

      +
        +
      • [PHPBB-17181] - If statement to highlight Reported PMS on the view message page doesn't work.
      • +
      • [PHPBB-17383] - HELO/EHLO error while using gethostbyaddr()
      • +
      • [PHPBB-17384] - Passing E_USER_ERROR to trigger_error() is deprecated in PHP 8.4
      • +
      • [PHPBB-17385] - Version check without SSL flag for CDB extensions fails
      • +
      • [PHPBB-17386] - Incorrect trace result while tracing user-based permissions
      • +
      • [PHPBB-17387] - PHP warnings in search results
      • +
      • [PHPBB-17390] - Missing buttons for approval of new messages
      • +
      • [PHPBB-17391] - PHP warnings on attempt to create user group having name already in use
      • +
      • [PHPBB-17398] - Ajax error on deleting cookies
      • +
      • [PHPBB-17405] - Function phpbb_gmgetdate() returns incorrect result for edge cases
      • +
      • [PHPBB-17410] - UCP tabs do not work when testing out another user's permissions
      • +
      +

      Improvement

      +
        +
      • [PHPBB-16852] - Addition of a new PHP event concerning bump topics
      • +
      • [PHPBB-17359] - Distinct disabled and not installed extensions in the list
      • +
      • [PHPBB-17376] - Link reference to quote post is a poor accessibility experience
      • +
      • [PHPBB-17382] - Incorrect grammar on FAQ page regarding searching for members
      • +
      • [PHPBB-17388] - Add php event to bump_topic_allowed function
      • +
      • [PHPBB-17394] - Check mergeability of PR on GitHub Actions
      • +
      • [PHPBB-17396] - Automatically handle merges of 3.3.x into master
      • +
      • [PHPBB-17397] - Add event for forum_data query in viewforum
      • +
      • [PHPBB-17402] - Add possibility to force reparsing BBCode via CLI
      • +
      • [PHPBB-17407] - Limit mergeability check to single comment
      • +
      • [PHPBB-17411] - Add core event to search.php
      • +
      + +

      Changes since 3.3.13-RC1

      +

      Bug

      +
        +
      • [PHPBB-17377] - MSSQL builds on GitHub actions broken
      • +
      + +

      Changes since 3.3.12

      +

      Bug

      +
        +
      • [PHPBB-13916] - Cancelling save draft removes previous notify setting on posting page
      • +
      • [PHPBB-14454] - Accessing ACP modules while testing user permissions returns a General Error
      • +
      • [PHPBB-15043] - Searching no longer working in 3.2.0
      • +
      • [PHPBB-15576] - PM subject truncated to shorter length than maxlength
      • +
      • [PHPBB-16213] - vendor and phpbb folders should have .htaccess files
      • +
      • [PHPBB-16907] - "phpbb" value in "hiddenSegments" blocks client requests for extensions in IIS
      • +
      • [PHPBB-17109] - Users without the "Can use signature" permission should not see checkboxes for signature
      • +
      • [PHPBB-17175] - Breadcrumbs show wrong forum and topic when using 'email topic'
      • +
      • [PHPBB-17301] - Wrong length parameter for fread in phpbb/cache/driver/file.php can lead to unusable forum
      • +
      • [PHPBB-17327] - Fix linting issue in console user add command
      • +
      • [PHPBB-17332] - New permission copied from existing permission ignores permission set options
      • +
      • [PHPBB-17337] - Transaction begin is missing from mysqli driver
      • +
      • [PHPBB-17338] - Incorrect members list sorting by user_last_visit
      • +
      • [PHPBB-17351] - phpBB2 password hashes incorrectly handled during rehash cron
      • +
      • [PHPBB-17352] - Long rank titles push other profile details below
      • +
      • [PHPBB-17353] - Gravatar avatar src is not image src
      • +
      • [PHPBB-17356] - Errors hidden by at are being displayed in PHP 8 or newer
      • +
      • [PHPBB-17358] - Redis cache never expires with the TTL of 0
      • +
      • [PHPBB-17362] - Missing declaration of property in extension manager
      • +
      • [PHPBB-17365] - Enforce the search word limit on queries containing operators without white space
      • +
      • [PHPBB-17366] - Captcha disappears on error message from registration & posting
      • +
      • [PHPBB-17369] - Permanently deleting soft-deleted topics returns incorrect forum in redirect link
      • +
      • [PHPBB-17370] - Deleting Cookies on FAQ/other pages
      • +
      • [PHPBB-17374] - ACP - Maintenance - Logs: Deleting Error / Bug
      • +
      • [PHPBB-17375] - User lastvisit gets updated too often in session garbage collection
      • +
      +

      Improvement

      +
        +
      • [PHPBB-16553] - Disapproving a reported post causes a "Module not accessible" error
      • +
      • [PHPBB-17308] - Rename tracker project key to PHPBB-
      • +
      • [PHPBB-17315] - Add new template events to group
      • +
      • [PHPBB-17316] - Add template events to ucp_groups_manage
      • +
      • [PHPBB-17317] - Update button text and make it more readable
      • +
      • [PHPBB-17325] - Show explicit message for "Re-Check version" if installed version is still up to date
      • +
      • [PHPBB-17340] - Update composer to 2.7.7
      • +
      • [PHPBB-17342] - Add PHP 8.4-dev tests to GitHub Actions
      • +
      • [PHPBB-17347] - Support deleting users by ID via console
      • +
      • [PHPBB-17350] - Add user IP address to log when installing extensions on fresh installs
      • +
      • [PHPBB-17355] - Update gravatar hash to sha256
      • +
      +

      Task

      +
        +
      • [PHPBB-13933] - Update tokens' definitions in acp_bbcodes
      • +
      • [PHPBB-16890] - Edit the config sample files and web.config to deny access to the "config" directory
      • +
      +

      Changes since 3.3.12-RC1

      Bug

        diff --git a/phpBB/docs/CREDITS.txt b/phpBB/docs/CREDITS.txt index 47621b2318..9155fc9a2b 100644 --- a/phpBB/docs/CREDITS.txt +++ b/phpBB/docs/CREDITS.txt @@ -106,5 +106,4 @@ MIT licensed: Symfony2 (c) 2004-2011 Fabien Potencier, https://symfony.com/ Cookie Consent (c) 2015 Silktide Ltd, https://cookieconsent.insites.com -Emoji by: -Twemoji (c) 2018 Twitter, Inc, https://twemoji.twitter.com/ +HiDPI smilies by rednoah: https://github.com/rednoah/phpBB-smilies diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index 07e3b83beb..cc37ede34e 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -94,6 +94,20 @@ acp_ext_list_enabled_title_after * Since: 3.1.11-RC1 * Purpose: Add text after enabled extensions section title. +acp_ext_list_not_installed_name_after +=== +* Location: adm/style/acp_ext_list.html +* Since: 3.3.14-RC1 +* Changed: 3.3.14 Renamed from acp_ext_list_available_name_after +* Purpose: Add content after the name of not installed extensions in the list + +acp_ext_list_not_installed_title_after +=== +* Location: adm/style/acp_ext_list.html +* Since: 3.3.14-RC1 +* Changed: 3.3.14 Renamed from acp_ext_list_available_title_after +* Purpose: Add text after not installed extensions section title. + acp_forums_custom_settings === * Location: adm/style/acp_forums.html diff --git a/phpBB/docs/lighttpd.sample.conf b/phpBB/docs/lighttpd.sample.conf index e783c809fc..fef922c2da 100644 --- a/phpBB/docs/lighttpd.sample.conf +++ b/phpBB/docs/lighttpd.sample.conf @@ -1,7 +1,7 @@ # Sample lighttpd configuration file for phpBB. # Global settings have been removed, copy them # from your system's lighttpd.conf. -# Tested with lighttpd 1.4.35 +# Tested with lighttpd 1.4.36 # Load moules server.modules += ( @@ -28,7 +28,7 @@ $HTTP["host"] == "www.myforums.com" { accesslog.filename = "/var/log/lighttpd/access-www.myforums.com.log" # Deny access to internal phpbb files. - $HTTP["url"] =~ "^/(config\.php|common\.php|cache|files|images/avatars/upload|includes|phpbb|store|vendor)" { + $HTTP["url"] =~ "^/(config|common\.php|cache|files|images/avatars/upload|includes|phpbb|store|vendor|vendor-ext)" { url.access-deny = ( "" ) } @@ -47,7 +47,8 @@ $HTTP["host"] == "www.myforums.com" { # by default accessed at /app.php/my/controller, but can also be accessed at # /my/controller url.rewrite-if-not-file = ( - "^/(.*)$" => "/app.php/$1" + "^/install/(.*)$" => "/install/app.php/$1", + "^/(.*)$" => "/app.php/$1" ) fastcgi.server = ( ".php" => diff --git a/phpBB/docs/nginx.sample.conf b/phpBB/docs/nginx.sample.conf index 9d2623c88b..ed1f25e023 100644 --- a/phpBB/docs/nginx.sample.conf +++ b/phpBB/docs/nginx.sample.conf @@ -55,7 +55,7 @@ server { } # Deny access to internal phpbb files. - location ~ /(config\.php|common\.php|cache|files|images/avatars/upload|includes|(? + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpBB/images/icons/misc/heart.svg b/phpBB/images/icons/misc/heart.svg new file mode 100644 index 0000000000..f25d0a3c64 --- /dev/null +++ b/phpBB/images/icons/misc/heart.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpBB/images/icons/misc/radioactive.svg b/phpBB/images/icons/misc/radioactive.svg new file mode 100644 index 0000000000..372c84e423 --- /dev/null +++ b/phpBB/images/icons/misc/radioactive.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpBB/images/icons/misc/star.svg b/phpBB/images/icons/misc/star.svg new file mode 100644 index 0000000000..e5e88794b9 --- /dev/null +++ b/phpBB/images/icons/misc/star.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/phpBB/images/icons/misc/thinking.svg b/phpBB/images/icons/misc/thinking.svg new file mode 100644 index 0000000000..b3fdc9fca3 --- /dev/null +++ b/phpBB/images/icons/misc/thinking.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpBB/images/icons/smile/alert.svg b/phpBB/images/icons/smile/alert.svg new file mode 100644 index 0000000000..173ba4d780 --- /dev/null +++ b/phpBB/images/icons/smile/alert.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpBB/images/icons/smile/info.svg b/phpBB/images/icons/smile/info.svg new file mode 100644 index 0000000000..f6e9ef7512 --- /dev/null +++ b/phpBB/images/icons/smile/info.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpBB/images/icons/smile/mrgreen.svg b/phpBB/images/icons/smile/mrgreen.svg new file mode 100644 index 0000000000..ef639a5ccd --- /dev/null +++ b/phpBB/images/icons/smile/mrgreen.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpBB/images/icons/smile/question.svg b/phpBB/images/icons/smile/question.svg new file mode 100644 index 0000000000..7a7bf1602a --- /dev/null +++ b/phpBB/images/icons/smile/question.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpBB/images/icons/smile/redface.svg b/phpBB/images/icons/smile/redface.svg new file mode 100644 index 0000000000..84fe0db5ef --- /dev/null +++ b/phpBB/images/icons/smile/redface.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_arrow.svg b/phpBB/images/smilies/icon_arrow.svg new file mode 100644 index 0000000000..4d190756ce --- /dev/null +++ b/phpBB/images/smilies/icon_arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_cool.svg b/phpBB/images/smilies/icon_cool.svg new file mode 100644 index 0000000000..500729316d --- /dev/null +++ b/phpBB/images/smilies/icon_cool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_cry.svg b/phpBB/images/smilies/icon_cry.svg new file mode 100644 index 0000000000..e9312f9d7a --- /dev/null +++ b/phpBB/images/smilies/icon_cry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_e_biggrin.svg b/phpBB/images/smilies/icon_e_biggrin.svg new file mode 100644 index 0000000000..6e3803be04 --- /dev/null +++ b/phpBB/images/smilies/icon_e_biggrin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_e_confused.svg b/phpBB/images/smilies/icon_e_confused.svg new file mode 100644 index 0000000000..b9561e3204 --- /dev/null +++ b/phpBB/images/smilies/icon_e_confused.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_e_geek.svg b/phpBB/images/smilies/icon_e_geek.svg new file mode 100644 index 0000000000..77af7d61f9 --- /dev/null +++ b/phpBB/images/smilies/icon_e_geek.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_e_sad.svg b/phpBB/images/smilies/icon_e_sad.svg new file mode 100644 index 0000000000..69a323f155 --- /dev/null +++ b/phpBB/images/smilies/icon_e_sad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_e_smile.svg b/phpBB/images/smilies/icon_e_smile.svg new file mode 100644 index 0000000000..c273c77f1f --- /dev/null +++ b/phpBB/images/smilies/icon_e_smile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_e_surprised.svg b/phpBB/images/smilies/icon_e_surprised.svg new file mode 100644 index 0000000000..ad5ce8bdc5 --- /dev/null +++ b/phpBB/images/smilies/icon_e_surprised.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_e_ugeek.svg b/phpBB/images/smilies/icon_e_ugeek.svg new file mode 100644 index 0000000000..0ae02fcc76 --- /dev/null +++ b/phpBB/images/smilies/icon_e_ugeek.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_e_wink.svg b/phpBB/images/smilies/icon_e_wink.svg new file mode 100644 index 0000000000..f07eaf36ac --- /dev/null +++ b/phpBB/images/smilies/icon_e_wink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_eek.svg b/phpBB/images/smilies/icon_eek.svg new file mode 100644 index 0000000000..d3bbc31ae9 --- /dev/null +++ b/phpBB/images/smilies/icon_eek.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_evil.svg b/phpBB/images/smilies/icon_evil.svg new file mode 100644 index 0000000000..2636a078ec --- /dev/null +++ b/phpBB/images/smilies/icon_evil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_exclaim.svg b/phpBB/images/smilies/icon_exclaim.svg new file mode 100644 index 0000000000..6d14ccd2e0 --- /dev/null +++ b/phpBB/images/smilies/icon_exclaim.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_idea.svg b/phpBB/images/smilies/icon_idea.svg new file mode 100644 index 0000000000..0f5ff849bb --- /dev/null +++ b/phpBB/images/smilies/icon_idea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_lol.svg b/phpBB/images/smilies/icon_lol.svg new file mode 100644 index 0000000000..93d0fe7643 --- /dev/null +++ b/phpBB/images/smilies/icon_lol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_mad.svg b/phpBB/images/smilies/icon_mad.svg new file mode 100644 index 0000000000..1575f95c14 --- /dev/null +++ b/phpBB/images/smilies/icon_mad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_mrgreen.svg b/phpBB/images/smilies/icon_mrgreen.svg new file mode 100644 index 0000000000..a8157d4041 --- /dev/null +++ b/phpBB/images/smilies/icon_mrgreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_neutral.svg b/phpBB/images/smilies/icon_neutral.svg new file mode 100644 index 0000000000..2363cce865 --- /dev/null +++ b/phpBB/images/smilies/icon_neutral.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_question.svg b/phpBB/images/smilies/icon_question.svg new file mode 100644 index 0000000000..caa067b21c --- /dev/null +++ b/phpBB/images/smilies/icon_question.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_razz.svg b/phpBB/images/smilies/icon_razz.svg new file mode 100644 index 0000000000..a959ffc79d --- /dev/null +++ b/phpBB/images/smilies/icon_razz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_redface.svg b/phpBB/images/smilies/icon_redface.svg new file mode 100644 index 0000000000..0b333e519a --- /dev/null +++ b/phpBB/images/smilies/icon_redface.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_rolleyes.svg b/phpBB/images/smilies/icon_rolleyes.svg new file mode 100644 index 0000000000..4d737dd4bc --- /dev/null +++ b/phpBB/images/smilies/icon_rolleyes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/images/smilies/icon_twisted.svg b/phpBB/images/smilies/icon_twisted.svg new file mode 100644 index 0000000000..264129041a --- /dev/null +++ b/phpBB/images/smilies/icon_twisted.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/includes/acp/acp_attachments.php b/phpBB/includes/acp/acp_attachments.php index 4eb06bd6f8..b0e086e65c 100644 --- a/phpBB/includes/acp/acp_attachments.php +++ b/phpBB/includes/acp/acp_attachments.php @@ -173,19 +173,17 @@ function main($id, $mode) 'img_max_width' => array('lang' => 'MAX_IMAGE_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false,), 'img_max_height' => array('lang' => 'MAX_IMAGE_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false,), - 'img_link_width' => array('lang' => 'IMAGE_LINK_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false,), - 'img_link_height' => array('lang' => 'IMAGE_LINK_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false,), 'allow_attachments' => array('lang' => 'ALLOW_ATTACHMENTS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), 'allow_pm_attach' => array('lang' => 'ALLOW_PM_ATTACHMENTS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), 'max_attachments' => array('lang' => 'MAX_ATTACHMENTS', 'validate' => 'int:0:999', 'type' => 'number:0:999', 'explain' => false), 'max_attachments_pm' => array('lang' => 'MAX_ATTACHMENTS_PM', 'validate' => 'int:0:999', 'type' => 'number:0:999', 'explain' => false), - 'display_order' => array('lang' => 'DISPLAY_ORDER', 'validate' => 'bool', 'type' => 'custom', 'method' => 'display_order', 'explain' => true), + 'display_order' => array('lang' => 'DISPLAY_ORDER', 'validate' => 'bool', 'type' => 'radio', 'function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', ['DESCENDING', 'ASCENDING']], 'explain' => true), 'attachment_quota' => array('lang' => 'ATTACH_QUOTA', 'validate' => 'string', 'type' => 'custom', 'method' => 'max_filesize', 'explain' => true), 'max_filesize' => array('lang' => 'ATTACH_MAX_FILESIZE', 'validate' => 'string', 'type' => 'custom', 'method' => 'max_filesize', 'explain' => true), 'max_filesize_pm' => array('lang' => 'ATTACH_MAX_PM_FILESIZE','validate' => 'string', 'type' => 'custom', 'method' => 'max_filesize', 'explain' => true), 'secure_downloads' => array('lang' => 'SECURE_DOWNLOADS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), - 'secure_allow_deny' => array('lang' => 'SECURE_ALLOW_DENY', 'validate' => 'int', 'type' => 'custom', 'method' => 'select_allow_deny', 'explain' => true), + 'secure_allow_deny' => array('lang' => 'SECURE_ALLOW_DENY', 'validate' => 'int', 'type' => 'radio', 'function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', [1 => 'ORDER_ALLOW_DENY', 0 => 'ORDER_DENY_ALLOW']], 'explain' => true), 'secure_allow_empty_referer' => array('lang' => 'SECURE_EMPTY_REFERRER', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'check_attachment_content' => array('lang' => 'CHECK_CONTENT', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), @@ -197,7 +195,6 @@ function main($id, $mode) 'img_max' => array('lang' => 'MAX_IMAGE_SIZE', 'validate' => 'int:0:9999', 'type' => 'dimension:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), 'img_strip_metadata' => array('lang' => 'IMAGE_STRIP_METADATA', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'img_quality' => array('lang' => 'IMAGE_QUALITY', 'validate' => 'int:50:90', 'type' => 'number:50:90', 'explain' => true, 'append' => ' %'), - 'img_link' => array('lang' => 'IMAGE_LINK_SIZE', 'validate' => 'int:0:9999', 'type' => 'dimension:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), ) ); @@ -331,7 +328,7 @@ function main($id, $mode) $l_explain = (isset($user->lang[$vars['lang'] . '_EXPLAIN'])) ? $user->lang[$vars['lang'] . '_EXPLAIN'] : ''; } - $content = build_cfg_template($type, $config_key, $this->new_config, $config_key, $vars); + $content = phpbb_build_cfg_template($type, $config_key, $this->new_config, $config_key, $vars); if (empty($content)) { continue; @@ -1718,16 +1715,6 @@ function perform_site_list() } } - /** - * Write display_order config field - */ - function display_order($value, $key = '') - { - $radio_ary = array(0 => 'DESCENDING', 1 => 'ASCENDING'); - - return h_radio('config[display_order]', $radio_ary, $value, $key); - } - /** * Adjust all three max_filesize config vars for display */ @@ -1756,15 +1743,4 @@ function max_filesize($value, $key = '') ] ]; } - - /** - * Write secure_allow_deny config field - */ - function select_allow_deny($value, $key = '') - { - $radio_ary = array(1 => 'ORDER_ALLOW_DENY', 0 => 'ORDER_DENY_ALLOW'); - - return h_radio('config[' . $key . ']', $radio_ary, $value, $key); - } - } diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index d6acf93cce..a905354040 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -110,6 +110,7 @@ function main($id, $mode) ); $bbcode_tokens = array('TEXT', 'SIMPLETEXT', 'INTTEXT', 'IDENTIFIER', 'NUMBER', 'EMAIL', 'URL', 'LOCAL_URL', 'RELATIVE_URL', 'COLOR'); + $bbcode_tokens = array_merge($bbcode_tokens, ['ALNUM', 'CHOICE', 'FLOAT', 'HASHMAP', 'INT', 'IP', 'IPPORT', 'IPV4', 'IPV6', 'MAP', 'RANGE', 'REGEXP', 'TIMESTAMP', 'UINT']); /** * Modify custom bbcode template data before we display the add/edit form diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index d0ced6788f..959388e075 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -78,6 +78,7 @@ function main($id, $mode) 'site_home_url' => array('lang' => 'SITE_HOME_URL', 'validate' => 'url', 'type' => 'url:40:255', 'explain' => true), 'site_home_text' => array('lang' => 'SITE_HOME_TEXT', 'validate' => 'string', 'type' => 'text:40:255', 'explain' => true), 'board_index_text' => array('lang' => 'BOARD_INDEX_TEXT', 'validate' => 'string', 'type' => 'text:40:255', 'explain' => true), + 'sitename_short' => array('lang' => 'SITE_NAME_SHORT', 'validate' => 'string', 'type' => 'text:40:12', 'explain' => true), 'board_disable' => array('lang' => 'DISABLE_BOARD', 'validate' => 'bool', 'type' => 'custom', 'method' => 'board_disable', 'explain' => true), 'board_disable_msg' => false, 'board_disable_access' => array('lang' => 'DISABLE_BOARD_ACCESS', 'validate' => 'int', 'type' => 'select', 'method' => 'board_disable_access', 'explain' => true), @@ -86,8 +87,8 @@ function main($id, $mode) 'board_timezone' => array('lang' => 'SYSTEM_TIMEZONE', 'validate' => 'timezone', 'type' => 'custom', 'method' => 'timezone_select', 'explain' => true), 'legend2' => 'BOARD_STYLE', - 'default_style' => array('lang' => 'DEFAULT_STYLE', 'validate' => 'int', 'type' => 'select', 'function' => 'style_select', 'params' => array('{CONFIG_VALUE}', false), 'explain' => true), - 'guest_style' => array('lang' => 'GUEST_STYLE', 'validate' => 'int', 'type' => 'select', 'function' => 'style_select', 'params' => array($this->guest_style_get(), false), 'explain' => true), + 'default_style' => array('lang' => 'DEFAULT_STYLE', 'validate' => 'int', 'type' => 'select', 'method' => 'phpbb_style_select', 'params' => array('{CONFIG_VALUE}', false), 'explain' => true), + 'guest_style' => array('lang' => 'GUEST_STYLE', 'validate' => 'int', 'type' => 'select', 'method' => 'phpbb_style_select', 'params' => array($this->guest_style_get(), false), 'explain' => true), 'override_user_style' => array('lang' => 'OVERRIDE_STYLE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'legend3' => 'WARNINGS', @@ -259,8 +260,6 @@ function main($id, $mode) 'max_sig_urls' => array('lang' => 'MAX_SIG_URLS', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), 'max_sig_font_size' => array('lang' => 'MAX_SIG_FONT_SIZE', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' %'), 'max_sig_smilies' => array('lang' => 'MAX_SIG_SMILIES', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), - 'max_sig_img_width' => array('lang' => 'MAX_SIG_IMG_WIDTH', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), - 'max_sig_img_height' => array('lang' => 'MAX_SIG_IMG_HEIGHT', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), 'legend3' => 'ACP_SUBMIT_CHANGES', ) @@ -392,7 +391,7 @@ function main($id, $mode) 'title' => 'ACP_AUTH_SETTINGS', 'vars' => array( 'legend1' => 'ACP_AUTH_SETTINGS', - 'auth_method' => array('lang' => 'AUTH_METHOD', 'validate' => 'string', 'type' => 'select:1:toggable', 'method' => 'select_auth_method', 'explain' => false), + 'auth_method' => array('lang' => 'AUTH_METHOD', 'validate' => 'string', 'type' => 'select:1:toggleable', 'method' => 'select_auth_method', 'explain' => false), ) ); break; @@ -432,10 +431,10 @@ function main($id, $mode) 'allow_autologin' => array('lang' => 'ALLOW_AUTOLOGIN', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'allow_password_reset' => array('lang' => 'ALLOW_PASSWORD_RESET', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'max_autologin_time' => array('lang' => 'AUTOLOGIN_LENGTH', 'validate' => 'int:0:99999', 'type' => 'number:0:99999', 'explain' => true, 'append' => ' ' . $user->lang['DAYS']), - 'ip_check' => array('lang' => 'IP_VALID', 'validate' => 'int', 'type' => 'custom', 'method' => 'select_ip_check', 'explain' => true), + 'ip_check' => array('lang' => 'IP_VALID', 'validate' => 'int', 'type' => 'radio', 'function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', [4 => 'ALL', 3 => 'CLASS_C', 2 => 'CLASS_B', 0 => 'NO_IP_VALIDATION']], 'explain' => true), 'browser_check' => array('lang' => 'BROWSER_VALID', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'forwarded_for_check' => array('lang' => 'FORWARDED_FOR_VALID', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), - 'referer_validation' => array('lang' => 'REFERRER_VALID', 'validate' => 'int:0:3','type' => 'custom', 'method' => 'select_ref_check', 'explain' => true), + 'referer_validation' => array('lang' => 'REFERRER_VALID', 'validate' => 'int:0:3','type' => 'radio', 'function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', [REFERER_VALIDATE_PATH => 'REF_PATH', REFERER_VALIDATE_HOST => 'REF_HOST', REFERER_VALIDATE_NONE => 'NO_REF_VALIDATION']], 'explain' => true), 'check_dnsbl' => array('lang' => 'CHECK_DNSBL', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'email_check_mx' => array('lang' => 'EMAIL_CHECK_MX', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'min_pass_chars' => array('lang' => 'PASSWORD_LENGTH', 'validate' => 'int:1', 'type' => 'custom', 'method' => 'password_length', 'explain' => true), @@ -494,6 +493,8 @@ function main($id, $mode) 'webpush_enable' => ['lang' => 'WEBPUSH_ENABLE', 'validate' => 'bool', 'type' => 'custom', 'method' => 'webpush_enable', 'explain' => true], 'webpush_vapid_public' => ['lang' => 'WEBPUSH_VAPID_PUBLIC', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], 'webpush_vapid_private' => ['lang' => 'WEBPUSH_VAPID_PRIVATE', 'validate' => 'string', 'type' => 'password:25:255', 'explain' => true], + 'webpush_method_default_enable' => ['lang' => 'WEBPUSH_METHOD_DEFAULT_ENABLE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], + 'webpush_dropdown_subscribe' => ['lang' => 'WEBPUSH_DROPDOWN_SUBSCRIBE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], 'legend3' => 'ACP_SUBMIT_CHANGES', ], @@ -591,6 +592,7 @@ function main($id, $mode) // Array of emoji-enabled configurations $config_name_ary = [ 'sitename', + 'sitename_short', 'site_desc', 'site_home_text', 'board_index_text', @@ -798,7 +800,7 @@ function main($id, $mode) $l_explain = (isset($user->lang[$vars['lang'] . '_EXPLAIN'])) ? $user->lang[$vars['lang'] . '_EXPLAIN'] : ''; } - $content = build_cfg_template($type, $config_key, $this->new_config, $config_key, $vars); + $content = phpbb_build_cfg_template($type, $config_key, $this->new_config, $config_key, $vars); if (empty($content)) { @@ -851,7 +853,7 @@ function select_auth_method($selected_method, $key = '') /* @var $auth_providers \phpbb\auth\provider_collection */ $auth_providers = $phpbb_container->get('auth.provider_collection'); - $auth_plugins = array(); + $auth_plugins = []; foreach ($auth_providers as $key => $value) { @@ -864,14 +866,22 @@ function select_auth_method($selected_method, $key = '') sort($auth_plugins); - $auth_select = ''; + $auth_select_options = []; foreach ($auth_plugins as $method) { - $selected = ($selected_method == $method) ? ' selected="selected"' : ''; - $auth_select .= "'; + $auth_select_options[] = [ + 'value' => $method, + 'selected' => $selected_method == $method, + 'label' => ucfirst($method), + 'data' => [ + 'toggle-setting' => "#auth_{$method}_settings", + ], + ]; } - return $auth_select; + return [ + 'options' => $auth_select_options, + ]; } /** @@ -881,15 +891,21 @@ function mail_auth_select($selected_method, $key = '') { global $user; - $auth_methods = array('PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5', 'POP-BEFORE-SMTP'); - $s_smtp_auth_options = ''; + $auth_methods = ['PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5', 'POP-BEFORE-SMTP']; + $s_smtp_auth_options = []; foreach ($auth_methods as $method) { - $s_smtp_auth_options .= ''; + $s_smtp_auth_options[] = [ + 'value' => $method, + 'selected' => $selected_method == $method, + 'label' => $user->lang('SMTP_' . str_replace('-', '_', $method)), + ]; } - return $s_smtp_auth_options; + return [ + 'options' => $s_smtp_auth_options, + ]; } /** @@ -899,27 +915,22 @@ function full_folder_select($value, $key = '') { global $user; - return ''; - } - - /** - * Select ip validation - */ - function select_ip_check($value, $key = '') - { - $radio_ary = array(4 => 'ALL', 3 => 'CLASS_C', 2 => 'CLASS_B', 0 => 'NO_IP_VALIDATION'); - - return h_radio('config[ip_check]', $radio_ary, $value, $key); - } - - /** - * Select referer validation - */ - function select_ref_check($value, $key = '') - { - $radio_ary = array(REFERER_VALIDATE_PATH => 'REF_PATH', REFERER_VALIDATE_HOST => 'REF_HOST', REFERER_VALIDATE_NONE => 'NO_REF_VALIDATION'); + $full_folder_select_options = [ + 0 => [ + 'value' => 1, + 'selected' => $value == 1, + 'label' => $user->lang('DELETE_OLDEST_MESSAGES'), + ], + 1 => [ + 'value' => 2, + 'selected' => $value == 2, + 'label' => $user->lang('HOLD_NEW_MESSAGES_SHORT'), + ], + ]; - return h_radio('config[referer_validation]', $radio_ary, $value, $key); + return [ + 'options' => $full_folder_select_options, + ]; } /** @@ -929,23 +940,28 @@ function select_acc_activation($selected_value, $value) { global $user, $config; - $act_ary = array( - 'ACC_DISABLE' => array(true, USER_ACTIVATION_DISABLE), - 'ACC_NONE' => array(true, USER_ACTIVATION_NONE), - 'ACC_USER' => array($config['email_enable'], USER_ACTIVATION_SELF), - 'ACC_ADMIN' => array($config['email_enable'], USER_ACTIVATION_ADMIN), - ); + $act_ary = [ + 'ACC_DISABLE' => [true, USER_ACTIVATION_DISABLE], + 'ACC_NONE' => [true, USER_ACTIVATION_NONE], + 'ACC_USER' => [$config['email_enable'], USER_ACTIVATION_SELF], + 'ACC_ADMIN' => [$config['email_enable'], USER_ACTIVATION_ADMIN], + ]; - $act_options = ''; + $act_options = []; foreach ($act_ary as $key => $data) { list($available, $value) = $data; - $selected = ($selected_value == $value) ? ' selected="selected"' : ''; - $class = (!$available) ? ' class="disabled-option"' : ''; - $act_options .= ''; + $act_options[] = [ + 'value' => $value, + 'selected' => $selected_value == $value, + 'label' => $user->lang($key), + 'class' => !$available ? 'disabled-option' : '', + ]; } - return $act_options; + return [ + 'options' => $act_options, + ]; } /** @@ -955,7 +971,27 @@ function username_length($value, $key = '') { global $user; - return ' ' . $user->lang['MIN_CHARS'] . '   ' . $user->lang['MAX_CHARS']; + return [ + [ + 'tag' => 'input', + 'id' => $key, + 'type' => 'number', + 'name' => 'config[min_name_chars]', + 'min' => 1, + 'max' => 999, + 'value' => $value, + 'append' => $user->lang('MIN_CHARS') . '  ', + ], + [ + 'tag' => 'input', + 'type' => 'number', + 'name' => 'config[max_name_chars]', + 'min' => 8, + 'max' => 180, + 'value' => $this->new_config['max_name_chars'], + 'append' => $user->lang('MAX_CHARS'), + ], + ]; } /** @@ -965,15 +1001,20 @@ function select_username_chars($selected_value, $key) { global $user; - $user_char_ary = array('USERNAME_CHARS_ANY', 'USERNAME_ALPHA_ONLY', 'USERNAME_ALPHA_SPACERS', 'USERNAME_LETTER_NUM', 'USERNAME_LETTER_NUM_SPACERS', 'USERNAME_ASCII'); - $user_char_options = ''; + $user_char_ary = ['USERNAME_CHARS_ANY', 'USERNAME_ALPHA_ONLY', 'USERNAME_ALPHA_SPACERS', 'USERNAME_LETTER_NUM', 'USERNAME_LETTER_NUM_SPACERS', 'USERNAME_ASCII']; + $user_char_options = []; foreach ($user_char_ary as $user_type) { - $selected = ($selected_value == $user_type) ? ' selected="selected"' : ''; - $user_char_options .= ''; + $user_char_options[] = [ + 'value' => $user_type, + 'selected' => $selected_value == $user_type, + 'label' => $user->lang($user_type), + ]; } - return $user_char_options; + return [ + 'options' => $user_char_options, + ]; } /** @@ -983,7 +1024,16 @@ function password_length($value, $key) { global $user; - return ' ' . $user->lang['MIN_CHARS']; + return [ + [ + 'tag' => 'input', + 'id' => $key, + 'type' => 'number', + 'name' => 'config[min_pass_chars]', + 'value' => $value, + 'append' => $user->lang('MIN_CHARS'), + ], + ]; } /** @@ -994,14 +1044,20 @@ function select_password_chars($selected_value, $key) global $user; $pass_type_ary = array('PASS_TYPE_ANY', 'PASS_TYPE_CASE', 'PASS_TYPE_ALPHA', 'PASS_TYPE_SYMBOL'); - $pass_char_options = ''; + $pass_char_options = []; foreach ($pass_type_ary as $pass_type) { - $selected = ($selected_value == $pass_type) ? ' selected="selected"' : ''; - $pass_char_options .= ''; + $pass_char_options[] = [ + 'tag' => 'select', + 'value' => $pass_type, + 'selected' => $selected_value == $pass_type, + 'label' => $user->lang[$pass_type], + ]; } - return $pass_char_options; + return [ + 'options' => $pass_char_options, + ]; } /** @@ -1050,7 +1106,22 @@ public function language_select(string $default = '', array $langdata = []): arr { global $db; - return phpbb_language_select($db, $default, $langdata); + return ['options' => phpbb_language_select($db, $default, $langdata)]; + } + + /** + * Wrapper function for style_select() + * + * @param int|string $default Style ID to be selected in the dropdown list + * @param bool $all Flag indicating if all styles data including inactive should be fetched + * + * @return array + */ + public function phpbb_style_select(int|string $default, bool $all): array + { + global $db; + + return ['options' => style_select($default, $all)]; } /** @@ -1058,9 +1129,20 @@ public function language_select(string $default = '', array $langdata = []): arr */ function board_disable($value, $key) { - $radio_ary = array(1 => 'YES', 0 => 'NO'); + $options = phpbb_build_radio($value, $key, [1 => 'YES', 0 => 'NO']); - return h_radio('config[board_disable]', $radio_ary, $value) . '
        '; + return [ + array_merge(['tag' => 'radio'], $options), + [ + 'tag' => 'input', + 'type' => 'text', + 'name' => 'config[board_disable_msg]', + 'maxlength' => 255, + 'size' => 40, + 'id' => $key, + 'value' => $this->new_config['board_disable_msg'] ?: '', + ], + ]; } /** @@ -1072,7 +1154,7 @@ function board_disable($value, $key) */ public function board_disable_access(int $value) : array { - return [ + return ['options' => [ [ 'value' => 0, 'selected' => $value == 0, @@ -1088,7 +1170,7 @@ public function board_disable_access(int $value) : array 'selected' => $value == 2, 'label' => $this->language->lang('DISABLE_BOARD_ACCESS_ADMIN_ALL_MODS'), ], - ]; + ]]; } /** @@ -1096,12 +1178,21 @@ public function board_disable_access(int $value) : array */ function quick_reply($value, $key) { - global $user; + global $language; - $radio_ary = array(1 => 'YES', 0 => 'NO'); + $options = phpbb_build_radio($value, $key, [1 => 'YES', 0 => 'NO']); - return h_radio('config[allow_quick_reply]', $radio_ary, $value) . - '

        '; + return [ + array_merge(['tag' => 'radio', 'append' => '

        '], $options), + [ + 'tag' => 'input', + 'type' => 'submit', + 'class' => 'button2', + 'name' => $key . '_enable', + 'id' => $key . '_enable', + 'value' => $language->lang('ALLOW_QUICK_REPLY_BUTTON'), + ], + ]; } /** @@ -1320,7 +1411,7 @@ function store_feed_forums($option, $key) */ function enable_mod_rewrite($value, $key) { - global $user; + global $language; // Determine whether mod_rewrite is enabled on the server // NOTE: This only works on Apache servers on which PHP is NOT @@ -1347,20 +1438,43 @@ function enable_mod_rewrite($value, $key) $value = ($mod_rewrite === false) ? 0 : $value; $message = $mod_rewrite === null ? 'MOD_REWRITE_INFORMATION_UNAVAILABLE' : ($mod_rewrite === false ? 'MOD_REWRITE_DISABLED' : false); - // Let's do some friendly HTML injection if we want to disable the - // form field because h_radio() has no pretty way of doing so - $field_name = 'config[enable_mod_rewrite]' . ($message === 'MOD_REWRITE_DISABLED' ? '" disabled="disabled' : ''); + $options = phpbb_build_radio($value, $key, [1 => 'YES', 0 => 'NO']); + foreach ($options['buttons'] as $i => $button) + { + $options['buttons'][$i]['disabled'] = $message === 'MOD_REWRITE_DISABLED'; + } - return h_radio($field_name, array(1 => 'YES', 0 => 'NO'), $value) . - ($message !== false ? '
        ' . $user->lang($message) . '' : ''); + $tpl = array_merge( + [ + 'tag' => 'radio', + 'append' => ($message !== false) ? '
        ' . $language->lang($message) . '' : '', + ], + $options + ); + + return $tpl; } function send_test_email($value, $key) { global $user; - return ' - '; + return [ + [ + 'tag' => 'input', + 'type' => 'submit', + 'name' => $key, + 'id' => $key, + 'class' => 'button2', + 'value' => $user->lang('SEND_TEST_EMAIL'), + ], + [ + 'tag' => 'textarea', + 'name' => $key . '_text', + 'id' => $key . '_text', + 'placeholder' => $user->lang('MESSAGE'), + ], + ]; } /** diff --git a/phpBB/includes/acp/acp_bots.php b/phpBB/includes/acp/acp_bots.php index 323dd1baee..982a8967b7 100644 --- a/phpBB/includes/acp/acp_bots.php +++ b/phpBB/includes/acp/acp_bots.php @@ -321,12 +321,15 @@ function main($id, $mode) unset($bot_row['user_lang'], $bot_row['user_style']); } - $s_active_options = ''; + $s_active_options = []; $_options = array('0' => 'NO', '1' => 'YES'); foreach ($_options as $value => $lang) { - $selected = ($bot_row['bot_active'] == $value) ? ' selected="selected"' : ''; - $s_active_options .= ''; + $s_active_options[] = [ + 'value' => $value, + 'selected' => $bot_row['bot_active'] == $value, + 'label' => $user->lang($lang), + ]; } $style_select = style_select($bot_row['bot_style'], true); @@ -345,14 +348,22 @@ function main($id, $mode) 'BOT_AGENT' => $bot_row['bot_agent'], 'S_EDIT_BOT' => true, - 'S_ACTIVE_OPTIONS' => $s_active_options, - 'S_STYLE_OPTIONS' => $style_select, + 'S_ACTIVE_OPTIONS' => [ + 'id' => 'bot_active', + 'name' => 'bot_active', + 'options' => $s_active_options, + ], + 'S_STYLE_OPTIONS' => [ + 'id' => 'bot_style', + 'name' => 'bot_style', + 'options' => $style_select, + ], 'LANG_OPTIONS' => [ 'id' => 'bot_lang', 'name' => 'bot_lang', 'options' => $lang_options, ], - 'S_ERROR' => (count($error)) ? true : false, + 'S_ERROR' => (bool) count($error), )); return; diff --git a/phpBB/includes/acp/acp_captcha.php b/phpBB/includes/acp/acp_captcha.php index ff9f515d32..e03d2a3901 100644 --- a/phpBB/includes/acp/acp_captcha.php +++ b/phpBB/includes/acp/acp_captcha.php @@ -95,7 +95,7 @@ function main($id, $mode) add_form_key($form_key); $submit = $request->variable('main_submit', false); - $error = $cfg_array = array(); + $errors = $cfg_array = array(); if ($submit) { @@ -103,13 +103,13 @@ function main($id, $mode) { $cfg_array[$config_var] = $request->variable($config_var, $options['default']); } - validate_config_vars($config_vars, $cfg_array, $error); + validate_config_vars($config_vars, $cfg_array, $errors); if (!check_form_key($form_key)) { - $error[] = $user->lang['FORM_INVALID']; + $errors[] = $user->lang['FORM_INVALID']; } - if ($error) + if ($errors) { $submit = false; } @@ -128,11 +128,9 @@ function main($id, $mode) if (isset($captchas['available'][$selected])) { $old_captcha = $factory->get_instance($config['captcha_plugin']); - $old_captcha->uninstall(); + $old_captcha->garbage_collect(); $config->set('captcha_plugin', $selected); - $new_captcha = $factory->get_instance($config['captcha_plugin']); - $new_captcha->install(); $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL'); } @@ -145,17 +143,24 @@ function main($id, $mode) } else { - $captcha_select = ''; + $captcha_options = []; foreach ($captchas['available'] as $value => $title) { - $current = ($selected !== false && $value == $selected) ? ' selected="selected"' : ''; - $captcha_select .= ''; + $captcha_options[] = [ + 'value' => $value, + 'label' => $user->lang($title), + 'selected' => $selected !== false && $value == $selected, + ]; } foreach ($captchas['unavailable'] as $value => $title) { - $current = ($selected !== false && $value == $selected) ? ' selected="selected"' : ''; - $captcha_select .= ''; + $captcha_options[] = [ + 'value' => $value, + 'label' => $user->lang($title), + 'selected' => $selected !== false && $value == $selected, + 'class' => 'disabled-option', + ]; } $demo_captcha = $factory->get_instance($selected); @@ -168,8 +173,12 @@ function main($id, $mode) $template->assign_vars(array( 'CAPTCHA_PREVIEW_TPL' => $demo_captcha->get_demo_template($id), 'S_CAPTCHA_HAS_CONFIG' => $demo_captcha->has_config(), - 'CAPTCHA_SELECT' => $captcha_select, - 'ERROR_MSG' => implode('
        ', $error), + 'CAPTCHA_SELECT' => [ + 'tag' => 'select', + 'name' => 'select_captcha', + 'options' => $captcha_options, + ], + 'ERRORS' => $errors, 'U_ACTION' => $this->u_action, )); diff --git a/phpBB/includes/acp/acp_database.php b/phpBB/includes/acp/acp_database.php index 7b8dcc3ee5..85943dc5e4 100644 --- a/phpBB/includes/acp/acp_database.php +++ b/phpBB/includes/acp/acp_database.php @@ -287,7 +287,7 @@ function main($id, $mode) fclose($fp); fclose($stream); } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { trigger_error($user->lang['RESTORE_DOWNLOAD_FAIL'] . adm_back_link($this->u_action)); } diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index 1a30d38d61..6faeee61fd 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -972,7 +972,7 @@ public function list_available_exts(array $managed_packages) catch (exception_interface $e) { $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); - $this->template->assign_block_vars('disabled', array( + $this->template->assign_block_vars('not_installed', array( 'META_DISPLAY_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $message), 'S_VERSIONCHECK' => false, )); @@ -986,9 +986,9 @@ public function list_available_exts(array $managed_packages) $block_vars['NAME'] = $name; $block_vars['U_DETAILS'] = $this->u_action . '&action=details&ext_name=' . urlencode($name); - $this->template->assign_block_vars('disabled', $block_vars); + $this->template->assign_block_vars('not_installed', $block_vars); - $this->output_actions('disabled', array( + $this->output_actions('not_installed', array( 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . urlencode($name), 'REMOVE' => $this->u_catalog_action . '&action=remove&extension=' . urlencode($block_vars['META_NAME']), )); diff --git a/phpBB/includes/acp/acp_forums.php b/phpBB/includes/acp/acp_forums.php index 6aafce12bc..920214448d 100644 --- a/phpBB/includes/acp/acp_forums.php +++ b/phpBB/includes/acp/acp_forums.php @@ -213,13 +213,7 @@ function main($id, $mode) phpbb_cache_moderators($db, $phpbb_container->get('dbal.tools'), $cache, $auth); $copied_permissions = true; } -/* Commented out because of questionable UI workflow - re-visit for 3.0.7 - else if (!$this->parent_id && $action != 'edit' && $auth->acl_get('a_fauth') && $auth->acl_get('a_authusers') && $auth->acl_get('a_authgroups') && $auth->acl_get('a_mauth')) - { - $this->copy_permission_page($forum_data); - return; - } -*/ + $auth->acl_clear_prefetch(); $acl_url = '&mode=setting_forum_local&forum_id[]=' . $forum_data['forum_id']; diff --git a/phpBB/includes/acp/acp_groups.php b/phpBB/includes/acp/acp_groups.php index b24014cb39..bafeafbc06 100644 --- a/phpBB/includes/acp/acp_groups.php +++ b/phpBB/includes/acp/acp_groups.php @@ -396,7 +396,7 @@ function main($id, $mode) $allow_desc_urls = $request->variable('desc_parse_urls', false); $allow_desc_smilies = $request->variable('desc_parse_smilies', false); - $submit_ary = array( + $submit_ary = [ 'colour' => $request->variable('group_colour', ''), 'rank' => $request->variable('group_rank', 0), 'receive_pm' => isset($_REQUEST['group_receive_pm']) ? 1 : 0, @@ -406,7 +406,13 @@ function main($id, $mode) 'max_recipients' => $request->variable('group_max_recipients', 0), 'founder_manage' => 0, 'skip_auth' => $request->variable('group_skip_auth', 0), - ); + + // Initialize avatar data + 'avatar' => $avatar_data['avatar'] ?? '', + 'avatar_type' => $avatar_data['avatar_type'] ?? '', + 'avatar_height' => $avatar_data['avatar_height'] ?? 0, + 'avatar_width' => $avatar_data['avatar_width'] ?? 0, + ]; if ($user->data['user_type'] == USER_FOUNDER) { diff --git a/phpBB/includes/acp/acp_logs.php b/phpBB/includes/acp/acp_logs.php index 266ac22725..c8f9db1a3b 100644 --- a/phpBB/includes/acp/acp_logs.php +++ b/phpBB/includes/acp/acp_logs.php @@ -50,7 +50,7 @@ function main($id, $mode) $pagination = $phpbb_container->get('pagination'); // Delete entries if requested and able - if (($deletemark || $deleteall) && $auth->acl_get('a_clearlogs')) + if (($deleteall || ($deletemark && count($marked))) && $auth->acl_get('a_clearlogs')) { if (confirm_box(true)) { diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 3ea1036167..2adf833562 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -11,6 +11,18 @@ * */ +use phpbb\db\driver\driver_interface; +use phpbb\di\service_collection; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\request\request; +use phpbb\storage\exception\storage_exception; +use phpbb\storage\helper; +use phpbb\storage\state_helper; +use phpbb\storage\update_type; +use phpbb\template\template; +use phpbb\user; + /** * @ignore */ @@ -21,22 +33,28 @@ class acp_storage { - /** @var \phpbb\config\config $config */ - protected $config; + /** @var driver_interface */ + protected $db; - /** @var \phpbb\language\language $lang */ + /** @var language */ protected $lang; - /** @var \phpbb\request\request */ + /** @var log_interface */ + protected $log; + + /** @var request */ protected $request; - /** @var \phpbb\template\template */ + /** @var template */ protected $template; - /** @var \phpbb\di\service_collection */ + /** @var user */ + protected $user; + + /** @var service_collection */ protected $provider_collection; - /** @var \phpbb\di\service_collection */ + /** @var service_collection */ protected $storage_collection; /** @var \phpbb\filesystem\filesystem */ @@ -54,133 +72,359 @@ class acp_storage /** @var string */ public $u_action; + /** @var state_helper */ + private $state_helper; + + /** @var helper */ + private $storage_helper; + + /** @var string */ + private $storage_table; + /** * @param string $id * @param string $mode */ - public function main($id, $mode) + public function main(string $id, string $mode): void { global $phpbb_container, $phpbb_dispatcher, $phpbb_root_path; - $this->config = $phpbb_container->get('config'); - $this->filesystem = $phpbb_container->get('filesystem'); + $this->db = $phpbb_container->get('dbal.conn'); $this->lang = $phpbb_container->get('language'); + $this->log = $phpbb_container->get('log'); $this->request = $phpbb_container->get('request'); $this->template = $phpbb_container->get('template'); + $this->user = $phpbb_container->get('user'); $this->provider_collection = $phpbb_container->get('storage.provider_collection'); $this->storage_collection = $phpbb_container->get('storage.storage_collection'); + $this->filesystem = $phpbb_container->get('filesystem'); $this->phpbb_root_path = $phpbb_root_path; + $this->state_helper = $phpbb_container->get('storage.state_helper'); + $this->storage_helper = $phpbb_container->get('storage.helper'); + $this->storage_table = $phpbb_container->getParameter('tables.storage'); - // Add necesary language files + // Add necessary language files $this->lang->add_lang(['acp/storage']); /** * Add language strings * * @event core.acp_storage_load - * @since 3.3.0-a1 + * @since 4.0.0-a1 */ $phpbb_dispatcher->trigger_event('core.acp_storage_load'); - $this->overview($id, $mode); + switch ($mode) + { + case 'settings': + $this->settings($id, $mode); + break; + } } /** + * Method to route the request to the correct page + * * @param string $id * @param string $mode */ - public function overview($id, $mode) + private function settings(string $id, string $mode): void { - $form_key = 'acp_storage'; - add_form_key($form_key); - - // Template from adm/style - $this->tpl_name = 'acp_storage'; - - // Set page title - $this->page_title = 'STORAGE_TITLE'; - - $messages = []; - if ($this->request->is_set_post('submit')) + $action = $this->request->variable('action', ''); + if ($action && !$this->request->is_set_post('cancel')) { - $modified_storages = []; + switch ($action) + { + case 'update': + $this->update_action(); + break; - if (!check_form_key($form_key)) + default: + trigger_error('NO_ACTION', E_USER_ERROR); + } + } + else + { + // If clicked to cancel (acp_storage_update_progress form) + if ($this->request->is_set_post('cancel')) { - $messages[] = $this->lang->lang('FORM_INVALID'); + $this->state_helper->clear_state(); } - foreach ($this->storage_collection as $storage) + // There is an updating in progress, show the form to continue or cancel + if ($this->state_helper->is_action_in_progress()) + { + $this->update_inprogress(); + } + else { - $storage_name = $storage->get_name(); + $this->settings_form(); + } + } + } - $options = $this->get_provider_options($this->get_current_provider($storage_name)); + /** + * Page to update storage settings and move files + * + * @return void + */ + private function update_action(): void + { + if (!check_link_hash($this->request->variable('hash', ''), 'acp_storage')) + { + trigger_error($this->lang->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + } - $this->validate_path($storage_name, $options, $messages); + // If update_type is copy or move, copy files from the old to the new storage + if (in_array($this->state_helper->update_type(), [update_type::COPY, update_type::MOVE], true)) + { + $i = 0; + foreach ($this->state_helper->storages() as $storage_name) + { + // Skip storages that have already copied files + if ($this->state_helper->storage_index() > $i++) + { + continue; + } - $modified = false; + $sql = 'SELECT file_id, file_path + FROM ' . $this->storage_table . " + WHERE storage = '" . $this->db->sql_escape($storage_name) . "' + AND file_id > " . $this->state_helper->file_index(); + $result = $this->db->sql_query($sql); - // Check if provider have been modified - if ($this->get_new_provider($storage_name) != $this->get_current_provider($storage_name)) + while ($row = $this->db->sql_fetchrow($result)) { - $modified = true; + if (!still_on_time()) + { + $this->db->sql_freeresult($result); + $this->display_progress_page(); + return; + } + + // Copy file from old adapter to the new one + $this->storage_helper->copy_file_to_new_adapter($storage_name, $row['file_path']); + + $this->state_helper->set_file_index($row['file_id']); // update last file index copied } - // Check if options have been modified - if (!$modified) + $this->db->sql_freeresult($result); + + // Copied all files of a storage, increase storage index and reset file index + $this->state_helper->set_storage_index($this->state_helper->storage_index()+1); + $this->state_helper->set_file_index(0); + } + + // If update_type is move files, remove the old files + if ($this->state_helper->update_type() === update_type::MOVE) + { + $i = 0; + foreach ($this->state_helper->storages() as $storage_name) { - foreach (array_keys($options) as $definition) + // Skip storages that have already moved files + if ($this->state_helper->remove_storage_index() > $i++) { - if ($this->get_new_definition($storage_name, $definition) != $this->get_current_definition($storage_name, $definition)) + continue; + } + + $sql = 'SELECT file_id, file_path + FROM ' . $this->storage_table . " + WHERE storage = '" . $this->db->sql_escape($storage_name) . "' + AND file_id > " . $this->state_helper->file_index(); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!still_on_time()) { - $modified = true; - break; + $this->db->sql_freeresult($result); + $this->display_progress_page(); + return; } + + // remove file from old (current) adapter + $current_adapter = $this->storage_helper->get_current_adapter($storage_name); + $current_adapter->delete($row['file_path']); + + $this->state_helper->set_file_index($row['file_id']); } - } - // If the storage have been modified, validate options - if ($modified) - { - $modified_storages[] = $storage_name; - $this->validate_data($storage_name, $messages); + $this->db->sql_freeresult($result); + + // Remove all files of a storage, increase storage index and reset file index + $this->state_helper->set_remove_storage_index($this->state_helper->remove_storage_index() + 1); + $this->state_helper->set_file_index(0); } } + } + // Here all files have been copied/moved, so save new configuration + foreach ($this->state_helper->storages() as $storage_name) + { + $this->storage_helper->update_storage_config($storage_name); + } + + $storages = $this->state_helper->storages(); + $this->state_helper->clear_state(); + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_STORAGE_UPDATE', false, [implode(', ', $storages)]); + trigger_error($this->lang->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action)); + } + + /** + * Page that show a form with the progress bar, and a button to continue or cancel + * + * @return void + */ + private function update_inprogress(): void + { + // Template from adm/style + $this->tpl_name = 'acp_storage_update_inprogress'; + + // Set page title + $this->page_title = 'STORAGE_TITLE'; + + $this->template->assign_vars([ + 'U_ACTION' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), + 'CONTINUE_PROGRESS' => $this->get_storage_update_progress(), + ]); + } + + /** + * Main settings page, shows a form with all the storages and their configuration options + * + * @return void + */ + private function settings_form(): void + { + $form_key = 'acp_storage'; + add_form_key($form_key); + + // Process form and create a "state" for the update, + // then show a confirm form + if ($this->request->is_set_post('submit')) + { + if (!check_form_key($form_key) || !check_link_hash($this->request->variable('hash', ''), 'acp_storage')) + { + trigger_error($this->lang->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + } + + $modified_storages = $this->get_modified_storages(); + + // validate submited paths if they are local + $messages = []; + foreach ($modified_storages as $storage_name) + { + $this->validate_data($storage_name, $messages); + } + if (!empty($messages)) + { + trigger_error(implode('
        ', $messages) . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Start process and show progress if (!empty($modified_storages)) { - if (empty($messages)) - { - foreach ($modified_storages as $storage_name) - { - $this->update_storage_config($storage_name); - } + // Create state + $this->state_helper->init(update_type::from((int) $this->request->variable('update_type', update_type::CONFIG->value)), $modified_storages, $this->request); - trigger_error($this->lang->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action), E_USER_NOTICE); - } - else - { - trigger_error(implode('
        ', $messages) . adm_back_link($this->u_action), E_USER_WARNING); - } + // Start displaying progress on first submit + $this->display_progress_page(); + return; } // If there is no changes trigger_error($this->lang->lang('STORAGE_NO_CHANGES') . adm_back_link($this->u_action), E_USER_WARNING); } - $storage_stats = []; + // Template from adm/style + $this->tpl_name = 'acp_storage'; + + // Set page title + $this->page_title = 'STORAGE_TITLE'; + + $this->storage_stats(); // Show table with storage stats + + // Validate local paths to check if everything is fine + $messages = []; + foreach ($this->storage_collection as $storage) + { + $this->validate_path($storage->get_name(), $messages); + } + + $this->template->assign_vars([ + 'STORAGES' => $this->storage_collection, + 'PROVIDERS' => $this->provider_collection, + + 'ERROR_MESSAGES' => $messages, + + 'U_ACTION' => $this->u_action . '&hash=' . generate_link_hash('acp_storage'), + + 'STORAGE_UPDATE_TYPE_CONFIG' => update_type::CONFIG->value, + 'STORAGE_UPDATE_TYPE_COPY' => update_type::COPY->value, + 'STORAGE_UPDATE_TYPE_MOVE' => update_type::MOVE->value, + ]); + } + + /** + * When submit the settings form, check which storages have been modified + * to update only those. + * + * @return array + */ + private function get_modified_storages(): array + { + $modified_storages = []; + foreach ($this->storage_collection as $storage) { $storage_name = $storage->get_name(); - $options = $this->get_provider_options($this->get_current_provider($storage_name)); + $options = $this->storage_helper->get_provider_options($this->storage_helper->get_current_provider($storage_name)); + + $modified = false; - $this->validate_path($storage_name, $options, $messages); + // Check if provider have been modified + if ($this->request->variable([$storage_name, 'provider'], '') != $this->storage_helper->get_current_provider($storage_name)) + { + $modified = true; + } + else + { + // Check if options have been modified + foreach (array_keys($options) as $definition) + { + if ($this->request->variable([$storage_name, $definition], '') != $this->storage_helper->get_current_definition($storage_name, $definition)) + { + $modified = true; + break; + } + } + } + if ($modified) + { + $modified_storages[] = $storage_name; + } + } + + return $modified_storages; + } + + /** + * Fill template variables to show storage stats in settings page + * + * @return void + */ + protected function storage_stats(): void + { + // Top table with stats of each storage + $storage_stats = []; + foreach ($this->storage_collection as $storage) + { try { $free_space = get_formatted_filesize($storage->free_space()); } - catch (\phpbb\storage\exception\exception $e) + catch (storage_exception $e) { $free_space = $this->lang->lang('STORAGE_UNKNOWN'); } @@ -194,70 +438,70 @@ public function overview($id, $mode) } $this->template->assign_vars([ - 'STORAGES' => $this->storage_collection, 'STORAGE_STATS' => $storage_stats, - 'PROVIDERS' => $this->provider_collection, - - 'ERROR_MSG' => implode('
        ', $messages), - 'S_ERROR' => !empty($messages), ]); } /** - * Get the current provider from config - * - * @param string $storage_name Storage name - * @return string The current provider - */ - protected function get_current_provider($storage_name) - { - return $this->config['storage\\' . $storage_name . '\\provider']; - } - - /** - * Get the new provider from the request - * - * @param string $storage_name Storage name - * @return string The new provider + * Display progress page */ - protected function get_new_provider($storage_name) + protected function display_progress_page() : void { - return $this->request->variable([$storage_name, 'provider'], ''); - } + $u_action = append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage')); + meta_refresh(1, $u_action); - /** - * Get adapter definitions from a provider - * - * @param string $provider Provider class - * @return array Adapter definitions - */ - protected function get_provider_options($provider) - { - return $this->provider_collection->get_by_class($provider)->get_options(); - } + adm_page_header($this->lang->lang('STORAGE_UPDATE_IN_PROGRESS')); + $this->template->set_filenames([ + 'body' => 'acp_storage_update_progress.html' + ]); - /** - * Get the current value of the definition of a storage from config - * - * @param string $storage_name Storage name - * @param string $definition Definition - * @return string Definition value - */ - protected function get_current_definition($storage_name, $definition) - { - return $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; + $this->template->assign_vars([ + 'INDEXING_TITLE' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS'), + 'INDEXING_EXPLAIN' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS_EXPLAIN'), + 'INDEXING_PROGRESS_BAR' => $this->get_storage_update_progress(), + ]); + adm_page_footer(); } /** - * Get the new value of the definition of a storage from the request + * Get storage update progress to show progress bar * - * @param string $storage_name Storage name - * @param string $definition Definition - * @return string Definition value + * @return array */ - protected function get_new_definition($storage_name, $definition) + protected function get_storage_update_progress(): array { - return $this->request->variable([$storage_name, $definition], ''); + $file_index = $this->state_helper->file_index(); + $stage_is_copy = $this->state_helper->storage_index() < count($this->state_helper->storages()); + $storage_name = $this->state_helper->storages()[$stage_is_copy ? $this->state_helper->storage_index() : $this->state_helper->remove_storage_index()]; + + $sql = 'SELECT COUNT(file_id) as done_count + FROM ' . $this->storage_table . ' + WHERE file_id <= ' . $file_index . " + AND storage = '" . $this->db->sql_escape($storage_name) . "'"; + $result = $this->db->sql_query($sql); + $done_count = (int) $this->db->sql_fetchfield('done_count'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT COUNT(file_id) as remain_count + FROM ' . $this->storage_table . " + WHERE file_id > ' . $file_index . ' + AND storage = '" . $this->db->sql_escape($storage_name) . "'"; + $result = $this->db->sql_query($sql); + $remain_count = (int) $this->db->sql_fetchfield('remain_count'); + $this->db->sql_freeresult($result); + + $total_count = $done_count + $remain_count; + $percent = $total_count > 0 ? $done_count / $total_count : 0; + + $steps = $this->state_helper->storage_index() + $this->state_helper->remove_storage_index() + $percent; + $multiplier = $this->state_helper->update_type() === update_type::MOVE ? 2 : 1; + $steps_total = count($this->state_helper->storages()) * $multiplier; + + return [ + 'VALUE' => $steps, + 'TOTAL' => $steps_total, + 'PERCENTAGE' => $steps / $steps_total * 100, + ]; } /** @@ -266,14 +510,14 @@ protected function get_new_definition($storage_name, $definition) * @param string $storage_name Storage name * @param array $messages Reference to messages array */ - protected function validate_data($storage_name, &$messages) + protected function validate_data(string $storage_name, array &$messages): void { $storage_title = $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'); // Check if provider exists try { - $new_provider = $this->provider_collection->get_by_class($this->get_new_provider($storage_name)); + $new_provider = $this->provider_collection->get_by_class($this->request->variable([$storage_name, 'provider'], '')); } catch (\Exception $e) { @@ -288,91 +532,109 @@ protected function validate_data($storage_name, &$messages) return; } + $this->validate_path($storage_name, $messages); + // Check options - $new_options = $this->get_provider_options($this->get_new_provider($storage_name)); + $new_options = $this->storage_helper->get_provider_options($this->request->variable([$storage_name, 'provider'], '')); foreach ($new_options as $definition_key => $definition_value) { - $provider = $this->provider_collection->get_by_class($this->get_new_provider($storage_name)); + $provider = $this->provider_collection->get_by_class($this->request->variable([$storage_name, 'provider'], '')); $definition_title = $this->lang->lang('STORAGE_ADAPTER_' . strtoupper($provider->get_name()) . '_OPTION_' . strtoupper($definition_key)); - $value = $this->get_new_definition($storage_name, $definition_key); + $value = $this->request->variable([$storage_name, $definition_key], ''); - switch ($definition_value['type']) + switch ($definition_value['tag']) { - case 'email': - if (!filter_var($value, FILTER_VALIDATE_EMAIL)) + case 'text': + if ($definition_value['type'] == 'email' && filter_var($value, FILTER_VALIDATE_EMAIL)) { $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_EMAIL_INCORRECT_FORMAT', $definition_title, $storage_title); } - case 'text': - case 'password': - $maxlength = isset($definition_value['maxlength']) ? $definition_value['maxlength'] : 255; + + $maxlength = $definition_value['max'] ?? 255; if (strlen($value) > $maxlength) { $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_TEXT_TOO_LONG', $definition_title, $storage_title); } - break; - case 'radio': - case 'select': - if (!in_array($value, array_values($definition_value['options']))) + + if ($provider->get_name() == 'local' && $definition_key == 'path') { - $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_SELECT_NOT_AVAILABLE', $definition_title, $storage_title); - } - break; - } - } - } + $path = $value; - /** - * Updates an storage with the info provided in the form - * - * @param string $storage_name Storage name - */ - protected function update_storage_config($storage_name) - { - $current_options = $this->get_provider_options($this->get_current_provider($storage_name)); + if (empty($path)) + { + $messages[] = $this->lang->lang('STORAGE_PATH_NOT_SET', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE')); + } + else if (!$this->filesystem->exists($this->phpbb_root_path . $path) || !$this->filesystem->is_writable($this->phpbb_root_path . $path)) + { + $messages[] = $this->lang->lang('STORAGE_PATH_NOT_EXISTS', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE')); + } + } + break; - // Remove old storage config - foreach (array_keys($current_options) as $definition) - { - $this->config->delete('storage\\' . $storage_name . '\\config\\' . $definition); - } + case 'radio': + $found = false; + foreach ($definition_value['buttons'] as $button) + { + if ($button['value'] == $value) + { + $found = true; + break; + } + } - // Update provider - $this->config->set('storage\\' . $storage_name . '\\provider', $this->get_new_provider($storage_name)); + if (!$found) + { + $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_SELECT_NOT_AVAILABLE', $definition_title, $storage_title); + } + break; - // Set new storage config - $new_options = $this->get_provider_options($this->get_new_provider($storage_name)); + case 'select': + $found = false; + foreach ($definition_value['options'] as $option) + { + if ($option['value'] == $value) + { + $found = true; + break; + } + } - foreach (array_keys($new_options) as $definition) - { - $this->config->set('storage\\' . $storage_name . '\\config\\' . $definition, $this->get_new_definition($storage_name, $definition)); + if (!$found) + { + $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_SELECT_NOT_AVAILABLE', $definition_title, $storage_title); + } + break; + } } } /** - * Validates path + * Validates path when the filesystem is local * * @param string $storage_name Storage name - * @param array $options Storage provider configuration keys - * @param array $messages Reference to error messages array + * @param array $messages Error messages array * @return void */ - protected function validate_path($storage_name, $options, &$messages) + protected function validate_path(string $storage_name, array &$messages) : void { - if ($this->provider_collection->get_by_class($this->get_current_provider($storage_name))->get_name() == 'local' && isset($options['path'])) + $current_provider = $this->storage_helper->get_current_provider($storage_name); + $options = $this->storage_helper->get_provider_options($current_provider); + + if ($this->provider_collection->get_by_class($current_provider)->get_name() == 'local' && isset($options['path'])) { - $path = $this->request->is_set_post('submit') ? $this->get_new_definition($storage_name, 'path') : $this->get_current_definition($storage_name, 'path'); + $path = $this->request->is_set_post('submit') ? $this->request->variable([$storage_name, 'path'], '') : $this->storage_helper->get_current_definition($storage_name, 'path'); if (empty($path)) { $messages[] = $this->lang->lang('STORAGE_PATH_NOT_SET', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE')); } - else if (!$this->filesystem->is_writable($this->phpbb_root_path . $path) || !$this->filesystem->exists($this->phpbb_root_path . $path)) + else if (!$this->filesystem->exists($this->phpbb_root_path . $path) || !$this->filesystem->is_writable($this->phpbb_root_path . $path)) { $messages[] = $this->lang->lang('STORAGE_PATH_NOT_EXISTS', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE')); } } } + } diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 21f6542410..75b1cde0d8 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1094,7 +1094,7 @@ function main($id, $mode) $s_action_options .= ''; } - $last_active = (!empty($user_row['session_time'])) ? $user_row['session_time'] : $user_row['user_last_active']; + $last_active = $user_row['user_last_active'] ?: ($user_row['session_time'] ?? 0); $inactive_reason = ''; if ($user_row['user_type'] == USER_INACTIVE) @@ -1842,7 +1842,11 @@ function main($id, $mode) 'name' => 'lang', 'options' => $lang_options, ], - 'S_STYLE_OPTIONS' => style_select($data['style']), + 'S_STYLE_OPTIONS' => [ + 'id' => 'style', + 'name' => 'style', + 'options' => style_select($data['style']) + ], 'TIMEZONE_OPTIONS' => [ 'tag' => 'select', 'name' => 'tz', diff --git a/phpBB/includes/bbcode.php b/phpBB/includes/bbcode.php index d0ced55823..b98fe1e6b3 100644 --- a/phpBB/includes/bbcode.php +++ b/phpBB/includes/bbcode.php @@ -651,38 +651,4 @@ function bbcode_second_pass_code($type, $code) return $code; } - - /** - * Function to perform custom bbcode second pass by extensions - * can be used to assign bbcode pattern replacement - * Example: '#\[list=([^\[]+):$uid\]#e' => "\$this->bbcode_second_pass_by_extension('\$1')" - * - * Accepts variable number of parameters - * - * @return bool Second pass result - * - * @deprecated 3.2.10 (To be removed 4.0.0) - */ - function bbcode_second_pass_by_extension() - { - global $phpbb_dispatcher; - - $return = false; - $params_array = func_get_args(); - - /** - * Event to perform bbcode second pass with - * the custom validating methods provided by extensions - * - * @event core.bbcode_second_pass_by_extension - * @var array params_array Array with the function parameters - * @var mixed return Second pass result to return - * - * @since 3.1.5-RC1 - */ - $vars = array('params_array', 'return'); - extract($phpbb_dispatcher->trigger_event('core.bbcode_second_pass_by_extension', compact($vars))); - - return $return; - } } diff --git a/phpBB/includes/compatibility_globals.php b/phpBB/includes/compatibility_globals.php index df152c81ab..ca0394e88f 100644 --- a/phpBB/includes/compatibility_globals.php +++ b/phpBB/includes/compatibility_globals.php @@ -49,8 +49,6 @@ function register_compatibility_globals() /* @var $request \phpbb\request\request_interface */ $request = $phpbb_container->get('request'); - // Inject request instance, so only this instance is used with request_var - request_var('', 0, false, false, $request); /* @var $user \phpbb\user */ $user = $phpbb_container->get('user'); @@ -67,8 +65,6 @@ function register_compatibility_globals() // Grab global variables, re-cache if necessary /* @var $config phpbb\config\db */ $config = $phpbb_container->get('config'); - set_config('', '', false, $config); - set_config_count('', 0, false, $config); /* @var $phpbb_log \phpbb\log\log_interface */ $phpbb_log = $phpbb_container->get('log'); diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index f560dbfbcb..825a328d39 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -152,9 +152,13 @@ define('FULL_FOLDER_HOLD', -1); // Confirm types +/** @deprecated 4.0.0-a1 Replaced by \phpbb\captcha\plugins\confirm_type::REGISTRATION, to be removed in 5.0.0-a1 */ define('CONFIRM_REG', 1); +/** @deprecated 4.0.0-a1 Replaced by \phpbb\captcha\plugins\confirm_type::LOGIN, to be removed in 5.0.0-a1 */ define('CONFIRM_LOGIN', 2); +/** @deprecated 4.0.0-a1 Replaced by \phpbb\captcha\plugins\confirm_type::POST, to be removed in 5.0.0-a1 */ define('CONFIRM_POST', 3); +/** @deprecated 4.0.0-a1 Replaced by \phpbb\captcha\plugins\confirm_type::REPORT, to be removed in 5.0.0-a1 */ define('CONFIRM_REPORT', 4); // Categories - Attachments @@ -174,7 +178,7 @@ // Number of core BBCodes define('NUM_CORE_BBCODES', 12); -define('NUM_PREDEFINED_BBCODES', 22); +define('NUM_PREDEFINED_BBCODES', 20); // BBCode IDs define('BBCODE_ID_QUOTE', 0); diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index bca9a97c48..8741047049 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -107,9 +107,17 @@ function phpbb_gmgetdate($time = false) } // getdate() interprets timestamps in local time. - // What follows uses the fact that getdate() and - // date('Z') balance each other out. - return getdate($time - date('Z')); + // So use UTC timezone temporarily to get UTC date info array. + $current_timezone = date_default_timezone_get(); + + // Set UTC timezone and get respective date info + date_default_timezone_set('UTC'); + $date_info = getdate($time); + + // Restore timezone back + date_default_timezone_set($current_timezone); + + return $date_info; } /** @@ -319,11 +327,14 @@ function style_select($default = '', $all = false, array $styledata = []) $db->sql_freeresult($result); } - $style_options = ''; + $style_options = []; foreach ($styledata as $row) { - $selected = ($row['style_id'] == $default) ? ' selected="selected"' : ''; - $style_options .= ''; + $style_options[] = [ + 'value' => $row['style_id'], + 'selected' => $row['style_id'] == $default, + 'label' => $row['style_name'], + ]; } return $style_options; @@ -2928,8 +2939,16 @@ function msg_handler($errno, $msg_text, $errfile, $errline): bool global $phpbb_root_path, $msg_title, $msg_long_text, $phpbb_log; global $phpbb_container; + // https://www.php.net/manual/en/language.operators.errorcontrol.php + // error_reporting() return a different error code inside the error handler after php 8.0 + $suppresed = E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE; + if (PHP_VERSION_ID < 80000) + { + $suppresed = 0; + } + // Do not display notices if we suppress them via @ - if (error_reporting() == 0 && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE) + if (error_reporting() == $suppresed && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE) { return true; } @@ -3541,127 +3560,6 @@ function phpbb_quoteattr($data, $entities = null) return $data; } -/** -* Get user avatar -* -* @deprecated 4.0.0 Use \phpbb\avatar\helper::get_user_avatar() instead -* -* @param array $user_row Row from the users table -* @param string $alt Optional language string for alt tag within image, can be a language key or text -* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP -* @param bool $lazy If true, will be lazy loaded (requires JS) -* -* @return string Avatar html -*/ -function phpbb_get_user_avatar($user_row, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false) -{ - $row = \phpbb\avatar\manager::clean_row($user_row, 'user'); - return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); -} - -/** -* Get group avatar -* -* @deprecated 4.0.0 Use \phpbb\avatar\helper::get_group_avatar() instead -* -* @param array $group_row Row from the groups table -* @param string $alt Optional language string for alt tag within image, can be a language key or text -* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP -* @param bool $lazy If true, will be lazy loaded (requires JS) -* -* @return string Avatar html -*/ -function phpbb_get_group_avatar($group_row, $alt = 'GROUP_AVATAR', $ignore_config = false, $lazy = false) -{ - $row = \phpbb\avatar\manager::clean_row($group_row, 'group'); - return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); -} - -/** -* Get avatar -* -* @deprecated 4.0.0 Use \phpbb\avatar\helper::get_avatar() instead -* -* @param array $row Row cleaned by \phpbb\avatar\manager::clean_row -* @param string $alt Optional language string for alt tag within image, can be a language key or text -* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP -* @param bool $lazy If true, will be lazy loaded (requires JS) -* -* @return string Avatar html -*/ -function phpbb_get_avatar($row, $alt, $ignore_config = false, $lazy = false) -{ - global $user, $config; - global $phpbb_container, $phpbb_dispatcher; - - if (!$config['allow_avatar'] && !$ignore_config) - { - return ''; - } - - $avatar_data = array( - 'src' => $row['avatar'], - 'width' => $row['avatar_width'], - 'height' => $row['avatar_height'], - ); - - /* @var $phpbb_avatar_manager \phpbb\avatar\manager */ - $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); - $driver = $phpbb_avatar_manager->get_driver($row['avatar_type'], !$ignore_config); - $html = ''; - - if ($driver) - { - $html = $driver->get_custom_html($user, $row, $alt); - $avatar_data = $driver->get_data($row); - } - else - { - $avatar_data['src'] = ''; - } - - if (empty($html) && !empty($avatar_data['src'])) - { - if ($lazy) - { - // This path is sent with the base template paths in the assign_vars() - // call below. We need to correct it in case we are accessing from a - // controller because the web paths will be incorrect otherwise. - $phpbb_path_helper = $phpbb_container->get('path_helper'); - $web_path = $phpbb_path_helper->get_web_root_path(); - - $theme = "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme'; - - $src = 'src="' . $theme . '/images/no_avatar.gif" data-src="' . $avatar_data['src'] . '"'; - } - else - { - $src = 'src="' . $avatar_data['src'] . '"'; - } - - $html = ''; - } - - /** - * Event to modify HTML tag of avatar - * - * @event core.get_avatar_after - * @var array row Row cleaned by \phpbb\avatar\manager::clean_row - * @var string alt Optional language string for alt tag within image, can be a language key or text - * @var bool ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP - * @var array avatar_data The HTML attributes for avatar tag - * @var string html The HTML tag of generated avatar - * @since 3.1.6-RC1 - */ - $vars = array('row', 'alt', 'ignore_config', 'avatar_data', 'html'); - extract($phpbb_dispatcher->trigger_event('core.get_avatar_after', compact($vars))); - - return $html; -} - /** * Generate page header */ @@ -3853,6 +3751,9 @@ function page_header($page_title = '', $display_online_list = false, $item_id = $timezone_name = $user->lang['timezones'][$timezone_name]; } + /** @var \phpbb\controller\helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + // Output the notifications $notifications = false; if ($config['load_notifications'] && $config['allow_board_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE) @@ -3869,10 +3770,22 @@ function page_header($page_title = '', $display_online_list = false, $item_id = { $template->assign_block_vars('notifications', $notification->prepare_for_display()); } + + // Assign web push template vars globally (if not done already by ucp_notifications) for the dropdown subscribe button + if ($config['webpush_enable'] && $config['webpush_dropdown_subscribe'] + && $template->retrieve_var('NOTIFICATIONS_WEBPUSH_ENABLE') === null) + { + $methods = $phpbb_notifications->get_subscription_methods(); + $webpush = $methods['notification.method.webpush'] ?? null; + + if ($webpush) + { + $form_helper = $phpbb_container->get('form_helper'); + $template->assign_vars($webpush['method']->get_ucp_template_data($controller_helper, $form_helper)); + } + } } - /** @var \phpbb\controller\helper $controller_helper */ - $controller_helper = $phpbb_container->get('controller.helper'); $notification_mark_hash = generate_link_hash('mark_all_notifications_read'); $phpbb_version_parts = explode('.', PHPBB_VERSION, 3); @@ -3950,7 +3863,7 @@ function page_header($page_title = '', $display_online_list = false, $item_id = 'U_SEARCH_UNANSWERED' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'), 'U_SEARCH_UNREAD' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unreadposts'), 'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'), - 'U_DELETE_COOKIES' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=delete_cookies'), + 'U_DELETE_COOKIES' => $controller_helper->route('phpbb_ucp_delete_cookies_controller'), 'U_CONTACT_US' => ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '', 'U_TEAM' => (!$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team'), 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'), @@ -3958,6 +3871,7 @@ function page_header($page_title = '', $display_online_list = false, $item_id = 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')), 'U_RESTORE_PERMISSIONS' => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '', 'U_FEED' => $controller_helper->route('phpbb_feed_index'), + 'U_MANIFEST' => $controller_helper->route('phpbb_manifest_controller'), 'S_ALLOW_MENTIONS' => ($config['allow_mentions'] && $auth->acl_get('u_mention') && (empty($forum_id) || $auth->acl_get('f_mention', $forum_id))) ? true : false, 'S_MENTION_NAMES_LIMIT' => $config['mention_names_limit'], @@ -4126,7 +4040,9 @@ function phpbb_generate_debug_output(\phpbb\db\driver\driver_interface $db, \php if ($auth->acl_get('a_')) { - $debug_info[] = 'SQL Explain'; + $page_url = build_url(); + $page_url .= ((!str_contains($page_url, '?')) ? '?' : '&') . 'explain=1'; + $debug_info[] = 'SQL Explain'; } } diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index b8f007cdfd..a3bae23ab5 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -203,19 +203,24 @@ function adm_page_footer($copyright_html = true) */ function adm_back_link($u_action) { - global $user; - return '

        « ' . $user->lang['BACK_TO_PREV'] . ''; + global $language; + return '

        « ' . $language->lang('BACK_TO_PREV') . ''; } /** -* Build select field options in acp pages -*/ -function build_select($option_ary, $option_default = false): array + * Build select field options in acp pages + * + * @param array $options_ary Configuration options data + * @param int|string|bool $option_default Configuration option selected value + * + * @return array + */ +function build_select(array $options_ary, int|string|bool $option_default = false): array { global $language; $options = []; - foreach ($option_ary as $value => $title) + foreach ($options_ary as $value => $title) { $options[] = [ 'value' => $value, @@ -228,37 +233,50 @@ function build_select($option_ary, $option_default = false): array } /** -* Build radio fields in acp pages -*/ -function h_radio($name, $input_ary, $input_default = false, $id = false, $key = false, $separator = '') + * Build radio fields in acp pages + * + * @param int|string $value Configuration option value + * @param string $key Configuration option key name + * @param array $options Configuration options data + * representing array of [values => language_keys] + * + * @return array + */ +function phpbb_build_radio(int|string $value, string $key, array $options): array { - global $user; + global $language; - $html = ''; - $id_assigned = false; - foreach ($input_ary as $value => $title) + $buttons = []; + foreach ($options as $val => $title) { - $selected = ($input_default !== false && $value == $input_default) ? ' checked="checked"' : ''; - $html .= '' . $separator; - $id_assigned = true; + $buttons[] = [ + 'type' => 'radio', + 'value' => $val, + 'name' => 'config[' . $key . ']', + 'checked' => $val == $value, + 'label' => $language->lang($title), + ]; } - return $html; + return [ + 'buttons' => $buttons, + ]; } /** - * HTML-less version of build_cfg_template + * Build configuration data arrays or templates for configuration settings * - * @param array $tpl_type Template type - * @param string $key Config key - * @param $new_ary - * @param $config_key - * @param $vars - * @return array + * @param array $tpl_type Configuration setting type data + * @param string $key Configuration option name + * @param array|object $new_ary Updated configuration data + * @param string $config_key Configuration option name + * @param array $vars Configuration setting data + * + * @return array|string */ -function phpbb_build_cfg_template(array $tpl_type, string $key, &$new_ary, $config_key, $vars): array +function phpbb_build_cfg_template(array $tpl_type, string $key, array|object &$new_ary, string $config_key, array $vars): array|string { - global $language; + global $language, $module, $phpbb_dispatcher; $tpl = []; $name = 'config[' . $config_key . ']'; @@ -368,154 +386,102 @@ function phpbb_build_cfg_template(array $tpl_type, string $key, &$new_ary, $conf break; case 'radio': - $tpl_type_cond = explode('_', $tpl_type[1]); - $type_no = $tpl_type_cond[0] != 'disabled' && $tpl_type_cond[0] != 'enabled'; - - $no_button = [ - 'type' => 'radio', - 'name' => $name, - 'value' => 0, - 'checked' => !$new_ary[$config_key], - 'label' => $type_no ? $language->lang('NO') : $language->lang('DISABLED'), - ]; - - $yes_button = [ - 'id' => $key, - 'type' => 'radio', - 'name' => $name, - 'value' => 1, - 'checked' => (bool) $new_ary[$config_key], - 'label' => $type_no ? $language->lang('YES') : $language->lang('ENABLED'), - ]; - - $tpl = ['tag' => 'radio']; - if ($tpl_type_cond[0] == 'yes' || $tpl_type_cond[0] == 'enabled') + if (!isset($vars['method']) && !isset($vars['function'])) { - $tpl['buttons'] = [$yes_button, $no_button]; - } - else - { - $tpl['buttons'] = [$no_button, $yes_button]; + if (in_array($tpl_type[1], ['yes_no', 'enabled_disabled'])) + { + $options = array_reverse(explode('_', strtoupper($tpl_type[1]))); + krsort($options); + $tpl_type = array_merge ($tpl_type, phpbb_build_radio($new_ary[$config_key], $config_key, $options)); + } } - break; - } - - return $tpl; -} - -/** -* Build configuration template for acp configuration pages -*/ -function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) -{ - global $module, $phpbb_dispatcher; - - $tpl = ''; - $name = 'config[' . $config_key . ']'; - - // Make sure there is no notice printed out for non-existent config options (we simply set them) - if (!isset($new_ary[$config_key])) - { - $new_ary[$config_key] = ''; - } - - switch ($tpl_type[0]) - { - case 'password': - case 'text': - case 'url': - case 'email': - case 'tel': - case 'search': - case 'color': - case 'datetime': - case 'datetime-local': - case 'month': - case 'week': - case 'date': - case 'time': - case 'number': - case 'range': - case 'dimension': - case 'textarea': - case 'radio': - $tpl = phpbb_build_cfg_template($tpl_type, $key, $new_ary, $config_key, $vars); - break; - + case 'button': case 'select': case 'custom': + $args = []; + $call = $vars['function'] ?? (isset($vars['method']) ? [$module->module, $vars['method']] : false); - if (isset($vars['method'])) + if ($call) { - $call = array($module->module, $vars['method']); - } - else if (isset($vars['function'])) - { - $call = $vars['function']; - } - else - { - break; - } - - if (isset($vars['params'])) - { - $args = array(); - foreach ($vars['params'] as $value) + if (isset($vars['params'])) { - switch ($value) + foreach ($vars['params'] as $value) { - case '{CONFIG_VALUE}': - $value = $new_ary[$config_key]; - break; - - case '{KEY}': - $value = $key; - break; + switch ($value) + { + case '{CONFIG_VALUE}': + $value = $new_ary[$config_key]; + break; + + case '{KEY}': + $value = $config_key; + break; + } + + $args[] = $value; } - - $args[] = $value; } - } - else - { - $args = array($new_ary[$config_key], $key); + else + { + $args = array($new_ary[$config_key], $config_key); + } } - $return = call_user_func_array($call, $args); + $return = $call ? call_user_func_array($call, $args) : []; - if ($tpl_type[0] == 'select') + if (in_array($tpl_type[0], ['select', 'radio', 'button'])) { - $size = (isset($tpl_type[1])) ? (int) $tpl_type[1] : 1; - - if (is_string($return)) - { - $data_toggle = (!empty($tpl_type[2])) ? ' data-togglable-settings="true"' : ''; + $tpl_type = array_merge($tpl_type, $return); - $tpl = ''; - } - else + if ($tpl_type[0] == 'select') { $tpl = [ 'tag' => 'select', + 'class' => $tpl_type['class'] ?? false, 'id' => $key, + 'data' => $tpl_type['data'] ?? [], 'name' => $name, - 'toggleable' => !empty($tpl_type[2]), - 'options' => $return, + 'toggleable' => !empty($tpl_type[2]) || !empty($tpl_type['toggleable']), + 'options' => $tpl_type['options'], + 'group_only' => $tpl_type['group_only'] ?? false, + 'size' => $tpl_type[1] ?? $tpl_type['size'] ?? 1, + 'multiple' => $tpl_type['multiple'] ?? false, ]; - - // Add size if it differs from default value of 1 - if ($size != 1) + } + else if ($tpl_type[0] == 'radio') + { + // Only assign id to the one (1st) radio button in the list + $id_assigned = false; + foreach ($tpl_type['buttons'] as $i => $button) { - $tpl['size'] = $size; + if (!$id_assigned) + { + $tpl_type['buttons'][$i]['id'] = $key; + $id_assigned = true; + } } + + $tpl = [ + 'tag' => 'radio', + 'buttons' => $tpl_type['buttons'], + ]; + } + else + { + $tpl = [ + 'tag' => 'input', + 'class' => $tpl_type['options']['class'], + 'id' => $key, + 'type' => $tpl_type['options']['type'], + 'name' => $tpl_type['options']['name'] ?? $name, + 'value' => $tpl_type['options']['value'], + ]; } } else { $tpl = $return; } - break; default: @@ -539,16 +505,17 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) * Overwrite the html code we display for the config value * * @event core.build_config_template - * @var array tpl_type Config type array: - * 0 => data type - * 1 [optional] => string: size, int: minimum - * 2 [optional] => string: max. length, int: maximum - * @var string key Should be used for the id attribute in html - * @var array new Array with the config values we display - * @var string name Should be used for the name attribute - * @var array vars Array with the options for the config - * @var string tpl The resulting html code we display + * @var array tpl_type Config type array: + * 0 => data type + * 1 [optional] => string: size, int: minimum + * 2 [optional] => string: max. length, int: maximum + * @var string key Should be used for the id attribute in html + * @var array new Array with the config values we display + * @var string name Should be used for the name attribute + * @var array vars Array with the options for the config + * @var array|string tpl The resulting html code we display * @since 3.1.0-a1 + * @changed 4.0.0-a1 The event location's function renamed from build_config_template() to phpbb_build_cfg_template() */ $vars = array('tpl_type', 'key', 'new', 'name', 'vars', 'tpl'); extract($phpbb_dispatcher->trigger_event('core.build_config_template', compact($vars))); diff --git a/phpBB/includes/functions_compatibility.php b/phpBB/includes/functions_compatibility.php index c5ca053f56..1efc8c9c2e 100644 --- a/phpBB/includes/functions_compatibility.php +++ b/phpBB/includes/functions_compatibility.php @@ -19,863 +19,6 @@ exit; } -/** -* Get user avatar -* -* @deprecated 3.1.0-a1 (To be removed: 4.0.0) -* -* @param string $avatar Users assigned avatar name -* @param int $avatar_type Type of avatar -* @param string $avatar_width Width of users avatar -* @param string $avatar_height Height of users avatar -* @param string $alt Optional language string for alt tag within image, can be a language key or text -* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP -* @param bool $lazy If true, will be lazy loaded (requires JS) -* -* @return string Avatar image -*/ -function get_user_avatar($avatar, $avatar_type, $avatar_width, $avatar_height, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false) -{ - // map arguments to new function phpbb_get_avatar() - $row = array( - 'avatar' => $avatar, - 'avatar_type' => $avatar_type, - 'avatar_width' => $avatar_width, - 'avatar_height' => $avatar_height, - ); - - return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); -} - -/** -* Hash the password -* -* @deprecated 3.1.0-a2 (To be removed: 4.0.0) -* -* @param string $password Password to be hashed -* -* @return string|bool Password hash or false if something went wrong during hashing -*/ -function phpbb_hash($password) -{ - global $phpbb_container; - - /* @var $passwords_manager \phpbb\passwords\manager */ - $passwords_manager = $phpbb_container->get('passwords.manager'); - return $passwords_manager->hash($password); -} - -/** -* Check for correct password -* -* @deprecated 3.1.0-a2 (To be removed: 4.0.0) -* -* @param string $password The password in plain text -* @param string $hash The stored password hash -* -* @return bool Returns true if the password is correct, false if not. -*/ -function phpbb_check_hash($password, $hash) -{ - global $phpbb_container; - - /* @var $passwords_manager \phpbb\passwords\manager */ - $passwords_manager = $phpbb_container->get('passwords.manager'); - return $passwords_manager->check($password, $hash); -} - -/** -* Eliminates useless . and .. components from specified path. -* -* Deprecated, use storage helper class instead -* -* @param string $path Path to clean -* @return string Cleaned path -* -* @deprecated 3.1.0 (To be removed: 4.0.0) -*/ -function phpbb_clean_path($path) -{ - return \phpbb\filesystem\helper::clean_path($path); -} - -/** -* Pick a timezone -* -* @param string $default A timezone to select -* @param boolean $truncate Shall we truncate the options text -* -* @return string Returns the options for timezone selector only -* -* @deprecated 3.1.0 (To be removed: 4.0.0) -*/ -function tz_select($default = '', $truncate = false) -{ - global $user; - - return phpbb_timezone_select($user, $default, $truncate); -} - -/** -* Cache moderators. Called whenever permissions are changed -* via admin_permissions. Changes of usernames and group names -* must be carried through for the moderators table. -* -* @deprecated 3.1.0 (To be removed: 4.0.0) -* @return void -*/ -function cache_moderators() -{ - global $db, $cache, $auth, $phpbb_container; - phpbb_cache_moderators($db, $phpbb_container->get('dbal.tools'), $cache, $auth); -} - -/** -* Removes moderators and administrators from foe lists. -* -* @deprecated 3.1.0 (To be removed: 4.0.0) -* @param array|bool $group_id If an array, remove all members of this group from foe lists, or false to ignore -* @param array|bool $user_id If an array, remove this user from foe lists, or false to ignore -* @return void -*/ -function update_foes($group_id = false, $user_id = false) -{ - global $db, $auth; - return phpbb_update_foes($db, $auth, $group_id, $user_id); -} - -/** -* Get user rank title and image -* -* @param int $user_rank the current stored users rank id -* @param int $user_posts the users number of posts -* @param string &$rank_title the rank title will be stored here after execution -* @param string &$rank_img the rank image as full img tag is stored here after execution -* @param string &$rank_img_src the rank image source is stored here after execution -* -* @deprecated 3.1.0-RC5 (To be removed: 4.0.0) -* -* Note: since we do not want to break backwards-compatibility, this function will only properly assign ranks to guests if you call it for them with user_posts == false -*/ -function get_user_rank($user_rank, $user_posts, &$rank_title, &$rank_img, &$rank_img_src) -{ - global $phpbb_root_path, $phpEx; - if (!function_exists('phpbb_get_user_rank')) - { - include($phpbb_root_path . 'includes/functions_display.' . $phpEx); - } - - $rank_data = phpbb_get_user_rank(array('user_rank' => $user_rank), $user_posts); - $rank_title = $rank_data['title']; - $rank_img = $rank_data['img']; - $rank_img_src = $rank_data['img_src']; -} - -/** - * Retrieve contents from remotely stored file - * - * @deprecated 3.1.2 Use file_downloader instead - */ -function get_remote_file($host, $directory, $filename, &$errstr, &$errno, $port = 80, $timeout = 6) -{ - global $phpbb_container; - - // Get file downloader and assign $errstr and $errno - /* @var $file_downloader \phpbb\file_downloader */ - $file_downloader = $phpbb_container->get('file_downloader'); - - $file_data = $file_downloader->get($host, $directory, $filename, $port, $timeout); - $errstr = $file_downloader->get_error_string(); - $errno = $file_downloader->get_error_number(); - - return $file_data; -} - -/** - * Add log entry - * - * string $mode The mode defines which log_type is used and from which log the entry is retrieved - * int $forum_id Mode 'mod' ONLY: forum id of the related item, NOT INCLUDED otherwise - * int $topic_id Mode 'mod' ONLY: topic id of the related item, NOT INCLUDED otherwise - * int $reportee_id Mode 'user' ONLY: user id of the reportee, NOT INCLUDED otherwise - * string $log_operation Name of the operation - * array $additional_data More arguments can be added, depending on the log_type - * - * @return int|bool Returns the log_id, if the entry was added to the database, false otherwise. - * - * @deprecated 3.1.0 (To be removed: 4.0.0) - */ -function add_log() -{ - global $phpbb_log, $user; - - $args = func_get_args(); - $mode = array_shift($args); - - // This looks kind of dirty, but add_log has some additional data before the log_operation - $additional_data = array(); - switch ($mode) - { - case 'admin': - case 'critical': - break; - case 'mod': - $additional_data['forum_id'] = array_shift($args); - $additional_data['topic_id'] = array_shift($args); - break; - case 'user': - $additional_data['reportee_id'] = array_shift($args); - break; - } - - $log_operation = array_shift($args); - $additional_data = array_merge($additional_data, $args); - - $user_id = (empty($user->data)) ? ANONYMOUS : $user->data['user_id']; - $user_ip = (empty($user->ip)) ? '' : $user->ip; - - return $phpbb_log->add($mode, $user_id, $user_ip, $log_operation, time(), $additional_data); -} - -/** - * Sets a configuration option's value. - * - * Please note that this function does not update the is_dynamic value for - * an already existing config option. - * - * @param string $config_name The configuration option's name - * @param string $config_value New configuration value - * @param bool $is_dynamic Whether this variable should be cached (false) or - * if it changes too frequently (true) to be - * efficiently cached. - * - * @return void - * - * @deprecated 3.1.0 (To be removed: 4.0.0) - */ -function set_config($config_name, $config_value, $is_dynamic = false, \phpbb\config\config $set_config = null) -{ - static $config = null; - - if ($set_config !== null) - { - $config = $set_config; - - if (empty($config_name)) - { - return; - } - } - - $config->set($config_name, $config_value, !$is_dynamic); -} - -/** - * Increments an integer config value directly in the database. - * - * @param string $config_name The configuration option's name - * @param int $increment Amount to increment by - * @param bool $is_dynamic Whether this variable should be cached (false) or - * if it changes too frequently (true) to be - * efficiently cached. - * - * @return void - * - * @deprecated 3.1.0 (To be removed: 4.0.0) - */ -function set_config_count($config_name, $increment, $is_dynamic = false, \phpbb\config\config $set_config = null) -{ - static $config = null; - if ($set_config !== null) - { - $config = $set_config; - if (empty($config_name)) - { - return; - } - } - $config->increment($config_name, $increment, !$is_dynamic); -} - -/** - * Wrapper function of \phpbb\request\request::variable which exists for backwards compatability. - * See {@link \phpbb\request\request_interface::variable \phpbb\request\request_interface::variable} for - * documentation of this function's use. - * - * @deprecated 3.1.0 (To be removed: 4.0.0) - * @param mixed $var_name The form variable's name from which data shall be retrieved. - * If the value is an array this may be an array of indizes which will give - * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") - * then specifying array("var", 1) as the name will return "a". - * If you pass an instance of {@link \phpbb\request\request_interface phpbb_request_interface} - * as this parameter it will overwrite the current request class instance. If you do - * not do so, it will create its own instance (but leave superglobals enabled). - * @param mixed $default A default value that is returned if the variable was not set. - * This function will always return a value of the same type as the default. - * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters - * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks - * @param bool $cookie This param is mapped to \phpbb\request\request_interface::COOKIE as the last param for - * \phpbb\request\request_interface::variable for backwards compatability reasons. - * @param \phpbb\request\request_interface|null|false $request - * If an instance of \phpbb\request\request_interface is given the instance is stored in - * a static variable and used for all further calls where this parameters is null. Until - * the function is called with an instance it automatically creates a new \phpbb\request\request - * instance on every call. By passing false this per-call instantiation can be restored - * after having passed in a \phpbb\request\request_interface instance. - * - * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the - * the same as that of $default. If the variable is not set $default is returned. - */ -function request_var($var_name, $default, $multibyte = false, $cookie = false, $request = null) -{ - // This is all just an ugly hack to add "Dependency Injection" to a function - // the only real code is the function call which maps this function to a method. - static $static_request = null; - if ($request instanceof \phpbb\request\request_interface) - { - $static_request = $request; - if (empty($var_name)) - { - return null; - } - } - else if ($request === false) - { - $static_request = null; - if (empty($var_name)) - { - return null; - } - } - $tmp_request = $static_request; - // no request class set, create a temporary one ourselves to keep backwards compatibility - if ($tmp_request === null) - { - // false param: enable super globals, so the created request class does not - // make super globals inaccessible everywhere outside this function. - $tmp_request = new \phpbb\request\request(new \phpbb\request\type_cast_helper(), false); - } - return $tmp_request->variable($var_name, $default, $multibyte, ($cookie) ? \phpbb\request\request_interface::COOKIE : \phpbb\request\request_interface::REQUEST); -} - -/** - * Get tables of a database - * - * @deprecated 3.1.0 (To be removed: 4.0.0) - */ -function get_tables($db) -{ - throw new BadFunctionCallException('function removed from phpBB core, use db_tools service instead.'); -} - -/** - * Global function for chmodding directories and files for internal use - * - * This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions. - * The function determines owner and group from common.php file and sets the same to the provided file. - * The function uses bit fields to build the permissions. - * The function sets the appropiate execute bit on directories. - * - * Supported constants representing bit fields are: - * - * CHMOD_ALL - all permissions (7) - * CHMOD_READ - read permission (4) - * CHMOD_WRITE - write permission (2) - * CHMOD_EXECUTE - execute permission (1) - * - * NOTE: The function uses POSIX extension and fileowner()/filegroup() functions. If any of them is disabled, this function tries to build proper permissions, by calling is_readable() and is_writable() functions. - * - * @param string $filename The file/directory to be chmodded - * @param int $perms Permissions to set - * - * @return bool true on success, otherwise false - * - * @deprecated 3.2.0-dev use \phpbb\filesystem\filesystem::phpbb_chmod() instead - */ -function phpbb_chmod($filename, $perms = CHMOD_READ) -{ - global $phpbb_filesystem; - - try - { - $phpbb_filesystem->phpbb_chmod($filename, $perms); - } - catch (\phpbb\filesystem\exception\filesystem_exception $e) - { - return false; - } - - return true; -} - -/** - * Test if a file/directory is writable - * - * This function calls the native is_writable() when not running under - * Windows and it is not disabled. - * - * @param string $file Path to perform write test on - * @return bool True when the path is writable, otherwise false. - * - * @deprecated 3.2.0-dev use \phpbb\filesystem\filesystem::is_writable() instead - */ -function phpbb_is_writable($file) -{ - global $phpbb_filesystem; - - return $phpbb_filesystem->is_writable($file); -} - -/** - * Checks if a path ($path) is absolute or relative - * - * @param string $path Path to check absoluteness of - * @return boolean - * - * @deprecated 3.2.0-dev use \phpbb\filesystem\helper::is_absolute_path() instead - */ -function phpbb_is_absolute($path) -{ - return \phpbb\filesystem\helper::is_absolute_path($path); -} - -/** - * A wrapper for realpath - * - * @deprecated 3.2.0-dev use \phpbb\filesystem\helper::realpath() instead - */ -function phpbb_realpath($path) -{ - return \phpbb\filesystem\helper::realpath($path); -} - -/** - * Determine which plural form we should use. - * For some languages this is not as simple as for English. - * - * @param int $rule ID of the plural rule we want to use, see https://area51.phpbb.com/docs/dev/3.3.x/language/plurals.html - * @param int|float $number The number we want to get the plural case for. Float numbers are floored. - * @return int The plural-case we need to use for the number plural-rule combination - * - * @deprecated 3.2.0-dev (To be removed: 4.0.0) - */ -function phpbb_get_plural_form($rule, $number) -{ - global $phpbb_container; - - /** @var \phpbb\language\language $language */ - $language = $phpbb_container->get('language'); - return $language->get_plural_form($number, $rule); -} - -/** -* @return bool Always true -* @deprecated 3.2.0-dev -*/ -function phpbb_pcre_utf8_support() -{ - return true; -} - -/** - * Casts a variable to the given type. - * - * @deprecated 3.1 (To be removed 4.0.0) - */ -function set_var(&$result, $var, $type, $multibyte = false) -{ - // no need for dependency injection here, if you have the object, call the method yourself! - $type_cast_helper = new \phpbb\request\type_cast_helper(); - $type_cast_helper->set_var($result, $var, $type, $multibyte); -} - -/** - * Delete Attachments - * - * @deprecated 3.2.0-a1 (To be removed: 4.0.0) - * - * @param string $mode can be: post|message|topic|attach|user - * @param mixed $ids can be: post_ids, message_ids, topic_ids, attach_ids, user_ids - * @param bool $resync set this to false if you are deleting posts or topics - */ -function delete_attachments($mode, $ids, $resync = true) -{ - global $phpbb_container; - - /** @var \phpbb\attachment\manager $attachment_manager */ - $attachment_manager = $phpbb_container->get('attachment.manager'); - $num_deleted = $attachment_manager->delete($mode, $ids, $resync); - - unset($attachment_manager); - - return $num_deleted; -} - -/** - * Delete attached file - * - * @deprecated 3.2.0-a1 (To be removed: 4.0.0) - */ -function phpbb_unlink($filename, $mode = 'file', $entry_removed = false) -{ - global $phpbb_container; - - /** @var \phpbb\attachment\manager $attachment_manager */ - $attachment_manager = $phpbb_container->get('attachment.manager'); - $unlink = $attachment_manager->unlink($filename, $mode, $entry_removed); - unset($attachment_manager); - - return $unlink; -} - -/** - * Display reasons - * - * @deprecated 3.2.0-dev (To be removed: 4.0.0) - */ -function display_reasons($reason_id = 0) -{ - global $phpbb_container; - - $phpbb_container->get('phpbb.report.report_reason_list_provider')->display_reasons($reason_id); -} - -/** - * Upload Attachment - filedata is generated here - * Uses upload class - * - * @deprecated 3.2.0-a1 (To be removed: 4.0.0) - * - * @param string $form_name The form name of the file upload input - * @param int $forum_id The id of the forum - * @param bool $local Whether the file is local or not - * @param string $local_storage The path to the local file - * @param bool $is_message Whether it is a PM or not - * @param array $local_filedata A filespec object created for the local file - * - * @return array File data array - */ -function upload_attachment($form_name, $forum_id, $local = false, $local_storage = '', $is_message = false, $local_filedata = false) -{ - global $phpbb_container; - - /** @var \phpbb\attachment\manager $attachment_manager */ - $attachment_manager = $phpbb_container->get('attachment.manager'); - $file = $attachment_manager->upload($form_name, $forum_id, $local, $local_storage, $is_message, $local_filedata); - unset($attachment_manager); - - return $file; -} - -/** -* Wrapper for php's checkdnsrr function. -* -* @param string $host Fully-Qualified Domain Name -* @param string $type Resource record type to lookup -* Supported types are: MX (default), A, AAAA, NS, TXT, CNAME -* Other types may work or may not work -* -* @return bool|null true if entry found, -* false if entry not found, -* null if this function is not supported by this environment -* -* Since null can also be returned, you probably want to compare the result -* with === true or === false, -* -* @deprecated 3.3.0-b2 (To be removed: 4.0.0) -*/ -function phpbb_checkdnsrr($host, $type = 'MX') -{ - return checkdnsrr($host, $type); -} - -/* - * Wrapper for inet_ntop() - * - * Converts a packed internet address to a human readable representation - * inet_ntop() is supported by PHP since 5.1.0, since 5.3.0 also on Windows. - * - * @param string $in_addr A 32bit IPv4, or 128bit IPv6 address. - * - * @return mixed false on failure, - * string otherwise - * - * @deprecated 3.3.0-b2 (To be removed: 4.0.0) - */ -function phpbb_inet_ntop($in_addr) -{ - return inet_ntop($in_addr); -} - -/** - * Wrapper for inet_pton() - * - * Converts a human readable IP address to its packed in_addr representation - * inet_pton() is supported by PHP since 5.1.0, since 5.3.0 also on Windows. - * - * @param string $address A human readable IPv4 or IPv6 address. - * - * @return false|string false if address is invalid, - * in_addr representation of the given address otherwise (string) - * - * @deprecated 3.3.0-b2 (To be removed: 4.0.0) - */ -function phpbb_inet_pton($address) -{ - return inet_pton($address); -} - -/** - * Hashes an email address to a big integer - * - * @param string $email Email address - * - * @return string Unsigned Big Integer - * - * @deprecated 3.3.0-b2 (To be removed: 4.0.0) - */ -function phpbb_email_hash($email) -{ - return sprintf('%u', crc32(strtolower($email))) . strlen($email); -} - -/** - * Load the autoloaders added by the extensions. - * - * @param string $phpbb_root_path Path to the phpbb root directory. - */ -function phpbb_load_extensions_autoloaders($phpbb_root_path) -{ - $iterator = new \phpbb\finder\recursive_path_iterator( - $phpbb_root_path . 'ext/', - \RecursiveIteratorIterator::SELF_FIRST, - \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS - ); - $iterator->setMaxDepth(2); - - foreach ($iterator as $file_info) - { - if ($file_info->getFilename() === 'vendor' && $iterator->getDepth() === 2) - { - $filename = $file_info->getRealPath() . '/autoload.php'; - if (file_exists($filename)) - { - require $filename; - } - } - } -} - -/** -* Login using http authenticate. -* -* @param array $param Parameter array, see $param_defaults array. -* -* @return void -* -* @deprecated 3.2.10 (To be removed 4.0.0) -*/ -function phpbb_http_login($param) -{ - global $auth, $user, $request; - global $config; - - $param_defaults = array( - 'auth_message' => '', - - 'autologin' => false, - 'viewonline' => true, - 'admin' => false, - ); - - // Overwrite default values with passed values - $param = array_merge($param_defaults, $param); - - // User is already logged in - // We will not overwrite his session - if (!empty($user->data['is_registered'])) - { - return; - } - - // $_SERVER keys to check - $username_keys = array( - 'PHP_AUTH_USER', - 'Authorization', - 'REMOTE_USER', 'REDIRECT_REMOTE_USER', - 'HTTP_AUTHORIZATION', 'REDIRECT_HTTP_AUTHORIZATION', - 'REMOTE_AUTHORIZATION', 'REDIRECT_REMOTE_AUTHORIZATION', - 'AUTH_USER', - ); - - $password_keys = array( - 'PHP_AUTH_PW', - 'REMOTE_PASSWORD', - 'AUTH_PASSWORD', - ); - - $username = null; - foreach ($username_keys as $k) - { - if ($request->is_set($k, \phpbb\request\request_interface::SERVER)) - { - $username = html_entity_decode($request->server($k), ENT_COMPAT); - break; - } - } - - $password = null; - foreach ($password_keys as $k) - { - if ($request->is_set($k, \phpbb\request\request_interface::SERVER)) - { - $password = html_entity_decode($request->server($k), ENT_COMPAT); - break; - } - } - - // Decode encoded information (IIS, CGI, FastCGI etc.) - if (!is_null($username) && is_null($password) && strpos($username, 'Basic ') === 0) - { - list($username, $password) = explode(':', base64_decode(substr($username, 6)), 2); - } - - if (!is_null($username) && !is_null($password)) - { - set_var($username, $username, 'string', true); - set_var($password, $password, 'string', true); - - $auth_result = $auth->login($username, $password, $param['autologin'], $param['viewonline'], $param['admin']); - - if ($auth_result['status'] == LOGIN_SUCCESS) - { - return; - } - else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS) - { - send_status_line(401, 'Unauthorized'); - - trigger_error('NOT_AUTHORISED'); - } - } - - // Prepend sitename to auth_message - $param['auth_message'] = ($param['auth_message'] === '') ? $config['sitename'] : $config['sitename'] . ' - ' . $param['auth_message']; - - // We should probably filter out non-ASCII characters - RFC2616 - $param['auth_message'] = preg_replace('/[\x80-\xFF]/', '?', $param['auth_message']); - - header('WWW-Authenticate: Basic realm="' . $param['auth_message'] . '"'); - send_status_line(401, 'Unauthorized'); - - trigger_error('NOT_AUTHORISED'); -} - -/** -* Converts query string (GET) parameters in request into hidden fields. -* -* Useful for forwarding GET parameters when submitting forms with GET method. -* -* It is possible to omit some of the GET parameters, which is useful if -* they are specified in the form being submitted. -* -* sid is always omitted. -* -* @param \phpbb\request\request $request Request object -* @param array $exclude A list of variable names that should not be forwarded -* @return string HTML with hidden fields -* -* @deprecated 3.2.10 (To be removed 4.0.0) -*/ -function phpbb_build_hidden_fields_for_query_params($request, $exclude = null) -{ - $names = $request->variable_names(\phpbb\request\request_interface::GET); - $hidden = ''; - foreach ($names as $name) - { - // Sessions are dealt with elsewhere, omit sid always - if ($name == 'sid') - { - continue; - } - - // Omit any additional parameters requested - if (!empty($exclude) && in_array($name, $exclude)) - { - continue; - } - - $escaped_name = phpbb_quoteattr($name); - - // Note: we might retrieve the variable from POST or cookies - // here. To avoid exposing cookies, skip variables that are - // overwritten somewhere other than GET entirely. - $value = $request->variable($name, '', true); - $get_value = $request->variable($name, '', true, \phpbb\request\request_interface::GET); - if ($value === $get_value) - { - $escaped_value = phpbb_quoteattr($value); - $hidden .= ""; - } - } - return $hidden; -} - -/** -* Delete all PM(s) for a given user and delete the ones without references -* -* @param int $user_id ID of the user whose private messages we want to delete -* -* @return boolean False if there were no pms found, true otherwise. -* -* @deprecated 3.2.10 (To be removed 4.0.0) -*/ -function phpbb_delete_user_pms($user_id) -{ - $user_id = (int) $user_id; - - if (!$user_id) - { - return false; - } - - return phpbb_delete_users_pms(array($user_id)); -} - -/** -* Casts a numeric string $input to an appropriate numeric type (i.e. integer or float) -* -* @param string $input A numeric string. -* -* @return int|float Integer $input if $input fits integer, -* float $input otherwise. -* -* @deprecated 3.2.10 (To be removed 4.0.0) -*/ -function phpbb_to_numeric($input) -{ - return ($input > PHP_INT_MAX) ? (float) $input : (int) $input; -} - -/** -* Check and display the SQL report if requested. -* -* @param \phpbb\request\request_interface $request Request object -* @param \phpbb\auth\auth $auth Auth object -* @param \phpbb\db\driver\driver_interface $db Database connection -* -* @deprecated 3.3.1 (To be removed: 4.0.0-a1); use controller helper's display_sql_report() -*/ -function phpbb_check_and_display_sql_report(\phpbb\request\request_interface $request, \phpbb\auth\auth $auth, \phpbb\db\driver\driver_interface $db) -{ - global $phpbb_container; - - /** @var \phpbb\controller\helper $controller_helper */ - $controller_helper = $phpbb_container->get('controller.helper'); - - $controller_helper->display_sql_report(); -} - /** * Parse cfg file * @param string $filename @@ -937,27 +80,3 @@ function parse_cfg_file($filename, $lines = false) return $parsed_items; } - -/** -* Wraps an url into a simple html page. Used to display attachments in IE. -* this is a workaround for now; might be moved to template system later -* direct any complaints to 1 Microsoft Way, Redmond -* -* @deprecated: 3.3.0-dev (To be removed: 4.0.0) -*/ -function wrap_img_in_html($src, $title) -{ - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo '' . $title . ''; - echo ''; - echo ''; - echo '
        '; - echo '' . $title . ''; - echo '
        '; - echo ''; - echo ''; -} diff --git a/phpBB/includes/functions_content.php b/phpBB/includes/functions_content.php index 941b2eb246..1e46bf4e9b 100644 --- a/phpBB/includes/functions_content.php +++ b/phpBB/includes/functions_content.php @@ -288,7 +288,27 @@ function make_jumpbox($action, $forum_id = false, $select_all = false, $acl_list */ function bump_topic_allowed($forum_id, $topic_bumped, $last_post_time, $topic_poster, $last_topic_poster) { - global $config, $auth, $user; + global $config, $auth, $user, $phpbb_dispatcher; + + /** + * Event to run code before the topic bump checks + * + * @event core.bump_topic_allowed_before + * @var int forum_id ID of the forum + * @var int topic_bumped Flag indicating if the topic was already bumped (0/1) + * @var int last_post_time The time of the topic last post + * @var int topic_poster User ID of the topic author + * @var int last_topic_poster User ID of the topic last post author + * @since 3.3.14-RC1 + */ + $vars = [ + 'forum_id', + 'topic_bumped', + 'last_post_time', + 'topic_poster', + 'last_topic_poster', + ]; + extract($phpbb_dispatcher->trigger_event('core.bump_topic_allowed_before', compact($vars))); // Check permission and make sure the last post was not already bumped if (!$auth->acl_get('f_bump', $forum_id) || $topic_bumped) @@ -311,6 +331,28 @@ function bump_topic_allowed($forum_id, $topic_bumped, $last_post_time, $topic_po return false; } + /** + * Event to run code after the topic bump checks + * + * @event core.bump_topic_allowed_after + * @var int forum_id ID of the forum + * @var int topic_bumped Flag indicating if the topic was already bumped (0/1) + * @var int last_post_time The time of the topic last post + * @var int topic_poster User ID of the topic author + * @var int last_topic_poster User ID of the topic last post author + * @var int bump_time Bump time range + * @since 3.3.14-RC1 + */ + $vars = [ + 'forum_id', + 'topic_bumped', + 'last_post_time', + 'topic_poster', + 'last_topic_poster', + 'bump_time', + ]; + extract($phpbb_dispatcher->trigger_event('core.bump_topic_allowed_after', compact($vars))); + // A bump time of 0 will completely disable the bump feature... not intended but might be useful. return $bump_time; } @@ -324,121 +366,95 @@ function bump_topic_allowed($forum_id, $topic_bumped, $last_post_time, $topic_po * * @return string Context of the specified words separated by "..." */ -function get_context(string $text, array $words, int $length = 400) +function get_context(string $text, array $words, int $length = 400): string { - // first replace all whitespaces with single spaces - $text = preg_replace('/ +/', ' ', strtr($text, "\t\n\r\x0C ", ' ')); + if ($length <= 0) + { + return $text; + } - // we need to turn the entities back into their original form, to not cut the message in between them - $entities = array('<', '>', '[', ']', '.', ':', ':'); - $characters = array('<', '>', '[', ']', '.', ':', ':'); - $text = str_replace($entities, $characters, $text); + // We need to turn the entities back into their original form, to not cut the message in between them + $text = htmlspecialchars_decode($text); - $word_indizes = array(); - if (count($words)) + // Replace all spaces/invisible characters with single spaces + $text = preg_replace("/[\p{Z}\h\v]+/u", ' ', $text); + + $text_length = utf8_strlen($text); + + // Get first occurrence of each word + $word_indexes = []; + foreach ($words as $word) { - $match = ''; - // find the starting indizes of all words - foreach ($words as $word) - { - if ($word) - { - if (preg_match('#(?:[^\w]|^)(' . $word . ')(?:[^\w]|$)#i', $text, $match)) - { - if (empty($match[1])) - { - continue; - } + $pos = utf8_stripos($text, $word); - $pos = utf8_strpos($text, $match[1]); - if ($pos !== false) - { - $word_indizes[] = $pos; - } - } - } + if ($pos !== false) + { + $word_indexes[$pos] = $word; } - unset($match); + } + if (!empty($word_indexes)) + { + ksort($word_indexes); + + // Size of the fragment of text per word + $num_indexes = count($word_indexes); + $characters_per_word = (int) ($length / $num_indexes) + 2; // 2 to leave one character of margin at the sides to don't cut words - if (count($word_indizes)) + // Get text fragment indexes + $fragments = []; + foreach ($word_indexes as $index => $word) { - $word_indizes = array_unique($word_indizes); - sort($word_indizes); - - $wordnum = count($word_indizes); - // number of characters on the right and left side of each word - $sequence_length = (int) ($length / (2 * $wordnum)) - 2; - $final_text = ''; - $word = $j = 0; - $final_text_index = -1; - - // cycle through every character in the original text - for ($i = $word_indizes[$word], $n = utf8_strlen($text); $i < $n; $i++) - { - // if the current position is the start of one of the words then append $sequence_length characters to the final text - if (isset($word_indizes[$word]) && ($i == $word_indizes[$word])) - { - if ($final_text_index < $i - $sequence_length - 1) - { - $final_text .= '... ' . preg_replace('#^([^ ]*)#', '', utf8_substr($text, $i - $sequence_length, $sequence_length)); - } - else - { - // if the final text is already nearer to the current word than $sequence_length we only append the text - // from its current index on and distribute the unused length to all other sequenes - $sequence_length += (int) (($final_text_index - $i + $sequence_length + 1) / (2 * $wordnum)); - $final_text .= utf8_substr($text, $final_text_index + 1, $i - $final_text_index - 1); - } - $final_text_index = $i - 1; + $word_length = utf8_strlen($word); + $start = max(0, min($text_length - 1 - $characters_per_word, (int) ($index + ($word_length / 2) - ($characters_per_word / 2)))); + $end = $start + $characters_per_word; - // add the following characters to the final text (see below) - $word++; - $j = 1; - } + // Check if we can merge this fragment into the previous fragment + if (!empty($fragments)) + { + [$prev_start, $prev_end] = end($fragments); - if ($j > 0) + if ($prev_end + $characters_per_word >= $index + $word_length) { - // add the character to the final text and increment the sequence counter - $final_text .= utf8_substr($text, $i, 1); - $final_text_index++; - $j++; - - // if this is a whitespace then check whether we are done with this sequence - if (utf8_substr($text, $i, 1) == ' ') - { - // only check whether we have to exit the context generation completely if we haven't already reached the end anyway - if ($i + 4 < $n) - { - if (($j > $sequence_length && $word >= $wordnum) || utf8_strlen($final_text) > $length) - { - $final_text .= ' ...'; - break; - } - } - else - { - // make sure the text really reaches the end - $j -= 4; - } - - // stop context generation and wait for the next word - if ($j > $sequence_length) - { - $j = 0; - } - } + array_pop($fragments); + $start = $prev_start; + $end = $prev_end + $characters_per_word; } } - return str_replace($characters, $entities, $final_text); + + $fragments[] = [$start, $end]; } } + else + { + // There is no coincidences, so we just create a fragment with the first $length characters + $fragments[] = [0, $length]; + $end = $length; + } - if (!count($words) || !count($word_indizes)) + $output = []; + foreach ($fragments as [$start, $end]) { - return str_replace($characters, $entities, ((utf8_strlen($text) >= $length + 3) ? utf8_substr($text, 0, $length) . '...' : $text)); + $fragment = utf8_substr($text, $start, $end - $start + 1); + + $fragment_start = 0; + $fragment_end = $end - $start + 1; + + // Find the first valid alphanumeric character in the fragment to don't cut words + if ($start > 0 && preg_match('/[^\p{L}\p{N}][\p{L}\p{N}]/u', $fragment, $matches, PREG_OFFSET_CAPTURE)) + { + $fragment_start = utf8_strlen(substr($fragment, 0, (int) $matches[0][1])) + 1; + } + + // Find the last valid alphanumeric character in the fragment to don't cut words + if ($end < $text_length - 1 && preg_match_all('/[\p{L}\p{N}][^\p{L}\p{N}]/u', $fragment, $matches, PREG_OFFSET_CAPTURE)) + { + $fragment_end = utf8_strlen(substr($fragment, 0, end($matches[0])[1])); + } + + $output[] = utf8_substr($fragment, $fragment_start, $fragment_end - $fragment_start + 1); } - return ''; + return ($fragments[0][0] !== 0 ? '... ' : '') . utf8_htmlspecialchars(implode(' ... ', $output)) . ($end < $text_length - 1 ? ' ...' : ''); } /** @@ -1260,19 +1276,7 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count_a { if ($config['img_display_inlined']) { - if ($config['img_link_width'] || $config['img_link_height']) - { - try - { - $file_info = $storage_attachment->file_info($filename); - - $display_cat = ($file_info->image_width <= $config['img_link_width'] && $file_info->image_height <= $config['img_link_height']) ? attachment_category::IMAGE : attachment_category::NONE; - } - catch (\Exception $e) - { - $display_cat = attachment_category::NONE; - } - } + $display_cat = attachment_category::IMAGE; } else { diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index 67affce13d..7a4e70f944 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -1595,7 +1595,7 @@ function phpbb_show_profile($data, $user_notes_enabled = false, $warn_user_enabl if ($data['user_allow_viewonline'] || $auth->acl_get('u_viewonline')) { - $last_active = (!empty($data['session_time'])) ? $data['session_time'] : $data['user_last_active']; + $last_active = $data['user_last_active'] ?: ($data['session_time'] ?? 0); } else { diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index 329a9f65dd..c94a2a5c42 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -1417,22 +1417,22 @@ function log_into_server($hostname, $username, $password, $default_auth_method) global $user; // Here we try to determine the *real* hostname (reverse DNS entry preferrably) - $local_host = $user->host; - - if (function_exists('php_uname')) + if (function_exists('php_uname') && !empty($local_host = php_uname('n'))) { - $local_host = php_uname('n'); - // Able to resolve name to IP if (($addr = @gethostbyname($local_host)) !== $local_host) { // Able to resolve IP back to name - if (($name = @gethostbyaddr($addr)) !== $addr) + if (!empty($name = @gethostbyaddr($addr)) && $name !== $addr) { $local_host = $name; } } } + else + { + $local_host = $user->host; + } // If we are authenticating through pop-before-smtp, we // have to login ones before we get authenticated diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index ae4e77e16a..d1f35107c5 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -480,7 +480,7 @@ static function module_auth($module_auth, $forum_id) */ function set_active($id = false, $mode = false) { - global $request; + global $auth, $request, $user; $icat = false; $this->active_module = false; @@ -502,6 +502,14 @@ function set_active($id = false, $mode = false) $id = $this->p_class . '_' . $id; } + // Fallback to acp main page for special test permission mode + if ($this->p_class === 'acp' && $user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) + { + $id = ''; + $mode = ''; + $icat = false; + } + $category = false; foreach ($this->module_ary as $row_id => $item_ary) { diff --git a/phpBB/includes/functions_privmsgs.php b/phpBB/includes/functions_privmsgs.php index 5e7033d09c..1075a5a2bc 100644 --- a/phpBB/includes/functions_privmsgs.php +++ b/phpBB/includes/functions_privmsgs.php @@ -1691,7 +1691,7 @@ function submit_pm($mode, $subject, &$data_ary, $put_in_outbox = true) } // First of all make sure the subject are having the correct length. - $subject = truncate_string($subject); + $subject = truncate_string($subject, $mode === 'post' ? 120 : 124); $db->sql_transaction('begin'); diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index c41e54098e..737332f4fa 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -1789,7 +1789,7 @@ function avatar_delete($mode, $row, $clean_db = false) return true; } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { // Fail is covered by return statement below } @@ -2131,7 +2131,7 @@ function group_correct_avatar($group_id, $old_entry) WHERE group_id = $group_id"; $db->sql_query($sql); } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { // If rename fail, dont execute the query } diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php index 185316a705..fbde670bd0 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -284,6 +284,7 @@ public function main($id, $mode) $post_data = array( 'S_MCP_QUEUE' => true, 'U_APPROVE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&p=$post_id"), + 'S_CAN_APPROVE' => $auth->acl_get('m_approve', $post_info['forum_id']), 'S_CAN_DELETE_POST' => $auth->acl_get('m_delete', $post_info['forum_id']), 'S_CAN_VIEWIP' => $auth->acl_get('m_info', $post_info['forum_id']), 'S_POST_REPORTED' => $post_info['post_reported'], diff --git a/phpBB/includes/message_parser.php b/phpBB/includes/message_parser.php index 337b6ccab8..9b381d22f8 100644 --- a/phpBB/includes/message_parser.php +++ b/phpBB/includes/message_parser.php @@ -1179,8 +1179,6 @@ function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcod // Set some config values $parser->set_vars(array( 'max_font_size' => $config['max_' . $this->mode . '_font_size'], - 'max_img_height' => $config['max_' . $this->mode . '_img_height'], - 'max_img_width' => $config['max_' . $this->mode . '_img_width'], 'max_smilies' => $config['max_' . $this->mode . '_smilies'], 'max_urls' => $config['max_' . $this->mode . '_urls'] )); diff --git a/phpBB/includes/questionnaire/questionnaire.php b/phpBB/includes/questionnaire/questionnaire.php index 8ab18ad8c8..74675e0e03 100644 --- a/phpBB/includes/questionnaire/questionnaire.php +++ b/phpBB/includes/questionnaire/questionnaire.php @@ -361,8 +361,6 @@ function get_data() 'hot_threshold' => true, 'img_create_thumbnail' => true, 'img_display_inlined' => true, - 'img_link_height' => true, - 'img_link_width' => true, 'img_max_height' => true, 'img_max_thumb_width' => true, 'img_max_width' => true, @@ -406,8 +404,6 @@ function get_data() 'max_reg_attempts' => true, 'max_sig_chars' => true, 'max_sig_font_size' => true, - 'max_sig_img_height' => true, - 'max_sig_img_width' => true, 'max_sig_smilies' => true, 'max_sig_urls' => true, 'min_name_chars' => true, diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index d2fc72621d..8c682cebc0 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -179,7 +179,7 @@ function main($id, $mode) $lang_options = phpbb_language_select($db, $data['lang'], $lang_row); - $template->assign_vars(array( + $template->assign_vars([ 'ERROR' => (count($error)) ? implode('
        ', $error) : '', 'S_NOTIFY_EMAIL' => ($data['notifymethod'] == NOTIFY_EMAIL) ? true : false, @@ -205,15 +205,19 @@ function main($id, $mode) 'name' => 'lang', 'options' => $lang_options, ], - 'S_STYLE_OPTIONS' => ($config['override_user_style']) ? '' : style_select($data['user_style'], false, $styles_row), + 'S_STYLE_OPTIONS' => ($config['override_user_style']) ? '' : [ + 'id' => 'user_style', + 'name' => 'user_style', + 'options' => style_select($data['user_style'], false, $styles_row) + ], 'TIMEZONE_OPTIONS' => [ 'tag' => 'select', 'name' => 'tz', 'options' => $timezone_select, ], - 'S_CAN_HIDE_ONLINE' => ($auth->acl_get('u_hideonline')) ? true : false, - 'S_SELECT_NOTIFY' => ($config['jab_enable'] && $user->data['user_jabber'] && @extension_loaded('xml')) ? true : false) - ); + 'S_CAN_HIDE_ONLINE' => (bool) $auth->acl_get('u_hideonline'), + 'S_SELECT_NOTIFY' => (bool) ($config['jab_enable'] && $user->data['user_jabber'] && @extension_loaded('xml')), + ]); break; diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 40b14a9b54..b8d0d78b40 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -235,8 +235,10 @@ function main($id, $mode) // The CAPTCHA kicks in here. We can't help that the information gets lost on language change. if ($config['enable_confirm']) { - $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']); - $captcha->init(CONFIRM_REG); + /** @var \phpbb\captcha\factory $captcha_factory */ + $captcha_factory = $phpbb_container->get('captcha.factory'); + $captcha = $captcha_factory->get_instance($config['captcha_plugin']); + $captcha->init(\phpbb\captcha\plugins\confirm_type::REGISTRATION); } $timezone = $config['board_timezone']; @@ -291,10 +293,9 @@ function main($id, $mode) if ($config['enable_confirm']) { - $vc_response = $captcha->validate($data); - if ($vc_response !== false) + if ($captcha->validate() !== true) { - $error[] = $vc_response; + $error[] = $captcha->get_error(); } if ($config['max_reg_attempts'] && $captcha->get_attempt_count() > $config['max_reg_attempts']) @@ -426,7 +427,7 @@ function main($id, $mode) } // Okay, captcha, your job is done. - if ($config['enable_confirm'] && isset($captcha)) + if ($config['enable_confirm']) { $captcha->reset(); } diff --git a/phpBB/includes/utf/utf_tools.php b/phpBB/includes/utf/utf_tools.php index b8c35a5048..ed86829b97 100644 --- a/phpBB/includes/utf/utf_tools.php +++ b/phpBB/includes/utf/utf_tools.php @@ -72,6 +72,22 @@ function utf8_strpos($str, $needle, $offset = null) } } +/** +* UTF-8 aware alternative to stripos +* @ignore +*/ +function utf8_stripos($str, $needle, $offset = null) +{ + if (is_null($offset)) + { + return mb_stripos($str, $needle); + } + else + { + return mb_stripos($str, $needle, $offset); + } +} + /** * UTF-8 aware alternative to strtolower * @ignore diff --git a/phpBB/install/convertors/functions_phpbb20.php b/phpBB/install/convertors/functions_phpbb20.php index 75a0b8def4..4063c2d01f 100644 --- a/phpBB/install/convertors/functions_phpbb20.php +++ b/phpBB/install/convertors/functions_phpbb20.php @@ -1676,8 +1676,6 @@ function phpbb_import_attach_config() $config->set('img_display_inlined', $attach_config['img_display_inlined']); $config->set('img_max_width', $attach_config['img_max_width']); $config->set('img_max_height', $attach_config['img_max_height']); - $config->set('img_link_width', $attach_config['img_link_width']); - $config->set('img_link_height', $attach_config['img_link_height']); $config->set('img_create_thumbnail', $attach_config['img_create_thumbnail']); $config->set('img_max_thumb_width', 400); $config->set('img_min_thumb_filesize', $attach_config['img_min_thumb_filesize']); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 68a97decb7..21b26ee9c8 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -77,6 +77,9 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_wave', INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_x_grid', '25'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_y_grid', '25'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_plugin', 'core.captcha.plugins.incomplete'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_turnstile_sitekey', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_turnstile_secret', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_turnstile_theme', 'light'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('check_attachment_content', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('check_dnsbl', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('chg_passforce', '0'); @@ -155,8 +158,6 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('hot_threshold', '2 INSERT INTO phpbb_config (config_name, config_value) VALUES ('icons_path', 'images/icons'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_create_thumbnail', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_display_inlined', '1'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_link_height', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_link_width', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_max_height', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_max_thumb_width', '400'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_max_width', '0'); @@ -227,8 +228,6 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_quote_depth', INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_reg_attempts', '5'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_chars', '255'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_font_size', '200'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_img_height', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_img_width', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_smilies', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_urls', '5'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('mention_batch_size', '50'); @@ -284,6 +283,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('site_desc', '{L_CO INSERT INTO phpbb_config (config_name, config_value) VALUES ('site_home_text', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('site_home_url', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('sitename', '{L_CONFIG_SITENAME}'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('sitename_short', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('smilies_path', 'images/smilies'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('smilies_per_page', '50'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('smtp_allow_self_signed', '0'); @@ -329,6 +329,8 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('storage\backup\con INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_enable', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_public', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_private', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_method_default_enable', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_dropdown_subscribe', '1'); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cache_last_gc', '0', 1); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cron_lock', '0', 1); @@ -710,60 +712,60 @@ INSERT INTO phpbb_posts (topic_id, forum_id, poster_id, icon_id, post_time, post INSERT INTO phpbb_topics_posted (user_id, topic_id, topic_posted) VALUES (2, 1, 1); # -- Smilies -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':D', 'icon_e_biggrin.gif', '{L_SMILIES_VERY_HAPPY}', 15, 17, 1); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-D', 'icon_e_biggrin.gif', '{L_SMILIES_VERY_HAPPY}', 15, 17, 2); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':grin:', 'icon_e_biggrin.gif', '{L_SMILIES_VERY_HAPPY}', 15, 17, 3); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':)', 'icon_e_smile.gif', '{L_SMILIES_SMILE}', 15, 17, 4); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-)', 'icon_e_smile.gif', '{L_SMILIES_SMILE}', 15, 17, 5); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':smile:', 'icon_e_smile.gif', '{L_SMILIES_SMILE}', 15, 17, 6); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (';)', 'icon_e_wink.gif', '{L_SMILIES_WINK}', 15, 17, 7); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (';-)', 'icon_e_wink.gif', '{L_SMILIES_WINK}', 15, 17, 8); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':wink:', 'icon_e_wink.gif', '{L_SMILIES_WINK}', 15, 17, 9); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':(', 'icon_e_sad.gif', '{L_SMILIES_SAD}', 15, 17, 10); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-(', 'icon_e_sad.gif', '{L_SMILIES_SAD}', 15, 17, 11); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':sad:', 'icon_e_sad.gif', '{L_SMILIES_SAD}', 15, 17, 12); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':o', 'icon_e_surprised.gif', '{L_SMILIES_SURPRISED}', 15, 17, 13); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-o', 'icon_e_surprised.gif', '{L_SMILIES_SURPRISED}', 15, 17, 14); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':eek:', 'icon_e_surprised.gif', '{L_SMILIES_SURPRISED}', 15, 17, 15); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':shock:', 'icon_eek.gif', '{L_SMILIES_SHOCKED}', 15, 17, 16); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':?', 'icon_e_confused.gif', '{L_SMILIES_CONFUSED}', 15, 17, 17); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-?', 'icon_e_confused.gif', '{L_SMILIES_CONFUSED}', 15, 17, 18); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':???:', 'icon_e_confused.gif', '{L_SMILIES_CONFUSED}', 15, 17, 19); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES ('8-)', 'icon_cool.gif', '{L_SMILIES_COOL}', 15, 17, 20); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':cool:', 'icon_cool.gif', '{L_SMILIES_COOL}', 15, 17, 21); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':lol:', 'icon_lol.gif', '{L_SMILIES_LAUGHING}', 15, 17, 22); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':x', 'icon_mad.gif', '{L_SMILIES_MAD}', 15, 17, 23); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-x', 'icon_mad.gif', '{L_SMILIES_MAD}', 15, 17, 24); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':mad:', 'icon_mad.gif', '{L_SMILIES_MAD}', 15, 17, 25); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':P', 'icon_razz.gif', '{L_SMILIES_RAZZ}', 15, 17, 26); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-P', 'icon_razz.gif', '{L_SMILIES_RAZZ}', 15, 17, 27); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':razz:', 'icon_razz.gif', '{L_SMILIES_RAZZ}', 15, 17, 28); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':oops:', 'icon_redface.gif', '{L_SMILIES_EMARRASSED}', 15, 17, 29); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':cry:', 'icon_cry.gif', '{L_SMILIES_CRYING}', 15, 17, 30); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':evil:', 'icon_evil.gif', '{L_SMILIES_EVIL}', 15, 17, 31); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':twisted:', 'icon_twisted.gif', '{L_SMILIES_TWISTED_EVIL}', 15, 17, 32); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':roll:', 'icon_rolleyes.gif', '{L_SMILIES_ROLLING_EYES}', 15, 17, 33); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':!:', 'icon_exclaim.gif', '{L_SMILIES_EXCLAMATION}', 15, 17, 34); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':?:', 'icon_question.gif', '{L_SMILIES_QUESTION}', 15, 17, 35); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':idea:', 'icon_idea.gif', '{L_SMILIES_IDEA}', 15, 17, 36); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':arrow:', 'icon_arrow.gif', '{L_SMILIES_ARROW}', 15, 17, 37); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':|', 'icon_neutral.gif', '{L_SMILIES_NEUTRAL}', 15, 17, 38); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-|', 'icon_neutral.gif', '{L_SMILIES_NEUTRAL}', 15, 17, 39); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':mrgreen:', 'icon_mrgreen.gif', '{L_SMILIES_MR_GREEN}', 15, 17, 40); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':geek:', 'icon_e_geek.gif', '{L_SMILIES_GEEK}', 17, 17, 41); -INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':ugeek:', 'icon_e_ugeek.gif', '{L_SMILIES_UBER_GEEK}', 17, 18, 42); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':D', 'icon_e_biggrin.svg', '{L_SMILIES_VERY_HAPPY}', 15, 17, 1); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-D', 'icon_e_biggrin.svg', '{L_SMILIES_VERY_HAPPY}', 15, 17, 2); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':grin:', 'icon_e_biggrin.svg', '{L_SMILIES_VERY_HAPPY}', 15, 17, 3); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':)', 'icon_e_smile.svg', '{L_SMILIES_SMILE}', 15, 17, 4); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-)', 'icon_e_smile.svg', '{L_SMILIES_SMILE}', 15, 17, 5); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':smile:', 'icon_e_smile.svg', '{L_SMILIES_SMILE}', 15, 17, 6); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (';)', 'icon_e_wink.svg', '{L_SMILIES_WINK}', 15, 17, 7); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (';-)', 'icon_e_wink.svg', '{L_SMILIES_WINK}', 15, 17, 8); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':wink:', 'icon_e_wink.svg', '{L_SMILIES_WINK}', 15, 17, 9); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':(', 'icon_e_sad.svg', '{L_SMILIES_SAD}', 15, 17, 10); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-(', 'icon_e_sad.svg', '{L_SMILIES_SAD}', 15, 17, 11); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':sad:', 'icon_e_sad.svg', '{L_SMILIES_SAD}', 15, 17, 12); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':o', 'icon_e_surprised.svg', '{L_SMILIES_SURPRISED}', 15, 17, 13); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-o', 'icon_e_surprised.svg', '{L_SMILIES_SURPRISED}', 15, 17, 14); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':eek:', 'icon_e_surprised.svg', '{L_SMILIES_SURPRISED}', 15, 17, 15); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':shock:', 'icon_eek.svg', '{L_SMILIES_SHOCKED}', 15, 17, 16); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':?', 'icon_e_confused.svg', '{L_SMILIES_CONFUSED}', 15, 17, 17); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-?', 'icon_e_confused.svg', '{L_SMILIES_CONFUSED}', 15, 17, 18); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':???:', 'icon_e_confused.svg', '{L_SMILIES_CONFUSED}', 15, 17, 19); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES ('8-)', 'icon_cool.svg', '{L_SMILIES_COOL}', 15, 17, 20); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':cool:', 'icon_cool.svg', '{L_SMILIES_COOL}', 15, 17, 21); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':lol:', 'icon_lol.svg', '{L_SMILIES_LAUGHING}', 15, 17, 22); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':x', 'icon_mad.svg', '{L_SMILIES_MAD}', 15, 17, 23); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-x', 'icon_mad.svg', '{L_SMILIES_MAD}', 15, 17, 24); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':mad:', 'icon_mad.svg', '{L_SMILIES_MAD}', 15, 17, 25); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':P', 'icon_razz.svg', '{L_SMILIES_RAZZ}', 15, 17, 26); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-P', 'icon_razz.svg', '{L_SMILIES_RAZZ}', 15, 17, 27); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':razz:', 'icon_razz.svg', '{L_SMILIES_RAZZ}', 15, 17, 28); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':oops:', 'icon_redface.svg', '{L_SMILIES_EMARRASSED}', 15, 17, 29); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':cry:', 'icon_cry.svg', '{L_SMILIES_CRYING}', 15, 17, 30); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':evil:', 'icon_evil.svg', '{L_SMILIES_EVIL}', 15, 17, 31); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':twisted:', 'icon_twisted.svg', '{L_SMILIES_TWISTED_EVIL}', 15, 17, 32); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':roll:', 'icon_rolleyes.svg', '{L_SMILIES_ROLLING_EYES}', 15, 17, 33); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':!:', 'icon_exclaim.svg', '{L_SMILIES_EXCLAMATION}', 15, 17, 34); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':?:', 'icon_question.svg', '{L_SMILIES_QUESTION}', 15, 17, 35); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':idea:', 'icon_idea.svg', '{L_SMILIES_IDEA}', 15, 17, 36); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':arrow:', 'icon_arrow.svg', '{L_SMILIES_ARROW}', 15, 17, 37); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':|', 'icon_neutral.svg', '{L_SMILIES_NEUTRAL}', 15, 17, 38); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-|', 'icon_neutral.svg', '{L_SMILIES_NEUTRAL}', 15, 17, 39); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':mrgreen:', 'icon_mrgreen.svg', '{L_SMILIES_MR_GREEN}', 15, 17, 40); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':geek:', 'icon_e_geek.svg', '{L_SMILIES_GEEK}', 17, 17, 41); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':ugeek:', 'icon_e_ugeek.svg', '{L_SMILIES_UBER_GEEK}', 17, 18, 42); # -- icons -INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/fire.gif', 16, 16, 1, 1); -INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/redface.gif', 16, 16, 9, 1); -INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/mrgreen.gif', 16, 16, 10, 1); -INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/heart.gif', 16, 16, 4, 1); -INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/star.gif', 16, 16, 2, 1); -INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/radioactive.gif', 16, 16, 3, 1); -INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/thinking.gif', 16, 16, 5, 1); -INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/info.gif', 16, 16, 8, 1); -INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/question.gif', 16, 16, 6, 1); -INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/alert.gif', 16, 16, 7, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/fire.svg', 16, 16, 1, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/redface.svg', 16, 16, 9, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/mrgreen.svg', 16, 16, 10, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/heart.svg', 16, 16, 4, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/star.svg', 16, 16, 2, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/radioactive.svg', 16, 16, 3, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/thinking.svg', 16, 16, 5, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/info.svg', 16, 16, 8, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/question.svg', 16, 16, 6, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/alert.svg', 16, 16, 7, 1); # -- reasons INSERT INTO phpbb_reports_reasons (reason_title, reason_description, reason_order) VALUES ('warez', '{L_REPORT_WAREZ}', 1); diff --git a/phpBB/install/startup.php b/phpBB/install/startup.php index 71f5744678..b8ceb37342 100644 --- a/phpBB/install/startup.php +++ b/phpBB/install/startup.php @@ -51,7 +51,15 @@ function installer_msg_handler($errno, $msg_text, $errfile, $errline): bool { global $phpbb_installer_container, $msg_long_text; - if (error_reporting() == 0) + // Acording to https://www.php.net/manual/en/language.operators.errorcontrol.php + // error_reporting() return a different error code inside the error handler after php 8.0 + $suppresed = E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE; + if (PHP_VERSION_ID < 80000) + { + $suppresed = 0; + } + + if (error_reporting() == $suppresed) { return true; } diff --git a/phpBB/language/en/acp/attachments.php b/phpBB/language/en/acp/attachments.php index 351f61dbef..630977b5ce 100644 --- a/phpBB/language/en/acp/attachments.php +++ b/phpBB/language/en/acp/attachments.php @@ -111,8 +111,6 @@ 'GO_TO_EXTENSIONS' => 'Go to extension management screen', 'GROUP_NAME' => 'Group name', - 'IMAGE_LINK_SIZE' => 'Image link dimensions', - 'IMAGE_LINK_SIZE_EXPLAIN' => 'Display image attachment as an inline text link if image is larger than this. To disable this behaviour, set the values to 0px by 0px.', 'IMAGE_QUALITY' => 'Quality of uploaded image attachments (JPEG only)', 'IMAGE_QUALITY_EXPLAIN' => 'Specify value between 50% (smaller file size) and 90% (higher quality). Quality higher than 90% increases filesize and is disabled. Setting only applies if maximum image dimensions are set to a value other than 0px by 0px.', 'IMAGE_STRIP_METADATA' => 'Strip image metadata (JPEG only)', diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index c57cefe685..8401f24e0a 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -69,6 +69,8 @@ 'SITE_HOME_URL' => 'Main website URL', 'SITE_HOME_URL_EXPLAIN' => 'If specified, a link to this URL will be prepended to your board’s breadcrumbs and the board logo will link to this URL instead of the forum index. An absolute URL is required, e.g. http://www.phpbb.com.', 'SITE_NAME' => 'Site name', + 'SITE_NAME_SHORT' => 'Short site name', + 'SITE_NAME_SHORT_EXPLAIN' => 'Short name will be used if your site is added to a mobile device’s home screen. It can not exceed 12 characters (Emoji is supported).', 'SYSTEM_TIMEZONE' => 'Guest timezone', 'SYSTEM_TIMEZONE_EXPLAIN' => 'Timezone to use for displaying times to users who are not logged in (guests, bots). Logged in users set their timezone during registration and can change it in their user control panel.', 'WARNINGS_EXPIRE' => 'Warning duration', @@ -206,10 +208,6 @@ 'MAX_SIG_FONT_SIZE' => 'Maximum signature font size', 'MAX_SIG_FONT_SIZE_EXPLAIN' => 'Maximum font size allowed in user signatures. Set to 0 for unlimited size.', - 'MAX_SIG_IMG_HEIGHT' => 'Maximum signature image height', - 'MAX_SIG_IMG_HEIGHT_EXPLAIN' => 'Maximum height of an image file in user signatures. Set to 0 for unlimited height.', - 'MAX_SIG_IMG_WIDTH' => 'Maximum signature image width', - 'MAX_SIG_IMG_WIDTH_EXPLAIN' => 'Maximum width of an image file in user signatures. Set to 0 for unlimited width.', 'MAX_SIG_LENGTH' => 'Maximum signature length', 'MAX_SIG_LENGTH_EXPLAIN' => 'Maximum number of characters in user signatures.', 'MAX_SIG_SMILIES' => 'Maximum smilies per signature', @@ -590,6 +588,10 @@ 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) public key is shared to authenticate push messages from your site.
        Caution: Modifying the VAPID public key will automatically render all Web Push subscriptions invalid.', 'WEBPUSH_VAPID_PRIVATE' => 'Server identification private key', 'WEBPUSH_VAPID_PRIVATE_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) private key is used to generate authenticated push messages dispatched from your site. The VAPID private key must form a valid public-private key pair alongside the VAPID public key.
        Caution: Modifying the VAPID private key will automatically render all Web Push subscriptions invalid.', + 'WEBPUSH_METHOD_DEFAULT_ENABLE' => 'Enable all user-based web push notification options by default', + 'WEBPUSH_METHOD_DEFAULT_ENABLE_EXPLAIN' => 'When this setting is enabled, users who subscribe and allow browser notifications will start receiving them automatically. Users only need to visit the UCP Notification settings to disable any unwanted notifications.

        If this setting is disabled, users will not receive any notifications, even if they have subscribed, until they visit the UCP Notification settings to enable the specific notification options they wish to receive.', + 'WEBPUSH_DROPDOWN_SUBSCRIBE' => 'Show “Subscribe” button in notification dropdown', + 'WEBPUSH_DROPDOWN_SUBSCRIBE_EXPLAIN' => 'Display a “Subscribe” button in the Notification dropdown, allowing users to easily subscribe to push notifications from anywhere in the forum.', ]); // Jabber settings diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index bcbbe5d931..62c08f0226 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -768,6 +768,8 @@ 'LOG_STYLE_EDIT_DETAILS' => 'Edited style
        » %s', 'LOG_STYLE_EXPORT' => 'Exported style
        » %s', + 'LOG_STORAGE_UPDATE' => 'Storage updated
        » %s', + 'LOG_UPDATE_DATABASE' => 'Updated Database from version %1$s to version %2$s', 'LOG_UPDATE_PHPBB' => 'Updated phpBB from version %1$s to version %2$s', diff --git a/phpBB/language/en/acp/extensions.php b/phpBB/language/en/acp/extensions.php index c6eb147b86..5ae3735cf7 100644 --- a/phpBB/language/en/acp/extensions.php +++ b/phpBB/language/en/acp/extensions.php @@ -36,14 +36,15 @@ $lang = array_merge($lang, array( - 'EXTENSION_ALREADY_INSTALLED' => 'The “%s” extension has already been installed.', - 'EXTENSION_ALREADY_INSTALLED_MANUALLY' => 'The “%s” extension has already been installed manually.', - 'EXTENSION_ALREADY_MANAGED' => 'The “%s” extension is already managed.', - 'EXTENSION_CANNOT_MANAGE_FILESYSTEM_ERROR' => 'The “%s” extension cannot be managed because the existing files could not be removed from the filesystem.', - 'EXTENSION_CANNOT_MANAGE_INSTALL_ERROR' => 'The “%s” extension could not be installed. The prior installation of this extension has been restored.', - 'EXTENSION_MANAGED_WITH_CLEAN_ERROR' => 'The “%1$s” extension has been installed but an error occurred and the old files could not be removed. You might want to delete the “%2$s” files manually.', - 'EXTENSION_MANAGED_WITH_ENABLE_ERROR' => 'The “%s” extension has been installed but an error occurred while enabling it.', - 'EXTENSION_NOT_INSTALLED' => 'The “%s” extension is not installed.', + 'EXTENSIONS_ALREADY_INSTALLED' => 'The “%s” extension has already been installed.', + 'EXTENSIONS_ALREADY_INSTALLED_MANUALLY' => 'The “%s” extension has already been installed manually.', + 'EXTENSIONS_ALREADY_MANAGED' => 'The “%s” extension is already managed.', + 'EXTENSIONS_CANNOT_MANAGE_FILESYSTEM_ERROR' => 'The “%s” extension cannot be managed because the existing files could not be removed from the filesystem.', + 'EXTENSIONS_CANNOT_MANAGE_INSTALL_ERROR' => 'The “%s” extension could not be installed. The prior installation of this extension has been restored.', + 'EXTENSIONS_MANAGED_WITH_CLEAN_ERROR' => 'The “%1$s” extension has been installed but an error occurred and the old files could not be removed. You might want to delete the “%2$s” files manually.', + 'EXTENSIONS_MANAGED_WITH_ENABLE_ERROR' => 'The “%s” extension has been installed but an error occurred while enabling it.', + 'EXTENSIONS_NOT_INSTALLED' => 'The “%s” extension is not installed.', + 'EXTENSIONS_NOT_MANAGED' => 'The “%s” extension is not being managed.', 'ENABLING_EXTENSIONS' => 'Enabling extensions', 'DISABLING_EXTENSIONS' => 'Disabling extensions', @@ -62,8 +63,9 @@ 'DETAILS' => 'Details', - 'EXTENSIONS_DISABLED' => 'Disabled Extensions', - 'EXTENSIONS_ENABLED' => 'Enabled Extensions', + 'EXTENSIONS_NOT_INSTALLED' => 'Not installed Extensions', + 'EXTENSIONS_DISABLED' => 'Disabled Extensions', + 'EXTENSIONS_ENABLED' => 'Enabled Extensions', 'EXTENSION_DELETE_DATA' => 'Delete data', 'EXTENSION_DISABLE' => 'Disable', @@ -74,6 +76,8 @@ 'EXTENSION_DELETE_DATA_EXPLAIN' => 'Deleting an extension’s data removes all of its data and settings. The extension files are retained so it can be enabled again.', 'EXTENSION_DISABLE_EXPLAIN' => 'Disabling an extension retains its files, data and settings but removes any functionality added by the extension.', 'EXTENSION_ENABLE_EXPLAIN' => 'Enabling an extension allows you to use it on your board.', + 'EXTENSION_REMOVE_EXPLAIN' => 'Removing an extension removes all of its files, data and settings.', + 'EXTENSION_UPDATE_EXPLAIN' => 'Updating an extension will install the latest version compatible with your board, removing old files and replacing them with new ones, and updating the database if necessary.', 'EXTENSION_DELETE_DATA_IN_PROGRESS' => 'The extension’s data is currently being deleted. Please do not leave or refresh this page until it is completed.', 'EXTENSION_DISABLE_IN_PROGRESS' => 'The extension is currently being disabled. Please do not leave or refresh this page until it is completed.', @@ -86,25 +90,25 @@ 'EXTENSION_NAME' => 'Extension Name', 'EXTENSION_ACTIONS' => 'Actions', 'EXTENSION_OPTIONS' => 'Options', - 'EXTENSION_INSTALL_HEADLINE'=> 'Installing an extension', - 'EXTENSION_INSTALL_EXPLAIN' => '
          -
        1. Download an extension from phpBB’s extensions database
        2. -
        3. Unzip the extension and upload it to the ext/ directory of your phpBB board
        4. -
        5. Enable the extension, here in the Extensions manager
        6. -
        ', - 'EXTENSION_UPDATE_HEADLINE' => 'Updating an extension', - 'EXTENSION_UPDATE_EXPLAIN' => '
          -
        1. Disable the extension
        2. -
        3. Delete the extension’s files from the filesystem
        4. -
        5. Upload the new files
        6. -
        7. Enable the extension
        8. -
        ', - 'EXTENSION_REMOVE_HEADLINE' => 'Completely removing an extension from your board', - 'EXTENSION_REMOVE_EXPLAIN' => '
          -
        1. Disable the extension
        2. -
        3. Delete the extension’s data
        4. -
        5. Delete the extension’s files from the filesystem
        6. -
        ', + 'EXTENSION_INSTALLING_HEADLINE' => 'Installing an extension', + 'EXTENSION_INSTALLING_EXPLAIN' => [ + 0 => 'Download an extension from phpBB’s extensions database', + 1 => 'Unzip the extension and upload it to the ext/ directory of your phpBB board', + 2 => 'Enable the extension, here in the Extensions manager', + ], + 'EXTENSION_REMOVING_HEADLINE' => 'Deleting an extension from your board', + 'EXTENSION_REMOVING_EXPLAIN' => [ + 0 => 'Disable the extension', + 1 => 'Delete the extension’s data', + 2 => 'Delete the extension‘s files from the filesystem', + ], + 'EXTENSION_UPDATING_HEADLINE' => 'Updating an extension', + 'EXTENSION_UPDATING_EXPLAIN' => [ + 0 => 'Disable the extension', + 1 => 'Delete the extension’s files from the filesystem', + 2 => 'Upload the new files', + 3 => 'Enable the extension', + ], 'EXTENSION_DELETE_DATA_CONFIRM' => 'Are you sure that you wish to delete the data associated with “%s”?

        This removes all of its data and settings and cannot be undone!', 'EXTENSION_DISABLE_CONFIRM' => 'Are you sure that you wish to disable the “%s” extension?', diff --git a/phpBB/language/en/acp/posting.php b/phpBB/language/en/acp/posting.php index 3bff6b9185..f252864eb6 100644 --- a/phpBB/language/en/acp/posting.php +++ b/phpBB/language/en/acp/posting.php @@ -88,6 +88,20 @@ 'LOCAL_URL' => 'A local URL. The URL must be relative to the topic page and cannot contain a server name or protocol, as links are prefixed with “%s”', 'RELATIVE_URL' => 'A relative URL. You can use this to match parts of a URL, but be careful: a full URL is a valid relative URL. When you want to use relative URLs of your board, use the LOCAL_URL token.', 'COLOR' => 'A HTML colour, can be either in the numeric form #FF1234 or a CSS colour keyword such as fuchsia or InactiveBorder', + 'ALNUM' => 'Characters from the latin alphabet (A-Z) and numbers.', + 'CHOICE' => 'A choice of specified values, e.g. {CHOICE=spades,hearts,diamonds,clubs}. The values are treated as case-insensitive by default and can be treated case-sensitive by specifying the caseSensitive option: {CHOICE=Spades,Hearts,Diamonds,Clubs;caseSensitive}', + 'FLOAT' => 'A decimal value, e.g. 0.5.', + 'HASHMAP' => 'Maps strings to their replacement in the form {HASHMAP=string1:replacement1,string2:replacement2}. Case-sensitive. Preserves unknown values by default.', + 'INT' => 'An integer value, e.g. 2.', + 'IP' => 'A valid IPv4 or IPv6 address.', + 'IPPORT' => 'A valid IPv4 or IPv6 address with port number.', + 'IPV4' => 'A valid IPv4 address.', + 'IPV6' => 'A valid IPv6 address.', + 'MAP' => 'Maps strings to their replacement in the form {MAP=string1:replacement1,string2:replacement2}. Case-insensitive. Preserves unknown values by default.', + 'RANGE' => 'Accepts an integer in the given range, e.g. {RANGE=-10,42}.', + 'REGEXP' => 'Validates its value against a given regexp, e.g. {REGEXP=/^foo\w+bar$/}.', + 'TIMESTAMP' => 'A timestamp such as 1h30m10s which will be converted to a number of seconds. Also accepts a number.', + 'UINT' => 'An unsigned integer value. Same as {INT}, but rejects values less than 0.', ), )); diff --git a/phpBB/language/en/acp/storage.php b/phpBB/language/en/acp/storage.php index 98ad84df53..9f928ee58e 100644 --- a/phpBB/language/en/acp/storage.php +++ b/phpBB/language/en/acp/storage.php @@ -36,18 +36,26 @@ // equally where a string contains only two placeholders which are used to wrap text // in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine -$lang = array_merge($lang, array( +$lang = array_merge($lang, [ // Template - 'STORAGE_TITLE' => 'Storage Settings', - 'STORAGE_TITLE_EXPLAIN' => 'Change storage providers for the file storage types of phpBB. Choose local or remote providers to store files added to or created by phpBB.', - 'STORAGE_SELECT' => 'Select storage', - 'STORAGE_SELECT_DESC' => 'Select a storage from the list.', - 'STORAGE_NAME' => 'Storage name', - 'STORAGE_NUM_FILES' => 'Number of files', - 'STORAGE_SIZE' => 'Size', - 'STORAGE_FREE' => 'Available space', - 'STORAGE_UNKNOWN' => 'Unknown', + 'STORAGE_TITLE' => 'Storage Settings', + 'STORAGE_TITLE_EXPLAIN' => 'Change storage providers for the file storage types of phpBB. Choose local or remote providers to store files added to or created by phpBB.', + 'STORAGE_SELECT' => 'Select storage', + 'STORAGE_SELECT_DESC' => 'Select a storage from the list.', + 'STORAGE_NAME' => 'Storage name', + 'STORAGE_NUM_FILES' => 'Number of files', + 'STORAGE_SIZE' => 'Size', + 'STORAGE_FREE' => 'Available space', + 'STORAGE_UNKNOWN' => 'Unknown', + 'STORAGE_UPDATE_TYPE' => 'Update type', + 'STORAGE_UPDATE_TYPE_CONFIG' => 'Update configuration only', + 'STORAGE_UPDATE_TYPE_COPY' => 'Update configuration and copy files', + 'STORAGE_UPDATE_TYPE_MOVE' => 'Update configuration and move files', + + // Template progress bar + 'STORAGE_UPDATE_IN_PROGRESS' => 'Storage update in progress', + 'STORAGE_UPDATE_IN_PROGRESS_EXPLAIN' => 'Files are being moved between storages. This can take some minutes.', // Storage names 'STORAGE_ATTACHMENT_TITLE' => 'Attachments storage', @@ -69,4 +77,4 @@ 'STORAGE_PATH_NOT_EXISTS' => '“%1$s” path does not exist or is not writable.', 'STORAGE_PATH_NOT_SET' => '“%1$s” path is not set.', -)); +]); diff --git a/phpBB/language/en/captcha_qa.php b/phpBB/language/en/captcha_qa.php index 637c4e035e..b883166159 100644 --- a/phpBB/language/en/captcha_qa.php +++ b/phpBB/language/en/captcha_qa.php @@ -61,4 +61,5 @@ 'QA_ERROR_MSG' => 'Please fill in all fields and enter at least one answer.', 'QA_LAST_QUESTION' => 'You cannot delete all questions while the plugin is active.', + 'QA_NO_QUESTIONS' => 'There are no questions yet.', )); diff --git a/phpBB/language/en/captcha_turnstile.php b/phpBB/language/en/captcha_turnstile.php new file mode 100644 index 0000000000..687ba4499d --- /dev/null +++ b/phpBB/language/en/captcha_turnstile.php @@ -0,0 +1,53 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'CAPTCHA_TURNSTILE' => 'Turnstile', + 'CAPTCHA_TURNSTILE_INCORRECT' => 'The solution you provided was incorrect', + 'CAPTCHA_TURNSTILE_NOSCRIPT' => 'Please enable JavaScript in your browser to load the challenge.', + 'CAPTCHA_TURNSTILE_NOT_AVAILABLE' => 'In order to use Turnstile you must create a Cloudflare account.', + 'CAPTCHA_TURNSTILE_SECRET' => 'Secret key', + 'CAPTCHA_TURNSTILE_SECRET_EXPLAIN' => 'Your Turnstile secret key. The secret key can be retrieved from your Cloudflare dashboard.', + 'CAPTCHA_TURNSTILE_SITEKEY' => 'Sitekey', + 'CAPTCHA_TURNSTILE_SITEKEY_EXPLAIN' => 'Your Turnstile sitekey. The sitekey can be retrieved from your Cloudflare dashboard.', + 'CAPTCHA_TURNSTILE_THEME' => 'Widget theme', + 'CAPTCHA_TURNSTILE_THEME_EXPLAIN' => 'The theme of the CAPTCHA widget. By default, light will be used. Other possibilities are dark and auto, which respects the user’s preference.', + 'CAPTCHA_TURNSTILE_THEME_AUTO' => 'Auto', + 'CAPTCHA_TURNSTILE_THEME_DARK' => 'Dark', + 'CAPTCHA_TURNSTILE_THEME_LIGHT' => 'Light', +]); diff --git a/phpBB/language/en/cli.php b/phpBB/language/en/cli.php index 7eb71d9e6d..700bf9799d 100644 --- a/phpBB/language/en/cli.php +++ b/phpBB/language/en/cli.php @@ -75,6 +75,7 @@ 'CLI_DESCRIPTION_REPARSER_REPARSE' => 'Reparses stored text with the current text_formatter services.', 'CLI_DESCRIPTION_REPARSER_REPARSE_ARG_1' => 'Type of text to reparse. Leave blank to reparse everything.', 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_DRY_RUN' => 'Do not save any changes; just print what would happen', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_FORCE_BBCODE' => 'Re-parse all BBCodes without exception. Note that any previously disabled BBCodes will be reprocessed, enabled, and fully rendered.', 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_MIN' => 'Lowest record ID to process', 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_MAX' => 'Highest record ID to process', 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_SIZE' => 'Approximate number of records to process at a time', @@ -112,6 +113,8 @@ 'CLI_DESCRIPTION_USER_ADD_OPTION_NOTIFY' => 'Send account activation email to the new user (not sent by default)', 'CLI_DESCRIPTION_USER_DELETE' => 'Delete a user account.', 'CLI_DESCRIPTION_USER_DELETE_USERNAME' => 'Username of the user to delete', + 'CLI_DESCRIPTION_USER_DELETE_ID' => 'Delete user accounts by ID.', + 'CLI_DESCRIPTION_USER_DELETE_ID_OPTION_ID' => 'User IDs of the users to delete', 'CLI_DESCRIPTION_USER_DELETE_OPTION_POSTS' => 'Delete all posts by the user. Without this option, the user’s posts will be retained.', 'CLI_DESCRIPTION_USER_RECLEAN' => 'Re-clean usernames.', @@ -171,10 +174,14 @@ 'CLI_THUMBNAIL_NOTHING_TO_GENERATE' => 'No thumbnails to generate.', 'CLI_THUMBNAIL_NOTHING_TO_DELETE' => 'No thumbnails to delete.', - 'CLI_USER_ADD_SUCCESS' => 'Successfully added user %s.', - 'CLI_USER_DELETE_CONFIRM' => 'Are you sure you want to delete ‘%s’? [y/N]', - 'CLI_USER_RECLEAN_START' => 'Re-cleaning usernames', - 'CLI_USER_RECLEAN_DONE' => [ + 'CLI_USER_ADD_SUCCESS' => 'Successfully added user %s.', + 'CLI_USER_DELETE_CONFIRM' => 'Are you sure you want to delete ‘%s’? [y/N]', + 'CLI_USER_DELETE_ID_CONFIRM' => 'Are you sure you want to delete the user IDs ‘%s’? [y/N]', + 'CLI_USER_DELETE_ID_SUCCESS' => 'Successfully deleted user IDs.', + 'CLI_USER_DELETE_ID_START' => 'Deleting users by ID', + 'CLI_USER_DELETE_NONE' => 'No users were deleted by user ID.', + 'CLI_USER_RECLEAN_START' => 'Re-cleaning usernames', + 'CLI_USER_RECLEAN_DONE' => [ 0 => 'Re-cleaning complete. No usernames needed to be cleaned.', 1 => 'Re-cleaning complete. %d username was cleaned.', 2 => 'Re-cleaning complete. %d usernames were cleaned.', diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 71790e499b..d528ef9b2b 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -512,6 +512,11 @@ ), 'NOTIFY_ADMIN' => 'Please notify the board administrator or webmaster.', 'NOTIFY_ADMIN_EMAIL' => 'Please notify the board administrator or webmaster: %1$s', + 'NOTIFY_WEB_PUSH_DENIED' => 'You have denied notifications from this site. To subscribe, please allow notifications in your browser settings.', + 'NOTIFY_WEB_PUSH_DISABLED' => 'Web Push not supported', + 'NOTIFY_WEB_PUSH_ENABLE' => 'Enable web push notifications', + 'NOTIFY_WEB_PUSH_SUBSCRIBE' => 'Subscribe', + 'NOTIFY_WEB_PUSH_SUBSCRIBED'=> 'Subscribed', 'NO_ACCESS_ATTACHMENT' => 'You are not allowed to access this file.', 'NO_ACTION' => 'No action specified.', 'NO_ADMINISTRATORS' => 'There are no administrators.', diff --git a/phpBB/language/en/composer.json b/phpBB/language/en/composer.json index 7402e5efa5..cd6bcdb581 100644 --- a/phpBB/language/en/composer.json +++ b/phpBB/language/en/composer.json @@ -26,6 +26,7 @@ "direction": "ltr", "user-lang": "en-gb", "plural-rule": 1, - "recaptcha-lang": "en-GB" + "recaptcha-lang": "en-GB", + "turnstile-lang": "en" } } diff --git a/phpBB/language/en/help/faq.php b/phpBB/language/en/help/faq.php index 175f9eb164..666c0ad0b7 100644 --- a/phpBB/language/en/help/faq.php +++ b/phpBB/language/en/help/faq.php @@ -41,6 +41,7 @@ 'HELP_FAQ_BLOCK_POSTING' => 'Posting Issues', 'HELP_FAQ_BLOCK_SEARCH' => 'Searching the Forums', 'HELP_FAQ_BLOCK_USERSETTINGS' => 'User Preferences and settings', + 'HELP_FAQ_BLOCK_WEBPUSH' => 'Web Push Notifications for Browsers', 'HELP_FAQ_BOOKMARKS_DIFFERENCE_ANSWER' => 'In phpBB 3.0, bookmarking topics worked much like bookmarking in a web browser. You were not alerted when there was an update. As of phpBB 3.1, bookmarking is more like subscribing to a topic. You can be notified when a bookmarked topic is updated. Subscribing, however, will notify you when there is an update to a topic or forum on the board. Notification options for bookmarks and subscriptions can be configured in the User Control Panel, under “Board preferences”.', 'HELP_FAQ_BOOKMARKS_DIFFERENCE_QUESTION' => 'What is the difference between bookmarking and subscribing?', @@ -158,7 +159,7 @@ 'HELP_FAQ_SEARCH_BLANK_QUESTION' => 'Why does my search return a blank page!?', 'HELP_FAQ_SEARCH_FORUM_ANSWER' => 'Enter a search term in the search box located on the index, forum or topic pages. Advanced search can be accessed by clicking the “Advance Search” link which is available on all pages on the forum. How to access the search may depend on the style used.', 'HELP_FAQ_SEARCH_FORUM_QUESTION' => 'How can I search a forum or forums?', - 'HELP_FAQ_SEARCH_MEMBERS_ANSWER' => 'Visit to the “Members” page and click the “Find a member” link.', + 'HELP_FAQ_SEARCH_MEMBERS_ANSWER' => 'Visit the memberlist and click the “Find a member” link.', 'HELP_FAQ_SEARCH_MEMBERS_QUESTION' => 'How do I search for members?', 'HELP_FAQ_SEARCH_NO_RESULT_ANSWER' => 'Your search was probably too vague and included many common terms which are not indexed by phpBB. Be more specific and use the options available within Advanced search.', 'HELP_FAQ_SEARCH_NO_RESULT_QUESTION' => 'Why does my search return no results?', @@ -183,4 +184,15 @@ 'HELP_FAQ_USERSETTINGS_SERVERTIME_QUESTION' => 'I changed the timezone and the time is still wrong!', 'HELP_FAQ_USERSETTINGS_TIMEZONE_ANSWER' => 'It is possible the time displayed is from a timezone different from the one you are in. If this is the case, visit your User Control Panel and change your timezone to match your particular area, e.g. London, Paris, New York, Sydney, etc. Please note that changing the timezone, like most settings, can only be done by registered users. If you are not registered, this is a good time to do so.', 'HELP_FAQ_USERSETTINGS_TIMEZONE_QUESTION' => 'The times are not correct!', + + 'HELP_FAQ_WEBPUSH_GENERAL_ANSWER' => 'Make sure this forum is allowed to send notifications in your browser settings. Also, verify that your device’s system settings permit notifications from your web browser or app. Some browsers deliver notifications even when closed, whilst others only do so when the browser is open. View this table for browser support information. Finally, if you’re using an ad blocker, review its settings to make sure it’s not configured to block push notifications.', + 'HELP_FAQ_WEBPUSH_GENERAL_QUESTION' => 'What if I’m still having trouble receiving notifications?', + 'HELP_FAQ_WEBPUSH_HOW_ANSWER' => 'Navigate to “Notification options” in your UCP (User Control Panel) and click “Subscribe.” Your browser may ask for permission to send notifications — be sure to allow it. If you’re still not receiving notifications, check your device’s system settings to ensure notifications are enabled for your browser. For mobile devices such as iPhone or iPad, you may need to add the forum site to your Home Screen for push notifications to work, effectively turning it into a standalone web app. Follow your mobile device’s instructions to enable push notifications for iPhone/iPad or Android.', + 'HELP_FAQ_WEBPUSH_HOW_QUESTION' => 'How can I receive forum notification alerts on my computer or mobile device?', + 'HELP_FAQ_WEBPUSH_SESSION_ANSWER' => 'Yes, you will continue to receive notifications even if you’re logged out.', + 'HELP_FAQ_WEBPUSH_SESSION_QUESTION' => 'Will I receive notifications if I am logged out?', + 'HELP_FAQ_WEBPUSH_SUBBING_ANSWER' => 'If the “Subscribe” button is visible but cannot be clicked, your browser or device likely doesn’t support push notifications. Try using a different browser or device that supports this feature.', + 'HELP_FAQ_WEBPUSH_SUBBING_QUESTION' => 'Why is the “Subscribe” button disabled?', + 'HELP_FAQ_WEBPUSH_WHAT_ANSWER' => 'Web push notifications enhance phpBB’s notification system by allowing real-time notifications to be sent directly to your desktop or mobile device, even if you’re not actively browsing the forum. These notifications function like app alerts, providing instant updates for private messages, post interactions, moderation actions, and more.', + 'HELP_FAQ_WEBPUSH_WHAT_QUESTION' => 'What are web push notifications?', )); diff --git a/phpBB/language/en/viewtopic.php b/phpBB/language/en/viewtopic.php index e5c9be0517..9334a633d4 100644 --- a/phpBB/language/en/viewtopic.php +++ b/phpBB/language/en/viewtopic.php @@ -112,6 +112,7 @@ 'VIEW_INFO' => 'Post details', 'VIEW_NEXT_TOPIC' => 'Next topic', 'VIEW_PREVIOUS_TOPIC' => 'Previous topic', + 'VIEW_QUOTED_POST' => 'View quoted post', 'VIEW_RESULTS' => 'View results', 'VIEW_TOPIC_POSTS' => array( 1 => '%d post', diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index ea7fd03afe..7521cca1df 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -1724,7 +1724,7 @@ { $row['session_time'] = $session_ary[$row['user_id']]['session_time'] ?? 0; $row['session_viewonline'] = $session_ary[$row['user_id']]['session_viewonline'] ?? 0; - $row['last_visit'] = (!empty($row['session_time'])) ? $row['session_time'] : $row['user_last_active']; + $row['last_visit'] = $row['user_last_active'] ?: $row['session_time']; $id_cache[$row['user_id']] = $row; } diff --git a/phpBB/phpbb/.htaccess b/phpBB/phpbb/.htaccess new file mode 100644 index 0000000000..92e78ba1a7 --- /dev/null +++ b/phpBB/phpbb/.htaccess @@ -0,0 +1,25 @@ +# With Apache 2.4 the "Order, Deny" syntax has been deprecated and moved from +# module mod_authz_host to a new module called mod_access_compat (which may be +# disabled) and a new "Require" syntax has been introduced to mod_authz_core. +# We could just conditionally provide both versions, but unfortunately Apache +# does not explicitly tell us its version if the module mod_version is not +# available. In this case, we check for the availability of module +# mod_authz_core (which should be on 2.4 or higher only) as a best guess. + + + Order Allow,Deny + Deny from All + + = 2.4> + Require all denied + + + + + Order Allow,Deny + Deny from All + + + Require all denied + + diff --git a/phpBB/phpbb/attachment/delete.php b/phpBB/phpbb/attachment/delete.php index 80fd6b62d4..2620a7a94f 100644 --- a/phpBB/phpbb/attachment/delete.php +++ b/phpBB/phpbb/attachment/delete.php @@ -464,7 +464,7 @@ public function unlink_attachment($filename, $mode = 'file', $entry_removed = fa return true; } } - catch (\phpbb\storage\exception\exception $exception) + catch (\phpbb\storage\exception\storage_exception $exception) { // Fail is covered by return statement below } diff --git a/phpBB/phpbb/attachment/upload.php b/phpBB/phpbb/attachment/upload.php index d5b961de5f..c0b0c490c4 100644 --- a/phpBB/phpbb/attachment/upload.php +++ b/phpBB/phpbb/attachment/upload.php @@ -351,7 +351,7 @@ protected function check_disk_space() return false; } } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { // Do nothing } diff --git a/phpBB/phpbb/auth/auth.php b/phpBB/phpbb/auth/auth.php index 2ca2d374bd..7777796736 100644 --- a/phpBB/phpbb/auth/auth.php +++ b/phpBB/phpbb/auth/auth.php @@ -772,7 +772,7 @@ function acl_group_raw_data($group_id = false, $opts = false, $forum_id = false) $sql_group = ($group_id !== false) ? ((!is_array($group_id)) ? 'group_id = ' . (int) $group_id : $db->sql_in_set('group_id', array_map('intval', $group_id))) : ''; $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; - $sql_is_local = $forum_id !== false ? 'AND ao.is_local <> 0' : ''; + $sql_is_local = !empty($forum_id) ? 'AND ao.is_local <> 0' : ''; $sql_opts = ''; $hold_ary = $sql_ary = array(); diff --git a/phpBB/phpbb/auth/provider/db.php b/phpBB/phpbb/auth/provider/db.php index ae9d968076..5f8ebd6ac3 100644 --- a/phpBB/phpbb/auth/provider/db.php +++ b/phpBB/phpbb/auth/provider/db.php @@ -176,9 +176,8 @@ public function login($username, $password) // Every auth module is able to define what to do by itself... if ($show_captcha) { - $captcha->init(CONFIRM_LOGIN); - $vc_response = $captcha->validate($row); - if ($vc_response) + $captcha->init(\phpbb\captcha\plugins\confirm_type::LOGIN); + if ($captcha->validate() !== true) { return array( 'status' => LOGIN_ERROR_ATTEMPTS, diff --git a/phpBB/phpbb/avatar/driver/gravatar.php b/phpBB/phpbb/avatar/driver/gravatar.php index ea77497390..d72e6df604 100644 --- a/phpBB/phpbb/avatar/driver/gravatar.php +++ b/phpBB/phpbb/avatar/driver/gravatar.php @@ -21,7 +21,7 @@ class gravatar extends \phpbb\avatar\driver\driver /** * The URL for the gravatar service */ - const GRAVATAR_URL = '//secure.gravatar.com/avatar/'; + const GRAVATAR_URL = '//gravatar.com/avatar/'; /** * {@inheritdoc} @@ -29,7 +29,7 @@ class gravatar extends \phpbb\avatar\driver\driver public function get_data($row) { return array( - 'src' => $row['avatar'], + 'src' => $this->get_gravatar_url($row), 'width' => $row['avatar_width'], 'height' => $row['avatar_height'], ); @@ -53,7 +53,7 @@ public function prepare_form($request, $template, $user, $row, &$error) { $template->assign_vars(array( 'AVATAR_GRAVATAR_WIDTH' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar_width']) ? $row['avatar_width'] : $request->variable('avatar_gravatar_width', ''), - 'AVATAR_GRAVATAR_HEIGHT' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar_height']) ? $row['avatar_height'] : $request->variable('avatar_gravatar_width', ''), + 'AVATAR_GRAVATAR_HEIGHT' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar_height']) ? $row['avatar_height'] : $request->variable('avatar_gravatar_height', ''), 'AVATAR_GRAVATAR_EMAIL' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar']) ? $row['avatar'] : '', )); @@ -175,7 +175,7 @@ protected function get_gravatar_url($row) global $phpbb_dispatcher; $url = self::GRAVATAR_URL; - $url .= md5(strtolower(trim($row['avatar']))); + $url .= hash('sha256', strtolower(trim($row['avatar']))); if ($row['avatar_width'] || $row['avatar_height']) { diff --git a/phpBB/phpbb/avatar/driver/upload.php b/phpBB/phpbb/avatar/driver/upload.php index 416034b674..51425b506f 100644 --- a/phpBB/phpbb/avatar/driver/upload.php +++ b/phpBB/phpbb/avatar/driver/upload.php @@ -15,11 +15,11 @@ use bantu\IniGetWrapper\IniGetWrapper; use phpbb\config\config; -use phpbb\controller\helper; use phpbb\event\dispatcher_interface; use phpbb\files\factory; use phpbb\path_helper; -use phpbb\storage\exception\exception as storage_exception; +use phpbb\routing\helper; +use phpbb\storage\exception\storage_exception; use phpbb\storage\storage; /** @@ -30,7 +30,7 @@ class upload extends \phpbb\avatar\driver\driver /** * @var helper */ - private $controller_helper; + private $routing_helper; /** * @var storage @@ -56,23 +56,23 @@ class upload extends \phpbb\avatar\driver\driver * Construct a driver object * * @param config $config phpBB configuration - * @param helper $controller_helper * @param string $phpbb_root_path Path to the phpBB root * @param string $php_ext PHP file extension * @param storage $storage phpBB avatar storage * @param path_helper $path_helper phpBB path helper + * @param helper $routing_helper phpBB routing helper * @param dispatcher_interface $dispatcher phpBB Event dispatcher object * @param factory $files_factory File classes factory * @param IniGetWrapper $php_ini ini_get() wrapper */ - public function __construct(config $config, helper $controller_helper, string $phpbb_root_path, string $php_ext, storage $storage, path_helper $path_helper, dispatcher_interface $dispatcher, factory $files_factory, IniGetWrapper $php_ini) + public function __construct(config $config, string $phpbb_root_path, string $php_ext, storage $storage, path_helper $path_helper, helper $routing_helper, dispatcher_interface $dispatcher, factory $files_factory, IniGetWrapper $php_ini) { $this->config = $config; - $this->controller_helper = $controller_helper; $this->phpbb_root_path = $phpbb_root_path; $this->php_ext = $php_ext; $this->storage = $storage; $this->path_helper = $path_helper; + $this->routing_helper = $routing_helper; $this->dispatcher = $dispatcher; $this->files_factory = $files_factory; $this->php_ini = $php_ini; @@ -84,7 +84,7 @@ public function __construct(config $config, helper $controller_helper, string $p public function get_data($row) { return array( - 'src' => $this->controller_helper->route('phpbb_storage_avatar', ['file' => $row['avatar']]), + 'src' => $this->routing_helper->route('phpbb_storage_avatar', ['file' => $row['avatar']]), 'width' => $row['avatar_width'], 'height' => $row['avatar_height'], ); diff --git a/phpBB/phpbb/cache/driver/file.php b/phpBB/phpbb/cache/driver/file.php index 0a4f0ae31e..ef62cd3d25 100644 --- a/phpBB/phpbb/cache/driver/file.php +++ b/phpBB/phpbb/cache/driver/file.php @@ -330,6 +330,27 @@ function sql_save(\phpbb\db\driver\driver_interface $db, $query, $query_result, return $query_result; } + /** + * Cleanup when loading invalid data global file + * + * @param string $file Filename + * @param resource $handle + * + * @return void + */ + private function cleanup_invalid_data_global(string $file, $handle): void + { + if (is_resource($handle)) + { + fclose($handle); + } + + $this->vars = $this->var_expires = []; + $this->is_modified = false; + + $this->remove_file($file); + } + /** * {@inheritDoc} */ @@ -368,14 +389,7 @@ protected function _read(string $var) if (!is_numeric($bytes) || ($bytes = (int) $bytes) === 0) { - // We cannot process the file without a valid number of bytes - // so we discard it - fclose($handle); - - $this->vars = $this->var_expires = array(); - $this->is_modified = false; - - $this->remove_file($file); + $this->cleanup_invalid_data_global($file, $handle); return false; } @@ -388,9 +402,17 @@ protected function _read(string $var) } $var_name = substr(fgets($handle), 0, -1); + $data_length = $bytes - strlen($var_name); + + if ($data_length <= 0) + { + $this->cleanup_invalid_data_global($file, $handle); + + return false; + } // Read the length of bytes that consists of data. - $data = fread($handle, $bytes - strlen($var_name)); + $data = fread($handle, $data_length); $data = @unserialize($data); // Don't use the data if it was invalid diff --git a/phpBB/phpbb/cache/driver/redis.php b/phpBB/phpbb/cache/driver/redis.php index c0b47dae46..6f103bf0f0 100644 --- a/phpBB/phpbb/cache/driver/redis.php +++ b/phpBB/phpbb/cache/driver/redis.php @@ -123,15 +123,17 @@ protected function _read(string $var) } /** - * {@inheritDoc} - */ + * Store data in the cache + * + * For the info, see https://phpredis.github.io/phpredis/Redis.html#method_set, + * https://redis.io/docs/latest/commands/set/ + * and https://redis.io/docs/latest/commands/expire/#appendix-redis-expires + * + * {@inheritDoc} + */ protected function _write(string $var, $data, int $ttl = 2592000): bool { - if ($ttl == 0) - { - return $this->redis->set($var, $data); - } - return $this->redis->setex($var, $ttl, $data); + return $this->redis->set($var, $data, ['EXAT' => time() + $ttl]); } /** diff --git a/phpBB/phpbb/captcha/factory.php b/phpBB/phpbb/captcha/factory.php index 2e75ce8667..fc974569bb 100644 --- a/phpBB/phpbb/captcha/factory.php +++ b/phpBB/phpbb/captcha/factory.php @@ -13,6 +13,9 @@ namespace phpbb\captcha; +use phpbb\captcha\plugins\legacy_wrapper; +use phpbb\captcha\plugins\plugin_interface; + class factory { /** @@ -41,11 +44,17 @@ public function __construct(\Symfony\Component\DependencyInjection\ContainerInte * Return a new instance of a given plugin * * @param $name - * @return object|null + * @return plugin_interface */ - public function get_instance($name) + public function get_instance($name): plugin_interface { - return $this->container->get($name); + $captcha = $this->container->get($name); + if ($captcha instanceof plugin_interface) + { + return $captcha; + } + + return new legacy_wrapper($captcha); } /** @@ -56,7 +65,7 @@ public function get_instance($name) function garbage_collect($name) { $captcha = $this->get_instance($name); - $captcha->garbage_collect(0); + $captcha->garbage_collect(); } /** diff --git a/phpBB/phpbb/captcha/plugins/base.php b/phpBB/phpbb/captcha/plugins/base.php new file mode 100644 index 0000000000..a30e679731 --- /dev/null +++ b/phpBB/phpbb/captcha/plugins/base.php @@ -0,0 +1,256 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\captcha\plugins; + +use phpbb\config\config; +use phpbb\db\driver\driver_interface; +use phpbb\language\language; +use phpbb\request\request_interface; +use phpbb\user; + +abstract class base implements plugin_interface +{ + /** @var config */ + protected config $config; + + /** @var driver_interface */ + protected driver_interface $db; + + /** @var language */ + protected language $language; + + /** @var request_interface */ + protected request_interface $request; + + /** @var user */ + protected user $user; + + /** @var int Attempts at solving the CAPTCHA */ + protected int $attempts = 0; + + /** @var string Stored random CAPTCHA code */ + protected string $code = ''; + + /** @var bool Resolved state of captcha */ + protected bool $solved = false; + + /** @var string User supplied confirm code */ + protected string $confirm_code = ''; + + /** @var string Confirm id hash */ + protected string $confirm_id = ''; + + /** @var confirm_type Confirmation type */ + protected confirm_type $type = confirm_type::UNDEFINED; + + /** @var string Last error message */ + protected string $last_error = ''; + + /** + * Constructor for abstract captcha base class + * + * @param config $config + * @param driver_interface $db + * @param language $language + * @param request_interface $request + * @param user $user + */ + public function __construct(config $config, driver_interface $db, language $language, request_interface $request, user $user) + { + $this->config = $config; + $this->db = $db; + $this->language = $language; + $this->request = $request; + $this->user = $user; + } + + /** + * {@inheritDoc} + */ + public function init(confirm_type $type): void + { + $this->confirm_id = $this->request->variable('confirm_id', ''); + $this->confirm_code = $this->request->variable('confirm_code', ''); + $this->type = $type; + + if (empty($this->confirm_id) || !$this->load_confirm_data()) + { + // we have no confirm ID, better get ready to display something + $this->generate_confirm_data(); + } + } + + /** + * {@inheritDoc} + */ + public function validate(): bool + { + if ($this->confirm_id && hash_equals($this->code, $this->confirm_code)) + { + $this->solved = true; + return true; + } + + $this->increment_attempts(); + $this->last_error = $this->language->lang('CONFIRM_CODE_WRONG'); + return false; + } + + /** + * {@inheritDoc} + */ + public function reset(): void + { + $sql = 'DELETE FROM ' . CONFIRM_TABLE . " + WHERE session_id = '" . $this->db->sql_escape($this->user->session_id) . "' + AND confirm_type = " . $this->type->value; + $this->db->sql_query($sql); + + $this->generate_confirm_data(); + } + + /** + * {@inheritDoc} + */ + public function get_attempt_count(): int + { + return $this->attempts; + } + + /** + * Look up attempts from confirm table + */ + protected function load_confirm_data(): bool + { + $sql = 'SELECT code, attempts + FROM ' . CONFIRM_TABLE . " + WHERE confirm_id = '" . $this->db->sql_escape($this->confirm_id) . "' + AND session_id = '" . $this->db->sql_escape($this->user->session_id) . "' + AND confirm_type = " . $this->type->value; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + $this->attempts = $row['attempts']; + $this->code = $row['code']; + + return true; + } + + return false; + } + + /** + * Generate confirm data for tracking attempts + * + * @return void + */ + protected function generate_confirm_data(): void + { + $this->code = gen_rand_string_friendly(CAPTCHA_MAX_CHARS); + $this->confirm_id = md5(unique_id()); + $this->attempts = 0; + + $sql = 'INSERT INTO ' . CONFIRM_TABLE . ' ' . $this->db->sql_build_array('INSERT', array( + 'confirm_id' => $this->confirm_id, + 'session_id' => (string) $this->user->session_id, + 'confirm_type' => $this->type->value, + 'code' => $this->code, + )); + $this->db->sql_query($sql); + } + + /** + * Increment number of attempts for confirm ID and session + * + * @return void + */ + protected function increment_attempts(): void + { + $sql = 'UPDATE ' . CONFIRM_TABLE . " + SET attempts = attempts + 1 + WHERE confirm_id = '{$this->db->sql_escape($this->confirm_id)}' + AND session_id = '{$this->db->sql_escape($this->user->session_id)}'"; + $this->db->sql_query($sql); + + $this->attempts++; + } + + /** + * {@inheritDoc} + */ + public function get_hidden_fields(): array + { + return [ + 'confirm_id' => $this->confirm_id, + 'confirm_code' => $this->solved === true ? $this->confirm_code : '', + ]; + } + + /** + * {@inheritDoc} + */ + public function is_solved(): bool + { + return $this->solved; + } + + /** + * {@inheritDoc} + */ + public function get_error(): string + { + return $this->last_error; + } + + /** + * @inheritDoc + */ + public function garbage_collect(confirm_type $confirm_type = confirm_type::UNDEFINED): void + { + $sql = 'SELECT DISTINCT c.session_id + FROM ' . CONFIRM_TABLE . ' c + LEFT JOIN ' . SESSIONS_TABLE . ' s ON (c.session_id = s.session_id) + WHERE s.session_id IS NULL' . + ((empty($confirm_type)) ? '' : ' AND c.confirm_type = ' . $confirm_type->value); + $result = $this->db->sql_query($sql); + + if ($row = $this->db->sql_fetchrow($result)) + { + $sql_in = []; + do + { + $sql_in[] = (string) $row['session_id']; + } + while ($row = $this->db->sql_fetchrow($result)); + + if (count($sql_in)) + { + $sql = 'DELETE FROM ' . CONFIRM_TABLE . ' + WHERE ' . $this->db->sql_in_set('session_id', $sql_in); + $this->db->sql_query($sql); + } + } + $this->db->sql_freeresult($result); + } + + /** + * {@inheritDoc} + */ + public function acp_page(mixed $id, mixed $module): void + { + } +} diff --git a/phpBB/phpbb/captcha/plugins/captcha_abstract.php b/phpBB/phpbb/captcha/plugins/captcha_abstract.php index 012e28c987..c6978a1bf1 100644 --- a/phpBB/phpbb/captcha/plugins/captcha_abstract.php +++ b/phpBB/phpbb/captcha/plugins/captcha_abstract.php @@ -179,16 +179,6 @@ function garbage_collect($type) $db->sql_freeresult($result); } - function uninstall() - { - $this->garbage_collect(0); - } - - function install() - { - return; - } - function validate() { global $user; diff --git a/phpBB/phpbb/captcha/plugins/confirm_type.php b/phpBB/phpbb/captcha/plugins/confirm_type.php new file mode 100644 index 0000000000..e3e11edf77 --- /dev/null +++ b/phpBB/phpbb/captcha/plugins/confirm_type.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\captcha\plugins; + +/** + * Confirmation types for CAPTCHA plugins + */ +enum confirm_type: int { + case UNDEFINED = 0; + case REGISTRATION = 1; + case LOGIN = 2; + case POST = 3; + case REPORT = 4; +} diff --git a/phpBB/phpbb/captcha/plugins/incomplete.php b/phpBB/phpbb/captcha/plugins/incomplete.php index ec3376c0a5..07998a7125 100644 --- a/phpBB/phpbb/captcha/plugins/incomplete.php +++ b/phpBB/phpbb/captcha/plugins/incomplete.php @@ -14,25 +14,34 @@ namespace phpbb\captcha\plugins; use phpbb\config\config; -use phpbb\exception\runtime_exception; +use phpbb\db\driver\driver_interface; +use phpbb\language\language; +use phpbb\request\request_interface; use phpbb\template\template; +use phpbb\user; -class incomplete extends captcha_abstract +class incomplete extends base { /** * Constructor for incomplete captcha * * @param config $config + * @param driver_interface $db + * @param language $language + * @param request_interface $request * @param template $template + * @param user $user * @param string $phpbb_root_path * @param string $phpEx */ - public function __construct(protected config $config, protected template $template, - protected string $phpbb_root_path, protected string $phpEx) - {} + public function __construct(config $config, driver_interface $db, language $language, request_interface $request, + protected template $template, user $user, protected string $phpbb_root_path, protected string $phpEx) + { + parent::__construct($config, $db, $language, $request, $user); + } /** - * @return bool True if captcha is available, false if not + * {@inheritDoc} */ public function is_available(): bool { @@ -40,70 +49,45 @@ public function is_available(): bool } /** - * Dummy implementation, not supported by this captcha - * - * @throws runtime_exception - * @return void + * {@inheritDoc} */ - public function get_generator_class(): void + public function has_config(): bool { - throw new runtime_exception('NO_GENERATOR_CLASS'); + return false; } /** - * Get CAPTCHA name language variable - * - * @return string Language variable + * {@inheritDoc} */ - public static function get_name(): string + public function get_name(): string { return 'CAPTCHA_INCOMPLETE'; } /** - * Init CAPTCHA - * - * @param int $type CAPTCHA type - * @return void + * {@inheritDoc} */ - public function init($type) + public function set_name(string $name): void { } /** - * Execute demo - * - * @return void + * {@inheritDoc} */ - public function execute_demo() + public function init(confirm_type $type): void { } /** - * Execute CAPTCHA - * - * @return void - */ - public function execute() - { - } - - /** - * Get template data for demo - * - * @param int|string $id ACP module ID - * - * @return string Demo template file name + * {@inheritDoc} */ - public function get_demo_template($id): string + public function get_demo_template(): string { return ''; } /** - * Get template data for CAPTCHA - * - * @return string CAPTCHA template file name + * {@inheritDoc} */ public function get_template(): string { @@ -118,9 +102,7 @@ public function get_template(): string } /** - * Validate CAPTCHA - * - * @return false Incomplete CAPTCHA will never validate + * {@inheritDoc} */ public function validate(): bool { @@ -128,12 +110,26 @@ public function validate(): bool } /** - * Check whether CAPTCHA is solved - * - * @return false Incomplete CAPTCHA will never be solved + * {@inheritDoc} + */ + public function get_error(): string + { + return ''; + } + + /** + * {@inheritDoc} */ public function is_solved(): bool { return false; } + + /** + * {@inheritDoc} + */ + public function get_attempt_count(): int + { + return 0; + } } diff --git a/phpBB/phpbb/captcha/plugins/legacy_wrapper.php b/phpBB/phpbb/captcha/plugins/legacy_wrapper.php new file mode 100644 index 0000000000..aaabddb48b --- /dev/null +++ b/phpBB/phpbb/captcha/plugins/legacy_wrapper.php @@ -0,0 +1,221 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\captcha\plugins; + +class legacy_wrapper implements plugin_interface +{ + /** @var object Legacy CAPTCHA instance, should implement functionality as required in phpBB 3.3 */ + private $legacy_captcha; + + /** @var string Last error */ + private string $last_error; + + /** + * Constructor for legacy CAPTCHA wrapper + * + * @param object $legacy_captcha + */ + public function __construct(object $legacy_captcha) + { + $this->legacy_captcha = $legacy_captcha; + } + + /** + * {@inheritDoc} + */ + public function is_available(): bool + { + if (method_exists($this->legacy_captcha, 'is_available')) + { + return $this->legacy_captcha->is_available(); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function has_config(): bool + { + if (method_exists($this->legacy_captcha, 'has_config')) + { + return $this->legacy_captcha->has_config(); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function get_name(): string + { + if (method_exists($this->legacy_captcha, 'get_name')) + { + return $this->legacy_captcha->get_name(); + } + + return ''; + } + + /** + * {@inheritDoc} + */ + public function set_name(string $name): void + { + if (method_exists($this->legacy_captcha, 'set_name')) + { + $this->legacy_captcha->set_name($name); + } + } + + /** + * {@inheritDoc} + */ + public function init(confirm_type $type): void + { + if (method_exists($this->legacy_captcha, 'init')) + { + $this->legacy_captcha->init($type->value); + } + } + + /** + * {@inheritDoc} + */ + public function get_hidden_fields(): array + { + if (method_exists($this->legacy_captcha, 'get_hidden_fields')) + { + return $this->legacy_captcha->get_hidden_fields(); + } + + return []; + } + + /** + * {@inheritDoc} + */ + public function validate(): bool + { + if (method_exists($this->legacy_captcha, 'validate')) + { + $error = $this->legacy_captcha->validate(); + if ($error) + { + $this->last_error = $error; + return false; + } + + return true; + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function get_error(): string + { + return $this->last_error; + } + + /** + * {@inheritDoc} + */ + public function is_solved(): bool + { + if (method_exists($this->legacy_captcha, 'is_solved')) + { + return $this->legacy_captcha->is_solved(); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function reset(): void + { + if (method_exists($this->legacy_captcha, 'reset')) + { + $this->legacy_captcha->reset(); + } + } + + /** + * {@inheritDoc} + */ + public function get_attempt_count(): int + { + if (method_exists($this->legacy_captcha, 'get_attempt_count')) + { + return $this->legacy_captcha->get_attempt_count(); + } + + // Ensure this is deemed as too many attempts + return PHP_INT_MAX; + } + + /** + * {@inheritDoc} + */ + public function get_template(): string + { + if (method_exists($this->legacy_captcha, 'get_template')) + { + return $this->legacy_captcha->get_template(); + } + + return ''; + } + + /** + * {@inheritDoc} + */ + public function get_demo_template(): string + { + if (method_exists($this->legacy_captcha, 'get_demo_template')) + { + return $this->legacy_captcha->get_demo_template(0); + } + + return ''; + } + + /** + * {@inheritDoc} + */ + public function garbage_collect(confirm_type $confirm_type = confirm_type::UNDEFINED): void + { + if (method_exists($this->legacy_captcha, 'garbage_collect')) + { + $this->legacy_captcha->garbage_collect($confirm_type->value); + } + } + + /** + * {@inheritDoc} + */ + public function acp_page(mixed $id, mixed $module): void + { + if (method_exists($this->legacy_captcha, 'acp_page')) + { + $this->legacy_captcha->acp_page($id, $module); + } + } +} diff --git a/phpBB/phpbb/captcha/plugins/plugin_interface.php b/phpBB/phpbb/captcha/plugins/plugin_interface.php new file mode 100644 index 0000000000..41483e45a5 --- /dev/null +++ b/phpBB/phpbb/captcha/plugins/plugin_interface.php @@ -0,0 +1,126 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\captcha\plugins; + +interface plugin_interface +{ + /** + * Check if the plugin is available + * + * @return bool True if the plugin is available, false if not + */ + public function is_available(): bool; + + /** + * Check if the plugin has a configuration + * + * @return bool True if the plugin has a configuration, false if not + */ + public function has_config(): bool; + + /** + * Get the name of the plugin, should be language variable + * + * @return string + */ + public function get_name(): string; + + /** + * Set the service name of the plugin + * + * @param string $name + */ + public function set_name(string $name): void; + + /** + * Display the captcha for the specified type + * + * @param confirm_type $type Type of captcha, should be one of the CONFIRMATION_* constants + * @return void + */ + public function init(confirm_type $type): void; + + /** + * Get hidden form fields for this captcha plugin + * + * @return array Hidden form fields + */ + public function get_hidden_fields(): array; + + /** + * Validate the captcha with the given request data + * + * @return bool True if request data was valid captcha reply, false if not + */ + public function validate(): bool; + + /** + * Get error string from captcha + * + * @return string Error string, empty string if there is no error + */ + public function get_error(): string; + + /** + * Return whether captcha was solved + * + * @return bool True if captcha was solved, false if not + */ + public function is_solved(): bool; + + /** + * Reset captcha state, e.g. after checking if it's valid + * + * @return void + */ + public function reset(): void; + + /** + * Get attempt count for this captcha and user + * + * @return int Number of attempts + */ + public function get_attempt_count(): int; + + /** + * Get template filename for captcha + * + * @return string Template file name + */ + public function get_template(): string; + + /** + * Get template filename for demo + * + * @return string Demo template file name + */ + public function get_demo_template(): string; + + /** + * Garbage collect captcha plugin + * + * @param confirm_type $confirm_type Confirm type to garbage collect, defaults to all (0) + * @return void + */ + public function garbage_collect(confirm_type $confirm_type = confirm_type::UNDEFINED): void; + + /** + * Display acp page + * + * @param mixed $id ACP module id + * @param mixed $module ACP module name + * @return void + */ + public function acp_page(mixed $id, mixed $module): void; +} diff --git a/phpBB/phpbb/captcha/plugins/qa.php b/phpBB/phpbb/captcha/plugins/qa.php index b9bfac33f1..6e09aaaf13 100644 --- a/phpBB/phpbb/captcha/plugins/qa.php +++ b/phpBB/phpbb/captcha/plugins/qa.php @@ -40,7 +40,7 @@ class qa protected $service_name; /** @var int Question ID */ - protected $question = -1; + private $question = -1; /** * Constructor @@ -323,71 +323,6 @@ function garbage_collect($type = 0) $db->sql_freeresult($result); } - /** - * API function - we don't drop the tables here, as that would cause the loss of all entered questions. - */ - function uninstall() - { - $this->garbage_collect(0); - } - - /** - * API function - set up shop - */ - function install() - { - global $phpbb_container; - - $db_tool = $phpbb_container->get('dbal.tools'); - $schemas = array( - $this->table_captcha_questions => array ( - 'COLUMNS' => array( - 'question_id' => array('UINT', null, 'auto_increment'), - 'strict' => array('BOOL', 0), - 'lang_id' => array('UINT', 0), - 'lang_iso' => array('VCHAR:30', ''), - 'question_text' => array('TEXT_UNI', ''), - ), - 'PRIMARY_KEY' => 'question_id', - 'KEYS' => array( - 'lang' => array('INDEX', 'lang_iso'), - ), - ), - $this->table_captcha_answers => array ( - 'COLUMNS' => array( - 'question_id' => array('UINT', 0), - 'answer_text' => array('STEXT_UNI', ''), - ), - 'KEYS' => array( - 'qid' => array('INDEX', 'question_id'), - ), - ), - $this->table_qa_confirm => array ( - 'COLUMNS' => array( - 'session_id' => array('CHAR:32', ''), - 'confirm_id' => array('CHAR:32', ''), - 'lang_iso' => array('VCHAR:30', ''), - 'question_id' => array('UINT', 0), - 'attempts' => array('UINT', 0), - 'confirm_type' => array('USINT', 0), - ), - 'KEYS' => array( - 'session_id' => array('INDEX', 'session_id'), - 'lookup' => array('INDEX', array('confirm_id', 'session_id', 'lang_iso')), - ), - 'PRIMARY_KEY' => 'confirm_id', - ), - ); - - foreach ($schemas as $table => $schema) - { - if (!$db_tool->sql_table_exists($table)) - { - $db_tool->sql_create_table($table, $schema); - } - } - } - /** * API function - see what has to be done to validate */ @@ -647,11 +582,6 @@ function acp_page($id, $module) $user->add_lang('acp/board'); $user->add_lang('captcha_qa'); - if (!self::is_installed()) - { - $this->install(); - } - $module->tpl_name = 'captcha_qa_acp'; $module->page_title = 'ACP_VC_SETTINGS'; $form_key = 'acp_captcha'; diff --git a/phpBB/phpbb/captcha/plugins/recaptcha.php b/phpBB/phpbb/captcha/plugins/recaptcha.php index ef30baaa9b..1c080ba67b 100644 --- a/phpBB/phpbb/captcha/plugins/recaptcha.php +++ b/phpBB/phpbb/captcha/plugins/recaptcha.php @@ -179,16 +179,6 @@ function get_hidden_fields() return $hidden_fields; } - function uninstall() - { - $this->garbage_collect(0); - } - - function install() - { - return; - } - function validate() { if (!parent::validate()) diff --git a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php index 67035ab1c6..e8399a5aef 100644 --- a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php +++ b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php @@ -361,6 +361,7 @@ protected function recaptcha_verify_token() if ($result->isSuccess()) { $this->solved = true; + $this->confirm_code = $this->code; return false; } diff --git a/phpBB/phpbb/captcha/plugins/turnstile.php b/phpBB/phpbb/captcha/plugins/turnstile.php new file mode 100644 index 0000000000..9a235edc3b --- /dev/null +++ b/phpBB/phpbb/captcha/plugins/turnstile.php @@ -0,0 +1,287 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\captcha\plugins; + +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use phpbb\config\config; +use phpbb\db\driver\driver_interface; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\request\request_interface; +use phpbb\template\template; +use phpbb\user; + +class turnstile extends base +{ + /** @var string URL to cloudflare turnstile API javascript */ + private const SCRIPT_URL = 'https://challenges.cloudflare.com/turnstile/v0/api.js'; + + /** @var string API endpoint for turnstile verification */ + private const VERIFY_ENDPOINT = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; + + /** @var Client */ + protected Client $client; + + /** @var language */ + protected language $language; + + /** @var log_interface */ + protected log_interface $log; + + /** @var template */ + protected template $template; + + /** @var string Service name */ + protected string $service_name = ''; + + /** @var array|string[] Supported themes for Turnstile CAPTCHA */ + protected static array $supported_themes = [ + 'light', + 'dark', + 'auto' + ]; + + /** + * Constructor for turnstile captcha plugin + * + * @param config $config + * @param driver_interface $db + * @param language $language + * @param log_interface $log + * @param request_interface $request + * @param template $template + * @param user $user + */ + public function __construct(config $config, driver_interface $db, language $language, log_interface $log, request_interface $request, template $template, user $user) + { + parent::__construct($config, $db, $language, $request, $user); + + $this->language = $language; + $this->log = $log; + $this->template = $template; + } + + /** + * {@inheritDoc} + */ + public function is_available(): bool + { + $this->init($this->type); + + return !empty($this->config->offsetGet('captcha_turnstile_sitekey')) + && !empty($this->config->offsetGet('captcha_turnstile_secret')); + } + + /** + * {@inheritDoc} + */ + public function has_config(): bool + { + return true; + } + + /** + * {@inheritDoc} + */ + public function get_name(): string + { + return 'CAPTCHA_TURNSTILE'; + } + + /** + * {@inheritDoc} + */ + public function set_name(string $name): void + { + $this->service_name = $name; + } + + /** + * {@inheritDoc} + */ + public function init(confirm_type $type): void + { + parent::init($type); + + $this->language->add_lang('captcha_turnstile'); + } + + /** + * {@inheritDoc} + */ + public function validate(): bool + { + if (parent::validate()) + { + return true; + } + + $turnstile_response = $this->request->variable('cf-turnstile-response', ''); + if (!$turnstile_response) + { + // Return without checking against server without a turnstile response + return false; + } + + // Retrieve form data for verification + $form_data = [ + 'secret' => $this->config['captcha_turnstile_secret'], + 'response' => $turnstile_response, + 'remoteip' => $this->user->ip, + ]; + + // Create guzzle client + $client = $this->get_client(); + + // Check captcha with turnstile API + try + { + $response = $client->request('POST', self::VERIFY_ENDPOINT, [ + 'form_params' => $form_data, + ]); + } + catch (GuzzleException) + { + // Something went wrong during the request to Cloudflare, assume captcha was bad + $this->solved = false; + return false; + } + + // Decode the JSON response + $result = json_decode($response->getBody(), true); + + // Check if the response indicates success + if (isset($result['success']) && $result['success'] === true) + { + $this->solved = true; + $this->confirm_code = $this->code; + return true; + } + else + { + $this->last_error = $this->language->lang('CAPTCHA_TURNSTILE_INCORRECT'); + return false; + } + } + + /** + * Get Guzzle client + * + * @return Client + */ + protected function get_client(): Client + { + if (!isset($this->client)) + { + $this->client = new Client(); + } + + return $this->client; + } + + /** + * {@inheritDoc} + */ + public function get_template(): string + { + if ($this->is_solved()) + { + return ''; + } + + $this->template->assign_vars([ + 'S_TURNSTILE_AVAILABLE' => $this->is_available(), + 'TURNSTILE_SITEKEY' => $this->config->offsetGet('captcha_turnstile_sitekey'), + 'TURNSTILE_THEME' => $this->config->offsetGet('captcha_turnstile_theme'), + 'U_TURNSTILE_SCRIPT' => self::SCRIPT_URL, + 'CONFIRM_TYPE_REGISTRATION' => $this->type->value, + ]); + + return 'captcha_turnstile.html'; + } + + /** + * {@inheritDoc} + */ + public function get_demo_template(): string + { + $this->template->assign_vars([ + 'TURNSTILE_THEME' => $this->config->offsetGet('captcha_turnstile_theme'), + 'U_TURNSTILE_SCRIPT' => self::SCRIPT_URL, + ]); + + return 'captcha_turnstile_acp_demo.html'; + } + + /** + * {@inheritDoc} + */ + public function acp_page(mixed $id, mixed $module): void + { + $captcha_vars = [ + 'captcha_turnstile_sitekey' => 'CAPTCHA_TURNSTILE_SITEKEY', + 'captcha_turnstile_secret' => 'CAPTCHA_TURNSTILE_SECRET', + ]; + + $module->tpl_name = 'captcha_turnstile_acp'; + $module->page_title = 'ACP_VC_SETTINGS'; + $form_key = 'acp_captcha'; + add_form_key($form_key); + + $submit = $this->request->is_set_post('submit'); + + if ($submit && check_form_key($form_key)) + { + $captcha_vars = array_keys($captcha_vars); + foreach ($captcha_vars as $captcha_var) + { + $value = $this->request->variable($captcha_var, ''); + if ($value) + { + $this->config->set($captcha_var, $value); + } + } + + $captcha_theme = $this->request->variable('captcha_turnstile_theme', self::$supported_themes[0]); + if (in_array($captcha_theme, self::$supported_themes)) + { + $this->config->set('captcha_turnstile_theme', $captcha_theme); + } + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_CONFIG_VISUAL'); + trigger_error($this->language->lang('CONFIG_UPDATED') . adm_back_link($module->u_action)); + } + else if ($submit) + { + trigger_error($this->language->lang('FORM_INVALID') . adm_back_link($module->u_action)); + } + else + { + foreach ($captcha_vars as $captcha_var => $template_var) + { + $var = $this->request->is_set($captcha_var) ? $this->request->variable($captcha_var, '') : $this->config->offsetGet($captcha_var); + $this->template->assign_var($template_var, $var); + } + + $this->template->assign_vars(array( + 'CAPTCHA_PREVIEW' => $this->get_demo_template(), + 'CAPTCHA_NAME' => $this->service_name, + 'CAPTCHA_TURNSTILE_THEME' => $this->config->offsetGet('captcha_turnstile_theme'), + 'CAPTCHA_TURNSTILE_THEMES' => self::$supported_themes, + 'U_ACTION' => $module->u_action, + )); + } + } +} diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index 28aaa4171b..cd7456baf6 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -22,10 +22,13 @@ use Composer\Json\JsonFile; use Composer\Json\JsonValidationException; use Composer\Package\BasePackage; +use Composer\Package\CompleteAliasPackage; use Composer\Package\CompletePackage; +use Composer\Package\PackageInterface; use Composer\PartialComposer; use Composer\Repository\ComposerRepository; use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\VersionParser; use Composer\Util\HttpDownloader; use phpbb\composer\io\null_io; use phpbb\config\config; @@ -352,7 +355,7 @@ protected function do_get_available_packages($type) $downloader = new HttpDownloader($io, $composer_config); $json = $downloader->get($url)->getBody(); - /** @var \Composer\Package\PackageInterface $package */ + /** @var PackageInterface $package */ foreach (JsonFile::parseJson($json, $url)['packageNames'] as $package) { $versions = $repository->findPackages($package); @@ -364,7 +367,7 @@ protected function do_get_available_packages($type) { // Pre-filter repo packages by their type $packages = []; - /** @var \Composer\Package\PackageInterface $package */ + /** @var PackageInterface $package */ foreach ($repository->getPackages() as $package) { if ($package->getType() === $type) @@ -390,15 +393,25 @@ protected function do_get_available_packages($type) foreach ($compatible_packages as $name => $versions) { // Determine the highest version of the package - /** @var CompletePackage $highest_version */ + /** @var CompletePackage|CompleteAliasPackage $highest_version */ $highest_version = null; - /** @var CompletePackage $version */ - foreach ($versions as $version) + // Sort the versions array in descending order + usort($versions, function ($a, $b) { - if (!$highest_version || version_compare($version->getVersion(), $highest_version->getVersion(), '>')) + return version_compare($b->getVersion(), $a->getVersion()); + }); + + // The first element in the sorted array is the highest version + if (!empty($versions)) + { + $highest_version = $versions[0]; + + // If highest version is a non-numeric dev branch, it's an instance of CompleteAliasPackage, + // so we need to get the package being aliased in order to show the true non-numeric version. + if ($highest_version instanceof CompleteAliasPackage) { - $highest_version = $version; + $highest_version = $highest_version->getAliasOf(); } } @@ -409,7 +422,7 @@ protected function do_get_available_packages($type) $available[$name]['composer_name'] = $highest_version->getName(); $available[$name]['version'] = $highest_version->getPrettyVersion(); - if ($version instanceof CompletePackage) + if ($highest_version instanceof CompletePackage) { $available[$name]['description'] = $highest_version->getDescription(); $available[$name]['url'] = $highest_version->getHomepage(); @@ -453,39 +466,65 @@ public function check_requirements() /** * Updates $compatible_packages with the versions of $versions compatibles with the $core_constraint * - * @param array $compatible_packages List of compatibles versions - * @param ConstraintInterface $core_constraint Constraint against the phpBB version + * @param array $compatible_packages List of compatibles versions + * @param ConstraintInterface $core_constraint Constraint against the phpBB version * @param string $core_stability Core stability - * @param string $package_name Considered package - * @param array $versions List of available versions + * @param string $package_name Considered package + * @param array $versions List of available versions * * @return array */ private function get_compatible_versions(array $compatible_packages, ConstraintInterface $core_constraint, $core_stability, $package_name, array $versions) { + $version_parser = new VersionParser(); + $core_stability_value = BasePackage::$stabilities[$core_stability]; - /** @var \Composer\Package\PackageInterface $version */ + /** @var PackageInterface $version */ foreach ($versions as $version) { try { + // Check stability first to avoid unnecessary operations if (BasePackage::$stabilities[$version->getStability()] > $core_stability_value) { continue; } - if (array_key_exists('phpbb/phpbb', $version->getRequires())) + $requires = $version->getRequires(); + $extra = $version->getExtra(); + + // Check for compatibility with phpBB if 'phpbb/phpbb' exists in 'requires' + if (isset($requires['phpbb/phpbb'])) { - /** @var ConstraintInterface $package_constraint */ - $package_constraint = $version->getRequires()['phpbb/phpbb']->getConstraint(); + $package_constraint = $requires['phpbb/phpbb']->getConstraint(); + if (!$package_constraint->matches($core_constraint)) + { + continue; + } + } + // Check for compatibility with phpBB if 'phpbb/phpbb' exists in 'soft-require' + if (isset($extra['soft-require']['phpbb/phpbb'])) + { + $package_constraint = $version_parser->parseConstraints($extra['soft-require']['phpbb/phpbb']); if (!$package_constraint->matches($core_constraint)) { continue; } } + // Check for compatibility with php if 'php' exists in 'requires' + if (isset($requires['php'])) + { + $php_constraint = $version_parser->parseConstraints(PHP_VERSION); + $package_constraint = $requires['php']->getConstraint(); + if (!$package_constraint->matches($php_constraint)) + { + continue; + } + } + $compatible_packages[$package_name][] = $version; } catch (\Exception $e) diff --git a/phpBB/phpbb/console/command/fixup/update_hashes.php b/phpBB/phpbb/console/command/fixup/update_hashes.php index 05bef48c92..b1ffa2159b 100644 --- a/phpBB/phpbb/console/command/fixup/update_hashes.php +++ b/phpBB/phpbb/console/command/fixup/update_hashes.php @@ -101,6 +101,14 @@ protected function execute(InputInterface $input, OutputInterface $output) while ($row = $this->db->sql_fetchrow($result)) { $old_hash = preg_replace('/^\$CP\$/', '', $row['user_password']); + + // If stored hash type is unknown then it's md5 hash with no prefix + // First rehash it using $H$ as hash type identifier (salted_md5) + if (!$this->passwords_manager->detect_algorithm($old_hash)) + { + $old_hash = $this->passwords_manager->hash($old_hash, '$H$'); + } + $new_hash = $this->passwords_manager->hash($old_hash, [$this->default_type]); $sql = 'UPDATE ' . USERS_TABLE . " diff --git a/phpBB/phpbb/console/command/reparser/reparse.php b/phpBB/phpbb/console/command/reparser/reparse.php index 1e66a22a73..6a197bf2d9 100644 --- a/phpBB/phpbb/console/command/reparser/reparse.php +++ b/phpBB/phpbb/console/command/reparser/reparse.php @@ -93,6 +93,12 @@ protected function configure() InputOption::VALUE_NONE, $this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_DRY_RUN') ) + ->addOption( + 'force-bbcode-reparsing', + null, + InputOption::VALUE_NONE, + $this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_FORCE_BBCODE') + ) ->addOption( 'resume', null, @@ -223,13 +229,15 @@ protected function reparse($name) // Start from $max and decrement $current by $size until we reach $min $current = $max; + + $force_bbcode_reparsing = (bool) $this->get_option('force-bbcode-reparsing'); while ($current >= $min) { $start = max($min, $current + 1 - $size); $end = max($min, $current); $progress->setMessage($this->user->lang('CLI_REPARSER_REPARSE_REPARSING', $reparser->get_name(), $start, $end)); - $reparser->reparse_range($start, $end); + $reparser->reparse_range($start, $end, $force_bbcode_reparsing); $current = $start - 1; $progress->setProgress($max + 1 - $start); diff --git a/phpBB/phpbb/console/command/user/add.php b/phpBB/phpbb/console/command/user/add.php index f0ad089508..c01aac7cb6 100644 --- a/phpBB/phpbb/console/command/user/add.php +++ b/phpBB/phpbb/console/command/user/add.php @@ -343,7 +343,7 @@ protected function get_activation_key(int $user_id): string $sql_ary = [ 'user_actkey' => $user_actkey, - 'user_actkey_expiration' => \phpbb\user::get_token_expiration(), + 'user_actkey_expiration' => user::get_token_expiration(), ]; $sql = 'UPDATE ' . USERS_TABLE . ' diff --git a/phpBB/phpbb/console/command/user/delete_id.php b/phpBB/phpbb/console/command/user/delete_id.php new file mode 100644 index 0000000000..07d98e7616 --- /dev/null +++ b/phpBB/phpbb/console/command/user/delete_id.php @@ -0,0 +1,232 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\console\command\user; + +use phpbb\console\command\command; +use phpbb\db\driver\driver_interface; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\user; +use phpbb\user_loader; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; + +class delete_id extends command +{ + /** @var driver_interface */ + protected $db; + + /** @var language */ + protected $language; + + /** @var log_interface */ + protected $log; + + /** @var user_loader */ + protected $user_loader; + + /** @var string Bots table */ + protected $bots_table; + + /** @var string User group table */ + protected $user_group_table; + + /** @var string Users table */ + protected $users_table; + + /** @var string phpBB root path */ + protected $phpbb_root_path; + + /** @var string PHP extension */ + protected $php_ext; + + /** + * Construct method + * + * @param driver_interface $db + * @param language $language + * @param log_interface $log + * @param user $user + * @param user_loader $user_loader + * @param string $bots_table + * @param string $user_group_table + * @param string $users_table + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(driver_interface $db, language $language, log_interface $log, user $user, user_loader $user_loader, + string $bots_table, string $user_group_table, string $users_table, string $phpbb_root_path, string $php_ext) + { + $this->db = $db; + $this->language = $language; + $this->log = $log; + $this->user_loader = $user_loader; + $this->bots_table = $bots_table; + $this->user_group_table = $user_group_table; + $this->users_table = $users_table; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->language->add_lang('acp/users'); + parent::__construct($user); + } + + /** + * Sets the command name and description + * + * @return void + */ + protected function configure(): void + { + $this + ->setName('user:delete_id') + ->setDescription($this->language->lang('CLI_DESCRIPTION_USER_DELETE_ID')) + ->addArgument( + 'user_ids', + InputArgument::REQUIRED | InputArgument::IS_ARRAY, + $this->language->lang('CLI_DESCRIPTION_USER_DELETE_ID_OPTION_ID') + ) + ->addOption( + 'delete-posts', + null, + InputOption::VALUE_NONE, + $this->language->lang('CLI_DESCRIPTION_USER_DELETE_OPTION_POSTS') + ) + ; + } + + /** + * Executes the command user:delete_ids + * + * Deletes a list of user ids from the database. An option to delete the users' posts + * is available, by default posts will be retained. + * + * @param InputInterface $input The input stream used to get the options + * @param OutputInterface $output The output stream, used to print messages + * + * @return int 0 if all is well, 1 if any errors occurred + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $user_ids = $input->getArgument('user_ids'); + $mode = ($input->getOption('delete-posts')) ? 'remove' : 'retain'; + $deleted_users = 0; + $io = new SymfonyStyle($input, $output); + + if (count($user_ids) > 0) + { + $this->user_loader->load_users($user_ids); + + $progress = $this->create_progress_bar(count($user_ids), $io, $output); + $progress->setMessage($this->language->lang('CLI_USER_DELETE_ID_START')); + $progress->start(); + + foreach ($user_ids as $user_id) + { + $user_row = $this->user_loader->get_user($user_id); + + // Skip anonymous user + if ($user_row['user_id'] == ANONYMOUS) + { + $progress->advance(); + continue; + } + else if ($user_row['user_type'] == USER_IGNORE) + { + $this->delete_bot_user($user_row); + } + else + { + if (!function_exists('user_delete')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + user_delete($mode, $user_row['user_id'], $user_row['username']); + + $this->log->add('admin', ANONYMOUS, '', 'LOG_USER_DELETED', false, array($user_row['username'])); + } + + $progress->advance(); + $deleted_users++; + } + + $progress->finish(); + + if ($deleted_users > 0) + { + $io->success($this->language->lang('CLI_USER_DELETE_ID_SUCCESS')); + } + } + + if (!$deleted_users) + { + $io->note($this->language->lang('CLI_USER_DELETE_NONE')); + } + + return 0; + } + + /** + * Interacts with the user. + * Confirm they really want to delete the account...last chance! + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + */ + protected function interact(InputInterface $input, OutputInterface $output): void + { + $helper = $this->getHelper('question'); + if (!$helper instanceof QuestionHelper) + { + return; + } + + $user_ids = $input->getArgument('user_ids'); + if (count($user_ids) > 0) + { + $question = new ConfirmationQuestion( + $this->language->lang('CLI_USER_DELETE_ID_CONFIRM', implode(',', $user_ids)), + false + ); + + if (!$helper->ask($input, $output, $question)) + { + $input->setArgument('user_ids', []); + } + } + } + + /** + * Deletes a bot user + * + * @param array $user_row + * @return void + */ + protected function delete_bot_user(array $user_row): void + { + $delete_tables = [$this->bots_table, $this->user_group_table, $this->users_table]; + foreach ($delete_tables as $table) + { + $sql = "DELETE FROM $table + WHERE user_id = " . (int) $user_row['user_id']; + $this->db->sql_query($sql); + } + } +} diff --git a/phpBB/phpbb/cron/task/core/update_hashes.php b/phpBB/phpbb/cron/task/core/update_hashes.php index 61ed0d0501..78221bb35f 100644 --- a/phpBB/phpbb/cron/task/core/update_hashes.php +++ b/phpBB/phpbb/cron/task/core/update_hashes.php @@ -107,6 +107,14 @@ public function run() while ($row = $this->db->sql_fetchrow($result)) { $old_hash = preg_replace('/^\$CP\$/', '', $row['user_password']); + + // If stored hash type is unknown then it's md5 hash with no prefix + // First rehash it using $H$ as hash type identifier (salted_md5) + if (!$this->passwords_manager->detect_algorithm($old_hash)) + { + $old_hash = $this->passwords_manager->hash($old_hash, '$H$'); + } + $new_hash = $this->passwords_manager->hash($old_hash, [$this->default_type]); // Increase number so we know that users were selected from the database diff --git a/phpBB/phpbb/db/driver/mysqli.php b/phpBB/phpbb/db/driver/mysqli.php index 485bb4dd74..ad5fa09343 100644 --- a/phpBB/phpbb/db/driver/mysqli.php +++ b/phpBB/phpbb/db/driver/mysqli.php @@ -154,7 +154,9 @@ protected function _sql_transaction(string $status = 'begin'): bool switch ($status) { case 'begin': - return @mysqli_autocommit($this->db_connect_id, false); + @mysqli_autocommit($this->db_connect_id, false); + $result = @mysqli_begin_transaction($this->db_connect_id); + return $result; case 'commit': $result = @mysqli_commit($this->db_connect_id); diff --git a/phpBB/phpbb/db/migration/data/v33x/v3313.php b/phpBB/phpbb/db/migration/data/v33x/v3313.php new file mode 100644 index 0000000000..a133213c81 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/v3313.php @@ -0,0 +1,36 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +class v3313 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.3.13', '>='); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\v3313rc1', + ]; + } + + public function update_data() + { + return [ + ['config.update', ['version', '3.3.13']], + ]; + } +} diff --git a/phpBB/phpbb/db/migration/data/v33x/v3313rc1.php b/phpBB/phpbb/db/migration/data/v33x/v3313rc1.php new file mode 100644 index 0000000000..4481a53821 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/v3313rc1.php @@ -0,0 +1,36 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +class v3313rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.3.13-RC1', '>='); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\v3312', + ]; + } + + public function update_data() + { + return [ + ['config.update', ['version', '3.3.13-RC1']], + ]; + } +} diff --git a/phpBB/phpbb/db/migration/data/v33x/v3314.php b/phpBB/phpbb/db/migration/data/v33x/v3314.php new file mode 100644 index 0000000000..ce333dbf5a --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/v3314.php @@ -0,0 +1,36 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +class v3314 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.3.14', '>='); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\v3314rc1', + ]; + } + + public function update_data() + { + return [ + ['config.update', ['version', '3.3.14']], + ]; + } +} diff --git a/phpBB/phpbb/db/migration/data/v33x/v3314rc1.php b/phpBB/phpbb/db/migration/data/v33x/v3314rc1.php new file mode 100644 index 0000000000..1632a65dda --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/v3314rc1.php @@ -0,0 +1,36 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +class v3314rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.3.14-RC1', '>='); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\v3313', + ]; + } + + public function update_data() + { + return [ + ['config.update', ['version', '3.3.14-RC1']], + ]; + } +} diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php b/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php new file mode 100644 index 0000000000..751ab9e5f4 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php @@ -0,0 +1,47 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class add_webpush_options extends migration +{ + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\add_webpush', + ]; + } + + public function effectively_installed(): bool + { + return $this->config->offsetExists('webpush_method_default_enable') || $this->config->offsetExists('webpush_dropdown_subscribe'); + } + + public function update_data(): array + { + return [ + ['config.add', ['webpush_method_default_enable', true]], + ['config.add', ['webpush_dropdown_subscribe', true]], + ]; + } + + public function revert_data(): array + { + return [ + ['config.remove', ['webpush_method_default_enable']], + ['config.remove', ['webpush_dropdown_subscribe']], + ]; + } +} diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush_token.php b/phpBB/phpbb/db/migration/data/v400/add_webpush_token.php new file mode 100644 index 0000000000..f87dadf9e7 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush_token.php @@ -0,0 +1,53 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class add_webpush_token extends migration +{ + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\add_webpush', + ]; + } + + public function effectively_installed(): bool + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'notification_push', 'push_token'); + } + + public function update_schema(): array + { + return [ + 'add_columns' => [ + $this->table_prefix . 'notification_push' => [ + 'push_token' => ['VCHAR', ''], + ], + ], + ]; + } + + public function revert_schema(): array + { + return [ + 'drop_columns' => [ + $this->table_prefix . 'notification_push' => [ + 'push_token', + ], + ], + ]; + } +} diff --git a/phpBB/phpbb/db/migration/data/v400/hidpi_icons.php b/phpBB/phpbb/db/migration/data/v400/hidpi_icons.php new file mode 100644 index 0000000000..6e91b15ca5 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/hidpi_icons.php @@ -0,0 +1,75 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class hidpi_icons extends migration +{ + private array $default_icons = [ + 'misc/fire', + 'misc/heart', + 'misc/radioactive', + 'misc/star', + 'misc/thinking', + 'smile/alert', + 'smile/info', + 'smile/mrgreen', + 'smile/question', + 'smile/redface', + ]; + + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\dev' + ]; + } + + public function update_data(): array + { + return [ + ['custom', [[$this, 'gif_to_svg_icons']]], + ]; + } + + public function revert_data(): array + { + return [ + ['custom', [[$this, 'svg_to_gif_icons']]], + ]; + } + + public function gif_to_svg_icons(): void + { + foreach ($this->default_icons as $smiley) + { + $sql = 'UPDATE ' . $this->tables['icons'] . " + SET icons_url = '" . $this->db->sql_escape($smiley) . ".svg' + WHERE icons_url = '" . $this->db->sql_escape($smiley) . ".gif'"; + $this->db->sql_query($sql); + } + } + + public function svg_to_gif_icons(): void + { + foreach ($this->default_icons as $smiley) + { + $sql = 'UPDATE ' . $this->tables['icons'] . " + SET icons_url = '" . $this->db->sql_escape($smiley) . ".gif' + WHERE icons_url = '" . $this->db->sql_escape($smiley) . ".svg'"; + $this->db->sql_query($sql); + } + } +} diff --git a/phpBB/phpbb/db/migration/data/v400/hidpi_smilies.php b/phpBB/phpbb/db/migration/data/v400/hidpi_smilies.php new file mode 100644 index 0000000000..f51810f095 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/hidpi_smilies.php @@ -0,0 +1,89 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class hidpi_smilies extends migration +{ + private array $default_smilies = [ + 'icon_arrow', + 'icon_cool', + 'icon_cry', + 'icon_e_biggrin', + 'icon_e_confused', + 'icon_e_geek', + 'icon_e_sad', + 'icon_e_smile', + 'icon_e_surprised', + 'icon_e_ugeek', + 'icon_e_wink', + 'icon_eek', + 'icon_evil', + 'icon_exclaim', + 'icon_idea', + 'icon_lol', + 'icon_mad', + 'icon_mrgreen', + 'icon_neutral', + 'icon_question', + 'icon_razz', + 'icon_redface', + 'icon_rolleyes', + 'icon_twisted', + ]; + + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\dev' + ]; + } + + public function update_data(): array + { + return [ + ['custom', [[$this, 'gif_to_svg_smilies']]], + ]; + } + + public function revert_data(): array + { + return [ + ['custom', [[$this, 'svg_to_gif_smilies']]], + ]; + } + + public function gif_to_svg_smilies(): void + { + foreach ($this->default_smilies as $smiley) + { + $sql = 'UPDATE ' . $this->tables['smilies'] . " + SET smiley_url = '" . $this->db->sql_escape($smiley) . ".svg' + WHERE smiley_url = '" . $this->db->sql_escape($smiley) . ".gif'"; + $this->db->sql_query($sql); + } + } + + public function svg_to_gif_smilies(): void + { + foreach ($this->default_smilies as $smiley) + { + $sql = 'UPDATE ' . $this->tables['smilies'] . " + SET smiley_url = '" . $this->db->sql_escape($smiley) . ".gif' + WHERE smiley_url = '" . $this->db->sql_escape($smiley) . ".svg'"; + $this->db->sql_query($sql); + } + } +} diff --git a/phpBB/phpbb/db/migration/data/v400/qa_captcha.php b/phpBB/phpbb/db/migration/data/v400/qa_captcha.php new file mode 100644 index 0000000000..69cca2dc15 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/qa_captcha.php @@ -0,0 +1,89 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class qa_captcha extends migration +{ + public function effectively_installed(): bool + { + return $this->db_tools->sql_table_exists($this->tables['captcha_qa_questions']) + && $this->db_tools->sql_table_exists($this->tables['captcha_qa_answers']) + && $this->db_tools->sql_table_exists($this->tables['captcha_qa_confirm']); + } + + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\dev', + ]; + } + + public function update_schema(): array + { + return [ + 'add_tables' => [ + $this->tables['captcha_qa_questions'] => [ + 'COLUMNS' => [ + 'question_id' => ['UINT', null, 'auto_increment'], + 'strict' => ['BOOL', 0], + 'lang_id' => ['UINT', 0], + 'lang_iso' => ['VCHAR:30', ''], + 'question_text' => ['TEXT_UNI', ''], + ], + 'PRIMARY_KEY' => 'question_id', + 'KEYS' => [ + 'lang' => ['INDEX', 'lang_iso'], + ], + ], + $this->tables['captcha_qa_answers'] => [ + 'COLUMNS' => [ + 'question_id' => ['UINT', 0], + 'answer_text' => ['STEXT_UNI', ''], + ], + 'KEYS' => [ + 'qid' => ['INDEX', 'question_id'], + ], + ], + $this->tables['captcha_qa_confirm'] => [ + 'COLUMNS' => [ + 'session_id' => ['CHAR:32', ''], + 'confirm_id' => ['CHAR:32', ''], + 'lang_iso' => ['VCHAR:30', ''], + 'question_id' => ['UINT', 0], + 'attempts' => ['UINT', 0], + 'confirm_type' => ['USINT', 0], + ], + 'KEYS' => [ + 'session_id' => ['INDEX', 'session_id'], + 'lookup' => ['INDEX', ['confirm_id', 'session_id', 'lang_iso']], + ], + 'PRIMARY_KEY' => 'confirm_id', + ], + ], + ]; + } + + public function revert_schema(): array + { + return [ + 'drop_tables' => [ + $this->tables['captcha_qa_questions'], + $this->tables['captcha_qa_answers'], + $this->tables['captcha_qa_confirm'] + ], + ]; + } +} diff --git a/phpBB/phpbb/db/migration/data/v400/remove_img_link.php b/phpBB/phpbb/db/migration/data/v400/remove_img_link.php new file mode 100644 index 0000000000..172bea5e1d --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/remove_img_link.php @@ -0,0 +1,41 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class remove_img_link extends migration +{ + public function effectively_installed() + { + return !$this->config->offsetExists('img_link_width') + && !$this->config->offsetExists('img_link_height'); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v400\dev' + ]; + } + + public function update_data() + { + return [ + ['config.remove', ['img_link_width']], + ['config.remove', ['img_link_height']], + ]; + } +} diff --git a/phpBB/phpbb/db/migration/data/v400/remove_max_img_size.php b/phpBB/phpbb/db/migration/data/v400/remove_max_img_size.php new file mode 100644 index 0000000000..8a5338a454 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/remove_max_img_size.php @@ -0,0 +1,34 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class remove_max_img_size extends migration +{ + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\dev', + ]; + } + + public function update_data(): array + { + return [ + ['config.remove', ['max_sig_img_width']], + ['config.remove', ['max_sig_img_height']], + ]; + } +} diff --git a/phpBB/phpbb/db/migration/data/v400/storage_track.php b/phpBB/phpbb/db/migration/data/v400/storage_track.php index 767eee5c0d..7cf05503d6 100644 --- a/phpBB/phpbb/db/migration/data/v400/storage_track.php +++ b/phpBB/phpbb/db/migration/data/v400/storage_track.php @@ -14,7 +14,7 @@ namespace phpbb\db\migration\data\v400; use phpbb\db\migration\container_aware_migration; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; use phpbb\storage\storage; class storage_track extends container_aware_migration @@ -97,7 +97,7 @@ public function track_avatars() { $storage->track_file($this->config['avatar_salt'] . '_' . ($avatar_group ? 'g' : '') . $filename . '.' . $ext); } - catch (exception $e) + catch (storage_exception $e) { // If file doesn't exist, don't track it } @@ -121,7 +121,7 @@ public function track_attachments() { $storage->track_file($row['physical_filename']); } - catch (exception $e) + catch (storage_exception $e) { // If file doesn't exist, don't track it } @@ -132,7 +132,7 @@ public function track_attachments() { $storage->track_file('thumb_' . $row['physical_filename']); } - catch (exception $e) + catch (storage_exception $e) { // If file doesn't exist, don't track it } @@ -157,7 +157,7 @@ public function track_backups() { $storage->track_file($row['filename']); } - catch (exception $e) + catch (storage_exception $e) { // If file doesn't exist, don't track it } diff --git a/phpBB/phpbb/db/migration/data/v400/turnstile_captcha.php b/phpBB/phpbb/db/migration/data/v400/turnstile_captcha.php new file mode 100644 index 0000000000..77e921c090 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/turnstile_captcha.php @@ -0,0 +1,51 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class turnstile_captcha extends migration +{ + public function effectively_installed(): bool + { + return $this->config->offsetExists('captcha_turnstile_sitekey') + && $this->config->offsetExists('captcha_turnstile_secret') + && $this->config->offsetExists('captcha_turnstile_theme'); + } + + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\dev', + ]; + } + + public function update_data(): array + { + return [ + ['config.add', ['captcha_turnstile_sitekey', '']], + ['config.add', ['captcha_turnstile_secret', '']], + ['config.add', ['captcha_turnstile_theme', 'light']], + ]; + } + + public function revert_data(): array + { + return [ + ['config.remove', ['captcha_turnstile_sitekey']], + ['config.remove', ['captcha_turnstile_secret']], + ['config.remove', ['captcha_turnstile_theme']], + ]; + } +} diff --git a/phpBB/phpbb/db/migration/tool/permission.php b/phpBB/phpbb/db/migration/tool/permission.php index 69f32fcb4c..94193629bf 100644 --- a/phpBB/phpbb/db/migration/tool/permission.php +++ b/phpBB/phpbb/db/migration/tool/permission.php @@ -444,15 +444,14 @@ public function permission_set($name, $auth_option, $type = 'role', $has_permiss } $this->db->sql_freeresult($result); - if (empty($new_auth)) + $type = (string) $type; // Prevent PHP bug. + if (empty($new_auth) || !in_array($type, ['role','group'])) { return; } $current_auth = array(); - $type = (string) $type; // Prevent PHP bug. - switch ($type) { case 'role': @@ -535,40 +534,32 @@ function ($option) use ($role_type) break; } - $sql_ary = array(); - switch ($type) + $sql_ary = $auth_update_list = []; + $table = $type == 'role' ? ACL_ROLES_DATA_TABLE : ACL_GROUPS_TABLE; + foreach ($new_auth as $auth_option_id) { - case 'role': - foreach ($new_auth as $auth_option_id) - { - if (!isset($current_auth[$auth_option_id])) - { - $sql_ary[] = array( - 'role_id' => $role_id, - 'auth_option_id' => $auth_option_id, - 'auth_setting' => $has_permission, - ); - } - } - - $this->db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary); - break; - - case 'group': - foreach ($new_auth as $auth_option_id) - { - if (!isset($current_auth[$auth_option_id])) - { - $sql_ary[] = array( - 'group_id' => $group_id, - 'auth_option_id' => $auth_option_id, - 'auth_setting' => $has_permission, - ); - } - } + if (!isset($current_auth[$auth_option_id])) + { + $sql_ary[] = [ + $type . '_id' => ${$type . '_id'}, + 'auth_option_id' => $auth_option_id, + 'auth_setting' => (int) $has_permission, + ]; + } + else + { + $auth_update_list[] = $auth_option_id; + } + } + $this->db->sql_multi_insert($table, $sql_ary); - $this->db->sql_multi_insert(ACL_GROUPS_TABLE, $sql_ary); - break; + if (count($auth_update_list)) + { + $sql = 'UPDATE ' . $table . ' + SET auth_setting = ' . (int) $has_permission . ' + WHERE ' . $this->db->sql_in_set('auth_option_id', $auth_update_list) . ' + AND ' . $type . '_id = ' . (int) ${$type . '_id'}; + $this->db->sql_query($sql); } $this->auth->acl_clear_prefetch(); diff --git a/phpBB/phpbb/file_downloader.php b/phpBB/phpbb/file_downloader.php index 873bccc9e8..1527cfe9d4 100644 --- a/phpBB/phpbb/file_downloader.php +++ b/phpBB/phpbb/file_downloader.php @@ -13,91 +13,116 @@ namespace phpbb; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\RequestException; +use phpbb\exception\runtime_exception; + class file_downloader { + const OK = 200; + const NOT_FOUND = 404; + const REQUEST_TIMEOUT = 408; + /** @var string Error string */ - protected $error_string = ''; + protected string $error_string = ''; /** @var int Error number */ - protected $error_number = 0; + protected int $error_number = 0; + + /** + * Create new guzzle client + * + * @param string $host + * @param int $port + * @param int $timeout + * + * @return Client + */ + protected function create_client(string $host, int $port = 443, int $timeout = 6): Client + { + // Only set URL scheme if not specified in URL + $url_parts = parse_url($host); + if (!isset($url_parts['scheme'])) + { + $host = (($port === 443) ? 'https://' : 'http://') . $host; + } + + // Initialize Guzzle client + return new Client([ + 'base_uri' => $host, + 'timeout' => $timeout, + ]); + } /** * Retrieve contents from remotely stored file * - * @param string $host File host - * @param string $directory Directory file is in - * @param string $filename Filename of file to retrieve - * @param int $port Port to connect to; default: 80 - * @param int $timeout Connection timeout in seconds; default: 6 + * @param string $host File host + * @param string $directory Directory file is in + * @param string $filename Filename of file to retrieve + * @param int $port Port to connect to; default: 443 + * @param int $timeout Connection timeout in seconds; default: 6 * * @return false|string File data as string if file can be read and there is no - * timeout, false if there were errors or the connection timed out + * timeout, false if there were errors or the connection timed out * - * @throws \phpbb\exception\runtime_exception If data can't be retrieved and no error - * message is returned + * @throws runtime_exception If data can't be retrieved and no error + * message is returned */ - public function get($host, $directory, $filename, $port = 80, $timeout = 6) + public function get(string $host, string $directory, string $filename, int $port = 443, int $timeout = 6): bool|string { + // Initialize Guzzle client + $client = $this->create_client($host, $port, $timeout); + // Set default values for error variables $this->error_number = 0; $this->error_string = ''; - if (function_exists('fsockopen') && - $socket = @fsockopen(($port == 443 ? 'ssl://' : '') . $host, $port, $this->error_number, $this->error_string, $timeout) - ) + try { - @fputs($socket, "GET $directory/$filename HTTP/1.0\r\n"); - @fputs($socket, "HOST: $host\r\n"); - @fputs($socket, "Connection: close\r\n\r\n"); - - $timer_stop = time() + $timeout; - stream_set_timeout($socket, $timeout); + $response = $client->request('GET', "$directory/$filename"); - $file_info = ''; - $get_info = false; - - while (!@feof($socket)) + // Check if the response status code is 200 (OK) + if ($response->getStatusCode() == self::OK) { - if ($get_info) - { - $file_info .= @fread($socket, 1024); - } - else - { - $line = @fgets($socket, 1024); - if ($line == "\r\n") - { - $get_info = true; - } - else if (stripos($line, '404 not found') !== false) - { - throw new \phpbb\exception\runtime_exception('FILE_NOT_FOUND', array($filename)); - } - } - - $stream_meta_data = stream_get_meta_data($socket); - - if ($stream_meta_data['timed_out'] || time() >= $timer_stop) - { - throw new \phpbb\exception\runtime_exception('FSOCK_TIMEOUT'); - } + return $response->getBody()->getContents(); + } + else + { + $this->error_number = $response->getStatusCode(); + throw new runtime_exception('FILE_NOT_FOUND', [$filename]); } - @fclose($socket); } - else + catch (RequestException $exception) { - if ($this->error_string) + if ($exception->hasResponse()) { - $this->error_string = utf8_convert_message($this->error_string); - return false; + $this->error_number = $exception->getResponse()->getStatusCode(); + + if ($this->error_number == self::NOT_FOUND) + { + throw new runtime_exception('FILE_NOT_FOUND', [$filename]); + } } else { - throw new \phpbb\exception\runtime_exception('FSOCK_DISABLED'); + $this->error_number = self::REQUEST_TIMEOUT; + throw new runtime_exception('FSOCK_TIMEOUT'); } - } - return $file_info; + $this->error_string = utf8_convert_message($exception->getMessage()); + return false; + } + catch (runtime_exception $exception) + { + // Rethrow runtime_exceptions, only handle unknown cases below + throw $exception; + } + catch (\Throwable $exception) + { + $this->error_string = utf8_convert_message($exception->getMessage()); + throw new runtime_exception('FSOCK_DISABLED'); + } } /** @@ -105,7 +130,7 @@ public function get($host, $directory, $filename, $port = 80, $timeout = 6) * * @return string Error string */ - public function get_error_string() + public function get_error_string(): string { return $this->error_string; } @@ -115,7 +140,7 @@ public function get_error_string() * * @return int Error number */ - public function get_error_number() + public function get_error_number(): int { return $this->error_number; } diff --git a/phpBB/phpbb/files/filespec.php b/phpBB/phpbb/files/filespec.php index 6e4c224e39..48e9284f69 100644 --- a/phpBB/phpbb/files/filespec.php +++ b/phpBB/phpbb/files/filespec.php @@ -398,7 +398,7 @@ public function check_content($disallowed_content) * @param bool $overwrite If set to true, an already existing file will be overwritten * @param bool $skip_image_check If set to true, the check for the file to be a valid image is skipped * @param string|bool $chmod Permission mask for chmodding the file after a successful move. - * The mode entered here reflects the mode defined by {@link phpbb_chmod()} + * The mode entered here reflects the mode defined by {@link \phpbb\filesystem\filesystem::phpbb_chmod()} * * @return bool True if file was moved, false if not * @access public diff --git a/phpBB/phpbb/files/filespec_storage.php b/phpBB/phpbb/files/filespec_storage.php index e775935756..83678718b5 100644 --- a/phpBB/phpbb/files/filespec_storage.php +++ b/phpBB/phpbb/files/filespec_storage.php @@ -453,7 +453,7 @@ public function move_file($storage, $overwrite = false, $skip_image_check = fals fclose($fp); } } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { $this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); $this->file_moved = false; diff --git a/phpBB/phpbb/filesystem.php b/phpBB/phpbb/filesystem.php deleted file mode 100644 index 6ac9459331..0000000000 --- a/phpBB/phpbb/filesystem.php +++ /dev/null @@ -1,21 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb; - -/** - * @deprecated 3.2.0-dev (To be removed 4.0.0) use \phpbb\filesystem\filesystem instead - */ -class filesystem extends \phpbb\filesystem\filesystem -{ -} diff --git a/phpBB/phpbb/help/controller/faq.php b/phpBB/phpbb/help/controller/faq.php index 0f63be5b56..62a82a3efe 100644 --- a/phpBB/phpbb/help/controller/faq.php +++ b/phpBB/phpbb/help/controller/faq.php @@ -146,6 +146,17 @@ public function display() 'HELP_FAQ_BOOKMARKS_REMOVE_QUESTION' => 'HELP_FAQ_BOOKMARKS_REMOVE_ANSWER', ) ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_WEBPUSH', + false, + array( + 'HELP_FAQ_WEBPUSH_WHAT_QUESTION' => 'HELP_FAQ_WEBPUSH_WHAT_ANSWER', + 'HELP_FAQ_WEBPUSH_HOW_QUESTION' => 'HELP_FAQ_WEBPUSH_HOW_ANSWER', + 'HELP_FAQ_WEBPUSH_SESSION_QUESTION' => 'HELP_FAQ_WEBPUSH_SESSION_ANSWER', + 'HELP_FAQ_WEBPUSH_SUBBING_QUESTION' => 'HELP_FAQ_WEBPUSH_SUBBING_ANSWER', + 'HELP_FAQ_WEBPUSH_GENERAL_QUESTION' => 'HELP_FAQ_WEBPUSH_GENERAL_ANSWER', + ) + ); $this->manager->add_block( 'HELP_FAQ_BLOCK_ATTACHMENTS', false, diff --git a/phpBB/phpbb/install/module/install_database/task/add_config_settings.php b/phpBB/phpbb/install/module/install_database/task/add_config_settings.php index bd3f0852e6..5b9a9da536 100644 --- a/phpBB/phpbb/install/module/install_database/task/add_config_settings.php +++ b/phpBB/phpbb/install/module/install_database/task/add_config_settings.php @@ -132,6 +132,7 @@ public function run() $updates = [ 'board_startdate' => (string) $current_time, + 'board_timezone' => $this->install_config->get('admin_timezone'), 'default_lang' => $this->install_config->get('default_lang'), 'server_name' => $this->install_config->get('server_name'), diff --git a/phpBB/phpbb/install/module/install_database/task/update_user_and_post_data.php b/phpBB/phpbb/install/module/install_database/task/update_user_and_post_data.php index ba178135b9..7cc5fd4f49 100644 --- a/phpBB/phpbb/install/module/install_database/task/update_user_and_post_data.php +++ b/phpBB/phpbb/install/module/install_database/task/update_user_and_post_data.php @@ -114,7 +114,8 @@ public function run() . ' user_lang = :lang,' . ' user_email = :email,' . ' user_dateformat = :dateformat,' - . ' username_clean = :clean_username' + . ' username_clean = :clean_username,' + . ' user_timezone = :timezone' . ' WHERE username = \'Admin\''; $this->create_and_execute_prepared_stmt($sql, [ @@ -125,6 +126,7 @@ public function run() 'email' => $this->install_config->get('board_email'), 'dateformat' => $this->language->lang('default_dateformat'), 'clean_username' => utf8_clean_string($this->install_config->get('admin_name')), + 'timezone' => $this->install_config->get('admin_timezone'), ]); $this->exec_sql('UPDATE ' . $this->user_table . ' SET user_regdate = ' . $current_time); diff --git a/phpBB/phpbb/install/module/install_finish/task/install_extensions.php b/phpBB/phpbb/install/module/install_finish/task/install_extensions.php index 019553053f..049694baba 100644 --- a/phpBB/phpbb/install/module/install_finish/task/install_extensions.php +++ b/phpBB/phpbb/install/module/install_finish/task/install_extensions.php @@ -157,7 +157,7 @@ protected function execute_step($key, $value) : void if (isset($extensions[$key]) && $extensions[$key]['ext_active']) { // Create log - $this->log->add('admin', ANONYMOUS, '', 'LOG_EXT_ENABLE', time(), array($key)); + $this->log->add('admin', ANONYMOUS, $this->user->ip, 'LOG_EXT_ENABLE', time(), array($key)); $this->iohandler->add_success_message(array('CLI_EXTENSION_ENABLE_SUCCESS', $key)); } else diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_admin_data.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_admin_data.php index 0560f2d46d..1175ab8626 100644 --- a/phpBB/phpbb/install/module/obtain_data/task/obtain_admin_data.php +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_admin_data.php @@ -71,6 +71,7 @@ protected function process_form() $admin_pass1 = $this->io_handler->get_input('admin_pass1', '', true); $admin_pass2 = $this->io_handler->get_input('admin_pass2', '', true); $board_email = $this->io_handler->get_input('board_email', '', true); + $admin_timezone = $this->io_handler->get_input('admin_timezone', 'UTC', true); $admin_data_valid = $this->check_admin_data($admin_name, $admin_pass1, $admin_pass2, $board_email); @@ -79,6 +80,7 @@ protected function process_form() $this->install_config->set('admin_name', $admin_name); $this->install_config->set('admin_passwd', $admin_pass1); $this->install_config->set('board_email', $board_email); + $this->install_config->set('admin_timezone', $admin_timezone); } else { diff --git a/phpBB/phpbb/install/module/update_database/task/update_extensions.php b/phpBB/phpbb/install/module/update_database/task/update_extensions.php index e47d23d052..6ea3a517bf 100644 --- a/phpBB/phpbb/install/module/update_database/task/update_extensions.php +++ b/phpBB/phpbb/install/module/update_database/task/update_extensions.php @@ -174,7 +174,7 @@ public function run() if (isset($extensions[$ext_name]) && $extensions[$ext_name]['ext_active']) { // Create log - $this->log->add('admin', ANONYMOUS, '', 'LOG_EXT_UPDATE', time(), array($ext_name)); + $this->log->add('admin', ANONYMOUS, $this->user->ip, 'LOG_EXT_UPDATE', time(), array($ext_name)); $this->iohandler->add_success_message(array('CLI_EXTENSION_UPDATE_SUCCESS', $ext_name)); } else diff --git a/phpBB/phpbb/language/language.php b/phpBB/phpbb/language/language.php index dee8b58486..1c51049de5 100644 --- a/phpBB/phpbb/language/language.php +++ b/phpBB/phpbb/language/language.php @@ -403,6 +403,7 @@ protected function inject_default_variables(): void $this->lang['USER_LANG'] = $lang_values['user_lang'] ?? 'en-gb'; $this->lang['PLURAL_RULE'] = $lang_values['plural_rule'] ?? 1; $this->lang['RECAPTCHA_LANG'] = $lang_values['recaptcha_lang'] ?? 'en-GB'; + $this->lang['TURNSTILE_LANG'] = $lang_values['turnstile_lang'] ?? 'auto'; // default to auto mode } /** diff --git a/phpBB/phpbb/language/language_file_helper.php b/phpBB/phpbb/language/language_file_helper.php index 995c7b4712..83244d674f 100644 --- a/phpBB/phpbb/language/language_file_helper.php +++ b/phpBB/phpbb/language/language_file_helper.php @@ -110,16 +110,17 @@ protected function get_language_data_from_json(array $data) : array } return [ - 'iso' => $data['extra']['language-iso'], - 'name' => $data['extra']['english-name'], - 'local_name' => $data['extra']['local-name'], - 'author' => implode(', ', $authors), - 'version' => $data['version'], - 'phpbb_version' => $data['extra']['phpbb-version'], - 'direction' => $data['extra']['direction'], - 'user_lang' => $data['extra']['user-lang'], - 'plural_rule' => $data['extra']['plural-rule'], - 'recaptcha_lang'=> $data['extra']['recaptcha-lang'], + 'iso' => $data['extra']['language-iso'], + 'name' => $data['extra']['english-name'], + 'local_name' => $data['extra']['local-name'], + 'author' => implode(', ', $authors), + 'version' => $data['version'], + 'phpbb_version' => $data['extra']['phpbb-version'], + 'direction' => $data['extra']['direction'], + 'user_lang' => $data['extra']['user-lang'], + 'plural_rule' => $data['extra']['plural-rule'], + 'recaptcha_lang' => $data['extra']['recaptcha-lang'], + 'turnstile_lang' => $data['extra']['turnstile-lang'] ?? '', ]; } } diff --git a/phpBB/phpbb/manifest.php b/phpBB/phpbb/manifest.php new file mode 100644 index 0000000000..1a7d0148e5 --- /dev/null +++ b/phpBB/phpbb/manifest.php @@ -0,0 +1,95 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb; + +use phpbb\config\config; +use phpbb\event\dispatcher_interface; +use phpbb\exception\http_exception; +use phpbb\language\language; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Response; + +class manifest +{ + /** @var config */ + protected $config; + + /** @var language */ + protected $language; + + /** @var path_helper */ + protected $path_helper; + + /** @var dispatcher_interface */ + protected $phpbb_dispatcher; + + /** @var user */ + protected $user; + + /** + * Constructor for manifest controller + * + * @param config $config + * @param language $language + * @param path_helper $path_helper + * @param dispatcher_interface $phpbb_dispatcher + * @param user $user + */ + public function __construct(config $config, language $language, path_helper $path_helper, dispatcher_interface $phpbb_dispatcher, user $user) + { + $this->config = $config; + $this->path_helper = $path_helper; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->language = $language; + $this->user = $user; + } + + /** + * Handle creation of a manifest json file for progressive web-app support + * + * @return JsonResponse + */ + public function handle(): JsonResponse + { + if ($this->user->data['is_bot']) + { + throw new http_exception(Response::HTTP_FORBIDDEN, 'NO_AUTH_OPERATION'); + } + + $board_path = $this->config['force_server_vars'] ? $this->config['script_path'] : $this->path_helper->get_web_root_path(); + + $manifest = [ + 'name' => $this->config['sitename'], + 'short_name' => $this->config['sitename_short'] ?: substr($this->config['sitename'], 0, 12), + 'display' => 'standalone', + 'orientation' => 'portrait', + 'dir' => $this->language->lang('DIRECTION'), + 'start_url' => $board_path, + 'scope' => $board_path, + ]; + + /** + * Event to modify manifest data before it is outputted + * + * @event core.modify_manifest + * @var array manifest Array of manifest members + * @var string board_path Path to the board root + * @since 4.0.0-a1 + */ + $vars = array('manifest', 'board_path'); + extract($this->phpbb_dispatcher->trigger_event('core.modify_manifest', compact($vars))); + + return new JsonResponse($manifest); + } +} diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index e5b8910a07..9d19cb2200 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -48,6 +48,12 @@ class webpush extends messenger_base implements extended_method_interface /** @var string Notification push subscriptions table */ protected $push_subscriptions_table; + /** @var int Fallback size for padding if endpoint is mozilla, see https://github.com/web-push-libs/web-push-php/issues/108#issuecomment-2133477054 */ + const MOZILLA_FALLBACK_PADDING = 2820; + + /** @var array Map for storing push token between db insertion and sending of notifications */ + private array $push_token_map = []; + /** * Notification Method Web Push constructor * @@ -91,6 +97,14 @@ public function is_available(type_interface $notification_type = null): bool && !empty($this->config['webpush_vapid_public']) && !empty($this->config['webpush_vapid_private']); } + /** + * {@inheritDoc} + */ + public function is_enabled_by_default() + { + return (bool) $this->config['webpush_method_default_enable']; + } + /** * {@inheritdoc} */ @@ -126,17 +140,16 @@ public function notify() { $data = $notification->get_insert_array(); $data += [ - 'push_data' => json_encode([ - 'heading' => $this->config['sitename'], - 'title' => strip_tags($notification->get_title()), - 'text' => strip_tags($notification->get_reference()), - 'url' => htmlspecialchars_decode($notification->get_url()), - 'avatar' => $notification->get_avatar(), - ]), + 'push_data' => json_encode(array_merge( + $data, + ['notification_type_name' => $notification->get_type()], + )), 'notification_time' => time(), + 'push_token' => hash('sha256', random_bytes(32)) ]; $data = self::clean_data($data); $insert_buffer->insert($data); + $this->push_token_map[$notification->notification_type_id][$notification->item_id] = $data['push_token']; } $insert_buffer->flush(); @@ -210,6 +223,9 @@ protected function notify_using_webpush(): void $data = [ 'item_id' => $notification->item_id, 'type_id' => $notification->notification_type_id, + 'user_id' => $notification->user_id, + 'version' => $this->config['assets_version'], + 'token' => hash('sha256', $user['user_form_salt'] . $this->push_token_map[$notification->notification_type_id][$notification->item_id]), ]; $json_data = json_encode($data); @@ -217,6 +233,7 @@ protected function notify_using_webpush(): void { try { + $this->set_endpoint_padding($web_push, $subscription['endpoint']); $push_subscription = Subscription::create([ 'endpoint' => $subscription['endpoint'], 'keys' => [ @@ -324,12 +341,21 @@ public static function clean_data(array $data): array 'item_parent_id' => null, 'user_id' => null, 'push_data' => null, + 'push_token' => null, 'notification_time' => null, ]; return array_intersect_key($data, $row); } + /** + * Get template data for the UCP + * + * @param helper $controller_helper + * @param form_helper $form_helper + * + * @return array + */ public function get_ucp_template_data(helper $controller_helper, form_helper $form_helper): array { $subscription_map = $this->get_user_subscription_map([$this->user->id()]); @@ -347,7 +373,7 @@ public function get_ucp_template_data(helper $controller_helper, form_helper $fo } return [ - 'NOTIFICATIONS_WEBPUSH_ENABLE' => true, + 'NOTIFICATIONS_WEBPUSH_ENABLE' => $this->config['webpush_dropdown_subscribe'] || stripos($this->user->page['page'], 'notification_options'), 'U_WEBPUSH_SUBSCRIBE' => $controller_helper->route('phpbb_ucp_push_subscribe_controller'), 'U_WEBPUSH_UNSUBSCRIBE' => $controller_helper->route('phpbb_ucp_push_unsubscribe_controller'), 'VAPID_PUBLIC_KEY' => $this->config['webpush_vapid_public'], @@ -429,4 +455,27 @@ protected function clean_expired_subscriptions(array $user_subscription_map, arr $this->remove_subscriptions($remove_subscriptions); } + + /** + * Set web push padding for endpoint + * + * @param \Minishlink\WebPush\WebPush $web_push + * @param string $endpoint + * + * @return void + */ + protected function set_endpoint_padding(\Minishlink\WebPush\WebPush $web_push, string $endpoint): void + { + if (str_contains($endpoint, 'mozilla.com') || str_contains($endpoint, 'mozaws.net')) + { + try + { + $web_push->setAutomaticPadding(self::MOZILLA_FALLBACK_PADDING); + } + catch (\Exception) + { + // This shouldn't happen since we won't pass padding length outside limits + } + } + } } diff --git a/phpBB/phpbb/path_helper.php b/phpBB/phpbb/path_helper.php index aa898c7b12..6ed8e5be5a 100644 --- a/phpBB/phpbb/path_helper.php +++ b/phpBB/phpbb/path_helper.php @@ -220,13 +220,13 @@ public function get_web_root_path() * * The referer must be specified as a parameter in the query. */ - if ($this->request->is_ajax() && $this->symfony_request->get('_referer')) + if ($this->request->is_ajax() && $this->request->header('Referer')) { // We need to escape $absolute_board_url because it can be partially concatenated to the result. $absolute_board_url = $this->request->escape($this->symfony_request->getSchemeAndHttpHost() . $this->symfony_request->getBasePath(), true); $referer_web_root_path = $this->get_web_root_path_from_ajax_referer( - $this->symfony_request->get('_referer'), + $this->request->header('Referer'), $absolute_board_url ); return $this->web_root_path = $referer_web_root_path; diff --git a/phpBB/phpbb/php/ini.php b/phpBB/phpbb/php/ini.php deleted file mode 100644 index 441e3ff7f6..0000000000 --- a/phpBB/phpbb/php/ini.php +++ /dev/null @@ -1,175 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\php; - -/** -* Wrapper class for ini_get function. -* -* Provides easier handling of the different interpretations of ini values. -* @deprecated 3.2.10 (To be removed 4.0.0) -*/ -class ini -{ - /** - * Simple wrapper for ini_get() - * See http://php.net/manual/en/function.ini-get.php - * - * @param string $varname The configuration option name. - * @return bool|string False if configuration option does not exist, - * the configuration option value (string) otherwise. - */ - public function get($varname) - { - return ini_get($varname); - } - - /** - * Gets the configuration option value as a trimmed string. - * - * @param string $varname The configuration option name. - * @return bool|string False if configuration option does not exist, - * the configuration option value (string) otherwise. - */ - public function get_string($varname) - { - $value = $this->get($varname); - - if ($value === false) - { - return false; - } - - return trim($value); - } - - /** - * Gets configuration option value as a boolean. - * Interprets the string value 'off' as false. - * - * @param string $varname The configuration option name. - * @return bool False if configuration option does not exist. - * False if configuration option is disabled. - * True otherwise. - */ - public function get_bool($varname) - { - $value = $this->get_string($varname); - - if (empty($value) || strtolower($value) == 'off') - { - return false; - } - - return true; - } - - /** - * Gets configuration option value as an integer. - * - * @param string $varname The configuration option name. - * @return bool|int False if configuration option does not exist, - * false if configuration option value is not numeric, - * the configuration option value (integer) otherwise. - */ - public function get_int($varname) - { - $value = $this->get_string($varname); - - if (!is_numeric($value)) - { - return false; - } - - return (int) $value; - } - - /** - * Gets configuration option value as a float. - * - * @param string $varname The configuration option name. - * @return bool|float False if configuration option does not exist, - * false if configuration option value is not numeric, - * the configuration option value (float) otherwise. - */ - public function get_float($varname) - { - $value = $this->get_string($varname); - - if (!is_numeric($value)) - { - return false; - } - - return (float) $value; - } - - /** - * Gets configuration option value in bytes. - * Converts strings like '128M' to bytes (integer or float). - * - * @param string $varname The configuration option name. - * @return bool|int|float False if configuration option does not exist, - * false if configuration option value is not well-formed, - * the configuration option value otherwise. - */ - public function get_bytes($varname) - { - $value = $this->get_string($varname); - - if ($value === false) - { - return false; - } - - if (is_numeric($value)) - { - // Already in bytes. - return phpbb_to_numeric($value); - } - else if (is_string($value)) - { - if (strlen($value) < 2) - { - // Single character. - return false; - } - else if (strlen($value) < 3 && $value[0] === '-') - { - // Two characters but the first one is a minus. - return false; - } - } - - $value_lower = strtolower($value); - $value_numeric = phpbb_to_numeric($value); - - switch ($value_lower[strlen($value_lower) - 1]) - { - case 'g': - $value_numeric *= 1024; - case 'm': - $value_numeric *= 1024; - case 'k': - $value_numeric *= 1024; - break; - - default: - // It's not already in bytes (and thus numeric) - // and does not carry a unit. - return false; - } - - return $value_numeric; - } -} diff --git a/phpBB/phpbb/plupload/plupload.php b/phpBB/phpbb/plupload/plupload.php index db5a1d6dd9..18fdfe588d 100644 --- a/phpBB/phpbb/plupload/plupload.php +++ b/phpBB/phpbb/plupload/plupload.php @@ -344,8 +344,9 @@ protected function integrate_uploaded_file($form_name, $chunk, $file_path) } $tmp_file = $this->temporary_filepath($upload['tmp_name']); + $filesystem = new \phpbb\filesystem\filesystem(); - if (!phpbb_is_writable($this->temporary_directory) || !move_uploaded_file($upload['tmp_name'], $tmp_file)) + if (!$filesystem->is_writable($this->temporary_directory) || !move_uploaded_file($upload['tmp_name'], $tmp_file)) { $this->emit_error(103, 'PLUPLOAD_ERR_MOVE_UPLOADED'); } diff --git a/phpBB/phpbb/report/controller/report.php b/phpBB/phpbb/report/controller/report.php index b8e2b04583..97dbdbd9bb 100644 --- a/phpBB/phpbb/report/controller/report.php +++ b/phpBB/phpbb/report/controller/report.php @@ -13,6 +13,8 @@ namespace phpbb\report\controller; +use phpbb\captcha\plugins\confirm_type; +use phpbb\captcha\plugins\plugin_interface; use phpbb\exception\http_exception; use phpbb\report\report_handler_interface; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -131,7 +133,7 @@ public function handle($id, $mode) if ($this->config['enable_post_confirm'] && !$this->user->data['is_registered']) { $captcha = $this->captcha_factory->get_instance($this->config['captcha_plugin']); - $captcha->init(CONFIRM_REPORT); + $captcha->init(confirm_type::REPORT); } //Has the report been cancelled? @@ -140,7 +142,7 @@ public function handle($id, $mode) return new RedirectResponse($redirect_url, 302); } - // Check CAPTCHA, if the form was submited + // Check CAPTCHA, if the form was submitted if (!empty($submit) && isset($captcha)) { $captcha_template_array = $this->check_captcha($captcha); @@ -298,18 +300,17 @@ protected function assign_template_data($mode, $id, $reason_id, $report_text, $u /** * Check CAPTCHA * - * @param object $captcha A phpBB CAPTCHA object + * @param plugin_interface $captcha A phpBB CAPTCHA object * @return array template variables which ensures that CAPTCHA's work correctly */ - protected function check_captcha($captcha) + protected function check_captcha(plugin_interface $captcha) { $error = array(); $captcha_hidden_fields = ''; - $visual_confirmation_response = $captcha->validate(); - if ($visual_confirmation_response) + if ($captcha->validate() !== true) { - $error[] = $visual_confirmation_response; + $error[] = $captcha->get_error(); } if (count($error) === 0) diff --git a/phpBB/phpbb/request/request.php b/phpBB/phpbb/request/request.php index cf81dc42c6..a28b9df541 100644 --- a/phpBB/phpbb/request/request.php +++ b/phpBB/phpbb/request/request.php @@ -184,7 +184,7 @@ public function overwrite($var_name, $value, $super_global = request_interface:: * @param int $super_global (\phpbb\request\request_interface::POST|GET|REQUEST|COOKIE) * Specifies which super global should be used * - * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * @return mixed The value of $_REQUEST[$var_name] run through {@link type_cast_helper_interface::set_var} to ensure that the type is the * the same as that of $default. If the variable is not set $default is returned. */ public function variable($var_name, $default, $multibyte = false, $super_global = request_interface::REQUEST) @@ -208,7 +208,7 @@ public function variable($var_name, $default, $multibyte = false, $super_global * @param int $super_global (\phpbb\request\request_interface::POST|GET|REQUEST|COOKIE) * Specifies which super global should be used * - * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * @return mixed The value of $_REQUEST[$var_name] run through {@link type_cast_helper_interface::set_var} to ensure that the type is the * the same as that of $default. If the variable is not set $default is returned. */ public function untrimmed_variable($var_name, $default, $multibyte = false, $super_global = request_interface::REQUEST) @@ -401,7 +401,7 @@ public function variable_names($super_global = request_interface::REQUEST) * Specifies which super global should be used * @param bool $trim Indicates whether trim() should be applied to string values. * - * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * @return mixed The value of $_REQUEST[$var_name] run through {@link type_cast_helper_interface::set_var} to ensure that the type is the * the same as that of $default. If the variable is not set $default is returned. */ protected function _variable($var_name, $default, $multibyte = false, $super_global = request_interface::REQUEST, $trim = true) diff --git a/phpBB/phpbb/request/request_interface.php b/phpBB/phpbb/request/request_interface.php index 9ebd4c25e3..add58736ea 100644 --- a/phpBB/phpbb/request/request_interface.php +++ b/phpBB/phpbb/request/request_interface.php @@ -59,7 +59,7 @@ public function overwrite($var_name, $value, $super_global = request_interface:: * @param int $super_global (\phpbb\request\request_interface::POST|GET|REQUEST|COOKIE) * Specifies which super global shall be changed * - * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * @return mixed The value of $_REQUEST[$var_name] run through {@link type_cast_helper_interface::set_var} to ensure that the type is the * the same as that of $default. If the variable is not set $default is returned. */ public function variable($var_name, $default, $multibyte = false, $super_global = request_interface::REQUEST); @@ -81,7 +81,7 @@ public function variable($var_name, $default, $multibyte = false, $super_global * @param int $super_global (\phpbb\request\request_interface::POST|GET|REQUEST|COOKIE) * Specifies which super global shall be changed * - * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * @return mixed The value of $_REQUEST[$var_name] run through {@link type_cast_helper_interface::set_var} to ensure that the type is the * the same as that of $default. If the variable is not set $default is returned. */ public function raw_variable($var_name, $default, $super_global = request_interface::REQUEST); diff --git a/phpBB/phpbb/search/backend/base.php b/phpBB/phpbb/search/backend/base.php index e195e0f4c0..9663c15f65 100644 --- a/phpBB/phpbb/search/backend/base.php +++ b/phpBB/phpbb/search/backend/base.php @@ -112,17 +112,10 @@ protected function obtain_ids(string $search_key, int &$result_count, array &$id } } - // change the start to the actual end of the current request if the sort direction differs - // from the direction in the cache and reverse the ids later + // If the sort direction differs from the direction in the cache, then reverse the ids array if ($reverse_ids) { - $start = $result_count - $start - $per_page; - - // the user requested a page past the last index - if ($start < 0) - { - return self::SEARCH_RESULT_NOT_IN_CACHE; - } + $stored_ids = array_reverse($stored_ids); } for ($i = $start, $n = $start + $per_page; ($i < $n) && ($i < $result_count); $i++) @@ -138,11 +131,6 @@ protected function obtain_ids(string $search_key, int &$result_count, array &$id } unset($stored_ids); - if ($reverse_ids) - { - $id_ary = array_reverse($id_ary); - } - if (!$complete) { return self::SEARCH_RESULT_INCOMPLETE; diff --git a/phpBB/phpbb/search/backend/fulltext_native.php b/phpBB/phpbb/search/backend/fulltext_native.php index ca71ae8bae..ffdcbff3db 100644 --- a/phpBB/phpbb/search/backend/fulltext_native.php +++ b/phpBB/phpbb/search/backend/fulltext_native.php @@ -308,7 +308,11 @@ public function split_keywords(string &$keywords, string $terms): bool ); $keywords = preg_replace($match, $replace, $keywords); - $num_keywords = count(explode(' ', $keywords)); + + // Ensure a space exists before +, - and | to make the split and count work correctly + $countable_keywords = preg_replace('/(?config['max_num_search_keywords'] && $num_keywords > $this->config['max_num_search_keywords']) diff --git a/phpBB/phpbb/session.php b/phpBB/phpbb/session.php index 5cfe3d91f3..cc86f6f3ce 100644 --- a/phpBB/phpbb/session.php +++ b/phpBB/phpbb/session.php @@ -451,7 +451,7 @@ function session_begin($update_session_page = true) $this->check_ban_for_current_session($config); // Update user last active time accordingly, but in a minute or so - if ((int) $this->data['session_time'] - (int) $this->data['user_last_active'] > 60) + if ($this->time_now - (int) $this->data['user_last_active'] > 60) { $this->update_last_active_time(); } @@ -832,7 +832,7 @@ function session_create($user_id = false, $set_admin = false, $persist_login = f // Update the form key $sql = 'UPDATE ' . USERS_TABLE . ' SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\', - user_last_active = ' . (int) $this->data['session_time'] . ' + user_last_active = ' . (int) $this->time_now . ' WHERE user_id = ' . (int) $this->data['user_id']; $db->sql_query($sql); } @@ -980,8 +980,8 @@ function session_gc() } /** - * Get most recent session for each registered user to sync user last visit with it - * Inner SELECT gets most recent sessions for each unique session_user_id + * Get expired sessions for registered users, only most recent for each user + * Inner SELECT gets most recent expired sessions for unique session_user_id * Outer SELECT gets data for them */ $sql_select = 'SELECT s1.session_page, s1.session_user_id, s1.session_time AS recent_time @@ -989,7 +989,8 @@ function session_gc() INNER JOIN ( SELECT session_user_id, MAX(session_time) AS recent_time FROM ' . SESSIONS_TABLE . ' - WHERE session_user_id <> ' . ANONYMOUS . ' + WHERE session_time < ' . ($this->time_now - (int) $config['session_length']) . ' + AND session_user_id <> ' . ANONYMOUS . ' GROUP BY session_user_id ) AS s2 ON s1.session_user_id = s2.session_user_id @@ -1718,7 +1719,7 @@ public function update_user_lastvisit() { $sql = 'UPDATE ' . USERS_TABLE . ' SET user_lastvisit = ' . (int) $this->data['session_time'] . ', - user_last_active = ' . (int) $this->data['session_time'] . ' + user_last_active = ' . $this->time_now . ' WHERE user_id = ' . (int) $this->data['user_id']; $db->sql_query($sql); } @@ -1733,10 +1734,10 @@ public function update_last_active_time() { global $db; - if (isset($this->data['session_time'], $this->data['user_id'])) + if (isset($this->time_now, $this->data['user_id'])) { $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_last_active = ' . (int) $this->data['session_time'] . ' + SET user_last_active = ' . $this->time_now . ' WHERE user_id = ' . (int) $this->data['user_id']; $db->sql_query($sql); } diff --git a/phpBB/phpbb/storage/adapter/adapter_interface.php b/phpBB/phpbb/storage/adapter/adapter_interface.php index 5bd6525a73..d7fcd77bcb 100644 --- a/phpBB/phpbb/storage/adapter/adapter_interface.php +++ b/phpBB/phpbb/storage/adapter/adapter_interface.php @@ -13,14 +13,14 @@ namespace phpbb\storage\adapter; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; interface adapter_interface { /** * Set adapter parameters * - * @param array options Storage-specific options. + * @param array $options options Storage-specific options. */ public function configure(array $options): void; @@ -29,7 +29,7 @@ public function configure(array $options): void; * * @param string $path * @param string $content - * @throws exception When the file cannot be written + * @throws storage_exception When the file cannot be written */ public function put_contents(string $path, string $content): void; @@ -39,7 +39,7 @@ public function put_contents(string $path, string $content): void; * @param string $path The file to read * * @return string Returns file contents - * @throws exception When cannot read file contents + * @throws storage_exception When cannot read file contents */ public function get_contents(string $path): string; @@ -57,7 +57,7 @@ public function exists(string $path): bool; * * @param string $path file/directory to remove * - * @throws exception When removal fails. + * @throws storage_exception When removal fails. */ public function delete(string $path): void; @@ -67,7 +67,7 @@ public function delete(string $path): void; * @param string $path_orig The original file/direcotry * @param string $path_dest The target file/directory * - * @throws exception When file/directory cannot be renamed + * @throws storage_exception When file/directory cannot be renamed */ public function rename(string $path_orig, string $path_dest): void; @@ -77,24 +77,26 @@ public function rename(string $path_orig, string $path_dest): void; * @param string $path_orig The original filename * @param string $path_dest The target filename * - * @throws exception When the file cannot be copied + * @throws storage_exception When the file cannot be copied */ public function copy(string $path_orig, string $path_dest): void; /** - * Get direct link + * Get file size in bytes * * @param string $path The file * - * @return string Returns link. + * @return int Size in bytes. + * + * @throws storage_exception When unable to retrieve file size */ - public function get_link(string $path): string; + public function file_size(string $path): int; /** * Get space available in bytes * * @return float Returns available space - * @throws exception When unable to retrieve available storage space + * @throws storage_exception When unable to retrieve available storage space */ public function free_space(): float; } diff --git a/phpBB/phpbb/storage/adapter/local.php b/phpBB/phpbb/storage/adapter/local.php index be4b3399d6..72d92096d5 100644 --- a/phpBB/phpbb/storage/adapter/local.php +++ b/phpBB/phpbb/storage/adapter/local.php @@ -14,12 +14,10 @@ namespace phpbb\storage\adapter; use phpbb\storage\stream_interface; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; use phpbb\filesystem\exception\filesystem_exception; use phpbb\filesystem\filesystem; use phpbb\filesystem\helper as filesystem_helper; -use phpbb\mimetype\guesser; -use FastImageSize\FastImageSize; /** * Experimental @@ -33,20 +31,6 @@ class local implements adapter_interface, stream_interface */ protected $filesystem; - /** - * FastImageSize - * - * @var FastImageSize - */ - protected $imagesize; - - /** - * Mimetype Guesser component - * - * @var guesser - */ - protected $mimetype_guesser; - /** * @var string path */ @@ -77,15 +61,11 @@ class local implements adapter_interface, stream_interface * Constructor * * @param filesystem $filesystem - * @param FastImageSize $imagesize - * @param guesser $mimetype_guesser * @param string $phpbb_root_path */ - public function __construct(filesystem $filesystem, FastImageSize $imagesize, guesser $mimetype_guesser, string $phpbb_root_path) + public function __construct(filesystem $filesystem, string $phpbb_root_path) { $this->filesystem = $filesystem; - $this->imagesize = $imagesize; - $this->mimetype_guesser = $mimetype_guesser; $this->phpbb_root_path = $phpbb_root_path; } @@ -117,7 +97,7 @@ public function put_contents(string $path, string $content): void } catch (filesystem_exception $e) { - throw new exception('STORAGE_CANNOT_WRITE_FILE', $path, array(), $e); + throw new storage_exception('STORAGE_CANNOT_WRITE_FILE', $path, array(), $e); } } @@ -130,7 +110,7 @@ public function get_contents(string $path): string if ($content === false) { - throw new exception('STORAGE_CANNOT_READ_FILE', $path); + throw new storage_exception('STORAGE_CANNOT_READ_FILE', $path); } return $content; @@ -155,7 +135,7 @@ public function delete(string $path): void } catch (filesystem_exception $e) { - throw new exception('STORAGE_CANNOT_DELETE', $path, array(), $e); + throw new storage_exception('STORAGE_CANNOT_DELETE', $path, array(), $e); } } @@ -172,7 +152,7 @@ public function rename(string $path_orig, string $path_dest): void } catch (filesystem_exception $e) { - throw new exception('STORAGE_CANNOT_RENAME', $path_orig, array(), $e); + throw new storage_exception('STORAGE_CANNOT_RENAME', $path_orig, array(), $e); } } @@ -189,7 +169,7 @@ public function copy(string $path_orig, string $path_dest): void } catch (filesystem_exception $e) { - throw new exception('STORAGE_CANNOT_COPY', $path_orig, array(), $e); + throw new storage_exception('STORAGE_CANNOT_COPY', $path_orig, array(), $e); } } @@ -198,7 +178,7 @@ public function copy(string $path_orig, string $path_dest): void * * @param string $path The directory path * - * @throws exception On any directory creation failure + * @throws storage_exception On any directory creation failure */ protected function create_dir(string $path): void { @@ -208,7 +188,7 @@ protected function create_dir(string $path): void } catch (filesystem_exception $e) { - throw new exception('STORAGE_CANNOT_CREATE_DIR', $path, array(), $e); + throw new storage_exception('STORAGE_CANNOT_CREATE_DIR', $path, array(), $e); } } @@ -217,7 +197,7 @@ protected function create_dir(string $path): void * * @param string $path The file path * - * @throws exception On any directory creation failure + * @throws storage_exception On any directory creation failure */ protected function ensure_directory_exists(string $path): void { @@ -264,7 +244,7 @@ public function read_stream(string $path) if (!$stream) { - throw new exception('STORAGE_CANNOT_OPEN_FILE', $path); + throw new storage_exception('STORAGE_CANNOT_OPEN_FILE', $path); } return $stream; @@ -281,104 +261,31 @@ public function write_stream(string $path, $resource): void if (!$stream) { - throw new exception('STORAGE_CANNOT_CREATE_FILE', $path); + throw new storage_exception('STORAGE_CANNOT_CREATE_FILE', $path); } if (stream_copy_to_stream($resource, $stream) === false) { fclose($stream); - throw new exception('STORAGE_CANNOT_COPY_RESOURCE'); + throw new storage_exception('STORAGE_CANNOT_COPY_RESOURCE'); } fclose($stream); } /** - * Get file size - * - * @param string $path The file - * - * @throws exception When cannot get size - * - * @return array Properties - * @throws exception When cannot get size - * + * {@inheritdoc} */ - public function file_size(string $path): array + public function file_size(string $path): int { $size = @filesize($this->root_path . $this->get_path($path) . $this->get_filename($path)); if ($size === null) { - throw new exception('STORAGE_CANNOT_GET_FILESIZE'); + throw new storage_exception('STORAGE_CANNOT_GET_FILESIZE'); } - return ['size' => $size]; - } - - /** - * Get file mimetype - * - * @param string $path The file - * - * @return array Properties - */ - public function file_mimetype(string $path): array - { - return ['mimetype' => $this->mimetype_guesser->guess($this->root_path . $this->get_path($path) . $this->get_filename($path))]; - } - - /** - * Get image dimensions - * - * @param string $path The file - * - * @return array Properties - */ - protected function image_dimensions(string $path): array - { - $size = $this->imagesize->getImageSize($this->root_path . $this->get_path($path) . $this->get_filename($path)); - - // For not supported types like swf - if ($size === false) - { - $imsize = getimagesize($this->root_path . $this->get_path($path) . $this->get_filename($path)); - $size = ['width' => $imsize[0], 'height' => $imsize[1]]; - } - - return ['image_width' => $size['width'], 'image_height' => $size['height']]; - } - - /** - * Get image width - * - * @param string $path The file - * - * @return array Properties - */ - public function file_image_width(string $path): array - { - return $this->image_dimensions($path); - } - - /** - * Get image height - * - * @param string $path The file - * - * @return array Properties - */ - public function file_image_height(string $path): array - { - return $this->image_dimensions($path); - } - - /** - * {@inheritdoc} - */ - public function get_link(string $path): string - { - return generate_board_url() . '/' . $this->path . $path; + return $size; } /** @@ -392,12 +299,12 @@ public function free_space(): float if ($free_space === false) { - throw new exception('STORAGE_CANNOT_GET_FREE_SPACE'); + throw new storage_exception('STORAGE_CANNOT_GET_FREE_SPACE'); } } else { - throw new exception('STORAGE_CANNOT_GET_FREE_SPACE'); + throw new storage_exception('STORAGE_CANNOT_GET_FREE_SPACE'); } return $free_space; diff --git a/phpBB/phpbb/storage/adapter_factory.php b/phpBB/phpbb/storage/adapter_factory.php index 5d08bfa7c2..61ea879482 100644 --- a/phpBB/phpbb/storage/adapter_factory.php +++ b/phpBB/phpbb/storage/adapter_factory.php @@ -15,7 +15,7 @@ use phpbb\config\config; use phpbb\di\service_collection; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; class adapter_factory { @@ -53,41 +53,44 @@ public function __construct(config $config, service_collection $adapters, servic * * @param string $storage_name * - * @return \phpbb\storage\adapter\adapter_interface + * @return mixed */ - public function get($storage_name) + public function get(string $storage_name): mixed { $provider_class = $this->config['storage\\' . $storage_name . '\\provider']; $provider = $this->providers->get_by_class($provider_class); - if (!$provider->is_available()) + $options = []; + foreach (array_keys($provider->get_options()) as $definition) { - throw new exception('STORAGE_ADAPTER_NOT_AVAILABLE'); + /** @psalm-suppress InvalidArrayOffset */ + $options[$definition] = $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; } - $adapter = $this->adapters->get_by_class($provider->get_adapter_class()); - $adapter->configure($this->build_options($storage_name, $provider->get_options())); - - return $adapter; + return $this->get_with_options($storage_name, $options); } /** - * Obtains configuration for a given storage + * Obtains a configured adapters for a given storage with custom options * * @param string $storage_name - * @param array $definitions + * @param array $options * - * @return array Returns storage configuration values + * @return mixed */ - public function build_options($storage_name, array $definitions) + public function get_with_options(string $storage_name, array $options): mixed { - $options = []; + $provider_class = $this->config['storage\\' . $storage_name . '\\provider']; + $provider = $this->providers->get_by_class($provider_class); - foreach (array_keys($definitions) as $definition) + if (!$provider->is_available()) { - $options[$definition] = $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; + throw new storage_exception('STORAGE_ADAPTER_NOT_AVAILABLE'); } - return $options; + $adapter = $this->adapters->get_by_class($provider->get_adapter_class()); + $adapter->configure($options); + + return $adapter; } } diff --git a/phpBB/phpbb/storage/controller/attachment.php b/phpBB/phpbb/storage/controller/attachment.php index d9e108e098..9d84fceb45 100644 --- a/phpBB/phpbb/storage/controller/attachment.php +++ b/phpBB/phpbb/storage/controller/attachment.php @@ -22,6 +22,7 @@ use phpbb\event\dispatcher_interface; use phpbb\exception\http_exception; use phpbb\language\language; +use phpbb\mimetype\extension_guesser; use phpbb\request\request; use phpbb\storage\storage; use phpbb\user; @@ -66,15 +67,16 @@ class attachment extends controller * @param content_visibility $content_visibility * @param driver_interface $db * @param dispatcher_interface $dispatcher + * @param extension_guesser $extension_guesser * @param language $language * @param request $request * @param storage $storage * @param symfony_request $symfony_request * @param user $user */ - public function __construct(auth $auth, service $cache, config $config, content_visibility $content_visibility, driver_interface $db, dispatcher_interface $dispatcher, language $language, request $request, storage $storage, symfony_request $symfony_request, user $user) + public function __construct(auth $auth, service $cache, config $config, content_visibility $content_visibility, driver_interface $db, dispatcher_interface $dispatcher, extension_guesser $extension_guesser, language $language, request $request, storage $storage, symfony_request $symfony_request, user $user) { - parent::__construct($cache, $db, $storage, $symfony_request); + parent::__construct($cache, $db, $extension_guesser, $storage, $symfony_request); $this->auth = $auth; $this->config = $config; diff --git a/phpBB/phpbb/storage/controller/avatar.php b/phpBB/phpbb/storage/controller/avatar.php index 7055961230..720745e2cc 100644 --- a/phpBB/phpbb/storage/controller/avatar.php +++ b/phpBB/phpbb/storage/controller/avatar.php @@ -16,6 +16,7 @@ use phpbb\cache\service; use phpbb\config\config; use phpbb\db\driver\driver_interface; +use phpbb\mimetype\extension_guesser; use phpbb\storage\storage; use Symfony\Component\HttpFoundation\Request as symfony_request; use Symfony\Component\HttpFoundation\Response; @@ -39,12 +40,13 @@ class avatar extends controller * @param service $cache * @param config $config * @param driver_interface $db + * @param extension_guesser $extension_guesser * @param storage $storage * @param symfony_request $symfony_request */ - public function __construct(service $cache, config $config, driver_interface $db, storage $storage, symfony_request $symfony_request) + public function __construct(service $cache, config $config, driver_interface $db, extension_guesser $extension_guesser, storage $storage, symfony_request $symfony_request) { - parent::__construct($cache, $db, $storage, $symfony_request); + parent::__construct($cache, $db , $extension_guesser, $storage, $symfony_request); $this->config = $config; } diff --git a/phpBB/phpbb/storage/controller/controller.php b/phpBB/phpbb/storage/controller/controller.php index 54f95aa224..94af901eed 100644 --- a/phpBB/phpbb/storage/controller/controller.php +++ b/phpBB/phpbb/storage/controller/controller.php @@ -16,7 +16,8 @@ use phpbb\cache\service; use phpbb\db\driver\driver_interface; use phpbb\exception\http_exception; -use phpbb\storage\exception\exception; +use phpbb\mimetype\extension_guesser; +use phpbb\storage\exception\storage_exception; use phpbb\storage\storage; use Symfony\Component\HttpFoundation\Request as symfony_request; use Symfony\Component\HttpFoundation\Response; @@ -33,6 +34,9 @@ class controller /** @var driver_interface */ protected $db; + /** @var extension_guesser */ + protected $extension_guesser; + /** @var storage */ protected $storage; @@ -47,10 +51,11 @@ class controller * @param storage $storage * @param symfony_request $symfony_request */ - public function __construct(service $cache, driver_interface $db, storage $storage, symfony_request $symfony_request) + public function __construct(service $cache, driver_interface $db, extension_guesser $extension_guesser, storage $storage, symfony_request $symfony_request) { $this->cache = $cache; $this->db = $db; + $this->extension_guesser = $extension_guesser; $this->storage = $storage; $this->symfony_request = $symfony_request; } @@ -63,7 +68,7 @@ public function __construct(service $cache, driver_interface $db, storage $stora * @return Response a Symfony response object * * @throws http_exception when can't access $file - * @throws exception when there is an error reading the file + * @throws storage_exception when there is an error reading the file */ public function handle(string $file): Response { @@ -120,20 +125,18 @@ protected function file_exists(string $file): bool * @param string $file File path * * @return void - * @throws exception when there is an error reading the file + * @throws storage_exception when there is an error reading the file */ protected function prepare(StreamedResponse $response, string $file): void { - $file_info = $this->storage->file_info($file); - // Add Content-Type header if (!$response->headers->has('Content-Type')) { try { - $content_type = $file_info->get('mimetype'); + $content_type = $this->extension_guesser->guess($file); } - catch (exception $e) + catch (storage_exception $e) { $content_type = 'application/octet-stream'; } @@ -146,9 +149,9 @@ protected function prepare(StreamedResponse $response, string $file): void { try { - $response->headers->set('Content-Length', $file_info->get('size')); + $response->headers->set('Content-Length', $this->storage->file_size($file)); } - catch (exception $e) + catch (storage_exception $e) { // Just don't send this header } diff --git a/phpBB/phpbb/storage/exception/action_in_progress_exception.php b/phpBB/phpbb/storage/exception/action_in_progress_exception.php new file mode 100644 index 0000000000..1e6f01495e --- /dev/null +++ b/phpBB/phpbb/storage/exception/action_in_progress_exception.php @@ -0,0 +1,18 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage\exception; + +class action_in_progress_exception extends storage_exception +{ +} diff --git a/phpBB/phpbb/storage/exception/no_action_in_progress_exception.php b/phpBB/phpbb/storage/exception/no_action_in_progress_exception.php new file mode 100644 index 0000000000..5b5e63fc07 --- /dev/null +++ b/phpBB/phpbb/storage/exception/no_action_in_progress_exception.php @@ -0,0 +1,18 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage\exception; + +class no_action_in_progress_exception extends storage_exception +{ +} diff --git a/phpBB/phpbb/storage/exception/exception.php b/phpBB/phpbb/storage/exception/storage_exception.php similarity index 96% rename from phpBB/phpbb/storage/exception/exception.php rename to phpBB/phpbb/storage/exception/storage_exception.php index 8268530c16..08c0cfa4ef 100644 --- a/phpBB/phpbb/storage/exception/exception.php +++ b/phpBB/phpbb/storage/exception/storage_exception.php @@ -15,7 +15,7 @@ use phpbb\exception\runtime_exception; -class exception extends runtime_exception +class storage_exception extends runtime_exception { /** * Constructor diff --git a/phpBB/phpbb/storage/file_info.php b/phpBB/phpbb/storage/file_info.php deleted file mode 100644 index 2e93846838..0000000000 --- a/phpBB/phpbb/storage/file_info.php +++ /dev/null @@ -1,85 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - * For full copyright and license information, please see - * the docs/CREDITS.txt file. - * - */ - -namespace phpbb\storage; - -use phpbb\storage\exception\exception; -use phpbb\storage\adapter\adapter_interface; - -class file_info -{ - /** - * @var adapter_interface - */ - protected $adapter; - - /** - * Path of the file - * - * @var string - */ - protected $path; - - /** - * Stores the properties of $path file, so dont have to be consulted multiple times. - * For example, when you need the width of an image, using getimagesize() you get - * both dimensions, so you store both here, and when you get the height, you dont have - * to call getimagesize() again - * - * @var array - */ - protected $properties; - - /** - * Constructor - * - * @param adapter_interface $adapter - * @param string $path - */ - public function __construct(adapter_interface $adapter, $path) - { - $this->adapter = $adapter; - $this->path = $path; - $this->properties = []; - } - - /** - * Load propertys lazily - * - * @param string $name The property name. - * - * @return string Returns the property value - */ - public function get($name) - { - if (!isset($this->properties[$name])) - { - if (!method_exists($this->adapter, 'file_' . $name)) - { - throw new exception('STORAGE_METHOD_NOT_IMPLEMENTED'); - } - - $this->properties = array_merge($this->properties, call_user_func([$this->adapter, 'file_' . $name], $this->path)); - } - - return $this->properties[$name]; - } - - /** - * Alias of \phpbb\storage\file_info->get() - */ - public function __get($name) - { - return $this->get($name); - } -} diff --git a/phpBB/phpbb/storage/helper.php b/phpBB/phpbb/storage/helper.php new file mode 100644 index 0000000000..66921b60a7 --- /dev/null +++ b/phpBB/phpbb/storage/helper.php @@ -0,0 +1,230 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage; + +use phpbb\config\config; +use phpbb\di\service_collection; + +class helper +{ + /** @var config */ + protected $config; + + /** @var adapter_factory */ + protected $adapter_factory; + + /** @var state_helper */ + protected $state_helper; + + /** @var service_collection */ + protected $provider_collection; + + /** @var service_collection */ + protected $adapter_collection; + + /** + * Constructor + * + * @param config $config + * @param adapter_factory $adapter_factory + * @param state_helper $state_helper + * @param service_collection $provider_collection + * @param service_collection $adapter_collection + */ + public function __construct(config $config, adapter_factory $adapter_factory, state_helper $state_helper, service_collection $provider_collection, service_collection $adapter_collection) + { + $this->config = $config; + $this->adapter_factory = $adapter_factory; + $this->state_helper = $state_helper; + $this->provider_collection = $provider_collection; + $this->adapter_collection = $adapter_collection; + } + + /** + * Get adapter definitions from a provider + * + * @param string $provider_class Provider class + * + * @return array Adapter definitions + */ + public function get_provider_options(string $provider_class) : array + { + return $this->provider_collection->get_by_class($provider_class)->get_options(); + } + + /** + * Get the current provider from config + * + * @param string $storage_name Storage name + * + * @return string The current provider + */ + public function get_current_provider(string $storage_name) : string + { + return (string) $this->config['storage\\' . $storage_name . '\\provider']; + } + + /** + * Get the current value of the definition of a storage from config + * + * @param string $storage_name Storage name + * @param string $definition Definition + * + * @return string Definition value + */ + public function get_current_definition(string $storage_name, string $definition) : string + { + return (string) $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; + } + + /** + * Get current storage adapter + * + * @param string $storage_name Storage adapter name + * + * @return object Storage adapter instance + */ + public function get_current_adapter(string $storage_name): object + { + static $adapters = []; + + if (!isset($adapters[$storage_name])) + { + $adapters[$storage_name] = $this->adapter_factory->get($storage_name); + } + + return $adapters[$storage_name]; + } + + /** + * Get new storage adapter + * + * @param string $storage_name + * + * @return mixed Storage adapter instance + */ + public function get_new_adapter(string $storage_name): mixed + { + static $adapters = []; + + if (!isset($adapters[$storage_name])) + { + $provider_class = $this->state_helper->new_provider($storage_name); + $definitions = array_keys($this->get_provider_options($provider_class)); + + $options = []; + foreach ($definitions as $definition) + { + $options[$definition] = $this->state_helper->new_definition_value($storage_name, $definition); + } + + $adapters[$storage_name] = $this->adapter_factory->get_with_options($storage_name, $options); + } + + return $adapters[$storage_name]; + } + + /** + * Delete configuration options for a given storage + * + * @param string $storage_name + * + * @return void + */ + public function delete_storage_options(string $storage_name): void + { + $provider = $this->get_current_provider($storage_name); + $options = $this->get_provider_options($provider); + + foreach (array_keys($options) as $definition) + { + $this->config->delete('storage\\' . $storage_name . '\\config\\' . $definition); + } + } + + /** + * Set a provider in configuration for a given storage + * + * @param string $storage_name + * @param string $provider + * + * @return void + */ + public function set_storage_provider(string $storage_name, string $provider): void + { + $this->config->set('storage\\' . $storage_name . '\\provider', $provider); + } + + /** + * Set storage options in configuration for a given storage + * + * @param string $storage_name + * @param string $definition + * @param string $value + * + * @return void + */ + public function set_storage_definition(string $storage_name, string $definition, string $value): void + { + $this->config->set('storage\\' . $storage_name . '\\config\\' . $definition, $value); + } + + /** + * Copy a file from the current adapter to the new adapter + * + * @param $storage_name + * @param $file + * + * @return void + */ + public function copy_file_to_new_adapter($storage_name, $file): void + { + $current_adapter = $this->get_current_adapter($storage_name); + $new_adapter = $this->get_new_adapter($storage_name); + + $stream = $current_adapter->read_stream($file); + $new_adapter->write_stream($file, $stream); + + if (is_resource($stream)) + { + fclose($stream); + } + } + + + /** + * Updates a storage with the info provided in the form (that is stored in the state at this point) + * + * @param string $storage_name Storage name + */ + public function update_storage_config(string $storage_name) : void + { + // Remove old storage config + $this->delete_storage_options($storage_name); + + // Update provider + $new_provider = $this->state_helper->new_provider($storage_name); + $this->set_storage_provider($storage_name, $new_provider); + + // Set new storage config + $new_options = $this->get_provider_options($new_provider); + + foreach (array_keys($new_options) as $definition) + { + $new_definition_value = $this->state_helper->new_definition_value($storage_name, $definition); + $this->set_storage_definition($storage_name, $definition, $new_definition_value); + } + } + +} diff --git a/phpBB/phpbb/storage/provider/local.php b/phpBB/phpbb/storage/provider/local.php index 3bec0c5ce5..c6338e0c31 100644 --- a/phpBB/phpbb/storage/provider/local.php +++ b/phpBB/phpbb/storage/provider/local.php @@ -37,7 +37,10 @@ public function get_adapter_class(): string public function get_options() { return [ - 'path' => ['type' => 'text'], + 'path' => [ + 'tag' => 'input', + 'type' => 'text', + ], ]; } diff --git a/phpBB/phpbb/storage/state_helper.php b/phpBB/phpbb/storage/state_helper.php new file mode 100644 index 0000000000..82523f0aa0 --- /dev/null +++ b/phpBB/phpbb/storage/state_helper.php @@ -0,0 +1,278 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage; + +use phpbb\config\config; +use phpbb\config\db_text; +use phpbb\di\service_collection; +use phpbb\request\request; +use phpbb\storage\exception\action_in_progress_exception; +use phpbb\storage\exception\no_action_in_progress_exception; + +class state_helper +{ + /** @var config */ + protected $config; + + /** @var db_text $config_text */ + protected $config_text; + + /** @var service_collection */ + protected $provider_collection; + + /** + * @param config $config + * @param db_text $config_text + * @param service_collection $provider_collection + */ + public function __construct(config $config, db_text $config_text, service_collection $provider_collection) + { + $this->config = $config; + $this->config_text = $config_text; + $this->provider_collection = $provider_collection; + } + + /** + * Returns if there is an action in progress + * + * @return bool + */ + public function is_action_in_progress(): bool + { + return !empty(json_decode($this->config_text->get('storage_update_state'), true)); + } + + /** + * Get new provider for the specified storage + * + * @param string $storage_name + * + * @return string + */ + public function new_provider(string $storage_name): string + { + $state = $this->load_state(); + + return $state['storages'][$storage_name]['provider']; + } + + /** + * Get new definition value for the specified storage + * + * @param string $storage_name + * @param string $definition + * + * @return string + */ + public function new_definition_value(string $storage_name, string $definition): string + { + $state = $this->load_state(); + + return $state['storages'][$storage_name]['config'][$definition]; + } + + /** + * Get the update type + * + * @return update_type + */ + public function update_type(): update_type + { + $state = $this->load_state(); + + return update_type::from($state['update_type']); + } + + /** + * Get the current storage index + * + * @return int + */ + public function storage_index(): int + { + $state = $this->load_state(); + + return $state['storage_index']; + } + + /** + * Update the storage index + * + * @param int $storage_index + * + * @return void + */ + public function set_storage_index(int $storage_index): void + { + $state = $this->load_state(); + + $state['storage_index'] = $storage_index; + + $this->save_state($state); + } + + /** + * Get the current remove storage index + * + * @return int + */ + public function remove_storage_index(): int + { + $state = $this->load_state(); + + return $state['remove_storage_index']; + } + + /** + * Update the remove storage index + * + * @param int $storage_index + * + * @return void + */ + public function set_remove_storage_index(int $storage_index): void + { + $state = $this->load_state(); + + $state['remove_storage_index'] = $storage_index; + + $this->save_state($state); + } + + /** + * Get the file index + * + * @return int + */ + public function file_index(): int + { + $state = $this->load_state(); + + return $state['file_index']; + } + + /** + * Set the file index + * + * @param int $file_index + * @return void + */ + public function set_file_index(int $file_index): void + { + $state = $this->load_state(); + + $state['file_index'] = $file_index; + + $this->save_state($state); + } + + /** + * Get the storage names to be updated + * + * @return array + */ + public function storages(): array + { + $state = $this->load_state(); + + return array_keys($state['storages']); + } + + /** + * Start a indexing or delete process. + * + * @param update_type $update_type + * @param array $modified_storages + * @param request $request + * + * @throws action_in_progress_exception If there is an action in progress + * @throws \JsonException + */ + public function init(update_type $update_type, array $modified_storages, request $request): void + { + // Is not possible to start a new process when there is one already running + if ($this->is_action_in_progress()) + { + throw new action_in_progress_exception(); + } + + $state = [ + // Save the value of the checkbox, to remove all files from the + // old storage once they have been successfully moved + 'update_type' => $update_type->value, + 'storages' => [], + 'storage_index' => 0, + 'file_index' => 0, + 'remove_storage_index' => 0, + ]; + + // Save in the state the selected storages and their new configuration + foreach ($modified_storages as $storage_name) + { + $state['storages'][$storage_name] = []; + + $state['storages'][$storage_name]['provider'] = $request->variable([$storage_name, 'provider'], ''); + + $options = $this->provider_collection->get_by_class($request->variable([$storage_name, 'provider'], ''))->get_options(); + + foreach (array_keys($options) as $definition) + { + /** @psalm-suppress InvalidArrayOffset */ + $state['storages'][$storage_name]['config'][$definition] = $request->variable([$storage_name, $definition], ''); + } + } + + $this->save_state($state); + } + + /** + * Clear the state + * + * @throws \JsonException + */ + public function clear_state(): void + { + $this->save_state(); + } + + /** + * Load the state from the database + * + * @return array + * + * @throws no_action_in_progress_exception If there is no action in progress + */ + private function load_state(): array + { + // Is not possible to execute an action over state if is empty + if (!$this->is_action_in_progress()) + { + throw new no_action_in_progress_exception(); + } + + return json_decode($this->config_text->get('storage_update_state'), true) ?? []; + } + + /** + * Save the specified state in the database + * + * @param array $state + * + * @throws \JsonException + */ + private function save_state(array $state = []): void + { + $this->config_text->set('storage_update_state', json_encode($state, JSON_THROW_ON_ERROR)); + } +} diff --git a/phpBB/phpbb/storage/storage.php b/phpBB/phpbb/storage/storage.php index 8e4620ab23..fa1ca0d204 100644 --- a/phpBB/phpbb/storage/storage.php +++ b/phpBB/phpbb/storage/storage.php @@ -15,7 +15,8 @@ use phpbb\cache\driver\driver_interface as cache; use phpbb\db\driver\driver_interface as db; -use phpbb\storage\exception\exception; +use phpbb\storage\adapter\adapter_interface; +use phpbb\storage\exception\storage_exception; /** * Experimental @@ -23,7 +24,7 @@ class storage { /** - * @var \phpbb\storage\adapter\adapter_interface + * @var adapter_interface */ protected $adapter; @@ -39,7 +40,7 @@ class storage protected $cache; /** - * @var \phpbb\storage\adapter_factory + * @var adapter_factory */ protected $factory; @@ -58,11 +59,11 @@ class storage * * @param db $db * @param cache $cache - * @param \phpbb\storage\adapter_factory $factory + * @param adapter_factory $factory * @param string $storage_name * @param string $storage_table */ - public function __construct(db $db, cache $cache, adapter_factory $factory, $storage_name, $storage_table) + public function __construct(db $db, cache $cache, adapter_factory $factory, string $storage_name, string $storage_table) { $this->db = $db; $this->cache = $cache; @@ -76,7 +77,7 @@ public function __construct(db $db, cache $cache, adapter_factory $factory, $sto * * @return string */ - public function get_name() + public function get_name(): string { return $this->storage_name; } @@ -84,9 +85,9 @@ public function get_name() /** * Returns an adapter instance * - * @return \phpbb\storage\adapter\adapter_interface + * @return adapter_interface */ - protected function get_adapter() + protected function get_adapter(): mixed { if ($this->adapter === null) { @@ -102,14 +103,14 @@ protected function get_adapter() * @param string $path The file to be written to. * @param string $content The data to write into the file. * - * @throws exception When the file already exists + * @throws storage_exception When the file already exists * When the file cannot be written */ - public function put_contents($path, $content) + public function put_contents(string $path, string $content): void { if ($this->exists($path)) { - throw new exception('STORAGE_FILE_EXISTS', $path); + throw new storage_exception('STORAGE_FILE_EXISTS', $path); } $this->get_adapter()->put_contents($path, $content); @@ -121,17 +122,17 @@ public function put_contents($path, $content) * * @param string $path The file to read * - * @throws exception When the file doesn't exist - * When cannot read file contents + * @return string Returns file contents * - * @return string Returns file contents + * @throws storage_exception When the file doesn't exist + * When cannot read file contents * */ - public function get_contents($path) + public function get_contents(string $path): string { if (!$this->exists($path)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); } return $this->get_adapter()->get_contents($path); @@ -145,7 +146,7 @@ public function get_contents($path) * * @return bool Returns true if the file/directory exist, false otherwise */ - public function exists($path, $full_check = false) + public function exists(string $path, bool $full_check = false): bool { return ($this->is_tracked($path) && (!$full_check || $this->get_adapter()->exists($path))); } @@ -153,16 +154,16 @@ public function exists($path, $full_check = false) /** * Removes files or directories * - * @param string $path file/directory to remove + * @param string $path file/directory to remove * - * @throws exception When removal fails + * @throws storage_exception When removal fails * When the file doesn't exist */ - public function delete($path) + public function delete(string $path): void { if (!$this->exists($path)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); } $this->get_adapter()->delete($path); @@ -172,23 +173,23 @@ public function delete($path) /** * Rename a file or a directory * - * @param string $path_orig The original file/direcotry - * @param string $path_dest The target file/directory + * @param string $path_orig The original file/direcotry + * @param string $path_dest The target file/directory * - * @throws exception When the file doesn't exist + * @throws storage_exception When the file doesn't exist * When target exists * When file/directory cannot be renamed */ - public function rename($path_orig, $path_dest) + public function rename(string $path_orig, string $path_dest): void { if (!$this->exists($path_orig)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path_orig); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path_orig); } if ($this->exists($path_dest)) { - throw new exception('STORAGE_FILE_EXISTS', $path_dest); + throw new storage_exception('STORAGE_FILE_EXISTS', $path_dest); } $this->get_adapter()->rename($path_orig, $path_dest); @@ -198,23 +199,23 @@ public function rename($path_orig, $path_dest) /** * Copies a file * - * @param string $path_orig The original filename - * @param string $path_dest The target filename + * @param string $path_orig The original filename + * @param string $path_dest The target filename * - * @throws exception When the file doesn't exist + * @throws storage_exception When the file doesn't exist * When target exists * When the file cannot be copied */ - public function copy($path_orig, $path_dest) + public function copy(string $path_orig, string $path_dest): void { if (!$this->exists($path_orig)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path_orig); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path_orig); } if ($this->exists($path_dest)) { - throw new exception('STORAGE_FILE_EXISTS', $path_dest); + throw new storage_exception('STORAGE_FILE_EXISTS', $path_dest); } $this->get_adapter()->copy($path_orig, $path_dest); @@ -224,18 +225,18 @@ public function copy($path_orig, $path_dest) /** * Reads a file as a stream * - * @param string $path File to read + * @param string $path File to read * - * @throws exception When the file doesn't exist + * @return resource Returns a file pointer + * @throws storage_exception When the file doesn't exist * When unable to open file * - * @return resource Returns a file pointer */ - public function read_stream($path) + public function read_stream(string $path) { if (!$this->exists($path)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); } $stream = null; @@ -259,22 +260,22 @@ public function read_stream($path) /** * Writes a new file using a stream * - * @param string $path The target file + * @param string $path The target file * @param resource $resource The resource * - * @throws exception When the file exist + * @throws storage_exception When the file exist * When target file cannot be created */ - public function write_stream($path, $resource) + public function write_stream(string $path, $resource): void { if ($this->exists($path)) { - throw new exception('STORAGE_FILE_EXISTS', $path); + throw new storage_exception('STORAGE_FILE_EXISTS', $path); } if (!is_resource($resource)) { - throw new exception('STORAGE_INVALID_RESOURCE'); + throw new storage_exception('STORAGE_INVALID_RESOURCE'); } $adapter = $this->get_adapter(); @@ -294,14 +295,14 @@ public function write_stream($path, $resource) /** * Track file in database * - * @param string $path The target file - * @param bool $update Update file size when already tracked + * @param string $path The target file + * @param bool $update Update file size when already tracked */ - public function track_file($path, $update = false) + public function track_file(string $path, bool $update = false): void { if (!$this->get_adapter()->exists($path)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); } $sql_ary = array( @@ -318,20 +319,15 @@ public function track_file($path, $update = false) if (!$row) { - // Don't call the file_info method, because it check's if the file is tracked - // and is not (for now). This method check if the file exists using the adapter - // at the beginning. - $file = new file_info($this->get_adapter(), $path); - $sql_ary['filesize'] = $file->size; + $sql_ary['filesize'] = $this->get_adapter()->file_size($path); $sql = 'INSERT INTO ' . $this->storage_table . $this->db->sql_build_array('INSERT', $sql_ary); $this->db->sql_query($sql); } else if ($update) { - $file = $this->file_info($path); $sql = 'UPDATE ' . $this->storage_table . ' - SET filesize = ' . $file->size . ' + SET filesize = ' . $this->get_adapter()->file_size($path) . ' WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); $this->db->sql_query($sql); } @@ -363,11 +359,11 @@ public function untrack_file($path) /** * Check if a file is tracked * - * @param string $path The file + * @param string $path The file * * @return bool True if file is tracked */ - public function is_tracked($path) + public function is_tracked(string $path): bool { $sql_ary = array( 'file_path' => $path, @@ -380,16 +376,16 @@ public function is_tracked($path) $row = $this->db->sql_fetchrow($result); $this->db->sql_freeresult($result); - return ($row) ? true : false; + return $row !== false; } /** * Rename tracked file * - * @param string $path_orig The original file/direcotry - * @param string $path_dest The target file/directory + * @param string $path_orig The original file/direcotry + * @param string $path_dest The target file/directory */ - protected function track_rename($path_orig, $path_dest) + protected function track_rename(string $path_orig, string $path_dest): void { $sql = 'UPDATE ' . $this->storage_table . " SET file_path = '" . $this->db->sql_escape($path_dest) . "' @@ -399,36 +395,28 @@ protected function track_rename($path_orig, $path_dest) } /** - * Get file info + * Get file size in bytes * - * @param string $path The file + * @param string $path The file * - * @throws exception When the adapter doesn't implement the method - * When the file doesn't exist + * @return int Size in bytes. * - * @return \phpbb\storage\file_info Returns file_info object + * @throws storage_exception When unable to retrieve file size */ - public function file_info($path) + public function file_size(string $path): int { - if (!$this->exists($path)) - { - throw new exception('STORAGE_FILE_NO_EXIST', $path); - } + $sql_ary = array( + 'file_path' => $path, + 'storage' => $this->get_name(), + ); - return new file_info($this->get_adapter(), $path); - } + $sql = 'SELECT filesize FROM ' . $this->storage_table . ' + WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); - /** - * Get direct link - * - * @param string $path The file - * - * @return string Returns link. - * - */ - public function get_link($path) - { - return $this->get_adapter()->get_link($path); + return $row !== false && !empty($row['filesize']) ? $row['filesize'] : $this->get_adapter()->file_size($path); } /** @@ -436,7 +424,7 @@ public function get_link($path) * * @return int Size in bytes */ - public function get_size() + public function get_size(): int { $total_size = $this->cache->get('_storage_' . $this->get_name() . '_totalsize'); @@ -461,7 +449,7 @@ public function get_size() * * @return int Number of files */ - public function get_num_files() + public function get_num_files(): int { $number_files = $this->cache->get('_storage_' . $this->get_name() . '_numfiles'); @@ -484,12 +472,13 @@ public function get_num_files() /** * Get space available in bytes * - * @throws exception When unable to retrieve available storage space + * @return float Returns available space + * @throws storage_exception When unable to retrieve available storage space * - * @return float Returns available space */ public function free_space() { return $this->get_adapter()->free_space(); } + } diff --git a/phpBB/phpbb/storage/stream_interface.php b/phpBB/phpbb/storage/stream_interface.php index 9687a2d910..424ffcb95c 100644 --- a/phpBB/phpbb/storage/stream_interface.php +++ b/phpBB/phpbb/storage/stream_interface.php @@ -13,7 +13,7 @@ namespace phpbb\storage; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; interface stream_interface { @@ -23,7 +23,7 @@ interface stream_interface * @param string $path File to read * * @return resource Returns a file pointer - * @throws exception When unable to open file + * @throws storage_exception When unable to open file */ public function read_stream(string $path); @@ -34,7 +34,7 @@ public function read_stream(string $path); * @param resource $resource The resource * * @return void - * @throws exception When target file exists + * @throws storage_exception When target file exists * When target file cannot be created */ public function write_stream(string $path, $resource): void; diff --git a/phpBB/phpbb/storage/update_type.php b/phpBB/phpbb/storage/update_type.php new file mode 100644 index 0000000000..32f25ad13d --- /dev/null +++ b/phpBB/phpbb/storage/update_type.php @@ -0,0 +1,21 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage; + +enum update_type: int +{ + case CONFIG = 0; + case COPY = 1; + case MOVE = 2; +} diff --git a/phpBB/phpbb/template/twig/extension.php b/phpBB/phpbb/template/twig/extension.php index 2e920dbe4b..7fde9e2cac 100644 --- a/phpBB/phpbb/template/twig/extension.php +++ b/phpBB/phpbb/template/twig/extension.php @@ -93,6 +93,7 @@ public function getFunctions() new \Twig\TwigFunction('lang', array($this, 'lang')), new \Twig\TwigFunction('lang_defined', array($this, 'lang_defined')), new \Twig\TwigFunction('lang_js', [$this, 'lang_js']), + new \Twig\TwigFunction('lang_raw', [$this, 'lang_raw']), new \Twig\TwigFunction('get_class', 'get_class'), ); } @@ -214,4 +215,16 @@ public function lang_js(): string return twig_escape_filter($this->environment, call_user_func_array([$this, 'lang'], $args), 'js'); } + + /** + * Get raw value associated with lang key + * + * @param string $key + * + * @return array|string Raw value associated with lang key + */ + public function lang_raw(string $key): array|string + { + return call_user_func_array(array($this->language, 'lang_raw'), [$key]); + } } diff --git a/phpBB/phpbb/template/twig/extension/avatar.php b/phpBB/phpbb/template/twig/extension/avatar.php index fb7ec92655..238caddaf5 100644 --- a/phpBB/phpbb/template/twig/extension/avatar.php +++ b/phpBB/phpbb/template/twig/extension/avatar.php @@ -13,16 +13,36 @@ namespace phpbb\template\twig\extension; +use phpbb\avatar\helper; +use phpbb\avatar\manager; +use phpbb\template\twig\environment; +use Twig\Error\Error; use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; class avatar extends AbstractExtension { + /** + * @var helper + */ + private $avatar_helper; + + /** + * Constructor for avatar extension + * + * @param helper $avatar_helper + */ + public function __construct(helper $avatar_helper) + { + $this->avatar_helper = $avatar_helper; + } + /** * Get the name of this extension * * @return string */ - public function getName() + public function getName(): string { return 'avatar'; } @@ -30,13 +50,13 @@ public function getName() /** * Returns a list of global functions to add to the existing list. * - * @return \Twig\TwigFunction[] An array of global functions + * @return TwigFunction[] An array of global functions */ public function getFunctions(): array { - return array( - new \Twig\TwigFunction('avatar', array($this, 'get_avatar')), - ); + return [ + new TwigFunction('avatar', [$this, 'get_avatar'], ['needs_environment' => true]), + ]; } /** @@ -48,35 +68,30 @@ public function getFunctions(): array * The mode and row (group_row or user_row) are required. * The other fields (alt|ignore_config|lazy) are optional. * - * @uses \phpbb_get_group_avatar() - * @uses \phpbb_get_user_avatar() - * * @return string The avatar HTML for the specified mode */ - public function get_avatar() + public function get_avatar(environment $environment, string $mode, array $row, ?string $alt, ?bool $ignore_config, ?bool $lazy): string { - $args = func_get_args(); + $alt = $alt ?? false; + $ignore_config = $ignore_config ?? false; + $lazy = $lazy ?? false; + $row = manager::clean_row($row, $mode); + $avatar = $this->avatar_helper->get_avatar($row, $alt, $ignore_config, $lazy); - $mode = (string) $args[0]; - $row = (array) $args[1]; - $alt = isset($args[2]) ? (string) $args[2] : false; - $ignore_config = isset($args[3]) ? (bool) $args[3] : false; - $lazy = isset($args[4]) ? (bool) $args[4] : false; - - // To prevent having to redefine alt attribute ('USER_AVATAR'|'GROUP_AVATAR'), we check if an alternative has been provided - switch ($mode) + try { - case 'group': - return $alt ? phpbb_get_group_avatar($row, $alt, $ignore_config, $lazy) : phpbb_get_group_avatar($row); - break; - - case 'user': - return $alt ? phpbb_get_user_avatar($row, $alt, $ignore_config, $lazy) : phpbb_get_user_avatar($row); - break; - - default: - return ''; - break; + return $environment->render('macros/avatar.twig', [ + 'SRC' => $avatar['lazy'] ? $this->avatar_helper->get_no_avatar_source() : $avatar['src'], + 'DATA_SRC' => $avatar['lazy'] ? $avatar['src'] : '', + 'WIDTH' => $avatar['width'], + 'HEIGHT' => $avatar['height'], + 'TITLE' => $avatar['title'], + 'LAZY' => $avatar['lazy'], + ]); + } + catch (Error $e) + { + return ''; } } } diff --git a/phpBB/phpbb/template/twig/extension/forms.php b/phpBB/phpbb/template/twig/extension/forms.php index 4d8de94b9d..b146aacfb9 100644 --- a/phpBB/phpbb/template/twig/extension/forms.php +++ b/phpBB/phpbb/template/twig/extension/forms.php @@ -129,6 +129,7 @@ public function input(environment $environment, array $form_data): string 'MAX' => (int) ($form_data['max'] ?? 0), 'STEP' => (int) ($form_data['step'] ?? 0), 'CHECKED' => (bool) ($form_data['checked'] ?? false), + 'DISABLED' => (bool) ($form_data['disabled'] ?? false), 'VALUE' => (string) ($form_data['value']), ]); } @@ -151,10 +152,7 @@ public function radio_buttons(environment $environment, array $form_data): strin try { return $environment->render('macros/forms/radio_buttons.twig', [ - 'FIRST_BUTTON' => $form_data['buttons'][0], - 'FIRST_BUTTON_LABEL' => $form_data['buttons'][0]['label'], - 'SECOND_BUTTON' => $form_data['buttons'][1], - 'SECOND_BUTTON_LABEL' => $form_data['buttons'][1]['label'], + 'BUTTONS' => $form_data['buttons'], ]); } catch (\Twig\Error\Error $e) @@ -179,12 +177,13 @@ public function select(environment $environment, array $form_data): string 'CLASS' => (string) ($form_data['class'] ?? ''), 'ID' => (string) ($form_data['id'] ?? ''), 'DATA' => $form_data['data'] ?? [], - 'NAME' => (string) $form_data['name'], + 'NAME' => (string) ($form_data['name'] ?? ''), 'TOGGLEABLE' => (bool) ($form_data['toggleable'] ?? false), 'OPTIONS' => $form_data['options'] ?? [], 'GROUP_ONLY' => (bool) ($form_data['group_only'] ?? false), 'SIZE' => (int) ($form_data['size'] ?? 0), 'MULTIPLE' => (bool) ($form_data['multiple'] ?? false), + 'ONCHANGE' => (string) ($form_data['onchange'] ?? ''), ]); } catch (\Twig\Error\Error $e) @@ -206,13 +205,14 @@ public function textarea(environment $environment, array $form_data): string try { return $environment->render('macros/forms/textarea.twig', [ - 'CLASS' => (string) ($form_data['class'] ?? ''), + 'CLASS' => (string) ($form_data['class'] ?? ''), 'ID' => (string) $form_data['id'], - 'DATA' => $form_data['data'] ?? [], + 'DATA' => $form_data['data'] ?? [], 'NAME' => (string) $form_data['name'], - 'ROWS' => (int) $form_data['rows'], - 'COLS' => (int) $form_data['cols'], - 'CONTENT' => (string) $form_data['content'], + 'ROWS' => (int) ($form_data['rows'] ?? ''), + 'COLS' => (int) ($form_data['cols'] ?? ''), + 'CONTENT' => (string) ($form_data['content'] ?? ''), + 'PLACEHOLDER' => (string) ($form_data['placeholder'] ?? ''), ]); } catch (\Twig\Error\Error $e) diff --git a/phpBB/phpbb/textformatter/parser_interface.php b/phpBB/phpbb/textformatter/parser_interface.php index ad611fb5b4..1430bfd5d3 100644 --- a/phpBB/phpbb/textformatter/parser_interface.php +++ b/phpBB/phpbb/textformatter/parser_interface.php @@ -91,8 +91,6 @@ public function get_errors(); * Set a variable to be used by the parser * * - max_font_size - * - max_img_height - * - max_img_width * - max_smilies * - max_urls * diff --git a/phpBB/phpbb/textformatter/s9e/factory.php b/phpBB/phpbb/textformatter/s9e/factory.php index 7195f95915..78f8cbe6c6 100644 --- a/phpBB/phpbb/textformatter/s9e/factory.php +++ b/phpBB/phpbb/textformatter/s9e/factory.php @@ -355,20 +355,10 @@ function ($m) // Register some vars with a default value. Those should be set at runtime by whatever calls // the parser $configurator->registeredVars['max_font_size'] = 0; - $configurator->registeredVars['max_img_height'] = 0; - $configurator->registeredVars['max_img_width'] = 0; // Load the Emoji plugin and modify its tag's template to obey viewsmilies $tag = $configurator->Emoji->getTag(); - $tag->template = ' - - {.} - - - {.} - - '; - $tag->template = '' . str_replace('class="emoji"', 'class="emoji smilies"', $tag->template) . ''; + $tag->template = ''; /** * Modify the s9e\TextFormatter configurator after the default settings are set diff --git a/phpBB/phpbb/textreparser/base.php b/phpBB/phpbb/textreparser/base.php index 84209a4127..1c26563f05 100644 --- a/phpBB/phpbb/textreparser/base.php +++ b/phpBB/phpbb/textreparser/base.php @@ -219,11 +219,11 @@ protected function guess_smilies(array $record) /** * {@inheritdoc} */ - public function reparse_range($min_id, $max_id) + public function reparse_range($min_id, $max_id, bool $force_bbcode_reparsing = false) { foreach ($this->get_records_by_range($min_id, $max_id) as $record) { - $this->reparse_record($record); + $this->reparse_record($record, $force_bbcode_reparsing); } } @@ -231,16 +231,17 @@ public function reparse_range($min_id, $max_id) * Reparse given record * * @param array $record Associative array containing the record's data + * @param bool $force_bbcode_reparsing Flag indicating if BBCode should be reparsed unconditionally */ - protected function reparse_record(array $record) + protected function reparse_record(array $record, bool $force_bbcode_reparsing = false) { // Guess magic URL state based on actual record content before adding fields $record['enable_magic_url'] = $this->guess_magic_url($record); $record = $this->add_missing_fields($record); - $flags = ($record['enable_bbcode']) ? OPTION_FLAG_BBCODE : 0; - $flags |= ($record['enable_smilies']) ? OPTION_FLAG_SMILIES : 0; - $flags |= ($record['enable_magic_url']) ? OPTION_FLAG_LINKS : 0; + $flags = ($record['enable_bbcode'] || $force_bbcode_reparsing) ? OPTION_FLAG_BBCODE : 0; + $flags |= ($record['enable_smilies'] || $force_bbcode_reparsing) ? OPTION_FLAG_SMILIES : 0; + $flags |= ($record['enable_magic_url'] || $force_bbcode_reparsing) ? OPTION_FLAG_LINKS : 0; $unparsed = array_merge( $record, generate_text_for_edit($record['text'], $record['bbcode_uid'], $flags) @@ -256,12 +257,12 @@ protected function reparse_record(array $record) $unparsed['bbcode_uid'], $bitfield, $flags, - $unparsed['enable_bbcode'], - $unparsed['enable_magic_url'], - $unparsed['enable_smilies'], - $unparsed['enable_img_bbcode'], - $unparsed['enable_quote_bbcode'], - $unparsed['enable_url_bbcode'], + $unparsed['enable_bbcode'] || $force_bbcode_reparsing, + $unparsed['enable_magic_url'] || $force_bbcode_reparsing, + $unparsed['enable_smilies'] || $force_bbcode_reparsing, + $unparsed['enable_img_bbcode'] || $force_bbcode_reparsing, + $unparsed['enable_quote_bbcode'] || $force_bbcode_reparsing, + $unparsed['enable_url_bbcode'] || $force_bbcode_reparsing, 'text_reparser.' . $this->get_name() ); diff --git a/phpBB/phpbb/textreparser/reparser_interface.php b/phpBB/phpbb/textreparser/reparser_interface.php index 912de10058..99e1c26778 100644 --- a/phpBB/phpbb/textreparser/reparser_interface.php +++ b/phpBB/phpbb/textreparser/reparser_interface.php @@ -41,6 +41,7 @@ public function set_name($name); * * @param integer $min_id Lower bound * @param integer $max_id Upper bound + * @param bool $force_bbcode_reparsing Flag indicating if BBCode should be reparsed unconditionally */ - public function reparse_range($min_id, $max_id); + public function reparse_range($min_id, $max_id, bool $force_bbcode_reparsing = false); } diff --git a/phpBB/phpbb/ucp/controller/delete_cookies.php b/phpBB/phpbb/ucp/controller/delete_cookies.php new file mode 100644 index 0000000000..869227abea --- /dev/null +++ b/phpBB/phpbb/ucp/controller/delete_cookies.php @@ -0,0 +1,134 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ucp\controller; + +use phpbb\config\config; +use phpbb\event\dispatcher_interface; +use phpbb\language\language; +use phpbb\request\request_interface; +use phpbb\user; + +class delete_cookies +{ + /** @var config */ + private $config; + + /** @var dispatcher_interface */ + private $dispatcher; + + /** @var language */ + private $language; + + /** @var request_interface */ + private $request; + + /** @var user */ + private $user; + + /** @var string phpBB root path */ + private $phpbb_root_path; + + /** @var string PHP extension */ + private $php_ext; + + /** + * Constructor for delete_cookies controller + * + * @param config $config + * @param dispatcher_interface $dispatcher + * @param language $language + * @param request_interface $request + * @param user $user + */ + public function __construct(config $config, dispatcher_interface $dispatcher, language $language, request_interface $request, user $user, string $phpbb_root_path, string $php_ext) + { + $this->config = $config; + $this->dispatcher = $dispatcher; + $this->language = $language; + $this->request = $request; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Handle delete cookies requests + * + * @return void + */ + public function handle() + { + $this->language->add_lang(['ucp']); + + // Delete Cookies with dynamic names (do NOT delete poll cookies) + if (confirm_box(true)) + { + $set_time = time() - 31536000; + + foreach ($this->request->variable_names(request_interface::COOKIE) as $cookie_name) + { + // Only delete board cookies + if (strpos($cookie_name, $this->config['cookie_name'] . '_') !== 0) + { + continue; + } + + $cookie_name = str_replace($this->config['cookie_name'] . '_', '', $cookie_name); + + /** + * Event to save custom cookies from deletion + * + * @event core.ucp_delete_cookies + * @var string cookie_name Cookie name to checking + * @var bool retain_cookie Do we retain our cookie or not, true if retain + * @since 3.1.3-RC1 + * @changed 3.3.13-RC1 Moved to new delete_cookies controller + */ + $retain_cookie = false; + $vars = ['cookie_name', 'retain_cookie']; + extract($this->dispatcher->trigger_event('core.ucp_delete_cookies', compact($vars))); + if ($retain_cookie) + { + continue; + } + + // Polls are stored as {cookie_name}_poll_{topic_id}, cookie_name_ got removed, therefore checking for poll_ + if (strpos($cookie_name, 'poll_') !== 0) + { + $this->user->set_cookie($cookie_name, '', $set_time); + } + } + + $this->user->set_cookie('track', '', $set_time); + $this->user->set_cookie('u', '', $set_time); + $this->user->set_cookie('k', '', $set_time); + $this->user->set_cookie('sid', '', $set_time); + + // We destroy the session here, the user will be logged out nevertheless + $this->user->session_kill(); + $this->user->session_begin(); + + meta_refresh(3, append_sid("{$this->phpbb_root_path}index.$this->php_ext")); + + $message = $this->language->lang('COOKIES_DELETED') . '

        ' . $this->language->lang('RETURN_INDEX', 'phpbb_root_path}index.$this->php_ext") . '">', ''); + trigger_error($message); + } + else + { + confirm_box(false, 'DELETE_COOKIES', ''); + } + + redirect(append_sid("{$this->phpbb_root_path}index.$this->php_ext")); + } +} diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index a7ebbffae8..313965db63 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -19,10 +19,13 @@ use phpbb\exception\http_exception; use phpbb\form\form_helper; use phpbb\json\sanitizer as json_sanitizer; +use phpbb\language\language; +use phpbb\notification\manager; use phpbb\path_helper; use phpbb\request\request_interface; use phpbb\symfony_request; use phpbb\user; +use phpbb\user_loader; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Twig\Environment; @@ -47,12 +50,21 @@ class webpush /** @var form_helper */ protected $form_helper; + /** @var language */ + protected $language; + + /** @var manager */ + protected $notification_manager; + /** @var path_helper */ protected $path_helper; /** @var request_interface */ protected $request; + /** @var user_loader */ + protected $user_loader; + /** @var user */ protected $user; @@ -72,22 +84,28 @@ class webpush * @param controller_helper $controller_helper * @param driver_interface $db * @param form_helper $form_helper + * @param language $language + * @param manager $notification_manager * @param path_helper $path_helper * @param request_interface $request + * @param user_loader $user_loader * @param user $user * @param Environment $template * @param string $notification_webpush_table * @param string $push_subscriptions_table */ - public function __construct(config $config, controller_helper $controller_helper, driver_interface $db, form_helper $form_helper, path_helper $path_helper, - request_interface $request, user $user, Environment $template, string $notification_webpush_table, string $push_subscriptions_table) + public function __construct(config $config, controller_helper $controller_helper, driver_interface $db, form_helper $form_helper, language $language, manager $notification_manager, + path_helper $path_helper, request_interface $request, user_loader $user_loader, user $user, Environment $template, string $notification_webpush_table, string $push_subscriptions_table) { $this->config = $config; $this->controller_helper = $controller_helper; $this->db = $db; $this->form_helper = $form_helper; + $this->language = $language; + $this->notification_manager = $notification_manager; $this->path_helper = $path_helper; $this->request = $request; + $this->user_loader = $user_loader; $this->user = $user; $this->template = $template; $this->notification_webpush_table = $notification_webpush_table; @@ -100,12 +118,39 @@ public function __construct(config $config, controller_helper $controller_helper * @return JsonResponse */ public function notification(): JsonResponse + { + if (!$this->request->is_ajax() || $this->user->data['is_bot'] || $this->user->data['user_type'] == USER_INACTIVE) + { + throw new http_exception(Response::HTTP_FORBIDDEN, 'NO_AUTH_OPERATION'); + } + + if ($this->user->id() !== ANONYMOUS) + { + $notification_data = $this->get_user_notifications(); + } + else + { + $notification_data = $this->get_anonymous_notifications(); + } + + // Decode and return data if everything is fine + $data = json_decode($notification_data, true); + $data['url'] = isset($data['url']) ? $this->path_helper->update_web_root_path($data['url']) : ''; + + return new JsonResponse($data); + } + + /** + * Get notification data for logged in user + * + * @return string Notification data + */ + private function get_user_notifications(): string { // Subscribe should only be available for logged-in "normal" users - if (!$this->request->is_ajax() || $this->user->id() == ANONYMOUS || $this->user->data['is_bot'] - || $this->user->data['user_type'] == USER_IGNORE || $this->user->data['user_type'] == USER_INACTIVE) + if ($this->user->data['user_type'] == USER_IGNORE) { - throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden'); + throw new http_exception(Response::HTTP_FORBIDDEN, 'NO_AUTH_OPERATION'); } $item_id = $this->request->variable('item_id', 0); @@ -119,10 +164,92 @@ public function notification(): JsonResponse $result = $this->db->sql_query($sql); $notification_data = $this->db->sql_fetchfield('push_data'); $this->db->sql_freeresult($result); - $data = json_decode($notification_data, true); - $data['url'] = isset($data['url']) ? $this->path_helper->update_web_root_path($data['url']) : ''; - return new JsonResponse($data); + return $this->get_notification_data($notification_data); + } + + /** + * Get notification data for not logged in user via token + * + * @return string + */ + private function get_anonymous_notifications(): string + { + $token = $this->request->variable('token', ''); + + if ($token) + { + $item_id = $this->request->variable('item_id', 0); + $type_id = $this->request->variable('type_id', 0); + $user_id = $this->request->variable('user_id', 0); + + $sql = 'SELECT push_data, push_token + FROM ' . $this->notification_webpush_table . ' + WHERE user_id = ' . (int) $user_id . ' + AND notification_type_id = ' . (int) $type_id . ' + AND item_id = ' . (int) $item_id; + $result = $this->db->sql_query($sql); + $notification_row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $notification_data = $notification_row['push_data']; + $push_token = $notification_row['push_token']; + + // Check if passed push token is valid + $sql = 'SELECT user_form_salt, user_lang + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . (int) $user_id; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $user_form_token = $row['user_form_salt']; + $user_lang = $row['user_lang']; + + $expected_push_token = hash('sha256', $user_form_token . $push_token); + if ($expected_push_token === $token) + { + if ($user_lang !== $this->language->get_used_language()) + { + $this->language->set_user_language($user_lang, true); + } + return $this->get_notification_data($notification_data); + } + } + + throw new http_exception(Response::HTTP_FORBIDDEN, 'NO_AUTH_OPERATION'); + } + + /** + * Get notification data for output from json encoded data stored in database + * + * @param string $notification_data Encoded data stored in database + * + * @return string Data for notification output with javascript + */ + private function get_notification_data(string $notification_data): string + { + $row_data = json_decode($notification_data, true); + + // Old notification data is pre-parsed and just needs to be returned + if (isset($row_data['heading'])) + { + return $notification_data; + } + + // Get notification from row_data + $notification = $this->notification_manager->get_item_type_class($row_data['notification_type_name'], $row_data); + + // Load users for notification + $this->user_loader->load_users($notification->users_to_query()); + + return json_encode([ + 'heading' => $this->config['sitename'], + 'title' => strip_tags(html_entity_decode($notification->get_title(), ENT_NOQUOTES, 'UTF-8')), + 'text' => strip_tags(html_entity_decode($notification->get_reference(), ENT_NOQUOTES, 'UTF-8')), + 'url' => htmlspecialchars_decode($notification->get_url()), + 'avatar' => $notification->get_avatar(), + ]); } /** @@ -135,9 +262,9 @@ public function notification(): JsonResponse */ public function worker(): Response { - // @todo: only work for logged in users, no anonymous & bot $content = $this->template->render('push_worker.js.twig', [ 'U_WEBPUSH_GET_NOTIFICATION' => $this->controller_helper->route('phpbb_ucp_push_get_notification_controller'), + 'ASSETS_VERSION' => $this->config['assets_version'], ]); $response = new Response($content); @@ -152,20 +279,6 @@ public function worker(): Response return $response; } - /** - * Get template variables for subscribe type pages - * - * @return array - */ - protected function get_subscribe_vars(): array - { - return [ - 'U_WEBPUSH_SUBSCRIBE' => $this->controller_helper->route('phpbb_ucp_push_subscribe_controller'), - 'U_WEBPUSH_UNSUBSCRIBE' => $this->controller_helper->route('phpbb_ucp_push_unsubscribe_controller'), - 'FORM_TOKENS' => $this->form_helper->get_form_tokens(self::FORM_TOKEN_UCP), - ]; - } - /** * Check (un)subscribe form for valid link hash * @@ -180,7 +293,7 @@ protected function check_subscribe_requests(): void } // Subscribe should only be available for logged-in "normal" users - if (!$this->request->is_ajax() || $this->user->id() == ANONYMOUS || $this->user->data['is_bot'] + if (!$this->request->is_ajax() || $this->user->id() === ANONYMOUS || $this->user->data['is_bot'] || $this->user->data['user_type'] == USER_IGNORE || $this->user->data['user_type'] == USER_INACTIVE) { throw new http_exception(Response::HTTP_FORBIDDEN, 'NO_AUTH_OPERATION'); diff --git a/phpBB/posting.php b/phpBB/posting.php index 09f5392f4e..b59f4a2b1f 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -454,8 +454,10 @@ if ($config['enable_post_confirm'] && !$user->data['is_registered']) { - $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']); - $captcha->init(CONFIRM_POST); + /** @var \phpbb\captcha\factory $captcha_factory */ + $captcha_factory = $phpbb_container->get('captcha.factory'); + $captcha = $captcha_factory->get_instance($config['captcha_plugin']); + $captcha->init(\phpbb\captcha\plugins\confirm_type::POST); } // Is the user able to post within this forum? @@ -845,6 +847,7 @@ 'disable_smilies' => false, 'disable_magic_url' => false, 'attach_sig' => true, + 'notify' => false, 'lock_topic' => false, 'topic_type' => POST_NORMAL, @@ -1206,15 +1209,9 @@ if ($config['enable_post_confirm'] && !$user->data['is_registered'] && in_array($mode, array('quote', 'post', 'reply'))) { - $captcha_data = array( - 'message' => $request->variable('message', '', true), - 'subject' => $request->variable('subject', '', true), - 'username' => $request->variable('username', '', true), - ); - $vc_response = $captcha->validate($captcha_data); - if ($vc_response) + if ($captcha->validate() !== true) { - $error[] = $vc_response; + $error[] = $captcha->get_error(); } } @@ -1598,7 +1595,7 @@ ); extract($phpbb_dispatcher->trigger_event('core.posting_modify_submit_post_after', compact($vars))); - if ($config['enable_post_confirm'] && !$user->data['is_registered'] && (isset($captcha) && $captcha->is_solved() === true) && ($mode == 'post' || $mode == 'reply' || $mode == 'quote')) + if ($config['enable_post_confirm'] && !$user->data['is_registered'] && $captcha->is_solved() === true && ($mode == 'post' || $mode == 'reply' || $mode == 'quote')) { $captcha->reset(); } diff --git a/phpBB/search.php b/phpBB/search.php index 0d5ee35d66..bec4cb96e2 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -90,8 +90,21 @@ break; } +$search_auth_check_override = false; +/** +* This event allows you to override search auth checks +* +* @event core.search_auth_check_override +* @var bool search_auth_check_override Whether or not the search auth check overridden +* @since 3.3.14-RC1 +*/ +$vars = [ + 'search_auth_check_override', +]; +extract($phpbb_dispatcher->trigger_event('core.search_auth_check_override', compact($vars))); + // Is user able to search? Has search been disabled? -if (!$auth->acl_get('u_search') || !$auth->acl_getf_global('f_search') || !$config['load_search']) +if (!$search_auth_check_override && (!$auth->acl_get('u_search') || !$auth->acl_getf_global('f_search') || !$config['load_search'])) { $template->assign_var('S_NO_SEARCH', true); trigger_error('NO_SEARCH'); diff --git a/phpBB/styles/all/js/push_worker.js.twig b/phpBB/styles/all/js/push_worker.js.twig index 8d6ec3c6af..cf0ca568ac 100644 --- a/phpBB/styles/all/js/push_worker.js.twig +++ b/phpBB/styles/all/js/push_worker.js.twig @@ -1,3 +1,18 @@ +/** + * Event listener for install event + */ +self.addEventListener('install', () => { + // Call to ensure service worker is correctly updated + self.skipWaiting(); +}); + +/** + * Event listener for activate event + */ +self.addEventListener('activate', event => { + event.waitUntil(self.clients.claim()); +}); + /** * Event listener for push event */ @@ -7,21 +22,35 @@ self.addEventListener('push', event => { } let itemId = 0; - let typeId = 0; + let typeId = 0; + let userId = 0; + let notificationVersion = 0; + let pushToken = ''; try { const notificationData = event.data.json(); itemId = notificationData.item_id; typeId = notificationData.type_id; + userId = notificationData.user_id; + notificationVersion = parseInt(notificationData.version, 10); + pushToken = notificationData.token; } catch { self.registration.showNotification(event.data.text()); return; } const getNotificationUrl = '{{ U_WEBPUSH_GET_NOTIFICATION }}'; + const assetsVersion = parseInt('{{ ASSETS_VERSION }}', 10); + + // Force update if versions differ + if (assetsVersion !== notificationVersion) { + self.registration.update(); + } const formData = new FormData(); formData.append('item_id', itemId.toString(10)); formData.append('type_id', typeId.toString(10)); + formData.append('user_id', userId.toString(10)); + formData.append('token', pushToken); fetch(getNotificationUrl, { method: 'POST', diff --git a/phpBB/styles/all/template/macros/avatar.twig b/phpBB/styles/all/template/macros/avatar.twig new file mode 100644 index 0000000000..6c3a3809cd --- /dev/null +++ b/phpBB/styles/all/template/macros/avatar.twig @@ -0,0 +1 @@ +{% if SRC %}{{ lang(TITLE) }}{% endif %} diff --git a/phpBB/styles/all/template/macros/forms/input.twig b/phpBB/styles/all/template/macros/forms/input.twig index 1a2ff13fa3..4016ad9da3 100644 --- a/phpBB/styles/all/template/macros/forms/input.twig +++ b/phpBB/styles/all/template/macros/forms/input.twig @@ -1,4 +1,4 @@ -{% apply replace({"\n": ' ', "\t": ''}) %} +{% apply replace({"\n\t": ' ', "\t": '', "\n": ''}) %} {% endapply %} diff --git a/phpBB/styles/all/template/macros/forms/radio_buttons.twig b/phpBB/styles/all/template/macros/forms/radio_buttons.twig index 1ef7804c29..4d5b5c438a 100644 --- a/phpBB/styles/all/template/macros/forms/radio_buttons.twig +++ b/phpBB/styles/all/template/macros/forms/radio_buttons.twig @@ -1,2 +1,3 @@ - - +{% for button in BUTTONS %} + +{% endfor %} diff --git a/phpBB/styles/all/template/macros/forms/select.twig b/phpBB/styles/all/template/macros/forms/select.twig index 3cab1e7199..1ada063e57 100644 --- a/phpBB/styles/all/template/macros/forms/select.twig +++ b/phpBB/styles/all/template/macros/forms/select.twig @@ -1,4 +1,4 @@ -{% apply replace({"\n": ' ', "\t": ''}) %} +{% apply replace({"\n\t": ' ', "\t": '', "\n": ''}) %} +{% endapply %} diff --git a/phpBB/styles/all/template/macros/forms/textarea.twig b/phpBB/styles/all/template/macros/forms/textarea.twig index e4fada13f2..10934f067f 100644 --- a/phpBB/styles/all/template/macros/forms/textarea.twig +++ b/phpBB/styles/all/template/macros/forms/textarea.twig @@ -1,10 +1,11 @@ -{% apply replace({"\n": ' ', '\t': ''}) %} +{% apply replace({"\n": ' ', '\t': ''}) %} + cols="{{ COLS }}" + {% if PLACEHOLDER %}placeholder="{{ PLACEHOLDER }}"{% endif %}>{% endapply %}{{ CONTENT }} diff --git a/phpBB/styles/prosilver/imgs/svg/toggle-off.svg b/phpBB/styles/prosilver/imgs/svg/toggle-off.svg new file mode 100644 index 0000000000..9bd31a7bf5 --- /dev/null +++ b/phpBB/styles/prosilver/imgs/svg/toggle-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/styles/prosilver/imgs/svg/toggle-on.svg b/phpBB/styles/prosilver/imgs/svg/toggle-on.svg new file mode 100644 index 0000000000..83d2e59c0c --- /dev/null +++ b/phpBB/styles/prosilver/imgs/svg/toggle-on.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpBB/styles/prosilver/template/ajax.js b/phpBB/styles/prosilver/template/ajax.js index 421c43f767..3fb6a88a87 100644 --- a/phpBB/styles/prosilver/template/ajax.js +++ b/phpBB/styles/prosilver/template/ajax.js @@ -337,30 +337,6 @@ $('[data-ajax]').each(function() { } }); -// Prevent accidental double submission of form -$('[data-prevent-flood] input[type=submit]').click(function(event) { - const $submitButton = $(this); // Store the button element - const $form = $submitButton.closest('form'); - - // Always add the disabled class for visual feedback - $submitButton.addClass('disabled'); - - // Submit form if it hasn't been submitted yet - if (!$form.prop('data-form-submitted')) { - $form.prop('data-form-submitted', true); - - return; - } - - // Prevent default submission for subsequent clicks within 5 seconds - event.preventDefault(); - - setTimeout(() => { - $form.prop('removeProp', 'data-form-submitted'); - $submitButton.removeClass('disabled'); // Re-enable after 5 seconds - }, 5000); -}); - /** * This simply appends #preview to the action of the * QR action when you click the Full Editor & Preview button diff --git a/phpBB/styles/prosilver/template/bbcode.html b/phpBB/styles/prosilver/template/bbcode.html index 583b764b2c..dc051eef3e 100644 --- a/phpBB/styles/prosilver/template/bbcode.html +++ b/phpBB/styles/prosilver/template/bbcode.html @@ -38,11 +38,11 @@ - + - + diff --git a/phpBB/styles/prosilver/template/captcha_turnstile.html b/phpBB/styles/prosilver/template/captcha_turnstile.html new file mode 100644 index 0000000000..1cf4f4c792 --- /dev/null +++ b/phpBB/styles/prosilver/template/captcha_turnstile.html @@ -0,0 +1,23 @@ +{% if CONFIRM_TYPE_REGISTRATION %} +
        +
        +

        {{ lang('CONFIRMATION') }}

        +
        +{% endif %} +{% if S_TURNSTILE_AVAILABLE %} + + + + + {# The cf-turnstile class is used in JavaScript #} +
        +{% else %} + {{ lang('CAPTCHA_TURNSTILE_NOT_AVAILABLE') }} +{% endif %} +{% if CONFIRM_TYPE_REGISTRATION %} +
        +
        +
        +{% endif %} diff --git a/phpBB/styles/prosilver/template/memberlist_view.html b/phpBB/styles/prosilver/template/memberlist_view.html index f3c40bebf1..fb5f4e83c9 100644 --- a/phpBB/styles/prosilver/template/memberlist_view.html +++ b/phpBB/styles/prosilver/template/memberlist_view.html @@ -9,7 +9,7 @@

        {PAGE_TITLE}

        -
        +
        {AVATAR_HTML}
        {RANK_TITLE}
        diff --git a/phpBB/styles/prosilver/template/navbar_header.html b/phpBB/styles/prosilver/template/navbar_header.html index ef6da4ab91..8269f27fa6 100644 --- a/phpBB/styles/prosilver/template/navbar_header.html +++ b/phpBB/styles/prosilver/template/navbar_header.html @@ -69,7 +69,6 @@ {% endif %} {% endif %} -
      • {% EVENT navbar_header_quick_links_after %} diff --git a/phpBB/styles/prosilver/template/notification_dropdown.html b/phpBB/styles/prosilver/template/notification_dropdown.html index 52bc546976..715f04e1a2 100644 --- a/phpBB/styles/prosilver/template/notification_dropdown.html +++ b/phpBB/styles/prosilver/template/notification_dropdown.html @@ -44,6 +44,13 @@ + {% if NOTIFICATIONS_WEBPUSH_ENABLE and notification_types is not defined %} + + {% endif %} {% EVENT notification_dropdown_footer_after %}
      diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html index 760c6d5d9d..1746df12de 100644 --- a/phpBB/styles/prosilver/template/overall_header.html +++ b/phpBB/styles/prosilver/template/overall_header.html @@ -4,6 +4,9 @@ + + + {META} <!-- IF UNREAD_NOTIFICATIONS_COUNT -->({UNREAD_NOTIFICATIONS_COUNT}) <!-- ENDIF --><!-- IF not S_VIEWTOPIC and not S_VIEWFORUM -->{SITENAME} - <!-- ENDIF --><!-- IF S_IN_MCP -->{L_MCP} - <!-- ELSEIF S_IN_UCP -->{L_UCP} - <!-- ENDIF -->{PAGE_TITLE}<!-- IF S_VIEWTOPIC or S_VIEWFORUM --> - {SITENAME}<!-- ENDIF --> @@ -60,12 +63,18 @@ + + {$STYLESHEETS} +{% if NOTIFICATIONS_WEBPUSH_ENABLE %} + {% include('ucp_notifications_webpush.html') %} +{% endif %} + diff --git a/phpBB/styles/prosilver/template/posting_buttons.html b/phpBB/styles/prosilver/template/posting_buttons.html index 096dbbe8fa..ee98cb57ce 100644 --- a/phpBB/styles/prosilver/template/posting_buttons.html +++ b/phpBB/styles/prosilver/template/posting_buttons.html @@ -81,7 +81,7 @@ - diff --git a/phpBB/styles/prosilver/template/posting_editor.html b/phpBB/styles/prosilver/template/posting_editor.html index 02390bf619..eebc96623e 100644 --- a/phpBB/styles/prosilver/template/posting_editor.html +++ b/phpBB/styles/prosilver/template/posting_editor.html @@ -97,7 +97,7 @@
      -
      +
      {S_HIDDEN_ADDRESS_FIELD} {S_HIDDEN_FIELDS} diff --git a/phpBB/styles/prosilver/template/ucp_notifications_options.html b/phpBB/styles/prosilver/template/ucp_notifications_options.html index a0d9caad12..5b953362fe 100644 --- a/phpBB/styles/prosilver/template/ucp_notifications_options.html +++ b/phpBB/styles/prosilver/template/ucp_notifications_options.html @@ -1,9 +1,5 @@ {% include('ucp_header.html') %} -{% if NOTIFICATIONS_WEBPUSH_ENABLE %} - {% include('ucp_notifications_webpush.html') %} -{% endif %} -

      {{ TITLE }}

      @@ -14,7 +10,7 @@

      {{ TITLE }}


      {{ lang('NOTIFY_WEBPUSH_ENABLE_EXPLAIN') }}
      - +
      diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html index 37b2a1c0b1..f5c7d26518 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html +++ b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html @@ -22,7 +22,7 @@ -
      +
      diff --git a/phpBB/styles/prosilver/template/ucp_prefs_personal.html b/phpBB/styles/prosilver/template/ucp_prefs_personal.html index 4e81b78fb3..fe1ea17c74 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_personal.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_personal.html @@ -61,7 +61,9 @@

      {L_TITLE}

      -
      +
      + {{ FormsSelect(S_STYLE_OPTIONS) }} +
      diff --git a/phpBB/styles/prosilver/theme/buttons.css b/phpBB/styles/prosilver/theme/buttons.css index e8f8fbe961..cf15eb394f 100644 --- a/phpBB/styles/prosilver/theme/buttons.css +++ b/phpBB/styles/prosilver/theme/buttons.css @@ -220,3 +220,9 @@ button::-moz-focus-inner { .avatar-cropper-buttons > .button-group { margin: 4px; } + +/* Notification buttons +--------------------------------------------- */ +.notification-subscribe-toggle:disabled { + opacity: 0.7; +} diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index 7bdd5f4b87..eb16158cc3 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -298,6 +298,14 @@ a:hover .icon.icon-black, /* DEPRECATED 4.0 */ color: inherit; } +.push-subscribe-toggle-icon.toggle-on { + color: #0059b3; +} + +.push-subscribe-toggle-icon.toggle-off { + color: #9e9e9e; +} + /* jumpbox */ .jumpbox .dropdown li { border-top-color: #dedede; @@ -1179,3 +1187,11 @@ input.disabled { background-color: #d41142; color: #ffffff; } + +.dropdown-extended .webpush-subscribe { + color: #536482; +} + +.notification-subscribe-toggle { + color: #47536b; +} diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 240ba25d03..59db92a032 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -560,7 +560,7 @@ a.header-avatar img { line-height: normal !important; text-align: left; white-space: nowrap; - border-top: 1px dotted transparent; + border-top: 1px solid transparent; display: list-item; float: none !important; margin: 0; @@ -814,6 +814,10 @@ table.info tbody th { max-width: 100%; } +.avatar-rank-container { + max-width: 20%; +} + .left-box.profile-details { width: 80%; } @@ -1220,14 +1224,9 @@ ul.linklist:after, content: ""; } -/* stylelint-disable declaration-property-unit-allowed-list */ .emoji { - width: 1em; - min-width: 18px; - height: 1em; - min-height: 18px; + font-size: 17px; } -/* stylelint-enable declaration-property-unit-allowed-list */ .smilies { vertical-align: text-bottom; @@ -1340,6 +1339,15 @@ ul.linklist:after, display: block; } +.dropdown-extended .webpush-subscribe { + white-space: nowrap; + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + padding: 5px 10px; + gap: 20px; +} + .notification-avatar, .notification-menu .notification-list .notification-item .avatar, .notification-menu .notification-list .notification-item .gravatar { diff --git a/phpBB/styles/prosilver/theme/icons.css b/phpBB/styles/prosilver/theme/icons.css index a6ad36d22a..d4b7dabefe 100644 --- a/phpBB/styles/prosilver/theme/icons.css +++ b/phpBB/styles/prosilver/theme/icons.css @@ -123,6 +123,12 @@ blockquote cite:before, height: 18px; } +.push-subscribe-toggle-icon { + width: 20px; + height: 20px; + cursor: pointer; +} + /* Contact icons ---------------------------------------- */ .contact-icon { diff --git a/phpBB/styles/prosilver/theme/responsive.css b/phpBB/styles/prosilver/theme/responsive.css index 5deb7224a6..c3b946c0eb 100644 --- a/phpBB/styles/prosilver/theme/responsive.css +++ b/phpBB/styles/prosilver/theme/responsive.css @@ -18,6 +18,10 @@ .dropdown-extended .dropdown-contents { width: auto; } + + .dropdown-extended .dropdown-contents .webpush-subscribe .notification-subscribe-toggle > span { + display: none; + } } @media (max-width: 430px) { @@ -494,6 +498,10 @@ width: auto; } + .avatar-rank-container { + max-width: 100%; + } + /* Polls ---------------------------------------------------------------- */ fieldset.polls dt { diff --git a/phpBB/ucp.php b/phpBB/ucp.php index 240d9f0741..45593ffb4e 100644 --- a/phpBB/ucp.php +++ b/phpBB/ucp.php @@ -154,68 +154,11 @@ break; case 'delete_cookies': + /** @var \phpbb\controller\helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); - // Delete Cookies with dynamic names (do NOT delete poll cookies) - if (confirm_box(true)) - { - $set_time = time() - 31536000; - - foreach ($request->variable_names(\phpbb\request\request_interface::COOKIE) as $cookie_name) - { - $cookie_data = $request->variable($cookie_name, '', true, \phpbb\request\request_interface::COOKIE); - - // Only delete board cookies, no other ones... - if (strpos($cookie_name, $config['cookie_name'] . '_') !== 0) - { - continue; - } - - $cookie_name = str_replace($config['cookie_name'] . '_', '', $cookie_name); - - /** - * Event to save custom cookies from deletion - * - * @event core.ucp_delete_cookies - * @var string cookie_name Cookie name to checking - * @var bool retain_cookie Do we retain our cookie or not, true if retain - * @since 3.1.3-RC1 - */ - $retain_cookie = false; - $vars = array('cookie_name', 'retain_cookie'); - extract($phpbb_dispatcher->trigger_event('core.ucp_delete_cookies', compact($vars))); - if ($retain_cookie) - { - continue; - } - - // Polls are stored as {cookie_name}_poll_{topic_id}, cookie_name_ got removed, therefore checking for poll_ - if (strpos($cookie_name, 'poll_') !== 0) - { - $user->set_cookie($cookie_name, '', $set_time); - } - } - - $user->set_cookie('track', '', $set_time); - $user->set_cookie('u', '', $set_time); - $user->set_cookie('k', '', $set_time); - $user->set_cookie('sid', '', $set_time); - - // We destroy the session here, the user will be logged out nevertheless - $user->session_kill(); - $user->session_begin(); - - meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx")); - - $message = $user->lang['COOKIES_DELETED'] . '

      ' . sprintf($user->lang['RETURN_INDEX'], '', ''); - trigger_error($message); - } - else - { - confirm_box(false, 'DELETE_COOKIES', ''); - } - - redirect(append_sid("{$phpbb_root_path}index.$phpEx")); - + // Redirect to controller + redirect($controller_helper->route('phpbb_ucp_delete_cookies_controller')); break; case 'switch_perm': diff --git a/phpBB/vendor-ext/.htaccess b/phpBB/vendor-ext/.htaccess new file mode 100644 index 0000000000..92e78ba1a7 --- /dev/null +++ b/phpBB/vendor-ext/.htaccess @@ -0,0 +1,25 @@ +# With Apache 2.4 the "Order, Deny" syntax has been deprecated and moved from +# module mod_authz_host to a new module called mod_access_compat (which may be +# disabled) and a new "Require" syntax has been introduced to mod_authz_core. +# We could just conditionally provide both versions, but unfortunately Apache +# does not explicitly tell us its version if the module mod_version is not +# available. In this case, we check for the availability of module +# mod_authz_core (which should be on 2.4 or higher only) as a best guess. + + + Order Allow,Deny + Deny from All + + = 2.4> + Require all denied + + + + + Order Allow,Deny + Deny from All + + + Require all denied + + diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index f662fbd3da..f4f6d43485 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -47,27 +47,45 @@ trigger_error('NO_FORUM'); } -$sql_from = FORUMS_TABLE . ' f'; +$sql_ary = [ + 'SELECT' => 'f.*', + 'FROM' => [ + FORUMS_TABLE => 'f', + ], + 'WHERE' => 'f.forum_id = ' . $forum_id, +]; + $lastread_select = ''; // Grab appropriate forum data if ($config['load_db_lastread'] && $user->data['is_registered']) { - $sql_from .= ' LEFT JOIN ' . FORUMS_TRACK_TABLE . ' ft ON (ft.user_id = ' . $user->data['user_id'] . ' - AND ft.forum_id = f.forum_id)'; - $lastread_select .= ', ft.mark_time'; + $sql_ary['LEFT_JOIN'][] = [ + 'FROM' => [FORUMS_TRACK_TABLE => 'ft'], + 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND ft.forum_id = f.forum_id', + ]; + $sql_ary['SELECT'] .= ', ft.mark_time'; } if ($user->data['is_registered']) { - $sql_from .= ' LEFT JOIN ' . FORUMS_WATCH_TABLE . ' fw ON (fw.forum_id = f.forum_id AND fw.user_id = ' . $user->data['user_id'] . ')'; - $lastread_select .= ', fw.notify_status'; + $sql_ary['LEFT_JOIN'][] = [ + 'FROM' => [FORUMS_WATCH_TABLE => 'fw'], + 'ON' => 'fw.forum_id = f.forum_id AND fw.user_id = ' . $user->data['user_id'], + ]; + $sql_ary['SELECT'] .= ', fw.notify_status'; } -$sql = "SELECT f.* $lastread_select - FROM $sql_from - WHERE f.forum_id = $forum_id"; -$result = $db->sql_query($sql); +/** + * You can use this event to modify the sql that selects the forum on the viewforum page. + * + * @event core.viewforum_modify_sql + * @var array sql_ary The SQL array to get the data for a forum + * @since 3.3.14-RC1 + */ +$vars = ['sql_ary']; +extract($phpbb_dispatcher->trigger_event('core.viewforum_modify_sql', compact($vars))); +$result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); $forum_data = $db->sql_fetchrow($result); $db->sql_freeresult($result); diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index f14cb5b6d8..152e7ee52f 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -2070,7 +2070,7 @@ 'U_EMAIL' => $user_cache[$poster_id]['email'], 'U_JABBER' => $user_cache[$poster_id]['jabber'], - 'U_APPROVE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&p={$row['post_id']}&redirect=" . urlencode(str_replace('&', '&', $viewtopic_url . '&p=' . $row['post_id'] . '#p' . $row['post_id']))), + 'U_APPROVE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&p={$row['post_id']}&f={$row['forum_id']}&redirect=" . urlencode(str_replace('&', '&', $viewtopic_url . '&p=' . $row['post_id'] . '#p' . $row['post_id']))), 'U_REPORT' => ($auth->acl_get('f_report', $forum_id)) ? $phpbb_container->get('controller.helper')->route('phpbb_report_post_controller', array('id' => $row['post_id'])) : '', 'U_MCP_REPORT' => ($auth->acl_get('m_report', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&mode=report_details&p=' . $row['post_id']) : '', 'U_MCP_APPROVE' => ($auth->acl_get('m_approve', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=approve_details&p=' . $row['post_id']) : '', diff --git a/phpBB/web.config b/phpBB/web.config index d0a3cb33fe..86ea774d8e 100644 --- a/phpBB/web.config +++ b/phpBB/web.config @@ -28,6 +28,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/acp_board/select_auth_method_test.php b/tests/acp_board/select_auth_method_test.php index e06b5f845d..a9efdbc0ad 100644 --- a/tests/acp_board/select_auth_method_test.php +++ b/tests/acp_board/select_auth_method_test.php @@ -22,8 +22,36 @@ class phpbb_acp_board_select_auth_method_test extends phpbb_test_case public static function select_auth_method_data() { return [ - ['acp_board_valid', ''], - ['acp_board_invalid', ''], + [ + 'acp_board_valid', + [ + 'options' => [ + 0 => [ + 'value' => 'acp_board_valid', + 'label' => 'Acp_board_valid', + 'selected' => true, + 'data' => [ + 'toggle-setting' => '#auth_acp_board_valid_settings', + ], + ] + ], + ] + ], + [ + 'acp_board_invalid', + [ + 'options' => [ + 0 => [ + 'value' => 'acp_board_valid', + 'label' => 'Acp_board_valid', + 'selected' => false, + 'data' => [ + 'toggle-setting' => '#auth_acp_board_valid_settings', + ], + ] + ], + ] + ], ]; } diff --git a/tests/attachment/delete_test.php b/tests/attachment/delete_test.php index faf2f960e6..e92319c078 100644 --- a/tests/attachment/delete_test.php +++ b/tests/attachment/delete_test.php @@ -86,29 +86,28 @@ public function test_attachment_delete($mode, $ids, $resync, $expected) public function data_attachment_unlink() { return array( - array(true, true, true), - array(true, false, false), - array(true, true, false, true), + array(true, true), + array(false, false), + array(true, false, true), ); } /** * @dataProvider data_attachment_unlink */ - public function test_attachment_delete_success($remove_success, $exists_success, $expected, $throw_exception = false) + public function test_attachment_delete_success($exists_success, $expected, $throw_exception = false) { $this->storage = $this->createMock('\phpbb\storage\storage'); if ($throw_exception) { $this->storage->expects($this->any()) ->method('delete') - ->willThrowException(new \phpbb\storage\exception\exception); + ->willThrowException(new \phpbb\storage\exception\storage_exception); } else { $this->storage->expects($this->any()) - ->method('delete') - ->willReturn($remove_success); + ->method('delete'); } $this->storage->expects($this->any()) ->method('exists') diff --git a/tests/auth/provider_apache_test.php b/tests/auth/provider_apache_test.php index cdccd2a358..bc211b4ed5 100644 --- a/tests/auth/provider_apache_test.php +++ b/tests/auth/provider_apache_test.php @@ -33,7 +33,7 @@ protected function setUp(): void $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); $lang = new \phpbb\language\language($lang_loader); $this->request = $this->createMock('\phpbb\request\request'); - $this->user = new \phpbb\user($lang, '\phpbb\datetime');; + $this->user = new \phpbb\user($lang, '\phpbb\datetime'); $this->provider = new \phpbb\auth\provider\apache($config, $db, $lang, $this->request, $this->user, $phpbb_root_path, $phpEx); } diff --git a/tests/avatar/driver_gravatar_test.php b/tests/avatar/driver_gravatar_test.php new file mode 100644 index 0000000000..27ab847ff7 --- /dev/null +++ b/tests/avatar/driver_gravatar_test.php @@ -0,0 +1,215 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +class phpbb_avatar_driver_gravatar_test extends \phpbb_database_test_case +{ + + /** @var \phpbb\config\config */ + private $config; + + /** @var gravatar */ + private $gravatar; + + /** @var request_interface */ + private $request; + + /** @var \phpbb\template\template */ + private $template; + + /** @var \phpbb\user */ + private $user; + + private $template_data = []; + + public function getDataSet() + { + return $this->createXMLDataSet(__DIR__ . '/fixtures/users.xml'); + } + + protected function setUp(): void + { + global $phpbb_root_path, $phpEx; + + $this->config = new \phpbb\config\config(array()); + $this->request = $this->getMockBuilder(request::class) + ->disableOriginalConstructor() + ->onlyMethods(['get_super_global']) + ->getMock(); + $this->request->method('get_super_global') + ->willReturn([]); + $this->template = $this->getMockBuilder(\phpbb\template\twig\twig::class) + ->disableOriginalConstructor() + ->onlyMethods(['assign_vars']) + ->getMock(); + $this->template->method('assign_vars') + ->will($this->returnCallback([$this, 'template_assign_vars'])); + $this->user = $this->getMockBuilder(\phpbb\user::class) + ->disableOriginalConstructor() + ->getMock(); + $imagesize = new \FastImageSize\FastImageSize(); + $cache = $this->createMock('\phpbb\cache\driver\driver_interface'); + $path_helper = new \phpbb\path_helper( + new \phpbb\symfony_request( + $this->request + ), + $this->request, + $phpbb_root_path, + $phpEx + ); + + global $phpbb_dispatcher; + $phpbb_dispatcher = $this->getMockBuilder(\phpbb\event\dispatcher::class) + ->disableOriginalConstructor() + ->onlyMethods(['trigger_event']) + ->getMock(); + $phpbb_dispatcher->method('trigger_event') + ->willReturnArgument(1); + + $this->gravatar = new gravatar($this->config, $imagesize, $phpbb_root_path, $phpEx, $path_helper, $cache); + $this->gravatar->set_name('avatar.driver.gravatar'); + } + + public function template_assign_vars($data) + { + $this->template_data = array_merge($this->template_data, $data); + } + + public function data_prepare_form(): array + { + return [ + [ + // Only default empty values, no request data + [ + 'AVATAR_GRAVATAR_WIDTH' => '', + 'AVATAR_GRAVATAR_HEIGHT' => '', + 'AVATAR_GRAVATAR_EMAIL' => '', + ], + [], + [ + 'avatar_type' => '', + 'avatar_width' => '', + 'avatar_height' => '', + ] + ], + [ + // Only default empty values, request data set + [ + 'AVATAR_GRAVATAR_WIDTH' => '80', + 'AVATAR_GRAVATAR_HEIGHT' => '90', + 'AVATAR_GRAVATAR_EMAIL' => '', + ], + [ + request_interface::POST => [ + 'avatar_type' => 'avatar.driver.gravatar', + 'avatar_gravatar_width' => '80', + 'avatar_gravatar_height' => '90', + ], + ], + [ + 'avatar_type' => '', + 'avatar_width' => '80', + 'avatar_height' => '90', + ] + ], + [ + // Only default empty values, request data set + [ + 'AVATAR_GRAVATAR_WIDTH' => '70', + 'AVATAR_GRAVATAR_HEIGHT' => '60', + 'AVATAR_GRAVATAR_EMAIL' => 'bar@foo.com', + ], + [ + request_interface::POST => [ + 'avatar_type' => 'avatar.driver.gravatar', + 'avatar_gravatar_width' => '80', + 'avatar_gravatar_height' => '90', + ], + ], + [ + 'avatar_type' => 'avatar.driver.gravatar', + 'avatar' => 'bar@foo.com', + 'avatar_width' => '70', + 'avatar_height' => '60', + ] + ], + ]; + } + + /** + * @dataProvider data_prepare_form + */ + public function test_prepare_form($expected_vars, $request_data, $row) + { + $error = []; + $this->template_data = []; + + $request = $this->getMockBuilder(request::class) + ->disableOriginalConstructor() + ->onlyMethods(['get_super_global']) + ->getMock(); + $request->method('get_super_global') + ->willReturn([]); + + $requestInputReflection = new \ReflectionProperty($request, 'input'); + $requestInputReflection->setAccessible(true); + $request_data[request_interface::GET] = $request_data[request_interface::GET] ?? []; + $request_data[request_interface::POST] = $request_data[request_interface::POST] ?? []; + $request_data[request_interface::REQUEST] = $request_data[request_interface::GET] + $request_data[request_interface::POST]; + $requestInputReflection->setValue($request, $request_data); + $requestTypeCastHelperReflection = new \ReflectionProperty($request, 'type_cast_helper'); + $requestTypeCastHelperReflection->setAccessible(true); + $requestTypeCastHelperReflection->setValue($request, new \phpbb\request\type_cast_helper()); + + $this->gravatar->prepare_form($request, $this->template, $this->user, $row, $error); + + // Error not touched by gravatar + $this->assertEquals([], $error); + + $this->assertEquals($expected_vars, $this->template_data); + } + + public function test_gravatar_misc(): void + { + $this->assertEquals('ucp_avatar_options_gravatar.html', $this->gravatar->get_template_name()); + $this->assertEquals('acp_avatar_options_gravatar.html', $this->gravatar->get_acp_template_name()); + + $row = [ + 'avatar_type' => 'avatar.driver.gravatar', + 'avatar' => 'bar@foo.com', + 'avatar_width' => '70', + 'avatar_height' => '60', + ]; + $this->assertEquals('', $this->gravatar->get_custom_html($this->user, $row)); + } + + public function test_get_data(): void + { + $row = [ + 'avatar_type' => 'avatar.driver.gravatar', + 'avatar' => 'bar@foo.com', + 'avatar_width' => '70', + 'avatar_height' => '60', + ]; + + $this->assertEquals([ + 'src' => '//gravatar.com/avatar/e0ee9d02824d4320a999507150c5b8a371c635c41f645ba3a7205f36384dc199?s=70', + 'width' => '70', + 'height' => '60', + ], $this->gravatar->get_data($row)); + } +} diff --git a/tests/avatar/manager_test.php b/tests/avatar/manager_test.php index 19b7797eb4..576950374e 100644 --- a/tests/avatar/manager_test.php +++ b/tests/avatar/manager_test.php @@ -58,6 +58,7 @@ protected function setUp(): void $phpbb_dispatcher = $dispatcher; $controller_helper = $this->createMock('\phpbb\controller\helper'); + $routing_helper = $this->createMock('\phpbb\routing\helper'); // $this->avatar_foobar will be needed later on $this->avatar_foobar = $this->getMockBuilder('\phpbb\avatar\driver\foobar') @@ -96,7 +97,7 @@ protected function setUp(): void { $cur_avatar = $this->getMockBuilder('\phpbb\avatar\driver\\' . $driver) ->setMethods(array('get_name')) - ->setConstructorArgs(array($this->config, $controller_helper, $phpbb_root_path, $phpEx, $storage, $path_helper, $dispatcher, $files_factory, $php_ini)) + ->setConstructorArgs(array($this->config, $phpbb_root_path, $phpEx, $storage, $path_helper, $routing_helper, $dispatcher, $files_factory, $php_ini)) ->getMock(); } $cur_avatar->expects($this->any()) @@ -191,9 +192,14 @@ public function test_get_avatar_settings() { $avatar_settings = $this->manager->get_avatar_settings($this->avatar_foobar); - $expected_settings = array( - 'allow_avatar_' . get_class($this->avatar_foobar) => array('lang' => 'ALLOW_' . strtoupper(get_class($this->avatar_foobar)), 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), - ); + $expected_settings = [ + 'allow_avatar_' . get_class($this->avatar_foobar) => [ + 'lang' => 'ALLOW_' . strtoupper(get_class($this->avatar_foobar)), + 'validate' => 'bool', + 'type' => 'radio:yes_no', + 'explain' => true + ], + ]; $this->assertEquals($expected_settings, $avatar_settings); } diff --git a/tests/cache/file_driver_test.php b/tests/cache/file_driver_test.php index 10c9aec182..c8fe44be74 100644 --- a/tests/cache/file_driver_test.php +++ b/tests/cache/file_driver_test.php @@ -17,6 +17,9 @@ class phpbb_cache_file_driver_test extends phpbb_cache_common_test_case { private $cache_dir; + /** @var \phpbb\cache\driver\file */ + private $cache_file; + public function getDataSet() { return $this->createXMLDataSet(__DIR__ . '/fixtures/config.xml'); @@ -36,7 +39,8 @@ protected function setUp(): void } $this->create_cache_dir(); - $this->driver = new \phpbb\cache\driver\file($this->cache_dir); + $this->cache_file = new \phpbb\cache\driver\file($this->cache_dir); + $this->driver = $this->cache_file; } protected function tearDown(): void @@ -49,6 +53,157 @@ protected function tearDown(): void parent::tearDown(); } + public function test_read_not_readable() + { + if (strtolower(substr(PHP_OS, 0, 3)) === 'win') + { + $this->markTestSkipped('Unable to test unreadable files on Windows'); + } + + global $phpEx; + + // Create file that is not readable + $this->assertTrue($this->cache_file->_write('unreadable', 'foo', time() + 86400)); + + $filename = "{$this->cache_dir}unreadable.$phpEx"; + @chmod($filename, 0000); + $readReflection = new \ReflectionMethod($this->cache_file, '_read'); + $readReflection->setAccessible(true); + $this->assertFalse($readReflection->invoke($this->cache_file, 'unreadable')); + @chmod($filename, 0600); + $this->assertNotFalse($readReflection->invoke($this->cache_file, 'unreadable')); + } + + public function test_read_data_global_invalid() + { + global $phpEx; + + $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); + $reflectionCacheVars->setAccessible(true); + $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); + + $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); + $reflectionCacheVarExpires->setAccessible(true); + $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() + 86400]); + + // Create file in invalid format + $this->assertTrue($this->cache_file->_write('data_global')); + $filename = "{$this->cache_dir}data_global.$phpEx"; + $cache_data = file_get_contents($filename); + // Force negative read when retrieving data_global + $cache_data = str_replace("\n13\n", "\n1\n", $cache_data); + file_put_contents($filename, $cache_data); + + $readReflection = new \ReflectionMethod($this->cache_file, '_read'); + $readReflection->setAccessible(true); + $this->assertFalse($readReflection->invoke($this->cache_file, 'data_global')); + } + + public function test_read_data_global_zero_bytes() + { + global $phpEx; + + $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); + $reflectionCacheVars->setAccessible(true); + $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); + + $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); + $reflectionCacheVarExpires->setAccessible(true); + $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() + 86400]); + + // Create file in invalid format + $this->assertTrue($this->cache_file->_write('data_global')); + $filename = "{$this->cache_dir}data_global.$phpEx"; + $cache_data = file_get_contents($filename); + // Force negative read when retrieving data_global + $cache_data = str_replace("\n13\n", "\n0\n", $cache_data); + file_put_contents($filename, $cache_data); + + $readReflection = new \ReflectionMethod($this->cache_file, '_read'); + $readReflection->setAccessible(true); + $this->assertFalse($readReflection->invoke($this->cache_file, 'data_global')); + } + + public function test_read_data_global_hex_bytes() + { + global $phpEx; + + $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); + $reflectionCacheVars->setAccessible(true); + $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); + + $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); + $reflectionCacheVarExpires->setAccessible(true); + $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() + 86400]); + + // Create file in invalid format + $this->assertTrue($this->cache_file->_write('data_global')); + $filename = "{$this->cache_dir}data_global.$phpEx"; + $cache_data = file_get_contents($filename); + // Force negative read when retrieving data_global + $cache_data = str_replace("\n13\n", "\nA\n", $cache_data); + file_put_contents($filename, $cache_data); + + $readReflection = new \ReflectionMethod($this->cache_file, '_read'); + $readReflection->setAccessible(true); + $this->assertFalse($readReflection->invoke($this->cache_file, 'data_global')); + } + + public function test_read_data_global_expired() + { + $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); + $reflectionCacheVars->setAccessible(true); + $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); + + $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); + $reflectionCacheVarExpires->setAccessible(true); + $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() - 86400]); + + // Create file in invalid format + $this->assertTrue($this->cache_file->_write('data_global')); + + // Clear data + $reflectionCacheVars->setValue($this->cache_file, []); + $reflectionCacheVarExpires->setValue($this->cache_file, []); + + $readReflection = new \ReflectionMethod($this->cache_file, '_read'); + $readReflection->setAccessible(true); + $this->assertTrue($readReflection->invoke($this->cache_file, 'data_global')); + + // Check data, should be empty + $this->assertEquals([], $reflectionCacheVars->getValue($this->cache_file)); + } + + public function test_read_data_global() + { + $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); + $reflectionCacheVars->setAccessible(true); + $expectedVars = ['foo' => 'bar']; + $reflectionCacheVars->setValue($this->cache_file, $expectedVars); + + $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); + $reflectionCacheVarExpires->setAccessible(true); + $expectedVarExpires = ['foo' => time() + 86400]; + $reflectionCacheVarExpires->setValue($this->cache_file, $expectedVarExpires); + + // Create file in invalid format + $this->assertTrue($this->cache_file->_write('data_global')); + + // Clear data + $reflectionCacheVars->setValue($this->cache_file, []); + $reflectionCacheVarExpires->setValue($this->cache_file, []); + $this->assertEquals([], $reflectionCacheVars->getValue($this->cache_file)); + $this->assertEquals([], $reflectionCacheVarExpires->getValue($this->cache_file)); + + $readReflection = new \ReflectionMethod($this->cache_file, '_read'); + $readReflection->setAccessible(true); + $this->assertTrue($readReflection->invoke($this->cache_file, 'data_global')); + + // Check data, should be empty + $this->assertEquals($expectedVars, $reflectionCacheVars->getValue($this->cache_file)); + $this->assertEquals($expectedVarExpires, $reflectionCacheVarExpires->getValue($this->cache_file)); + } + private function create_cache_dir() { $this->get_test_case_helpers()->makedirs($this->cache_dir); diff --git a/tests/captcha/incomplete_test.php b/tests/captcha/incomplete_test.php index c1da2b6c48..4c5e7c6b27 100644 --- a/tests/captcha/incomplete_test.php +++ b/tests/captcha/incomplete_test.php @@ -11,11 +11,15 @@ * */ +use phpbb\captcha\plugins\confirm_type; use phpbb\captcha\plugins\incomplete; use phpbb\config\config; +use phpbb\language\language; +use phpbb\request\request; use phpbb\template\template; +use phpbb\user; -class phpbb_captcha_incomplete_test extends phpbb_test_case +class phpbb_captcha_incomplete_test extends phpbb_database_test_case { protected config $config; @@ -32,21 +36,34 @@ public function assign_vars(array $vars): void $this->assigned_vars = array_merge($this->assigned_vars, $vars); } + public function getDataSet() + { + return $this->createXMLDataSet(__DIR__ . '/../fixtures/empty.xml'); + } + protected function setUp(): void { global $phpbb_root_path, $phpEx; $this->config = new config([]); $this->template = $this->getMockBuilder('\phpbb\template\twig\twig') - ->setMethods(['assign_vars']) + ->onlyMethods(['assign_vars']) ->disableOriginalConstructor() ->getMock(); $this->template->method('assign_vars') ->willReturnCallback([$this, 'assign_vars']); + $db = $this->new_dbal(); + $language = $this->createMock(language::class); + $request = $this->createMock(request::class); + $user = $this->createMock(user::class); $this->incomplete_captcha = new incomplete( $this->config, + $db, + $language, + $request, $this->template, + $user, $phpbb_root_path, $phpEx ); @@ -57,29 +74,25 @@ public function test_miscellaneous_incomplete(): void $this->assertTrue($this->incomplete_captcha->is_available()); $this->assertFalse($this->incomplete_captcha->is_solved()); $this->assertFalse($this->incomplete_captcha->validate()); - $this->assertSame('CAPTCHA_INCOMPLETE', incomplete::get_name()); - $this->incomplete_captcha->init(0); - $this->incomplete_captcha->execute(); - $this->incomplete_captcha->execute_demo(); + $this->assertFalse($this->incomplete_captcha->has_config()); + $this->incomplete_captcha->set_name('foo'); + $this->assertSame('CAPTCHA_INCOMPLETE', $this->incomplete_captcha->get_name()); + $this->incomplete_captcha->init(confirm_type::UNDEFINED); $this->assertEmpty($this->assigned_vars); $this->assertEmpty($this->incomplete_captcha->get_demo_template(0)); - } - - public function test_get_generator_class(): void - { - $this->expectException(\phpbb\exception\runtime_exception::class); - $this->incomplete_captcha->get_generator_class(); + $this->assertEmpty($this->incomplete_captcha->get_error()); + $this->assertSame(0, $this->incomplete_captcha->get_attempt_count()); } public function test_get_tempate(): void { - $this->incomplete_captcha->init(CONFIRM_REG); + $this->incomplete_captcha->init(confirm_type::REGISTRATION); $this->assertSame('captcha_incomplete.html', $this->incomplete_captcha->get_template()); $this->assertEquals('CONFIRM_INCOMPLETE', $this->assigned_vars['CONFIRM_LANG']); $this->assigned_vars = []; - $this->incomplete_captcha->init(CONFIRM_POST); + $this->incomplete_captcha->init(confirm_type::POST); $this->assertSame('captcha_incomplete.html', $this->incomplete_captcha->get_template()); $this->assertEquals('CONFIRM_INCOMPLETE', $this->assigned_vars['CONFIRM_LANG']); } diff --git a/tests/captcha/legacy_wrapper_test.php b/tests/captcha/legacy_wrapper_test.php new file mode 100644 index 0000000000..873012bd6a --- /dev/null +++ b/tests/captcha/legacy_wrapper_test.php @@ -0,0 +1,261 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use phpbb\captcha\plugins\confirm_type; +use phpbb\captcha\plugins\legacy_wrapper; + +class phpbb_captcha_legacy_wrapper_test extends phpbb_test_case +{ + private $legacy_captcha; + private $legacy_wrapper; + + public function setUp(): void + { + $this->legacy_captcha = $this->createMock(stdClass::class); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + } + + public function test_is_available_with_method_exists(): void + { + // Simulate is_available method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['is_available']) + ->getMock(); + $this->legacy_captcha->method('is_available')->willReturn(true); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + + $this->assertTrue($this->legacy_wrapper->is_available()); + } + + public function test_is_available_without_method_exists(): void + { + // Simulate is_available method does not exist in the legacy captcha + $this->assertFalse($this->legacy_wrapper->is_available()); + } + + public function test_has_config_with_method_exists(): void + { + // Simulate has_config method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['has_config']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->method('has_config')->willReturn(true); + + $this->assertTrue($this->legacy_wrapper->has_config()); + } + + public function test_has_config_without_method_exists(): void + { + // Simulate has_config method does not exist in the legacy captcha + $this->assertFalse($this->legacy_wrapper->has_config()); + } + + public function test_get_name_with_method_exists(): void + { + // Simulate get_name method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['get_name']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->method('get_name')->willReturn('LegacyCaptchaName'); + + $this->assertSame('LegacyCaptchaName', $this->legacy_wrapper->get_name()); + } + + public function test_get_name_without_method_exists(): void + { + // Simulate get_name method does not exist in the legacy captcha + $this->assertSame('', $this->legacy_wrapper->get_name()); + } + + public function test_set_name_with_method_exists(): void + { + // Simulate set_name method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['set_name']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->expects($this->once())->method('set_name')->with('NewName'); + + $this->legacy_wrapper->set_name('NewName'); + } + + public function test_init_with_method_exists(): void + { + // Simulate init method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['init']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->expects($this->once())->method('init')->with(confirm_type::REGISTRATION->value); + + $this->legacy_wrapper->init(confirm_type::REGISTRATION); + } + + public function test_get_hidden_fields_with_method_exists(): void + { + // Simulate get_hidden_fields method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['get_hidden_fields']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->method('get_hidden_fields')->willReturn(['field1' => 'value1']); + + $this->assertSame(['field1' => 'value1'], $this->legacy_wrapper->get_hidden_fields()); + } + + public function test_get_hidden_fields_without_method_exists(): void + { + // Simulate get_hidden_fields method does not exist in the legacy captcha + $this->assertSame([], $this->legacy_wrapper->get_hidden_fields()); + } + + public function test_validate_with_error(): void + { + // Simulate validate method returns an error + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['validate']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->method('validate')->willReturn('Captcha Error'); + + $this->assertFalse($this->legacy_wrapper->validate()); + $this->assertSame('Captcha Error', $this->legacy_wrapper->get_error()); + } + + public function test_validate_without_method_exists(): void + { + $this->assertFalse($this->legacy_wrapper->validate()); + } + + public function test_validate_without_error(): void + { + // Simulate validate method does not return an error + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['validate']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->method('validate')->willReturn(null); + + $this->assertTrue($this->legacy_wrapper->validate()); + } + + public function test_is_solved_with_method_exists(): void + { + // Simulate is_solved method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['is_solved']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->method('is_solved')->willReturn(true); + + $this->assertTrue($this->legacy_wrapper->is_solved()); + } + + public function test_is_solved_without_method_exists(): void + { + // Simulate is_solved method does not exist in the legacy captcha + $this->assertFalse($this->legacy_wrapper->is_solved()); + } + + public function test_reset_with_method_exists(): void + { + // Simulate reset method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['reset']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->expects($this->once())->method('reset'); + + $this->legacy_wrapper->reset(); + } + + public function test_get_attempt_count_with_method_exists(): void + { + // Simulate get_attempt_count method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['get_attempt_count']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->method('get_attempt_count')->willReturn(5); + + $this->assertSame(5, $this->legacy_wrapper->get_attempt_count()); + } + + public function test_get_attempt_count_without_method_exists(): void + { + // Simulate get_attempt_count method does not exist in the legacy captcha + $this->assertSame(PHP_INT_MAX, $this->legacy_wrapper->get_attempt_count()); + } + + public function test_get_template_with_method_exists(): void + { + // Simulate get_template method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['get_template']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->method('get_template')->willReturn('template_content'); + + $this->assertSame('template_content', $this->legacy_wrapper->get_template()); + } + + public function test_get_template_without_method_exists(): void + { + // Simulate get_template method does not exist in the legacy captcha + $this->assertSame('', $this->legacy_wrapper->get_template()); + } + + public function test_get_demo_template_with_method_exists(): void + { + // Simulate get_demo_template method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['get_demo_template']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->method('get_demo_template')->willReturn('demo_template_content'); + + $this->assertSame('demo_template_content', $this->legacy_wrapper->get_demo_template()); + } + + public function test_get_demo_template_without_method_exists(): void + { + // Simulate get_demo_template method does not exist in the legacy captcha + $this->assertSame('', $this->legacy_wrapper->get_demo_template()); + } + + public function test_garbage_collect_with_method_exists(): void + { + // Simulate garbage_collect method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['garbage_collect']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->expects($this->once())->method('garbage_collect')->with(confirm_type::REGISTRATION->value); + + $this->legacy_wrapper->garbage_collect(confirm_type::REGISTRATION); + } + + public function test_acp_page_with_method_exists(): void + { + // Simulate acp_page method exists in the legacy captcha + $this->legacy_captcha = $this->getMockBuilder(stdClass::class) + ->addMethods(['acp_page']) + ->getMock(); + $this->legacy_wrapper = new legacy_wrapper($this->legacy_captcha); + $this->legacy_captcha->expects($this->once())->method('acp_page')->with(1, 'module'); + + $this->legacy_wrapper->acp_page(1, 'module'); + } +} diff --git a/tests/captcha/qa_test.php b/tests/captcha/qa_test.php index d429336104..1c61a7d10c 100644 --- a/tests/captcha/qa_test.php +++ b/tests/captcha/qa_test.php @@ -41,10 +41,6 @@ protected function setUp(): void public function test_is_installed() { - $this->assertFalse($this->qa->is_installed()); - - $this->qa->install(); - $this->assertTrue($this->qa->is_installed()); } diff --git a/tests/captcha/turnstile_test.php b/tests/captcha/turnstile_test.php new file mode 100644 index 0000000000..f78c4d3177 --- /dev/null +++ b/tests/captcha/turnstile_test.php @@ -0,0 +1,547 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use phpbb\captcha\plugins\confirm_type; +use phpbb\captcha\plugins\turnstile; +use phpbb\config\config; +use phpbb\db\driver\driver_interface; +use phpbb\form\form_helper; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\request\request; +use phpbb\request\request_interface; +use phpbb\template\template; +use phpbb\user; +use GuzzleHttp\Client; +use GuzzleHttp\Psr7\Response; + +require_once __DIR__ . '/../../phpBB/includes/functions_acp.php'; + +class phpbb_captcha_turnstile_test extends \phpbb_database_test_case +{ + /** @var turnstile */ + protected $turnstile; + + /** @var PHPUnit\Framework\MockObject\MockObject */ + protected $config; + + /** @var PHPUnit\Framework\MockObject\MockObject */ + protected $db; + + /** @var PHPUnit\Framework\MockObject\MockObject */ + protected $language; + + /** @var PHPUnit\Framework\MockObject\MockObject */ + protected $log; + + /** @var PHPUnit\Framework\MockObject\MockObject */ + protected $request; + + /** @var PHPUnit\Framework\MockObject\MockObject */ + protected $template; + + /** @var PHPUnit\Framework\MockObject\MockObject */ + protected $user; + + public function getDataSet() + { + return $this->createXMLDataSet(__DIR__ . '/../fixtures/empty.xml'); + } + + protected function setUp(): void + { + // Mock the dependencies + $this->config = $this->createMock(config::class); + $this->db = $this->new_dbal(); + $this->language = $this->createMock(language::class); + $this->log = $this->createMock(log_interface::class); + $this->request = $this->createMock(request::class); + $this->template = $this->createMock(template::class); + $this->user = $this->createMock(user::class); + + $this->language->method('lang')->willReturnArgument(0); + + // Instantiate the turnstile class with the mocked dependencies + $this->turnstile = new turnstile( + $this->config, + $this->db, + $this->language, + $this->log, + $this->request, + $this->template, + $this->user + ); + } + + public function test_is_available(): void + { + // Test when both sitekey and secret are present + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_secret', 'secret_value'], + ]); + + $this->request->method('variable')->willReturnMap([ + ['confirm_id', '', false, request_interface::REQUEST, 'confirm_id'], + ['confirm_code', '', false, request_interface::REQUEST, 'confirm_code'] + ]); + + $this->assertTrue($this->turnstile->is_available()); + + $this->assertEquals(0, $this->turnstile->get_attempt_count()); + } + + public function test_attempt_count_increase(): void + { + // Test when both sitekey and secret are present + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_secret', 'secret_value'], + ]); + + $this->request->method('variable')->willReturnMap([ + ['confirm_id', '', false, request_interface::REQUEST, 'confirm_id'], + ['confirm_code', '', false, request_interface::REQUEST, 'confirm_code'] + ]); + + $this->turnstile->init(confirm_type::REGISTRATION); + $this->assertFalse($this->turnstile->validate()); + + $confirm_id_reflection = new \ReflectionProperty($this->turnstile, 'confirm_id'); + $confirm_id = $confirm_id_reflection->getValue($this->turnstile); + + $this->request = $this->createMock(request::class); + $this->request->method('variable')->willReturnMap([ + ['confirm_id', '', false, request_interface::REQUEST, $confirm_id], + ['confirm_code', '', false, request_interface::REQUEST, 'confirm_code'] + ]); + + $this->turnstile = new turnstile( + $this->config, + $this->db, + $this->language, + $this->log, + $this->request, + $this->template, + $this->user + ); + + $this->turnstile->init(confirm_type::REGISTRATION); + $this->assertEquals(1, $this->turnstile->get_attempt_count()); + + // Run some garbage collection + $this->turnstile->garbage_collect(confirm_type::REGISTRATION); + + // Start again at 0 after garbage collection + $this->turnstile->init(confirm_type::REGISTRATION); + $this->assertEquals(0, $this->turnstile->get_attempt_count()); + } + + public function test_reset(): void + { + // Test when both sitekey and secret are present + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_secret', 'secret_value'], + ]); + + $this->request->method('variable')->willReturnMap([ + ['confirm_id', '', false, request_interface::REQUEST, 'confirm_id'], + ['confirm_code', '', false, request_interface::REQUEST, 'confirm_code'] + ]); + + $this->turnstile->init(confirm_type::REGISTRATION); + $this->assertFalse($this->turnstile->validate()); + $this->turnstile->reset(); + + $confirm_id_reflection = new \ReflectionProperty($this->turnstile, 'confirm_id'); + $confirm_id = $confirm_id_reflection->getValue($this->turnstile); + + $this->request = $this->createMock(request::class); + $this->request->method('variable')->willReturnMap([ + ['confirm_id', '', false, request_interface::REQUEST, $confirm_id], + ['confirm_code', '', false, request_interface::REQUEST, 'confirm_code'] + ]); + + $this->turnstile = new turnstile( + $this->config, + $this->db, + $this->language, + $this->log, + $this->request, + $this->template, + $this->user + ); + + $this->turnstile->init(confirm_type::REGISTRATION); + // Should be zero attempts since we reset the captcha + $this->assertEquals(0, $this->turnstile->get_attempt_count()); + } + + public function test_get_hidden_fields(): void + { + // Test when both sitekey and secret are present + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_secret', 'secret_value'], + ]); + + $this->request->method('variable')->willReturnMap([ + ['confirm_id', '', false, request_interface::REQUEST, 'confirm_id'], + ['confirm_code', '', false, request_interface::REQUEST, 'confirm_code'] + ]); + + $this->turnstile->init(confirm_type::REGISTRATION); + $this->assertFalse($this->turnstile->validate()); + $this->turnstile->reset(); + + $confirm_id_reflection = new \ReflectionProperty($this->turnstile, 'confirm_id'); + $confirm_id = $confirm_id_reflection->getValue($this->turnstile); + + $this->assertEquals( + [ + 'confirm_id' => $confirm_id, + 'confirm_code' => '', + ], + $this->turnstile->get_hidden_fields(), + ); + $this->assertEquals('CONFIRM_CODE_WRONG', $this->turnstile->get_error()); + } + + public function test_not_available(): void + { + $this->request->method('variable')->willReturnMap([ + ['confirm_id', '', false, request_interface::REQUEST, 'confirm_id'], + ['confirm_code', '', false, request_interface::REQUEST, 'confirm_code'] + ]); + + // Test when sitekey or secret is missing + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', ''], + ['captcha_turnstile_secret', 'secret_value'], + ]); + + $this->assertFalse($this->turnstile->is_available()); + } + + public function test_get_name(): void + { + $this->assertEquals('CAPTCHA_TURNSTILE', $this->turnstile->get_name()); + } + + public function test_set_Name(): void + { + $this->turnstile->set_name('custom_service'); + $service_name_property = new \ReflectionProperty($this->turnstile, 'service_name'); + $service_name_property->setAccessible(true); + $this->assertEquals('custom_service', $service_name_property->getValue($this->turnstile)); + } + + public function test_validate_without_response(): void + { + // Test when there is no Turnstile response + $this->request->method('variable')->with('cf-turnstile-response')->willReturn(''); + + $this->assertFalse($this->turnstile->validate()); + } + + public function test_validate_with_response_success(): void + { + // Mock the request and response from the Turnstile API + $this->request->method('variable')->with('cf-turnstile-response')->willReturn('valid_response'); + $this->request->method('header')->with('CF-Connecting-IP')->willReturn('127.0.0.1'); + + // Mock the GuzzleHttp client and response + $client_mock = $this->createMock(Client::class); + $response_mock = $this->createMock(Response::class); + + $client_mock->method('request')->willReturn($response_mock); + $response_mock->method('getBody')->willReturn(json_encode(['success' => true])); + + // Mock config values for secret + $this->config->method('offsetGet')->willReturn('secret_value'); + + // Use reflection to inject the mocked client into the turnstile class + $reflection = new \ReflectionClass($this->turnstile); + $client_property = $reflection->getProperty('client'); + $client_property->setAccessible(true); + $client_property->setValue($this->turnstile, $client_mock); + + // Validate that the CAPTCHA was solved successfully + $this->assertTrue($this->turnstile->validate()); + } + + public function test_validate_with_guzzle_exception(): void + { + // Mock the request and response from the Turnstile API + $this->request->method('variable')->with('cf-turnstile-response')->willReturn('valid_response'); + $this->request->method('header')->with('CF-Connecting-IP')->willReturn('127.0.0.1'); + + // Mock the GuzzleHttp client and response + $client_mock = $this->createMock(Client::class); + + $request_mock = $this->createMock(\GuzzleHttp\Psr7\Request::class); + $exception = new \GuzzleHttp\Exception\ConnectException('Failed at connecting', $request_mock); + $client_mock->method('request')->willThrowException($exception); + + // Mock config values for secret + $this->config->method('offsetGet')->willReturn('secret_value'); + + // Use reflection to inject the mocked client into the turnstile class + $reflection = new \ReflectionClass($this->turnstile); + $client_property = $reflection->getProperty('client'); + $client_property->setAccessible(true); + $client_property->setValue($this->turnstile, $client_mock); + + // Validatation fails due to guzzle exception + $this->assertFalse($this->turnstile->validate()); + } + + public function test_validate_previous_solve(): void + { + // Use reflection to inject the mocked client into the turnstile class + $reflection = new \ReflectionClass($this->turnstile); + $confirm_id = $reflection->getProperty('confirm_id'); + $confirm_id->setValue($this->turnstile, 'confirm_id'); + $code_property = $reflection->getProperty('code'); + $code_property->setValue($this->turnstile, 'test_code'); + $confirm_code_property = $reflection->getProperty('confirm_code'); + $confirm_code_property->setValue($this->turnstile, 'test_code'); + + // Validate that the CAPTCHA was solved successfully + $this->assertTrue($this->turnstile->validate()); + $this->assertTrue($this->turnstile->is_solved()); + } + + public function test_has_config(): void + { + $this->assertTrue($this->turnstile->has_config()); + } + + public function test_get_client(): void + { + $turnstile_reflection = new \ReflectionClass($this->turnstile); + $get_client_method = $turnstile_reflection->getMethod('get_client'); + $get_client_method->setAccessible(true); + $client_property = $turnstile_reflection->getProperty('client'); + $client_property->setAccessible(true); + + $this->assertFalse($client_property->isInitialized($this->turnstile)); + $client = $get_client_method->invoke($this->turnstile); + $this->assertNotNull($client); + $this->assertInstanceOf(\GuzzleHttp\Client::class, $client); + $this->assertTrue($client === $get_client_method->invoke($this->turnstile)); + } + + public function test_validate_with_response_failure(): void + { + // Mock the request and response from the Turnstile API + $this->request->method('variable')->with('cf-turnstile-response')->willReturn('valid_response'); + $this->request->method('header')->with('CF-Connecting-IP')->willReturn('127.0.0.1'); + + // Mock the GuzzleHttp client and response + $client_mock = $this->createMock(Client::class); + $response_mock = $this->createMock(Response::class); + + $client_mock->method('request')->willReturn($response_mock); + $response_mock->method('getBody')->willReturn(json_encode(['success' => false])); + + // Mock config values for secret + $this->config->method('offsetGet')->willReturn('secret_value'); + + // Use reflection to inject the mocked client into the turnstile class + $reflection = new \ReflectionClass($this->turnstile); + $client_property = $reflection->getProperty('client'); + $client_property->setAccessible(true); + $client_property->setValue($this->turnstile, $client_mock); + + // Validate that the CAPTCHA was not solved + $this->assertFalse($this->turnstile->validate()); + } + + public function test_get_template(): void + { + // Mock is_solved to return false + $is_solved_property = new \ReflectionProperty($this->turnstile, 'solved'); + $is_solved_property->setAccessible(true); + $is_solved_property->setValue($this->turnstile, false); + + // Mock the template assignments + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_theme', 'light'], + ]); + + $this->request->method('variable')->willReturnMap([ + ['confirm_id', '', false, request_interface::REQUEST, 'confirm_id'], + ['confirm_code', '', false, request_interface::REQUEST, 'confirm_code'] + ]); + + $this->template->expects($this->once())->method('assign_vars')->with([ + 'S_TURNSTILE_AVAILABLE' => $this->turnstile->is_available(), + 'TURNSTILE_SITEKEY' => 'sitekey_value', + 'TURNSTILE_THEME' => 'light', + 'U_TURNSTILE_SCRIPT' => 'https://challenges.cloudflare.com/turnstile/v0/api.js', + 'CONFIRM_TYPE_REGISTRATION' => confirm_type::UNDEFINED->value, + ]); + + $this->assertEquals('captcha_turnstile.html', $this->turnstile->get_template()); + + $is_solved_property->setValue($this->turnstile, true); + $this->assertEquals('', $this->turnstile->get_template()); + } + + public function test_get_demo_template(): void + { + // Mock the config assignments + $this->config->method('offsetGet')->willReturn('light'); + + $this->template->expects($this->once())->method('assign_vars')->with([ + 'TURNSTILE_THEME' => 'light', + 'U_TURNSTILE_SCRIPT' => 'https://challenges.cloudflare.com/turnstile/v0/api.js', + ]); + + $this->assertEquals('captcha_turnstile_acp_demo.html', $this->turnstile->get_demo_template()); + } + + public function test_acp_page_display(): void + { + global $phpbb_container, $phpbb_dispatcher, $template; + + $phpbb_container = new phpbb_mock_container_builder(); + $form_helper = new form_helper($this->config, $this->request, $this->user); + $phpbb_container->set('form_helper', $form_helper); + $this->user->data['user_id'] = ANONYMOUS; + $this->user->data['user_form_salt'] = 'foobar'; + + $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + $template = $this->template; + + // Mock the template assignments + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_theme', 'light'], + ]); + + $this->request->method('variable')->willReturn(''); + + $expected = [ + 1 => [ + 'TURNSTILE_THEME' => 'light', + 'U_TURNSTILE_SCRIPT' => 'https://challenges.cloudflare.com/turnstile/v0/api.js', + ], + 2 => [ + 'CAPTCHA_PREVIEW' => 'captcha_turnstile_acp_demo.html', + 'CAPTCHA_NAME' => '', + 'CAPTCHA_TURNSTILE_THEME' => 'light', + 'CAPTCHA_TURNSTILE_THEMES' => ['light', 'dark', 'auto'], + 'U_ACTION' => 'test_u_action', + ], + ]; + $matcher = $this->exactly(count($expected)); + $this->template + ->expects($matcher) + ->method('assign_vars') + ->willReturnCallback(function ($template_data) use ($matcher, $expected) { + $callNr = $matcher->getInvocationCount(); + $this->assertEquals($expected[$callNr], $template_data); + }); + + $module_mock = new ModuleMock(); + + $this->turnstile->acp_page('', $module_mock); + } + + public function test_acp_page_submit_without_form(): void + { + global $language, $phpbb_container, $phpbb_dispatcher, $template; + + $language = $this->language; + $phpbb_container = new phpbb_mock_container_builder(); + $form_helper = new form_helper($this->config, $this->request, $this->user); + $phpbb_container->set('form_helper', $form_helper); + $this->user->data['user_id'] = ANONYMOUS; + $this->user->data['user_form_salt'] = 'foobar'; + + $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + $template = $this->template; + + // Mock the template assignments + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_theme', 'light'], + ]); + + $this->request->method('is_set_post')->willReturnMap([ + ['creation_time', ''], + ['submit', true] + ]); + + $this->setExpectedTriggerError(E_USER_NOTICE, 'FORM_INVALID'); + + $module_mock = new ModuleMock(); + + $this->turnstile->acp_page('', $module_mock); + } + + public function test_acp_page_submit_valid(): void + { + global $language, $phpbb_container, $phpbb_dispatcher, $template; + + $language = $this->language; + $phpbb_container = new phpbb_mock_container_builder(); + $form_helper = new form_helper($this->config, $this->request, $this->user); + $phpbb_container->set('form_helper', $form_helper); + $this->user->data['user_id'] = ANONYMOUS; + $this->user->data['user_form_salt'] = 'foobar'; + + $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + $template = $this->template; + + $form_tokens = $form_helper->get_form_tokens('acp_captcha'); + + // Mock the template assignments + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_theme', 'light'], + ]); + $this->config['form_token_lifetime'] = 3600; + + $this->request->method('is_set_post')->willReturnMap([ + ['creation_time', true], + ['form_token', true], + ['submit', true] + ]); + + $this->request->method('variable')->willReturnMap([ + ['creation_time', 0, false, request_interface::REQUEST, $form_tokens['creation_time']], + ['form_token', '', false, request_interface::REQUEST, $form_tokens['form_token']], + ['captcha_turnstile_sitekey', '', false, request_interface::REQUEST, 'newsitekey'], + ['captcha_turnstile_theme', 'light', false, request_interface::REQUEST, 'auto'], + ]); + + $this->setExpectedTriggerError(E_USER_NOTICE, 'CONFIG_UPDATED'); + + $module_mock = new ModuleMock(); + sleep(1); // sleep for a second to ensure form token validation succeeds + + $this->turnstile->acp_page('', $module_mock); + } +} + +class ModuleMock +{ + public string $tpl_name = ''; + public string $page_title = ''; + public string $u_action = 'test_u_action'; +} diff --git a/tests/config/db_test.php b/tests/config/db_test.php index 11c99435f8..5bed8d405d 100644 --- a/tests/config/db_test.php +++ b/tests/config/db_test.php @@ -136,7 +136,7 @@ public function test_increment() public function test_increment_new() { $this->config->increment('foobar', 3); - $this->assertEquals(3, $this->config['foobar']);; + $this->assertEquals(3, $this->config['foobar']); } public function test_delete() diff --git a/tests/console/user/delete_id_test.php b/tests/console/user/delete_id_test.php new file mode 100644 index 0000000000..fb5b60ba0e --- /dev/null +++ b/tests/console/user/delete_id_test.php @@ -0,0 +1,129 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Tester\CommandTester; +use phpbb\console\command\user\delete_id; + +require_once __DIR__ . '/base.php'; + +class phpbb_console_user_delete_ids_test extends phpbb_console_user_base +{ + public function get_command_tester() + { + $application = new Application(); + $application->add(new delete_id( + $this->db, + $this->language, + $this->log, + $this->user, + $this->user_loader, + BOTS_TABLE, + USER_GROUP_TABLE, + USERS_TABLE, + $this->phpbb_root_path, + $this->php_ext + )); + + $command = $application->find('user:delete_id'); + $this->command_name = $command->getName(); + $this->question = $command->getHelper('question'); + + return new CommandTester($command); + } + + public function test_delete() + { + $command_tester = $this->get_command_tester(); + + $command_tester->setInputs(['yes', '']); + + $command_tester->execute(array( + 'command' => $this->command_name, + 'user_ids' => [3, 4], + '--delete-posts' => false, + )); + + $this->assertNull($this->get_user_id('Test')); + $this->assertNull($this->get_user_id('Test 2')); + $this->assertStringContainsString('CLI_USER_DELETE_ID_SUCCESS', $command_tester->getDisplay()); + } + + public function test_delete_one() + { + $command_tester = $this->get_command_tester(); + + $command_tester->setInputs(['yes', '']); + + $command_tester->execute(array( + 'command' => $this->command_name, + 'user_ids' => [3], + '--delete-posts' => false, + )); + + $this->assertNull($this->get_user_id('Test')); + $this->assertNotNull($this->get_user_id('Test 2')); + $this->assertStringContainsString('CLI_USER_DELETE_ID_SUCCESS', $command_tester->getDisplay()); + } + + public function test_delete_bot() + { + $command_tester = $this->get_command_tester(); + + $this->assertNotNull($this->get_user_id('Test Bot')); + + $command_tester->setInputs(['yes', '']); + + $command_tester->execute(array( + 'command' => $this->command_name, + 'user_ids' => [6], + '--delete-posts' => false, + )); + + $this->assertNull($this->get_user_id('Test Bot')); + $this->assertStringContainsString('CLI_USER_DELETE_ID_SUCCESS', $command_tester->getDisplay()); + } + + public function test_delete_non_user() + { + $command_tester = $this->get_command_tester(); + + $command_tester->setInputs(['yes', '']); + + $command_tester->execute(array( + 'command' => $this->command_name, + 'user_ids' => [999], + '--delete-posts' => false, + )); + + $this->assertStringContainsString('CLI_USER_DELETE_NONE', $command_tester->getDisplay()); + } + + public function test_delete_cancel() + { + $command_tester = $this->get_command_tester(); + + $this->assertEquals(3, $this->get_user_id('Test')); + + $command_tester->setInputs(['no', '']); + + $command_tester->execute(array( + 'command' => $this->command_name, + 'user_ids' => [3, 4], + '--delete-posts' => false, + )); + + $this->assertNotNull($this->get_user_id('Test')); + $this->assertNotNull($this->get_user_id('Test 2')); + } +} diff --git a/tests/console/user/fixtures/config.xml b/tests/console/user/fixtures/config.xml index a988ba463f..eba2eba637 100644 --- a/tests/console/user/fixtures/config.xml +++ b/tests/console/user/fixtures/config.xml @@ -47,6 +47,14 @@ 0 + + 6 + + Test Bot + Test Bot + + 2 + group_id @@ -59,5 +67,23 @@ 3foobar + + 6 + BOTS + 3 + + +
      + + group_id + user_id + group_leader + user_pending + + 6 + 6 + 0 + 0 +
      diff --git a/tests/controller/common_helper_route.php b/tests/controller/common_helper_route.php index 45e0386195..0cd52b7d8b 100644 --- a/tests/controller/common_helper_route.php +++ b/tests/controller/common_helper_route.php @@ -111,7 +111,7 @@ protected function generate_route_objects() $this->config = new \phpbb\config\config(array('enable_mod_rewrite' => '0')); $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); $lang = new \phpbb\language\language($lang_loader); - $this->user = new \phpbb\user($lang, '\phpbb\datetime');; + $this->user = new \phpbb\user($lang, '\phpbb\datetime'); $container = new phpbb_mock_container_builder(); $container->setParameter('core.environment', PHPBB_ENVIRONMENT); diff --git a/tests/dbal/migrator_tool_permission_role_test.php b/tests/dbal/migrator_tool_permission_role_test.php index 55bda8c0ce..dc180dc154 100644 --- a/tests/dbal/migrator_tool_permission_role_test.php +++ b/tests/dbal/migrator_tool_permission_role_test.php @@ -28,6 +28,12 @@ class phpbb_dbal_migrator_tool_permission_role_test extends phpbb_database_test_ 'ADMINISTRATORS' => 5, ]; + public $role_ids = [ + 'ROLE_ADMIN_STANDARD' => 1, + 'ROLE_USER_FULL' => 5, + 'ROLE_MOD_FULL' => 10, + ]; + public $new_roles = [ [ 'ROLE_ADMIN_NEW', @@ -196,4 +202,32 @@ public function test_permission_new_role_remove($ug_type, $forum_id, $group_name $this->assertFalse($this->db->sql_fetchfield('auth_role_id')); $this->db->sql_freeresult($result); } + + public function test_copied_permission_set() + { + $sql = 'SELECT rdt.auth_setting + FROM ' . ACL_OPTIONS_TABLE. ' ot, ' . ACL_ROLES_DATA_TABLE . ' rdt + WHERE rdt.role_id = ' . $this->role_ids['ROLE_ADMIN_STANDARD'] . " + AND auth_option = 'u_copied_permission' + AND ot.auth_option_id = rdt.auth_option_id"; + + // Add new local 'u_copied_permission' copied from 'u_test' + // It should be added to the ROLE_ADMIN_STANDARD role automatically similar to 'u_test' permission + $this->tool->add('u_copied_permission', false, 'u_test'); + $this->assertEquals(true, $this->tool->exists('u_copied_permission', false)); + + // Copied permission setting should be equal to what it was copied from + $result = $this->db->sql_query($sql); + $this->assertEquals(0, $this->db->sql_fetchfield('auth_setting')); + $this->db->sql_freeresult($result); + + // Set new permission for copied auth option for the role + $this->tool->permission_set('ROLE_ADMIN_STANDARD', 'u_copied_permission', 'role', true); + + // Copied permission setting should be updated + $result = $this->db->sql_query($sql); + $this->assertEquals(1, $this->db->sql_fetchfield('auth_setting')); + $this->db->sql_freeresult($result); + } + } diff --git a/tests/dbal/write_test.php b/tests/dbal/write_test.php index f0093ff869..f94aebd85b 100644 --- a/tests/dbal/write_test.php +++ b/tests/dbal/write_test.php @@ -73,6 +73,67 @@ public function test_delete() $db->sql_freeresult($result); } + public function test_delete_rollback() + { + $db = $this->new_dbal(); + + $is_myisam = false; + if ($db->get_sql_layer() === 'mysqli') + { + $table_status = $db->get_table_status('phpbb_config'); + $is_myisam = isset($table_status['Engine']) && $table_status['Engine'] === 'MyISAM'; + } + + $db->sql_transaction('begin'); + + $sql = "DELETE FROM phpbb_config + WHERE config_name = 'config1'"; + $db->sql_query($sql); + + // Rollback and check that nothing was changed + $db->sql_transaction('rollback'); + + $sql = 'SELECT * + FROM phpbb_config'; + $result = $db->sql_query($sql); + $rows = $db->sql_fetchrowset($result); + $db->sql_freeresult($result); + + if (!$is_myisam) + { + $this->assertEquals(2, count($rows)); + $this->assertEquals('config1', $rows[0]['config_name']); + } + else + { + // Rollback does not work on MyISAM + $this->assertEquals(1, count($rows)); + $this->assertEquals('config2', $rows[0]['config_name']); + + // Restore deleted config value on MyISAM + $sql = "INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('config1', 'foo', 0)"; + $db->sql_query($sql); + } + + $db->sql_transaction('begin'); + + $sql = "DELETE FROM phpbb_config + WHERE config_name = 'config1'"; + $db->sql_query($sql); + + // Commit and check that data was actually changed + $db->sql_transaction('commit'); + + $sql = 'SELECT * + FROM phpbb_config'; + $result = $db->sql_query($sql); + $rows = $db->sql_fetchrowset($result); + $db->sql_freeresult($result); + + $this->assertEquals(1, count($rows)); + $this->assertEquals('config2', $rows[0]['config_name']); + } + public function test_multiple_insert() { $db = $this->new_dbal(); diff --git a/tests/functional/acp_groups_test.php b/tests/functional/acp_groups_test.php index 20f1271011..34cb16b99a 100644 --- a/tests/functional/acp_groups_test.php +++ b/tests/functional/acp_groups_test.php @@ -123,4 +123,20 @@ public function test_acp_groups_teampage($group_id, $tick_legend, $tick_teampage $this->assertEquals((bool) $tick_teampage, (bool) ($this->form_data['group_teampage'] ?? false)); } } + + public function test_acp_groups_create_existing_name() + { + $this->group_manage_login(); + + $crawler = self::request('GET', 'adm/index.php?i=groups&mode=manage&sid=' . $this->sid); + $form = $crawler->selectButton($this->lang('SUBMIT'))->form([ + 'group_name' => 'Guests', // 'Guests' is the group name already in use for predefined Guests group + ]); + + $crawler = self::submit($form); + $form = $crawler->selectButton($this->lang('SUBMIT'))->form(); + $crawler = self::submit($form); // Just submit the form with selected group name + + $this->assertStringContainsString($this->lang('GROUP_NAME_TAKEN'), $crawler->text()); + } } diff --git a/tests/functional/acp_main_test.php b/tests/functional/acp_main_test.php index d392102e1a..2caa3bbae2 100644 --- a/tests/functional/acp_main_test.php +++ b/tests/functional/acp_main_test.php @@ -28,4 +28,42 @@ public function test_acp_database_size() $this->assertContainsLang('DATABASE_SIZE', $crawler->filter('tbody > tr')->eq(2)->filter('td[class="tabled"]')->eq(0)->text()); $this->assertNotContainsLang('NOT_AVAILABLE', $crawler->filter('tbody > tr')->eq(2)->filter('td[class="tabled"]')->eq(1)->text()); } + + public function test_all_acp_module_links() + { + $this->add_lang('common'); + $this->login(); + $this->admin_login(); + + // Browse ACP main page + $crawler = self::request('GET', 'index.php'); + $crawler = self::$client->click($crawler->selectLink($this->lang('ACP_SHORT'))->link()); + + // Get all ACP module URLs array + $acp_modules = $crawler->filter('.tabs a')->each( + function ($node, $i) + { + return $node->link(); + } + ); + + // Browse all ACP modules and get their mode URLs array + $acp_submodules = []; + foreach ($acp_modules as $module) + { + $crawler = self::$client->click($module); + $acp_submodules = array_merge($acp_submodules, $crawler->filter('.menu-block > ul a')->each( + function ($node, $i) + { + return $node->link(); + } + )); + } + + // Browse all ACP submodules' modes + foreach ($acp_submodules as $acp_submodule) + { + self::$client->click($acp_submodule); + } + } } diff --git a/tests/functional/acp_permissions_test.php b/tests/functional/acp_permissions_test.php index 92980dbfd7..39ecbf99e2 100644 --- a/tests/functional/acp_permissions_test.php +++ b/tests/functional/acp_permissions_test.php @@ -149,4 +149,26 @@ public function test_forum_permissions_misc() $this->assertContainsLang('ACL_M_EDIT', $page_text); $this->assertContainsLang('ACL_M_MOVE', $page_text); } + + public function test_tracing_user_based_permissions() + { + $this->create_user('newlyregistereduser'); + + // Open user-based permissions masks page + $crawler = self::request('GET', "adm/index.php?i=acp_permissions&icat=16&mode=view_user_global&sid=" . $this->sid); + + // Select newlyregistereduser + $form = $crawler->filter('#add_user')->form(['username' => ['newlyregistereduser']]); + $crawler = self::submit($form); + + // Test 1st "Yes" permission tracing result match + $trace_link_yes = $crawler->filter('td.yes')->eq(0)->siblings()->filter('th > a.trace')->link(); + $crawler_trace_yes = self::$client->click($trace_link_yes); + $this->assertEquals(1, $crawler_trace_yes->filter('tr.row2 > td.yes')->count()); + + // Test 1st "Never" permission tracing result match + $trace_link_never = $crawler->filter('td.never')->eq(0)->siblings()->filter('th > a.trace')->link(); + $crawler_trace_never = self::$client->click($trace_link_never); + $this->assertEquals(1, $crawler_trace_never->filter('tr.row2 > td.never')->count()); + } } diff --git a/tests/functional/acp_smilies_test.php b/tests/functional/acp_smilies_test.php index 955a00b488..c8aa1d9b7d 100644 --- a/tests/functional/acp_smilies_test.php +++ b/tests/functional/acp_smilies_test.php @@ -24,8 +24,8 @@ public function test_htmlspecialchars() // Create the BBCode $crawler = self::request('GET', 'adm/index.php?i=acp_icons&sid=' . $this->sid . '&mode=smilies&action=edit&id=1'); $form = $crawler->selectButton('Submit')->form(array( - 'code[icon_e_biggrin.gif]' => '>:D', - 'emotion[icon_e_biggrin.gif]' => '>:D' + 'code[icon_e_biggrin.svg]' => '>:D', + 'emotion[icon_e_biggrin.svg]' => '>:D' )); self::submit($form); diff --git a/tests/functional/acp_storage_settings_test.php b/tests/functional/acp_storage_settings_test.php index b99e3e8fa9..f8c1728dcb 100644 --- a/tests/functional/acp_storage_settings_test.php +++ b/tests/functional/acp_storage_settings_test.php @@ -68,9 +68,9 @@ public function test_storage_settings() // Visit ACP Storage settings again - warning should be displayed $crawler = self::request('GET', 'adm/index.php?i=acp_storage&mode=settings&sid=' . $this->sid); $this->assertContainsLang('WARNING', $crawler->filter('div[class="errorbox"] > h3')->text()); - $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_ATTACHMENT_TITLE')), $crawler->filter('div[class="errorbox"] > p')->text()); - $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_AVATAR_TITLE')), $crawler->filter('div[class="errorbox"] > p')->text()); - $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_BACKUP_TITLE')), $crawler->filter('div[class="errorbox"] > p')->text()); + $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_ATTACHMENT_TITLE')), $crawler->filter('div[class="errorbox"]')->text()); + $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_AVATAR_TITLE')), $crawler->filter('div[class="errorbox"]')->text()); + $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_BACKUP_TITLE')), $crawler->filter('div[class="errorbox"]')->text()); // Restore default state $filesystem->chmod($phpbb_root_path . $attachments_storage_path, 777); diff --git a/tests/functional/extension_acp_test.php b/tests/functional/extension_acp_test.php index 02b0ef45f2..30dcaa89c7 100644 --- a/tests/functional/extension_acp_test.php +++ b/tests/functional/extension_acp_test.php @@ -84,7 +84,8 @@ public function test_list() $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid); $this->assertCount(1, $crawler->filter('.ext_enabled')); - $this->assertCount(7, $crawler->filter('.ext_disabled')); + $this->assertCount(3, $crawler->filter('.ext_disabled')); + $this->assertCount(4, $crawler->filter('.ext_not_installed')); $this->assertStringContainsString('phpBB Foo Extension', $crawler->filter('.ext_enabled')->eq(0)->text()); $this->assertContainsLang('EXTENSION_DISABLE', $crawler->filter('.ext_enabled')->eq(0)->text()); @@ -98,9 +99,9 @@ public function test_list() $this->assertStringContainsString('The “vendor/test3” extension is not valid.', $crawler->filter('.ext_disabled')->eq(1)->text()); - $this->assertStringContainsString('phpBB Bar Extension', $crawler->filter('.ext_disabled')->eq(3)->text()); - $this->assertContainsLang('DETAILS', $crawler->filter('.ext_disabled')->eq(3)->text()); - $this->assertContainsLang('EXTENSION_ENABLE', $crawler->filter('.ext_disabled')->eq(3)->text()); + $this->assertStringContainsString('phpBB Bar Extension', $crawler->filter('.ext_not_installed')->eq(0)->text()); + $this->assertContainsLang('DETAILS', $crawler->filter('.ext_not_installed')->eq(0)->text()); + $this->assertContainsLang('EXTENSION_ENABLE', $crawler->filter('.ext_not_installed')->eq(0)->text()); // Check that invalid extensions are not listed. $this->assertStringNotContainsString('phpBB BarFoo Extension', $crawler->filter('.table1')->text()); diff --git a/tests/functional/extension_module_test.php b/tests/functional/extension_module_test.php index 67dc35ae73..0e56e43c78 100644 --- a/tests/functional/extension_module_test.php +++ b/tests/functional/extension_module_test.php @@ -54,6 +54,7 @@ protected static function setup_extensions() public function test_acp() { + $this->add_lang('common'); $this->login(); $this->admin_login(); @@ -64,43 +65,86 @@ public function test_acp() $this->assertStringContainsString('SETTING_0', $crawler->filter('dl')->eq(0)->filter('dt > label[for="setting_0"]')->text()); $this->assertStringContainsString('SETTING_0_EXPLAIN', $crawler->filter('dl')->eq(0)->filter('dt > span')->text()); $this->assertEquals(2, $crawler->filter('dl')->eq(0)->filter('dd > input[type="number"]')->count()); + $this->assertEquals(1, $crawler->filter('dl')->eq(0)->filter('dd > input[type="number"]')->eq(0)->attr('value')); + $this->assertEquals(17, $crawler->filter('dl')->eq(0)->filter('dd > input[type="number"]')->eq(1)->attr('value')); $this->assertStringContainsString('SETTING_1', $crawler->filter('dl')->eq(1)->filter('dt > label[for="setting_1"]')->text()); $this->assertStringContainsString('CUSTOM_LANG_EXPLAIN', $crawler->filter('dl')->eq(1)->filter('dt > span')->text()); $this->assertEquals(1, $crawler->filter('dl')->eq(1)->filter('dd > input[type="submit"]')->count()); + $this->assertEquals('Test submit button', $crawler->filter('dl')->eq(1)->filter('dd > input[type="submit"]')->attr('value')); $this->assertStringContainsString('SETTING_2', $crawler->filter('dl')->eq(2)->filter('dt > label[for="setting_2"]')->text()); $this->assertEquals(0, $crawler->filter('dl')->eq(2)->filter('dt > span')->count()); $this->assertEquals(2, $crawler->filter('dl')->eq(2)->filter('dd > label > input[type="radio"]')->count()); + $this->assertContainsLang('YES', $crawler->filter('dl')->eq(2)->filter('dd > label')->eq(0)->text()); + $this->assertEquals(1, $crawler->filter('dl')->eq(2)->filter('dd > label > input[type="radio"]')->eq(0)->attr('value')); + $this->assertEquals('checked', $crawler->filter('dl')->eq(2)->filter('dd > label > input[type="radio"]')->eq(0)->attr('checked')); + $this->assertContainsLang('NO', $crawler->filter('dl')->eq(2)->filter('dd > label')->eq(1)->text()); + $this->assertEquals(0, $crawler->filter('dl')->eq(2)->filter('dd > label > input[type="radio"]')->eq(1)->attr('value')); $this->assertStringContainsString('SETTING_3', $crawler->filter('dl')->eq(3)->filter('dt > label[for="setting_3"]')->text()); $this->assertStringContainsString('SETTING_3_EXPLAIN', $crawler->filter('dl')->eq(3)->filter('dt > span')->text()); $this->assertEquals(1, $crawler->filter('dl')->eq(3)->filter('dd > input[type="number"]')->count()); + $this->assertEquals(15, $crawler->filter('dl')->eq(3)->filter('dd > input[type="number"]')->attr('value')); $this->assertStringContainsString('SETTING_4', $crawler->filter('dl')->eq(4)->filter('dt > label[for="setting_4"]')->text()); $this->assertStringContainsString('SETTING_4_EXPLAIN', $crawler->filter('dl')->eq(4)->filter('dt > span')->text()); $this->assertEquals(1, $crawler->filter('dl')->eq(4)->filter('dd > select[id="setting_4"]')->count()); $this->assertEquals(3, $crawler->filter('dl')->eq(4)->filter('dd > select > option')->count()); + $this->assertEquals(2, $crawler->filter('dl')->eq(4)->filter('dd > select > option')->eq(1)->attr('value')); + $this->assertEquals('selected', $crawler->filter('dl')->eq(4)->filter('dd > select > option')->eq(1)->attr('selected')); $this->assertStringContainsString('SETTING_5', $crawler->filter('dl')->eq(5)->filter('dt > label[for="setting_5"]')->text()); $this->assertStringContainsString('SETTING_5_EXPLAIN', $crawler->filter('dl')->eq(5)->filter('dt > span')->text()); $this->assertEquals(1, $crawler->filter('dl')->eq(5)->filter('dd > input[type="text"]')->count()); + $this->assertEquals('Setting 5', $crawler->filter('dl')->eq(5)->filter('dd > input[type="text"]')->attr('value')); $this->assertStringContainsString('SETTING_6', $crawler->filter('dl')->eq(6)->filter('dt > label[for="setting_6"]')->text()); $this->assertStringContainsString('SETTING_6_EXPLAIN', $crawler->filter('dl')->eq(6)->filter('dt > span')->text()); $this->assertEquals(1, $crawler->filter('dl')->eq(6)->filter('dd > input[type="password"]')->count()); + $this->assertEquals('********', $crawler->filter('dl')->eq(6)->filter('dd > input[type="password"]')->attr('value')); $this->assertStringContainsString('SETTING_7', $crawler->filter('dl')->eq(7)->filter('dt > label[for="setting_7"]')->text()); $this->assertStringContainsString('SETTING_7_EXPLAIN', $crawler->filter('dl')->eq(7)->filter('dt > span')->text()); $this->assertEquals(1, $crawler->filter('dl')->eq(7)->filter('dd > input[type="email"]')->count()); + $this->assertEquals('test@example.dom', $crawler->filter('dl')->eq(7)->filter('dd > input[type="email"]')->attr('value')); $this->assertStringContainsString('SETTING_8', $crawler->filter('dl')->eq(8)->filter('dt > label[for="setting_8"]')->text()); $this->assertStringContainsString('SETTING_8_EXPLAIN', $crawler->filter('dl')->eq(8)->filter('dt > span')->text()); $this->assertEquals(1, $crawler->filter('dl')->eq(8)->filter('dd > textarea[name="config[setting_8]"]')->count()); + $this->assertEquals('Textarea', $crawler->filter('dl')->eq(8)->filter('dd > textarea[name="config[setting_8]"]')->text()); $this->assertStringContainsString('SETTING_9', $crawler->filter('dl')->eq(9)->filter('dt > label[for="setting_9"]')->text()); $this->assertStringContainsString('SETTING_9_EXPLAIN', $crawler->filter('dl')->eq(9)->filter('dt > span')->text()); $this->assertEquals(2, $crawler->filter('dl')->eq(9)->filter('dd > label > input[type="radio"]')->count()); + $this->assertEquals('checked', $crawler->filter('dl')->eq(9)->filter('dd > label > input[type="radio"]')->eq(0)->attr('checked')); + + $this->assertStringContainsString('SETTING_10', $crawler->filter('dl')->eq(10)->filter('dt > label[for="setting_10"]')->text()); + $this->assertStringContainsString('SETTING_10_EXPLAIN', $crawler->filter('dl')->eq(10)->filter('dt > span')->text()); + $this->assertEquals(3, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->count()); + $this->assertEquals(1, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->eq(0)->attr('value')); + $this->assertStringContainsString('LABEL_1', $crawler->filter('dl')->eq(10)->filter('dd > label')->eq(0)->text()); + $this->assertEquals(3, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->eq(1)->attr('value')); + $this->assertEquals('checked', $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->eq(1)->attr('checked')); + $this->assertStringContainsString('LABEL_3', $crawler->filter('dl')->eq(10)->filter('dd > label')->eq(1)->text()); + $this->assertEquals(2, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->eq(2)->attr('value')); + $this->assertStringContainsString('LABEL_2', $crawler->filter('dl')->eq(10)->filter('dd > label')->eq(2)->text()); + + $this->assertStringContainsString('SETTING_11', $crawler->filter('dl')->eq(11)->filter('dt > label[for="setting_11"]')->text()); + $this->assertStringContainsString('SETTING_11_EXPLAIN', $crawler->filter('dl')->eq(11)->filter('dt > span')->text()); + $this->assertEquals('1', $crawler->filter('dl')->eq(11)->filter('dd > label > input[type="radio"]')->eq(0)->attr('value')); + $this->assertEquals('0', $crawler->filter('dl')->eq(11)->filter('dd > label > input[type="radio"]')->eq(1)->attr('value')); + $this->assertEquals('checked', $crawler->filter('dl')->eq(11)->filter('dd > label > input[type="radio"]')->eq(1)->attr('checked')); + $this->assertContainsLang('YES', $crawler->filter('dl')->eq(11)->filter('dd > label')->eq(0)->text()); + $this->assertContainsLang('NO', $crawler->filter('dl')->eq(11)->filter('dd > label')->eq(1)->text()); + + $this->assertStringContainsString('SETTING_12', $crawler->filter('dl')->eq(12)->filter('dt > label[for="setting_12"]')->text()); + $this->assertStringContainsString('SETTING_12_EXPLAIN', $crawler->filter('dl')->eq(12)->filter('dt > span')->text()); + $this->assertContainsLang('ENABLED', $crawler->filter('dl')->eq(12)->filter('dd > label')->eq(0)->text()); + $this->assertEquals(1, $crawler->filter('dl')->eq(12)->filter('dd > label > input[type="radio"]')->eq(0)->attr('value')); + $this->assertContainsLang('DISABLED', $crawler->filter('dl')->eq(12)->filter('dd > label')->eq(1)->text()); + $this->assertEquals(0, $crawler->filter('dl')->eq(12)->filter('dd > label > input[type="radio"]')->eq(1)->attr('value')); + $this->assertEquals('checked', $crawler->filter('dl')->eq(12)->filter('dd > label > input[type="radio"]')->eq(1)->attr('checked')); } public function test_ucp() diff --git a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php index 08cb73da07..ec9e8d0145 100644 --- a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php +++ b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php @@ -41,18 +41,39 @@ function main($id, $mode) 'setting_0_height' => ['lang' => 'SETTING_0', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false], 'setting_0' => ['lang' => 'SETTING_0', 'validate' => 'int:0:16', 'type' => 'dimension:0:999', 'explain' => true, 'append' => ' ' . $language->lang('PIXEL')], 'setting_1' => ['lang' => 'SETTING_1', 'validate' => 'bool', 'type' => 'custom', 'method' => 'submit_button', 'lang_explain' => 'CUSTOM_LANG_EXPLAIN', 'explain' => true], - 'setting_2' => ['lang' => 'SETTING_2', 'validate' => 'bool', 'type' => 'radio:yes_no'], + 'setting_2' => ['lang' => 'SETTING_2', 'validate' => 'bool', 'type' => 'radio', 'function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', [1 => 'YES', 0 => 'NO']]], 'setting_3' => ['lang' => 'SETTING_3', 'validate' => 'int:0:99999','type' => 'number:0:99999', 'explain' => true], 'setting_4' => ['lang' => 'SETTING_4', 'validate' => 'string', 'type' => 'select', 'method' => 'create_select', 'explain' => true], 'setting_5' => ['lang' => 'SETTING_5', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], 'setting_6' => ['lang' => 'SETTING_6', 'validate' => 'string', 'type' => 'password:25:255', 'explain' => true], 'setting_7' => ['lang' => 'SETTING_7', 'validate' => 'email', 'type' => 'email:0:100', 'explain' => true], 'setting_8' => ['lang' => 'SETTING_8', 'validate' => 'string', 'type' => 'textarea:5:30', 'explain' => true], - 'setting_9' => ['lang' => 'SETTING_9', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true], + 'setting_9' => ['lang' => 'SETTING_9', 'validate' => 'bool', 'type' => 'radio', 'function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', [1 => 'ENABLED', 0 => 'DISABLED']], 'explain' => true], + 'setting_10'=> ['lang' => 'SETTING_10', 'validate' => 'int', 'type' => 'radio', 'function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', [1 => 'LABEL_1', 3 => 'LABEL_3', 2 => 'LABEL_2']], 'explain' => true], + 'setting_11'=> ['lang' => 'SETTING_11', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], + 'setting_12'=> ['lang' => 'SETTING_12', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true], ] ]; - $this->new_config = $cfg_array = $error = []; + $config = new \phpbb\config\config([ + 'setting_0_width' => '1', + 'setting_0_height' => '17', + 'setting_0' => '10', + 'setting_2' => '1', + 'setting_3' => '15', + 'setting_4' => '2', + 'setting_5' => 'Setting 5', + 'setting_6' => 'password', + 'setting_7' => 'test@example.dom', + 'setting_8' => 'Textarea', + 'setting_9' => '1', + 'setting_10' => '3', + 'setting_11' => '0', + 'setting_12' => '0', + ]); + $this->new_config = clone $config; + $cfg_array = (isset($_REQUEST['config'])) ? $request->variable('config', ['' => ''], true) : $this->new_config; + $error = []; validate_config_vars($display_vars['vars'], $cfg_array, $error); @@ -104,7 +125,7 @@ function main($id, $mode) $l_explain = $language->lang($vars['lang_explain'] ?: $vars['lang'] . '_EXPLAIN'); } - $content = build_cfg_template($type, $config_key, $this->new_config, $config_key, $vars); + $content = phpbb_build_cfg_template($type, $config_key, $this->new_config, $config_key, $vars); if (empty($content)) { @@ -123,13 +144,15 @@ function main($id, $mode) } } - function create_select() + function create_select($value) { - return ' - - - - '; + $options = [ + 1 => 'Option 1', + 2 => 'Option 2', + 3 => 'Option 3', + ]; + + return ['options' => build_select($options, $value)]; } function submit_button() diff --git a/tests/functional/manifest_test.php b/tests/functional/manifest_test.php new file mode 100644 index 0000000000..2099dd7661 --- /dev/null +++ b/tests/functional/manifest_test.php @@ -0,0 +1,49 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @group functional +*/ +class phpbb_functional_manifest_test extends phpbb_functional_test_case +{ + public function test_manifest() + { + $url_path = preg_replace('#^(/.+)/$#', '$1', parse_url(self::$root_url, PHP_URL_PATH)); + + $expected = [ + 'name' => 'yourdomain.com', + 'short_name' => 'yourdomain', + 'display' => 'standalone', + 'orientation' => 'portrait', + 'dir' => 'ltr', + 'start_url' => $url_path, + 'scope' => $url_path, + ]; + + $this->login(); + $this->admin_login(); + + $crawler = self::request('GET', 'adm/index.php?i=acp_board&mode=settings&sid=' . $this->sid); + + $form_data = [ + 'config[sitename]' => $expected['name'], + 'config[sitename_short]' => $expected['short_name'], + ]; + $form = $crawler->selectButton('submit')->form($form_data); + $crawler = self::submit($form); + $this->assertStringContainsString($this->lang('CONFIG_UPDATED'), $crawler->filter('.successbox')->text()); + + self::request('GET', 'app.php/manifest', [], false); + $this->assertEquals(json_encode($expected), self::get_content()); + } +} diff --git a/tests/functional/notification_webpush_test.php b/tests/functional/notification_webpush_test.php new file mode 100644 index 0000000000..acd15a261d --- /dev/null +++ b/tests/functional/notification_webpush_test.php @@ -0,0 +1,127 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @group functional +*/ +class phpbb_functional_notification_webpush_test extends phpbb_functional_test_case +{ + public function test_acp_module() + { + $this->login(); + $this->admin_login(); + + $this->add_lang(['acp/board', 'acp/common']); + + $crawler = self::request('GET', 'adm/index.php?i=acp_board&mode=webpush&sid=' . $this->sid); + + $this->assertContainsLang('ACP_WEBPUSH_SETTINGS', $crawler->filter('div.main > h1')->text()); + $this->assertContainsLang('ACP_WEBPUSH_SETTINGS_EXPLAIN', $crawler->filter('div.main > p')->text()); + $this->assertContainsLang('WEBPUSH_GENERATE_VAPID_KEYS', $crawler->filter('input[type="button"]')->attr('value')); + + $form_data = [ + 'config[webpush_enable]' => 1, + 'config[webpush_vapid_public]' => 'BDnYSJHVZBxq834LqDGr893IfazEez7q-jYH2QBNlT0ji2C9UwGosiqz8Dp_ZN23lqAngBZyRjXVWF4ZLA8X2zI', + 'config[webpush_vapid_private]' => 'IE5OYlmfWsMbBU1lzvr0bxrxVAXIteSkAnwGlZIhmRk', + 'config[webpush_method_default_enable]' => 1, + 'config[webpush_dropdown_subscribe]' => 1, + ]; + $form = $crawler->selectButton('submit')->form($form_data); + $crawler = self::submit($form); + $this->assertStringContainsString($this->lang('CONFIG_UPDATED'), $crawler->filter('.successbox')->text()); + + $crawler = self::request('GET', 'adm/index.php?i=acp_board&mode=webpush&sid=' . $this->sid); + + foreach ($form_data as $config_name => $config_value) + { + $config_value = ($config_name === 'config[webpush_vapid_private]') ? '********' : $config_value; + $this->assertEquals($config_value, $crawler->filter('input[name="' . $config_name . '"]')->attr('value')); + } + } + + public function test_ucp_module() + { + $this->login(); + $this->admin_login(); + + $this->add_lang('ucp'); + + $crawler = self::request('GET', 'ucp.php?i=ucp_notifications&mode=notification_options'); + + $this->assertContainsLang('NOTIFY_WEBPUSH_ENABLE', $crawler->filter('label[for="subscribe_webpush"]')->text()); + $this->assertContainsLang('NOTIFICATION_METHOD_WEBPUSH', $crawler->filter('th.mark')->eq(2)->text()); + + // Assert checkbox is checked + $wp_list = $crawler->filter('.table1'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.bookmark_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.mention_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.post_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.quote_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.topic_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.forum_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.group_request_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.pm_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.report_pm_closed_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.report_post_closed_notification.method.webpush'); + + $this->set_acp_option('webpush_method_default_enable', 0); + + $crawler = self::request('GET', 'ucp.php?i=ucp_notifications&mode=notification_options'); + + // Assert checkbox is unchecked + $wp_list = $crawler->filter('.table1'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.bookmark_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.mention_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.post_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.quote_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.topic_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.forum_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.group_request_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.pm_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.report_pm_closed_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.report_post_closed_notification.method.webpush'); + } + + public function test_dropdown_subscribe_button() + { + $this->login(); + $this->admin_login(); + + // Assert subscribe dropdown is present + $crawler = self::request('GET', 'index.php'); + $this->assertCount(1, $crawler->filter('.webpush-subscribe')); + $this->assertContainsLang('NOTIFY_WEB_PUSH_SUBSCRIBE', $crawler->filter('.webpush-subscribe #subscribe_webpush')->text()); + $this->assertContainsLang('NOTIFY_WEB_PUSH_SUBSCRIBED', $crawler->filter('.webpush-subscribe #unsubscribe_webpush')->text()); + + // Assert subscribe button is not displayed in UCP when dropdown subscribe is present + $crawler = self::request('GET', 'ucp.php?i=ucp_notifications&mode=notification_options'); + $this->assertCount(0, $crawler->filter('.webpush-subscribe')); + + $this->set_acp_option('webpush_dropdown_subscribe', 0); + + // Assert subscribe dropdown is not present by default + $crawler = self::request('GET', 'index.php'); + $this->assertCount(0, $crawler->filter('.webpush-subscribe')); + } + + protected function set_acp_option($option, $value) + { + $crawler = self::request('GET', 'adm/index.php?i=acp_board&mode=webpush&sid=' . $this->sid); + $form = $crawler->selectButton('Submit')->form(); + $values = $form->getValues(); + $values["config[{$option}]"] = $value; + $form->setValues($values); + $crawler = self::submit($form); + $this->assertEquals(1, $crawler->filter('.successbox')->count()); + } +} diff --git a/tests/functional/search/base.php b/tests/functional/search/base.php index c3f3f6f7dd..a34eff385e 100644 --- a/tests/functional/search/base.php +++ b/tests/functional/search/base.php @@ -51,6 +51,30 @@ protected function assert_search_topics_by_author($author, $topics_found, $sort_ $this->assertStringContainsString("Search found $topics_found match", $crawler->filter('.searchresults-title')->text(), $this->search_backend); } + protected function assert_search_posts_by_author_id($author_id, $posts_found, $sort_key = '', $sort_dir = '') + { + // Test obtaining data from cache if sorting direction is set + if (!$sort_dir) + { + $this->purge_cache(); + } + $crawler = self::request('GET', 'search.php?author_id=' . $author_id . ($sort_key ? "&sk=$sort_key" : '') . ($sort_dir ? "&sk=$sort_dir" : '')); + $this->assertEquals($posts_found, $crawler->filter('.postbody')->count(), $this->search_backend); + $this->assertStringContainsString("Search found $posts_found match", $crawler->filter('.searchresults-title')->text(), $this->search_backend); + } + + protected function assert_search_topics_by_author_id($author_id, $topics_found, $sort_key = '', $sort_dir = '') + { + // Test obtaining data from cache if sorting direction is set + if (!$sort_dir) + { + $this->purge_cache(); + } + $crawler = self::request('GET', 'search.php?sr=topics&author_id=' . $author_id . ($sort_key ? "&sk=$sort_key" : '') . ($sort_dir ? "&sk=$sort_dir" : '')); + $this->assertEquals($topics_found, $crawler->filter('.row')->count(), $this->search_backend); + $this->assertStringContainsString("Search found $topics_found match", $crawler->filter('.searchresults-title')->text(), $this->search_backend); + } + protected function assert_search_in_topic($topic_id, $keywords, $posts_found, $sort_key = '') { $this->purge_cache(); @@ -95,10 +119,14 @@ public function test_search_backend() $this->add_lang('common'); // Create a new standard user if needed, topic and post to test searh for author - if (!$this->user_exists('searchforauthoruser')) + if (!$searchforauthoruser_id = $this->user_exists('searchforauthoruser')) { $searchforauthoruser_id = $this->create_user('searchforauthoruser'); } + else + { + $searchforauthoruser_id = key($searchforauthoruser_id); + } $this->remove_user_group('NEWLY_REGISTERED', ['searchforauthoruser']); $this->set_flood_interval(0); $this->login('searchforauthoruser'); @@ -165,7 +193,7 @@ public function test_search_backend() foreach (['', 'a', 't', 'f', 'i', 's'] as $sort_key) { - $this->assert_search_found('phpbb3+installation', 1, 3, $sort_key); + $this->assert_search_found('phpbb3+installation', 1, 4, $sort_key); $this->assert_search_found('foosubject+barsearch', 1, 2, $sort_key); $this->assert_search_found('barsearch-testing', 1, 2, $sort_key); // test hyphen ignored $this->assert_search_found('barsearch+-+testing', 1, 2, $sort_key); // test hyphen wrapped with space ignored @@ -180,6 +208,11 @@ public function test_search_backend() $this->assert_search_posts_by_author('searchforauthoruser', 2, $sort_key); $this->assert_search_topics_by_author('searchforauthoruser', 1, $sort_key); + + $this->assert_search_posts_by_author_id($searchforauthoruser_id, 2, $sort_key); + $this->assert_search_topics_by_author_id($searchforauthoruser_id, 1, $sort_key); + $this->assert_search_posts_by_author_id($searchforauthoruser_id, 2, $sort_key, 'a'); //search asc order + $this->assert_search_topics_by_author_id($searchforauthoruser_id, 1, $sort_key, 'a'); // search asc order } $this->assert_search_not_found('loremipsumdedo'); diff --git a/tests/functional/switch_permissions_test.php b/tests/functional/switch_permissions_test.php new file mode 100644 index 0000000000..b7e8819a6e --- /dev/null +++ b/tests/functional/switch_permissions_test.php @@ -0,0 +1,119 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +/** + * @group functional + */ +class phpbb_functional_switch_permissions_test extends phpbb_functional_test_case +{ + private const TEST_USER = 'switch-permissions-test'; + + protected function setUp(): void + { + parent::setUp(); + + $this->login(); + $this->admin_login(); + + $this->add_lang(['common', 'ucp']); + } + + public function test_switch_permissions_acp() + { + $user_id = $this->create_user(self::TEST_USER); + + // Open user administration page for new user + $crawler = self::request('GET', "adm/index.php?i=users&mode=overview&u={$user_id}&sid={$this->sid}"); + + // Use permissions + $link = $crawler->selectLink($this->lang('USE_PERMISSIONS'))->link(); + $crawler = self::$client->click($link); + + // Check that we switched permissions to test user + $this->assertStringContainsString( + str_replace('
      ', '
      ', $this->lang('PERMISSIONS_TRANSFERRED', self::TEST_USER)), + $crawler->html() + ); + + // Check that ACP pages get forced to acp main with restore permission info + $this->add_lang('acp/common'); + $crawler = self::request('GET', "adm/index.php?i=users&mode=overview&u={$user_id}&sid={$this->sid}"); + $this->assertStringContainsString( + $this->lang('PERMISSIONS_TRANSFERRED'), + $crawler->text() + ); + + // Check that restore permissions link exists + $crawler = self::$client->request('GET', '../index.php?sid=' . $this->sid); + $this->assertStringContainsString( + $this->lang('RESTORE_PERMISSIONS'), + $crawler->text() + ); + + // Check that restore permissions works + $crawler = self::$client->request('GET', 'ucp.php?mode=restore_perm&sid=' . $this->sid); + $this->assertStringContainsString( + $this->lang('PERMISSIONS_RESTORED'), + $crawler->text() + ); + } + + /** + * @depends test_switch_permissions_acp + */ + public function test_switch_permissions_ucp() + { + $db = $this->get_db(); + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . " + WHERE username = '" . self::TEST_USER . "'"; + $result = $db->sql_query($sql); + $user_id = $db->sql_fetchfield('user_id'); + $db->sql_freeresult($result); + + // Open memberlist profile page for user + $crawler = self::request('GET', "memberlist.php?mode=viewprofile&u={$user_id}&sid={$this->sid}"); + + // Use permissions + $link = $crawler->selectLink($this->lang('USE_PERMISSIONS'))->link(); + $crawler = self::$client->click($link); + + // Check that we switched permissions to test user + $this->assertStringContainsString( + str_replace('
      ', '
      ', $this->lang('PERMISSIONS_TRANSFERRED', self::TEST_USER)), + $crawler->html() + ); + + // Check that UCP pages don't get forced to UCP main with restore permission info + $this->add_lang(['memberlist', 'ucp']); + $crawler = self::request('GET', "ucp.php?i=ucp_profile&mode=profile_info&sid={$this->sid}"); + $this->assertStringContainsString( + $this->lang('EDIT_PROFILE'), + $crawler->text() + ); + + // Check that restore permissions link exists + $crawler = self::$client->request('GET', 'index.php?sid=' . $this->sid); + $this->assertStringContainsString( + $this->lang('RESTORE_PERMISSIONS'), + $crawler->text() + ); + + // Check that restore permissions works + $crawler = self::$client->request('GET', 'ucp.php?mode=restore_perm&sid=' . $this->sid); + $this->assertStringContainsString( + $this->lang('PERMISSIONS_RESTORED'), + $crawler->text() + ); + } +} diff --git a/tests/functions/build_hidden_fields_for_query_params_test.php b/tests/functions/build_hidden_fields_for_query_params_test.php deleted file mode 100644 index aee7a569d4..0000000000 --- a/tests/functions/build_hidden_fields_for_query_params_test.php +++ /dev/null @@ -1,73 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -class phpbb_build_hidden_fields_for_query_params_test extends phpbb_test_case -{ - public function build_hidden_fields_for_query_params_test_data() - { - return array( - // get - // post - // exclude - // expected - array( - array('foo' => 'bar'), - array(), - array(), - "", - ), - array( - array('foo' => 'bar', 'a' => 'b'), - array(), - array(), - "", - ), - array( - array('a' => 'quote"', 'b' => ''), - array(), - array(), - "", - ), - array( - array('a' => "quotes'\""), - array(), - array(), - "", - ), - array( - array('foo' => 'bar', 'a' => 'b'), - array('a' => 'c'), - array(), - "", - ), - // strict equality check - array( - array('foo' => 'bar', 'a' => '0'), - array('a' => ''), - array(), - "", - ), - ); - } - - /** - * @dataProvider build_hidden_fields_for_query_params_test_data - */ - public function test_build_hidden_fields_for_query_params($get, $post, $exclude, $expected) - { - $request = new phpbb_mock_request($get, $post); - $result = phpbb_build_hidden_fields_for_query_params($request, $exclude); - - $this->assertEquals($expected, $result); - } -} diff --git a/tests/functions/get_remote_file_test.php b/tests/functions/get_remote_file_test.php deleted file mode 100644 index c0330d01f8..0000000000 --- a/tests/functions/get_remote_file_test.php +++ /dev/null @@ -1,80 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -/** -* @group slow -*/ -class phpbb_functions_get_remote_file extends phpbb_test_case -{ - public function test_version_phpbb_com() - { - global $phpbb_container; - $phpbb_container = new phpbb_mock_container_builder(); - $phpbb_container->set('file_downloader', new \phpbb\file_downloader()); - - $hostname = 'version.phpbb.com'; - - if (!checkdnsrr($hostname, 'A')) - { - $this->markTestSkipped(sprintf( - 'Could not find a DNS record for hostname %s. ' . - 'Assuming network is down.', - $hostname - )); - } - - $errstr = $errno = null; - $file = get_remote_file($hostname, '/phpbb', '30x.txt', $errstr, $errno); - - $this->assertNotEquals( - 0, - strlen($file), - 'Failed asserting that the response is not empty.' - ); - - $this->assertSame( - '', - $errstr, - 'Failed asserting that the error string is empty.' - ); - - $this->assertSame( - 0, - $errno, - 'Failed asserting that the error number is 0 (i.e. no error occurred).' - ); - - $lines = explode("\n", $file); - - $this->assertGreaterThanOrEqual( - 2, - count($lines), - 'Failed asserting that the version file has at least two lines.' - ); - - $this->assertStringStartsWith( - '3.', - $lines[0], - "Failed asserting that the first line of the version file starts with '3.'" - ); - - $this->assertNotSame( - false, - filter_var($lines[1], FILTER_VALIDATE_URL), - 'Failed asserting that the second line of the version file is a valid URL.' - ); - - $this->assertStringContainsString('http', $lines[1]); - $this->assertStringContainsString('phpbb.com', $lines[1], '', true); - } -} diff --git a/tests/functions/style_select_test.php b/tests/functions/style_select_test.php index e36b799bde..aadacadb02 100644 --- a/tests/functions/style_select_test.php +++ b/tests/functions/style_select_test.php @@ -20,14 +20,119 @@ public function getDataSet() static public function style_select_data() { - return array( - array('', false, ''), - array('', true, ''), - array('1', false, ''), - array('1', true, ''), - array('3', false, ''), - array('3', true, ''), - ); + return [ + [ + '', + false, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + ] + ], + [ + '', + true, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + [ + 'value' => '3', + 'selected' => false, + 'label' => 'zoo', + ], + ] + ], + [ + '1', + false, + [ + [ + 'value' => '1', + 'selected' => true, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + ] + ], + [ + '1', + true, + [ + [ + 'value' => '1', + 'selected' => true, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + [ + 'value' => '3', + 'selected' => false, + 'label' => 'zoo', + ], + ] + ], + [ + '3', + false, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + ] + ], + [ + '3', + true, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + [ + 'value' => '3', + 'selected' => true, + 'label' => 'zoo', + ], + ] + ], + ]; } /** diff --git a/tests/functions_acp/build_cfg_template_test.php b/tests/functions_acp/build_cfg_template_test.php index 76558152ff..bdb2bb36e4 100644 --- a/tests/functions_acp/build_cfg_template_test.php +++ b/tests/functions_acp/build_cfg_template_test.php @@ -81,7 +81,7 @@ public function test_build_cfg_template_text($tpl_type, $key, $new, $config_key, $language = new phpbb_mock_lang(); $user->lang = $language; - $this->assertEquals($expected, build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); + $this->assertEquals($expected, phpbb_build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); } public function build_cfg_template_dimension_data() @@ -151,7 +151,7 @@ public function test_build_cfg_template_dimension($tpl_type, $key, $new, $config $user = new phpbb_mock_user(); $user->lang = new phpbb_mock_lang(); - $this->assertEquals($expected, build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); + $this->assertEquals($expected, phpbb_build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); } public function build_cfg_template_number_data() @@ -219,7 +219,7 @@ public function test_build_cfg_template_number($tpl_type, $key, $new, $config_ke $user = new phpbb_mock_user(); $user->lang = new phpbb_mock_lang(); - $this->assertEquals($expected, build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); + $this->assertEquals($expected, phpbb_build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); } public function build_cfg_template_textarea_data() @@ -254,18 +254,18 @@ public function test_build_cfg_template_textarea($tpl_type, $key, $new, $config_ $user = new phpbb_mock_user(); $user->lang = new phpbb_mock_lang(); - $this->assertEquals($expected, build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); + $this->assertEquals($expected, phpbb_build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); } public function build_cfg_template_radio_data() { - return array( - array( - array('radio', 'enabled_disabled'), + return [ + [ + ['radio', 'enabled_disabled'], 'key_name', - array('config_key_name' => '0'), + ['config_key_name' => '0'], 'config_key_name', - array(), + [], [ 'tag' => 'radio', 'buttons' => [ @@ -286,13 +286,40 @@ public function build_cfg_template_radio_data() ], ], ], - ), - array( - array('radio', 'enabled_disabled'), + ], + [ + ['radio'], 'key_name', - array('config_key_name' => '1'), + ['config_key_name' => '0'], 'config_key_name', - array(), + ['function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', [1 => 'ENABLED', 0 => 'DISABLED']]], + [ + 'tag' => 'radio', + 'buttons' => [ + [ + 'id' => 'key_name', + 'type' => 'radio', + 'value' => 1, + 'name' => 'config[config_key_name]', + 'label' => 'ENABLED', + 'checked' => false, + ], + [ + 'type' => 'radio', + 'value' => 0, + 'checked' => true, + 'name' => 'config[config_key_name]', + 'label' => 'DISABLED', + ], + ], + ], + ], + [ + ['radio', 'enabled_disabled'], + 'key_name', + ['config_key_name' => '1'], + 'config_key_name', + [], [ 'tag' => 'radio', 'buttons' => [ @@ -313,13 +340,40 @@ public function build_cfg_template_radio_data() ], ], ], - ), - array( - array('radio', 'yes_no'), + ], + [ + ['radio'], 'key_name', - array('config_key_name' => '0'), + ['config_key_name' => '1'], 'config_key_name', - array(), + ['function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', [1 => 'ENABLED', 0 => 'DISABLED']]], + [ + 'tag' => 'radio', + 'buttons' => [ + [ + 'id' => 'key_name', + 'type' => 'radio', + 'value' => 1, + 'name' => 'config[config_key_name]', + 'label' => 'ENABLED', + 'checked' => true, + ], + [ + 'type' => 'radio', + 'value' => 0, + 'checked' => false, + 'name' => 'config[config_key_name]', + 'label' => 'DISABLED', + ], + ], + ], + ], + [ + ['radio', 'yes_no'], + 'key_name', + ['config_key_name' => '0'], + 'config_key_name', + [], [ 'tag' => 'radio', 'buttons' => [ @@ -340,13 +394,40 @@ public function build_cfg_template_radio_data() ], ], ], - ), - array( - array('radio', 'yes_no'), + ], + [ + ['radio'], 'key_name', - array('config_key_name' => '1'), + ['config_key_name' => '0'], 'config_key_name', - array(), + ['function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', [1 => 'YES', 0 => 'NO']]], + [ + 'tag' => 'radio', + 'buttons' => [ + [ + 'id' => 'key_name', + 'type' => 'radio', + 'value' => 1, + 'name' => 'config[config_key_name]', + 'label' => 'YES', + 'checked' => false, + ], + [ + 'type' => 'radio', + 'value' => 0, + 'checked' => true, + 'name' => 'config[config_key_name]', + 'label' => 'NO', + ], + ], + ], + ], + [ + ['radio', 'yes_no'], + 'key_name', + ['config_key_name' => '1'], + 'config_key_name', + [], [ 'tag' => 'radio', 'buttons' => [ @@ -367,8 +448,35 @@ public function build_cfg_template_radio_data() ], ], ], - ), - ); + ], + [ + ['radio'], + 'key_name', + ['config_key_name' => '1'], + 'config_key_name', + ['function' => 'phpbb_build_radio', 'params' => ['{CONFIG_VALUE}', '{KEY}', [1 => 'YES', 0 => 'NO']]], + [ + 'tag' => 'radio', + 'buttons' => [ + [ + 'id' => 'key_name', + 'type' => 'radio', + 'value' => 1, + 'name' => 'config[config_key_name]', + 'label' => 'YES', + 'checked' => true, + ], + [ + 'type' => 'radio', + 'value' => 0, + 'checked' => false, + 'name' => 'config[config_key_name]', + 'label' => 'NO', + ], + ], + ], + ], + ]; } /** @@ -381,7 +489,7 @@ public function test_build_cfg_template_radio($tpl_type, $key, $new, $config_key $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); $language = new \phpbb_mock_lang(); - $this->assertEquals($expected, build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); + $this->assertEquals($expected, phpbb_build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); } public function build_cfg_template_append_data() @@ -417,7 +525,7 @@ public function test_build_cfg_template_append($tpl_type, $key, $new, $config_ke $user = new phpbb_mock_user(); $user->lang = new phpbb_mock_lang(); - $this->assertEquals($expected, build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); + $this->assertEquals($expected, phpbb_build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); } public function build_cfg_template_select_data() @@ -431,8 +539,11 @@ public function build_cfg_template_select_data() ['method' => 'select_helper'], [ 'tag' => 'select', + 'class' => false, 'id' => 'key_name', + 'data' => [], 'name' => 'config[config_key_name]', + 'toggleable' => false, 'options' => [ [ 'value' => 1, @@ -450,7 +561,9 @@ public function build_cfg_template_select_data() 'selected' => false, ] ], - 'toggleable' => false, + 'group_only' => false, + 'size' => 1, + 'multiple' => false, ], ], [ @@ -461,9 +574,11 @@ public function build_cfg_template_select_data() ['method' => 'select_helper'], [ 'tag' => 'select', + 'class' => false, 'id' => 'key_name', + 'data' => [], 'name' => 'config[config_key_name]', - 'size' => 8, + 'toggleable' => false, 'options' => [ [ 'value' => 1, @@ -481,7 +596,9 @@ public function build_cfg_template_select_data() 'selected' => false, ] ], - 'toggleable' => false, + 'group_only' => false, + 'size' => 8, + 'multiple' => false, ], ], ]; @@ -501,18 +618,19 @@ public function test_build_cfg_template_select($tpl_type, $key, $new, $config_ke $user->module = $this; $module = $user; - $this->assertEquals($expected, build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); + $this->assertEquals($expected, phpbb_build_cfg_template($tpl_type, $key, $new, $config_key, $vars)); } public function select_helper() { - return build_select( - array( - '1' => 'First_Option', - '2' => 'Second_Option', - '3' => 'Third_Option', - ), - '2' - ); + return [ + 'options' => build_select( + [ + '1' => 'First_Option', + '2' => 'Second_Option', + '3' => 'Third_Option', + ], + '2'), + ]; } } diff --git a/tests/functions_acp/h_radio_test.php b/tests/functions_acp/h_radio_test.php deleted file mode 100644 index acba1068f4..0000000000 --- a/tests/functions_acp/h_radio_test.php +++ /dev/null @@ -1,130 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -require_once __DIR__ . '/../../phpBB/includes/functions_acp.php'; - -class phpbb_functions_acp_h_radio_test extends phpbb_test_case -{ - protected function setUp(): void - { - parent::setUp(); - - global $user; - - $user = new phpbb_mock_user(); - $user->lang = new phpbb_mock_lang(); - } - - public function h_radio_data() - { - return array( - array( - 'test_name', - array( - 'test' => 'TEST', - 'second' => 'SEC_OPTION', - ), - false, - false, - false, - '', - ), - array( - 'test_name', - array( - 'test' => 'TEST', - 'second' => 'SEC_OPTION', - ), - 'test', - false, - false, - '', - ), - array( - 'test_name', - array( - 'test' => 'TEST', - 'second' => 'SEC_OPTION', - ), - false, - 'test_id', - false, - '', - ), - array( - 'test_name', - array( - 'test' => 'TEST', - 'second' => 'SEC_OPTION', - ), - 'test', - 'test_id', - false, - '', - ), - - array( - 'test_name', - array( - 'test' => 'TEST', - 'second' => 'SEC_OPTION', - ), - false, - false, - 'k', - '', - ), - array( - 'test_name', - array( - 'test' => 'TEST', - 'second' => 'SEC_OPTION', - ), - 'test', - false, - 'k', - '', - ), - array( - 'test_name', - array( - 'test' => 'TEST', - 'second' => 'SEC_OPTION', - ), - false, - 'test_id', - 'k', - '', - ), - array( - 'test_name', - array( - 'test' => 'TEST', - 'second' => 'SEC_OPTION', - ), - 'test', - 'test_id', - 'k', - '', - ), - ); - } - - /** - * @dataProvider h_radio_data - */ - public function test_h_radio($name, $input_ary, $input_default, $id, $key, $expected) - { - $this->assertEquals($expected, h_radio($name, $input_ary, $input_default, $id, $key)); - } -} diff --git a/tests/functions_content/get_context_test.php b/tests/functions_content/get_context_test.php new file mode 100644 index 0000000000..f2865f31ee --- /dev/null +++ b/tests/functions_content/get_context_test.php @@ -0,0 +1,235 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use PHPUnit\Framework\TestCase; + +class phpbb_functions_content_get_context_test extends TestCase +{ + /** + * Data provider for get_context test cases. + * + * @return array + */ + public function data_get_context(): array + { + return [ + 'text contains words and length greater than text' => [ + 'text' => 'This is a sample text containing several words, including sample, text, and words.', + 'words' => ['sample', 'words'], + 'length' => 100, + 'expected' => 'This is a sample text containing several words, including sample, text, and words.', + ], + 'text contains words and length less than text' => [ + 'text' => 'This is a sample text containing several words, including sample, text, and words.', + 'words' => ['sample', 'words'], + 'length' => 50, + 'expected' => 'This is a sample text containing several words ...', + ], + 'text does not contain words' => [ + 'text' => 'This is a sample text containing several words, but none of them match the given words.', + 'words' => ['nonexistent'], + 'length' => 50, + 'expected' => 'This is a sample text containing several words ...', + ], + 'desired length equal to text length' => [ + 'text' => 'Exact length text.', + 'words' => ['Exact', 'text'], + 'length' => 18, + 'expected' => 'Exact length text.', + ], + 'text with html entities' => [ + 'text' => 'This is a sample text containing & and < and > entities.', + 'words' => ['sample', 'containing'], + 'length' => 50, + 'expected' => 'This is a sample text containing & and < and ...', + ], + 'text with html entities and contains last word' => [ + 'text' => 'This is a sample text containing & and < and > entities.', + 'words' => ['sample', 'entities'], + 'length' => 50, + 'expected' => 'This is a sample text ... and < and > entities.', + ], + 'text with multiple spaces and special characters' => [ + 'text' => 'This is a sample text containing several words.', + 'words' => ['sample', 'several'], + 'length' => 50, + 'expected' => 'This is a sample text containing several words.', + ], + 'empty text' => [ + 'text' => '', + 'words' => ['sample', 'words'], + 'length' => 50, + 'expected' => '', + ], + 'empty words array' => [ + 'text' => 'This is a sample text containing several words.', + 'words' => [], + 'length' => 50, + 'expected' => 'This is a sample text containing several words.', + ], + 'zero length' => [ + 'text' => 'This is a sample text.', + 'words' => ['sample'], + 'length' => 0, + 'expected' => 'This is a sample text.', + ], + 'negative length' => [ + 'text' => 'This is a sample text.', + 'words' => ['sample'], + 'length' => -10, + 'expected' => 'This is a sample text.', + ], + 'ellipses_beginning' => [ + 'text' => 'foo foo foo foo foo foo foo foo bar', + 'words' => ['bar'], + 'length' => 10, + 'expected' => '... foo foo bar', + ], + 'ellipsis_end' => [ + 'text' => 'bar foo foo foo foo foo foo foo foo', + 'words' => ['bar'], + 'length' => 10, + 'expected' => 'bar foo foo ...', + ], + 'ellipsis_middle' => [ + 'text' => 'foo word1 foo foo foo foo foo foo foo foo foo word2 foo', + 'words' => ['word1', 'word2'], + 'length' => 10, + 'expected' => '... word1 ... word2 ...', + ], + 'ellipsis_middle2' => [ + 'text' => 'word1 foo foo foo foo foo foo foo foo foo word2', + 'words' => ['word1', 'word2'], + 'length' => 10, + 'expected' => 'word1 ... word2', + ], + ]; + } + + /** + * Data provider for unicode get_context test cases. + * + * @return array + */ + public function data_get_context_unicode(): array + { + return [ + 'text contains words and length greater than text' => [ + 'text' => 'Это пример текста, содержащего разнообразные слова, включая пример, текст и слова.', + 'words' => ['пример', 'слова'], + 'length' => 100, + 'expected' => 'Это пример текста, содержащего разнообразные слова, включая пример, текст и слова.', + ], + 'text contains words and length less than text' => [ + 'text' => 'Это пример текста, содержащего разнообразные слова, включая шаблон, текст и слова.', + 'words' => ['пример', 'слова'], + 'length' => 50, + 'expected' => 'Это пример текста, содержащего разнообразные слова ...', + ], + 'text does not contain words' => [ + 'text' => 'Это пример текста, содержащего разнообразные слова, но ни одно из них не совпадает с искомыми.', + 'words' => ['nonexistent'], + 'length' => 50, + 'expected' => 'Это пример текста, содержащего разнообразные слова ...', + ], + 'desired length equal to text length' => [ + 'text' => 'Текст точной длины.', + 'words' => ['Текст', 'точной'], + 'length' => 19, + 'expected' => 'Текст точной длины.', + ], + 'text with html entities' => [ + 'text' => 'Это пример текста, содержащего & и < и > лексемы.', + 'words' => ['пример', 'содержащего'], + 'length' => 40, + 'expected' => 'Это пример текста, содержащего & и < и ...', + ], + 'text with html entities and contains last word' => [ + 'text' => 'Это пример текста, содержащего & и < и > лексемы.', + 'words' => ['пример', 'лексемы'], + 'length' => 40, + 'expected' => 'Это пример текста ... и < и > лексемы.', + ], + 'text with multiple spaces and special characters' => [ + 'text' => 'Это пример текста, содержащего разнообразные слова.', + 'words' => ['пример', 'разнообразные'], + 'length' => 50, + 'expected' => 'Это пример текста, содержащего разнообразные слова.', + ], + 'empty text' => [ + 'text' => '', + 'words' => ['пример', 'слова'], + 'length' => 50, + 'expected' => '', + ], + 'empty words array' => [ + 'text' => 'Это пример текста, содержащего разнообразные слова.', + 'words' => [], + 'length' => 50, + 'expected' => 'Это пример текста, содержащего разнообразные слова.', + ], + 'zero length' => [ + 'text' => 'Это пример текста.', + 'words' => ['пример'], + 'length' => 0, + 'expected' => 'Это пример текста.', + ], + 'negative length' => [ + 'text' => 'Это пример текста.', + 'words' => ['sample'], + 'length' => -10, + 'expected' => 'Это пример текста.', + ], + 'ellipses_beginning' => [ + 'text' => 'раз раз раз раз раз раз раз раз два', + 'words' => ['два'], + 'length' => 10, + 'expected' => '... раз раз два', + ], + 'ellipsis_end' => [ + 'text' => 'два раз раз раз раз раз раз раз раз', + 'words' => ['два'], + 'length' => 10, + 'expected' => 'два раз раз ...', + ], + 'ellipsis_middle' => [ + 'text' => 'раз слово1 раз раз раз раз раз раз раз раз раз слово2 раз', + 'words' => ['слово1', 'слово2'], + 'length' => 15, + 'expected' => '... слово1 ... слово2 ...', + ], + 'ellipsis_middle2' => [ + 'text' => 'слово1 foo foo foo foo foo foo foo foo foo слово2', + 'words' => ['слово1', 'слово2'], + 'length' => 10, + 'expected' => 'слово1 ... слово2', + ], + 'fruits_spanish' => [ + 'text' => 'Manzana,plátano,naranja,fresa,mango,uva,piña,pera,kiwi,cereza,sandía,melón,papaya,arándano,durazno', + 'words' => ['piña'], + 'length' => 20, + 'expected' => '... uva,piña,pera ...', + ] + ]; + } + + /** + * @dataProvider data_get_context + * @dataProvider data_get_context_unicode + */ + public function test_get_context($text, $words, $length, $expected) + { + $this->assertEquals($expected, get_context($text, $words, $length)); + } + +} diff --git a/tests/log/function_add_log_test.php b/tests/log/function_add_log_test.php index bcb4db0705..8d89c15ab8 100644 --- a/tests/log/function_add_log_test.php +++ b/tests/log/function_add_log_test.php @@ -172,19 +172,63 @@ public function test_add_log_function($expected, $user_id, $mode, $required1, $a if ($additional3 != null) { - add_log($mode, $required1, $additional1, $additional2, $additional3); + $additional_data = [ + 'forum_id' => $required1, + 'topic_id' => $additional1, + $additional3, + ]; + $phpbb_log->add($mode, $user_id, '', $additional2, false, $additional_data); } else if ($additional2 != null) { - add_log($mode, $required1, $additional1, $additional2); + if ($mode == 'user') + { + $additional_data = [ + 'reportee_id' => $required1, + $additional2, + ]; + $log_operation = $additional1; + } + else if ($mode == 'mod') + { + $additional_data = [ + 'forum_id' => $required1, + 'topic_id' => $additional1, + ]; + $log_operation = $additional2; + } + else + { + $log_operation = $required1; + $additional_data = [ + $additional1, + $additional2, + ]; + } + $phpbb_log->add($mode, $user_id, '', $log_operation, false, $additional_data); } else if ($additional1 != null) { - add_log($mode, $required1, $additional1); + if ($mode == 'user') + { + $additional_data = [ + 'reportee_id' => $required1, + ]; + $log_operation = $additional1; + } + else + { + $log_operation = $required1; + $additional_data = [ + $additional1, + ]; + } + + $phpbb_log->add($mode, $user_id, '', $log_operation, false, $additional_data); } else { - add_log($mode, $required1); + $phpbb_log->add($mode, $user_id, '', $required1); } $result = $db->sql_query('SELECT user_id, log_type, log_operation, log_data, reportee_id, forum_id, topic_id diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index ff95c74b09..1116a62c6f 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -653,6 +653,47 @@ public function test_prune_notifications($notification_type, $post_data, $expect $this->assertCount(0, $cur_notifications, 'Assert that no notifications have been pruned'); } + public function data_set_endpoint_padding(): array + { + return [ + [ + 'foo.mozilla.com', + webpush::MOZILLA_FALLBACK_PADDING + ], + [ + 'foo.mozaws.net', + webpush::MOZILLA_FALLBACK_PADDING + ], + [ + 'foo.android.googleapis.com', + \Minishlink\WebPush\Encryption::MAX_COMPATIBILITY_PAYLOAD_LENGTH, + ], + ]; + } + + /** + * @dataProvider data_set_endpoint_padding + */ + public function test_set_endpoint_padding($endpoint, $expected_padding): void + { + $web_push_reflection = new \ReflectionMethod($this->notification_method_webpush, 'set_endpoint_padding'); + $web_push_reflection->setAccessible(true); + + $auth = [ + 'VAPID' => [ + 'subject' => generate_board_url(), + 'publicKey' => $this->config['webpush_vapid_public'], + 'privateKey' => $this->config['webpush_vapid_private'], + ], + ]; + + $web_push = new \Minishlink\WebPush\WebPush($auth); + + $this->assertEquals(\Minishlink\WebPush\Encryption::MAX_COMPATIBILITY_PAYLOAD_LENGTH, $web_push->getAutomaticPadding()); + $web_push_reflection->invoke($this->notification_method_webpush, $web_push, $endpoint); + $this->assertEquals($expected_padding, $web_push->getAutomaticPadding()); + } + protected function create_subscription_for_user($user_id, bool $invalidate_endpoint = false): array { $client = new \GuzzleHttp\Client(); diff --git a/tests/path_helper/path_helper_test.php b/tests/path_helper/path_helper_test.php index a2abefb108..86aaa3c734 100644 --- a/tests/path_helper/path_helper_test.php +++ b/tests/path_helper/path_helper_test.php @@ -464,9 +464,6 @@ public function test_get_web_root_path_ajax() ->setConstructorArgs([new phpbb_mock_request()]) ->setMethods(['get', 'getSchemeAndHttpHost', 'getBasePath', 'getPathInfo']) ->getMock(); - $symfony_request->method('get') - ->with('_referer') - ->willReturn('http://www.phpbb.com/community/route1/route2/'); $symfony_request->method('getSchemeAndHttpHost') ->willReturn('http://www.phpbb.com'); $symfony_request->method('getBasePath') @@ -480,6 +477,9 @@ public function test_get_web_root_path_ajax() ->willReturn(true); $request->method('escape') ->willReturnArgument(0); + $request->method('header') + ->with('Referer') + ->willReturn('http://www.phpbb.com/community/route1/route2/'); $path_helper = new \phpbb\path_helper( $symfony_request, diff --git a/tests/privmsgs/delete_user_pms_test.php b/tests/privmsgs/delete_user_pms_test.php index a50240aae3..e618d7c2e6 100644 --- a/tests/privmsgs/delete_user_pms_test.php +++ b/tests/privmsgs/delete_user_pms_test.php @@ -97,7 +97,7 @@ public function test_delete_user_pms($delete_user, $remaining_privmsgs, $remaini // Works as a workaround for tests $phpbb_container->set('attachment.manager', new \phpbb\attachment\delete(new \phpbb\config\config(array()), $db, new \phpbb_mock_event_dispatcher(), new \phpbb\attachment\resync($db), $storage)); - phpbb_delete_user_pms($delete_user); + phpbb_delete_users_pms([$delete_user]); $sql = 'SELECT msg_id FROM ' . PRIVMSGS_TABLE; diff --git a/tests/request/request_var_test.php b/tests/request/request_var_test.php index c95dce46d0..ea1a1ac89e 100644 --- a/tests/request/request_var_test.php +++ b/tests/request/request_var_test.php @@ -13,15 +13,6 @@ class phpbb_request_var_test extends phpbb_test_case { - /** - * Makes sure request_var has its standard behaviour. - */ - protected function setUp(): void - { - parent::setUp(); - request_var(false, false, false, false, false); - } - /** * @dataProvider request_variables */ @@ -33,7 +24,8 @@ public function test_post($variable_value, $default, $multibyte, $expected) $_POST[$variable_name] = $variable_value; $_REQUEST[$variable_name] = $variable_value; - $result = request_var($variable_name, $default, $multibyte); + $request = new \phpbb\request\request(new \phpbb\request\type_cast_helper(), false); + $result = $request->variable($variable_name, $default, $multibyte); $label = 'Requesting POST variable, converting from ' . gettype($variable_value) . ' to ' . gettype($default) . (($multibyte) ? ' multibyte' : ''); $this->assertEquals($expected, $result, $label); @@ -50,7 +42,8 @@ public function test_get($variable_value, $default, $multibyte, $expected) $_GET[$variable_name] = $variable_value; $_REQUEST[$variable_name] = $variable_value; - $result = request_var($variable_name, $default, $multibyte); + $request = new \phpbb\request\request(new \phpbb\request\type_cast_helper(), false); + $result = $request->variable($variable_name, $default, $multibyte); $label = 'Requesting GET variable, converting from ' . gettype($variable_value) . ' to ' . gettype($default) . (($multibyte) ? ' multibyte' : ''); $this->assertEquals($expected, $result, $label); @@ -69,7 +62,8 @@ public function test_cookie($variable_value, $default, $multibyte, $expected) $_REQUEST[$variable_name] = false; $_COOKIE[$variable_name] = $variable_value; - $result = request_var($variable_name, $default, $multibyte, true); + $request = new \phpbb\request\request(new \phpbb\request\type_cast_helper(), false); + $result = $request->variable($variable_name, $default, $multibyte, \phpbb\request\request_interface::COOKIE); $label = 'Requesting COOKIE variable, converting from ' . gettype($variable_value) . ' to ' . gettype($default) . (($multibyte) ? ' multibyte' : ''); $this->assertEquals($expected, $result, $label); @@ -109,7 +103,8 @@ public function test_deep_multi_dim_array_access($path, $default, $expected) ), ); - $result = request_var($path, $default); + $request = new \phpbb\request\request(new \phpbb\request\type_cast_helper(), false); + $result = $request->variable($path, $default); $this->assertEquals($expected, $result); } diff --git a/tests/search/native_test.php b/tests/search/native_test.php index 234bfff47d..0d8d8e6810 100644 --- a/tests/search/native_test.php +++ b/tests/search/native_test.php @@ -34,6 +34,10 @@ protected function setUp(): void $language = new \phpbb\language\language(new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx)); $user = $this->createMock('\phpbb\user'); + $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); + $lang = new \phpbb\language\language($lang_loader); + $user = new \phpbb\user($lang, '\phpbb\datetime'); + $this->db = $this->new_dbal(); $tools_factory = new \phpbb\db\tools\factory(); $this->db_tools = $tools_factory->get($this->new_doctrine_dbal()); @@ -41,6 +45,7 @@ protected function setUp(): void $class = self::get_search_wrapper('\phpbb\search\backend\fulltext_native'); $config['fulltext_native_min_chars'] = 2; $config['fulltext_native_max_chars'] = 14; + $config['max_num_search_keywords'] = 10; $this->search = new $class($config, $this->db, $this->db_tools, $phpbb_dispatcher, $language, $user, SEARCH_RESULTS_TABLE, SEARCH_WORDLIST_TABLE, SEARCH_WORDMATCH_TABLE, $phpbb_root_path, $phpEx); } @@ -262,4 +267,78 @@ public function test_split_keywords($keywords, $terms, $ok, $must_contain, $must } $this->assert_array_content_equals($common, $this->search->get_common_words()); } + + public function data_split_keywords_max(): array + { + return [ + 'character count within limits separated by more spaces' => [ + 'foo bar baz boo far faz roo rar raz zoo', + 'all', + false, + ], + 'character count within limits separated by spaces' => [ + 'foo bar baz boo far faz roo rar raz zoo', + 'all', + false, + ], + 'character count within limits separated by +, spaces after +' => [ + 'foo+ bar+ baz+ boo+ far+ faz+ roo+ rar+ raz+ zoo', + 'all', + false, + ], + 'character count within limits separated by +, no spaces' => [ + 'foo+bar+baz+boo+far+faz+roo+rar+raz+zoo', + 'all', + false, + ], + 'character count outside limits separated by +, no spaces' => [ + 'foo+bar+baz+boo+far+faz+roo+rar+raz+zoo+zar', + 'all', + true, + ], + 'character count outside limits separated by + and spaces' => [ + 'foo +bar +baz +boo +far +faz +roo +rar +raz +zoo +zar', + 'all', + true, + ], + 'character count outside limits separated by spaces' => [ + 'foo bar baz boo far faz roo rar raz zoo zar', + 'all', + true, + ], + 'character count outside limits separated by -, no spaces' => [ + 'foo-bar-baz-boo-far-faz-roo-rar-raz-zoo-zar', + 'all', + true, + ], + 'character count outside limits separated by - and spaces' => [ + 'foo -bar -baz -boo -far -faz -roo -rar -raz -zoo -zar', + 'all', + true, + ], + 'character count outside limits separated by |, no spaces' => [ + 'foo|bar|baz|boo|far|faz|roo|rar|raz|zoo|zar', + 'all', + true, + ], + 'character count outside limits separated by | and spaces' => [ + 'foo |bar |baz |boo |far |faz |roo |rar |raz |zoo |zar', + 'all', + true, + ], + ]; + } + + /** + * @dataProvider data_split_keywords_max + */ + public function test_split_max_keywords($keywords, $terms, $expect_error) + { + if ($expect_error) + { + $this->setExpectedTriggerError(E_USER_NOTICE, 'MAX_NUM_SEARCH_KEYWORDS_REFINE'); + } + + $this->assertTrue($this->search->split_keywords($keywords, $terms)); + } } diff --git a/tests/security/hash_test.php b/tests/security/hash_test.php index 49eefa3411..203d142cb7 100644 --- a/tests/security/hash_test.php +++ b/tests/security/hash_test.php @@ -40,16 +40,26 @@ protected function setUp(): void public function test_check_hash_with_phpass() { - $this->assertTrue(phpbb_check_hash('test', '$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1')); - $this->assertTrue(phpbb_check_hash('test', '$P$9isfrtKXWqrz8PvztXlL3.daw4U0zI1')); - $this->assertFalse(phpbb_check_hash('foo', '$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1')); + global $phpbb_container; + + /** @var \phpbb\passwords\manager $passwords_manager */ + $passwords_manager = $phpbb_container->get('passwords.manager'); + + $this->assertTrue($passwords_manager->check('test', '$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1')); + $this->assertTrue($passwords_manager->check('test', '$P$9isfrtKXWqrz8PvztXlL3.daw4U0zI1')); + $this->assertFalse($passwords_manager->check('foo', '$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1')); } public function test_check_hash_with_large_input() { + global $phpbb_container; + + /** @var \phpbb\passwords\manager $passwords_manager */ + $passwords_manager = $phpbb_container->get('passwords.manager'); + // 16 MB password, should be rejected quite fast $start_time = time(); - $this->assertFalse(phpbb_check_hash(str_repeat('a', 1024 * 1024 * 16), '$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1')); + $this->assertFalse($passwords_manager->check(str_repeat('a', 1024 * 1024 * 16), '$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1')); $this->assertLessThanOrEqual(5, time() - $start_time); } } diff --git a/tests/storage/adapter/local_test_case.php b/tests/storage/adapter/local_test_case.php index 2c008120e7..dff331c694 100644 --- a/tests/storage/adapter/local_test_case.php +++ b/tests/storage/adapter/local_test_case.php @@ -33,8 +33,6 @@ protected function setUp(): void $this->adapter = new local( $this->filesystem, - new FastImageSize(), - new guesser(array(new extension_guesser)), $phpbb_root_path ); diff --git a/tests/template/extension_test.php b/tests/template/extension_test.php index 207aa5e99f..5caae111ca 100644 --- a/tests/template/extension_test.php +++ b/tests/template/extension_test.php @@ -11,8 +11,6 @@ * */ -use phpbb\controller\helper; - require_once __DIR__ . '/template_test_case.php'; class phpbb_template_extension_test extends phpbb_template_template_test_case @@ -34,7 +32,7 @@ protected function setup_engine(array $new_config = []) $this->user->style['style_parent_id'] = 0; global $auth, $request, $symfony_request, $user; - $user = new phpbb_mock_user(); + $user = $this->createMock(\phpbb\user::class); $user->optionset('user_id', 2); $user->style['style_path'] = ''; $user->data['user_id'] = 2; @@ -68,9 +66,8 @@ protected function setup_engine(array $new_config = []) ->disableOriginalConstructor() ->getMock(); - $controller_helper = $this->createMock(helper::class); - $controller_helper - ->method('route') + $routing_helper = $this->createMock(\phpbb\routing\helper::class); + $routing_helper->method('route') ->willReturnCallback(function($route, $params) { return 'download/avatar/' . $params['file']; }); @@ -78,7 +75,7 @@ protected function setup_engine(array $new_config = []) $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); $phpbb_container = new phpbb_mock_container_builder(); $files = new phpbb\files\factory($phpbb_container); - $upload_avatar_driver = new phpbb\avatar\driver\upload($config, $controller_helper, $phpbb_root_path, $phpEx, $storage, $phpbb_path_helper, $phpbb_dispatcher, $files, new \bantu\IniGetWrapper\IniGetWrapper()); + $upload_avatar_driver = new phpbb\avatar\driver\upload($config, $phpbb_root_path, $phpEx, $storage, $phpbb_path_helper, $routing_helper, $phpbb_dispatcher, $files, new \bantu\IniGetWrapper\IniGetWrapper()); $upload_avatar_driver->set_name('avatar.driver.upload'); $phpbb_container->set('avatar.manager', new \phpbb\avatar\manager($config, $phpbb_dispatcher, [ $upload_avatar_driver, @@ -89,6 +86,14 @@ protected function setup_engine(array $new_config = []) $enabled_drivers = $class->getProperty('enabled_drivers'); $enabled_drivers->setAccessible(true); $enabled_drivers->setValue($class, false); + $avatar_helper = new phpbb\avatar\helper( + $config, + $phpbb_dispatcher, + $lang, + $phpbb_container->get('avatar.manager'), + $phpbb_path_helper, + $user + ); $this->template_path = $this->test_path . '/templates'; @@ -122,7 +127,7 @@ protected function setup_engine(array $new_config = []) $this->user, [ new \phpbb\template\twig\extension($context, $twig, $this->lang), - new \phpbb\template\twig\extension\avatar(), + new \phpbb\template\twig\extension\avatar($avatar_helper), new \phpbb\template\twig\extension\config($config), new \phpbb\template\twig\extension\icon($this->user), new \phpbb\template\twig\extension\username(), @@ -153,7 +158,7 @@ public function data_template_extensions() ], [], [], - 'foo', + 'foo', [] ], [ @@ -171,7 +176,7 @@ public function data_template_extensions() ], [], [], - 'foo', + 'foo', [] ], [ @@ -190,6 +195,56 @@ public function data_template_extensions() '', [] ], + [ + 'avatar_group.html', + [ + 'row' => [ + 'group_avatar' => 'great_avatar.png', + 'group_avatar_type' => 'avatar.driver.upload', + 'group_avatar_width' => 90, + 'group_avatar_height' => 90, + ], + 'alt' => 'foo' + ], + [], + [], + 'foo', + [] + ], + [ + 'avatar_group.html', + [ + 'row' => [ + 'group_avatar' => 'great_avatar.png', + 'group_avatar_type' => 'avatar.driver.upload', + 'group_avatar_width' => 90, + 'group_avatar_height' => 90, + ], + 'alt' => 'foo', + 'ignore_config' => true, + 'lazy' => true, + ], + [], + [], + 'foo', + [] + ], + [ + 'avatar_group.html', + [ + 'row' => [ + 'group_avatar' => 'foo@bar.com', + 'group_avatar_type' => 'avatar.driver.gravatar', + 'group_avatar_width' => 90, + 'group_avatar_height' => 90, + ], + 'alt' => 'foo' + ], + [], + [], + '', + [] + ], [ 'extension_username_test.html', [ diff --git a/tests/template/template_test.php b/tests/template/template_test.php index 8faa52d8ac..4fc9858366 100644 --- a/tests/template/template_test.php +++ b/tests/template/template_test.php @@ -365,15 +365,15 @@ public function template_data() array(), array(), array(), - "VARIABLE\n1_VARIABLE\nVARIABLE\n1_VARIABLE", + "VARIABLE\n1_VARIABLE\nVARIABLE\n1_VARIABLE\nVARIABLE\n1_VARIABLE\nARY_VARIABLE", ), array( 'lang_twig.html', array(), array(), array(), - "Value'\n1 O'Clock\nValue\\u0027\n1\\u0020O\\u0027Clock", - array('VARIABLE' => "Value'", '1_VARIABLE' => "1 O'Clock"), + "Value'\n1 O'Clock\nValue\\u0027\n1\\u0020O\\u0027Clock\nValue'\n1 O'Clock\nfoo|bar", + array('VARIABLE' => "Value'", '1_VARIABLE' => "1 O'Clock", 'ARY_VARIABLE' => ['foo', 'bar']), ), array( 'loop_nested_multilevel_ref.html', diff --git a/tests/template/templates/avatar_group.html b/tests/template/templates/avatar_group.html new file mode 100644 index 0000000000..95ae9c08c0 --- /dev/null +++ b/tests/template/templates/avatar_group.html @@ -0,0 +1 @@ +{{ avatar('group', row, alt, ignore_config, lazy) }} diff --git a/tests/template/templates/lang_twig.html b/tests/template/templates/lang_twig.html index bf31012819..a331c7f506 100644 --- a/tests/template/templates/lang_twig.html +++ b/tests/template/templates/lang_twig.html @@ -3,3 +3,7 @@ {{ lang_js('VARIABLE') }} {{ lang_js('1_VARIABLE') }} + +{{ lang_raw('VARIABLE') }} +{{ lang_raw('1_VARIABLE') }} +{{ lang_raw('ARY_VARIABLE')|join('|') }} diff --git a/tests/test_framework/phpbb_database_test_case.php b/tests/test_framework/phpbb_database_test_case.php index 030c8100ea..09de77bc35 100644 --- a/tests/test_framework/phpbb_database_test_case.php +++ b/tests/test_framework/phpbb_database_test_case.php @@ -286,7 +286,7 @@ public function getConnection() return $this->createDefaultDBConnection($manager->get_pdo(), 'testdb'); } - public function new_dbal() + public function new_dbal() : \phpbb\db\driver\driver_interface { $config = $this->get_database_config(); diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index 02d5911459..2ce670d390 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -1567,9 +1567,9 @@ protected function set_flood_interval($flood_interval) * @param string $username The username to check or empty if user_id is used * @param int $user_id The user id to check or empty if username is used * - * @return bool Returns true if a user exists, false otherwise + * @return array Returns user_id => username array or empty array if user does not exist */ - protected function user_exists($username, $user_id = null) + protected function user_exists($username = '', $user_id = '') { global $db; @@ -1584,6 +1584,8 @@ protected function user_exists($username, $user_id = null) require_once(__DIR__ . '/../../phpBB/includes/functions_user.php'); } - return user_get_id_name($user_id, $username) ? false : true; + user_get_id_name($user_id, $username, false, true); + + return $username; } } diff --git a/tests/test_framework/phpbb_test_case_helpers.php b/tests/test_framework/phpbb_test_case_helpers.php index 466be97d4c..3bb279c25f 100644 --- a/tests/test_framework/phpbb_test_case_helpers.php +++ b/tests/test_framework/phpbb_test_case_helpers.php @@ -104,7 +104,7 @@ public function restore_original_ext_dir() } } - public function setExpectedTriggerError($errno, $message = '') + public function setExpectedTriggerError($errno, $message = ''): void { set_error_handler( static function ($errno, $errstr) @@ -112,7 +112,7 @@ static function ($errno, $errstr) restore_error_handler(); throw new Exception($errstr, $errno); }, - E_ALL + E_ALL ^ E_DEPRECATED ); $this->expectedTriggerError = true; diff --git a/tests/text_formatter/s9e/default_formatting_test.php b/tests/text_formatter/s9e/default_formatting_test.php index 20127204b3..3f637676b3 100644 --- a/tests/text_formatter/s9e/default_formatting_test.php +++ b/tests/text_formatter/s9e/default_formatting_test.php @@ -283,7 +283,7 @@ public function get_default_formatting_tests() ), array( '[quote=Username post_id=123]...[/quote]', - '
      Username wrote: ...
      ' + '
      Username wrote: ...
      ' ), array( // Users are not allowed to submit their own URL for the post @@ -310,11 +310,11 @@ public function get_default_formatting_tests() ), array( "Emoji: \xF0\x9F\x98\x80", - 'Emoji: ' . ' + "Emoji: \xF0\x9F\x98\x80", ), array( "Emoji: \xF0\x9F\x98\x80", - "Emoji: \xF0\x9F\x98\x80", + "Emoji: \xF0\x9F\x98\x80", function ($container) { $container->get('text_formatter.renderer')->set_viewsmilies(false); diff --git a/tests/text_processing/generate_text_for_display_test.php b/tests/text_processing/generate_text_for_display_test.php index 9a909a007c..7d96152c21 100644 --- a/tests/text_processing/generate_text_for_display_test.php +++ b/tests/text_processing/generate_text_for_display_test.php @@ -21,7 +21,6 @@ protected function setUp(): void $phpbb_dispatcher = new phpbb_mock_event_dispatcher; $config = new \phpbb\config\config(array()); - set_config(null, null, null, $config); } /** diff --git a/tests/text_processing/generate_text_for_storage_test.php b/tests/text_processing/generate_text_for_storage_test.php index c47c9b9f5e..86c257a9f6 100644 --- a/tests/text_processing/generate_text_for_storage_test.php +++ b/tests/text_processing/generate_text_for_storage_test.php @@ -20,7 +20,6 @@ protected function setUp(): void parent::setUp(); $config = new \phpbb\config\config(array()); - set_config(null, null, null, $config); $phpbb_container = new phpbb_mock_container_builder; $phpbb_container->set('config', $config); diff --git a/tests/text_processing/message_parser_test.php b/tests/text_processing/message_parser_test.php index 0f74eacbe6..93c0bd6652 100644 --- a/tests/text_processing/message_parser_test.php +++ b/tests/text_processing/message_parser_test.php @@ -40,8 +40,6 @@ protected function prepare_s9e_services($setup = null) $map = array( array('MAX_FONT_SIZE_EXCEEDED', 120, 'You may only use fonts up to size 120.'), array('MAX_FONT_SIZE_EXCEEDED', 200, 'You may only use fonts up to size 200.'), - array('MAX_IMG_HEIGHT_EXCEEDED', 12, 'Your images may only be up to 12 pixels high.'), - array('MAX_IMG_WIDTH_EXCEEDED', 34, 'Your images may only be up to 34 pixels wide.'), array('TOO_MANY_SMILIES', 3, 'Your message contains too many smilies. The maximum number of smilies allowed is 3.'), array('TOO_MANY_URLS', 2, 'Your message contains too many URLs. The maximum number of URLs allowed is 2.'), array('UNAUTHORISED_BBCODE', '[img]', 'You cannot use certain BBCodes: [img].'), diff --git a/tests/text_processing/tickets_data/PHPBB3-15348.html b/tests/text_processing/tickets_data/PHPBB3-15348.html index 33336083e3..564e6fda59 100644 --- a/tests/text_processing/tickets_data/PHPBB3-15348.html +++ b/tests/text_processing/tickets_data/PHPBB3-15348.html @@ -1 +1 @@ -:o k: :ok: \ No newline at end of file +:o k: :ok: \ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-16074.html b/tests/text_processing/tickets_data/PHPBB3-16074.html index 8b2e5aad8a..04d72697b8 100644 --- a/tests/text_processing/tickets_data/PHPBB3-16074.html +++ b/tests/text_processing/tickets_data/PHPBB3-16074.html @@ -1 +1 @@ -:man_judge: 👨‍⚖️ \ No newline at end of file +:man_judge: 👨‍⚖️ \ No newline at end of file diff --git a/tests/version/fixture/30x.txt b/tests/version/fixture/30x.txt new file mode 100644 index 0000000000..f138ad0c83 --- /dev/null +++ b/tests/version/fixture/30x.txt @@ -0,0 +1,4 @@ +3.0.14 +https://www.phpbb.com/community/viewtopic.php?f=14&t=2313941 +3.3.12 +https://www.phpbb.com/community/viewtopic.php?t=2653732 diff --git a/tests/version/version_helper_remote_test.php b/tests/version/version_helper_remote_test.php index de88c281ff..1c616bc1fd 100644 --- a/tests/version/version_helper_remote_test.php +++ b/tests/version/version_helper_remote_test.php @@ -1,4 +1,7 @@ cache->expects($this->any()) ->method('get') - ->with($this->anything()) + ->withAnyParameters() ->will($this->returnValue(false)); - $this->file_downloader = new phpbb_mock_file_downloader(); + + $this->guzzle_mock = $this->getMockBuilder('\GuzzleHttp\Client') + ->addMethods(['set_data']) + ->onlyMethods(['request']) + ->getMock(); + $this->guzzle_mock->method('set_data') + ->will($this->returnCallback(function($data) + { + $this->guzzle_data = $data; + } + )); + $this->guzzle_mock->method('request') + ->will($this->returnCallback(function() + { + return new \GuzzleHttp\Psr7\Response($this->guzzle_status, [], $this->guzzle_data); + } + )); + + $this->file_downloader = $this->getMockBuilder('\phpbb\file_downloader') + ->onlyMethods(['create_client']) + ->getMock(); + $this->file_downloader->method('create_client') + ->will($this->returnValue($this->guzzle_mock)); $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); @@ -203,7 +233,7 @@ public function provider_get_versions() */ public function test_get_versions($input, $valid_data, $expected_return = '', $expected_exception = '') { - $this->file_downloader->set($input); + $this->guzzle_mock->set_data($input); // version_helper->get_versions() doesn't return a value on VERSIONCHECK_FAIL but only throws exception // so the $return is undefined. Define it here @@ -214,7 +244,7 @@ public function test_get_versions($input, $valid_data, $expected_return = '', $e try { $return = $this->version_helper->get_versions(); } catch (\phpbb\exception\runtime_exception $e) { - $this->assertEquals((string)$e->getMessage(), $expected_exception); + $this->assertEquals($expected_exception, $e->getMessage()); } } else @@ -224,4 +254,206 @@ public function test_get_versions($input, $valid_data, $expected_return = '', $e $this->assertEquals($expected_return, $return); } + + public function test_version_phpbb_com() + { + $guzzle_mock = $this->getMockBuilder('\GuzzleHttp\Client') + ->onlyMethods(['request']) + ->getMock(); + + $guzzle_mock->method('request') + ->will($this->returnCallback(function() + { + return new \GuzzleHttp\Psr7\Response(200, [], file_get_contents(__DIR__ . '/fixture/30x.txt')); + } + )); + + $file_downloader = $this->getMockBuilder(\phpbb\file_downloader::class) + ->onlyMethods(['create_client']) + ->getMock(); + + $file_downloader->method('create_client') + ->willReturn($guzzle_mock); + + $hostname = 'version.phpbb.com'; + + $file = $file_downloader->get($hostname, '/phpbb', '30x.txt'); + $errstr = $file_downloader->get_error_string(); + $errno = $file_downloader->get_error_number(); + + $this->assertNotEquals( + 0, + strlen($file), + 'Failed asserting that the response is not empty.' + ); + + $this->assertSame( + '', + $errstr, + 'Failed asserting that the error string is empty.' + ); + + $this->assertSame( + 0, + $errno, + 'Failed asserting that the error number is 0 (i.e. no error occurred).' + ); + + $lines = explode("\n", $file); + + $this->assertGreaterThanOrEqual( + 2, + count($lines), + 'Failed asserting that the version file has at least two lines.' + ); + + $this->assertStringStartsWith( + '3.', + $lines[0], + "Failed asserting that the first line of the version file starts with '3.'" + ); + + $this->assertNotSame( + false, + filter_var($lines[1], FILTER_VALIDATE_URL), + 'Failed asserting that the second line of the version file is a valid URL.' + ); + + $this->assertStringContainsString('http', $lines[1]); + $this->assertStringContainsString('phpbb.com', $lines[1], '', true); + } + + public function test_file_downloader_file_not_found() + { + $this->guzzle_mock = $this->getMockBuilder('\GuzzleHttp\Client') + ->onlyMethods(['request']) + ->getMock(); + + $this->guzzle_mock->method('request') + ->will($this->returnCallback(function() + { + return new \GuzzleHttp\Psr7\Response(404, [], ''); + } + )); + + $file_downloader = $this->getMockBuilder(\phpbb\file_downloader::class) + ->onlyMethods(['create_client']) + ->getMock(); + + $file_downloader->method('create_client') + ->willReturn($this->guzzle_mock); + + $this->expectException(\phpbb\exception\runtime_exception::class); + $this->expectExceptionMessage('FILE_NOT_FOUND'); + + $file_downloader->get('foo.com', 'bar', 'foo.txt'); + } + + public function test_file_downloader_exception_not_found() + { + $this->guzzle_mock = $this->getMockBuilder('\GuzzleHttp\Client') + ->onlyMethods(['request']) + ->getMock(); + + $this->guzzle_mock->method('request') + ->will($this->returnCallback(function($method, $uri) + { + $request = new \GuzzleHttp\Psr7\Request('GET', $uri); + $response = new \GuzzleHttp\Psr7\Response(404, [], ''); + throw new RequestException('FILE_NOT_FOUND', $request, $response); + } + )); + + $file_downloader = $this->getMockBuilder(\phpbb\file_downloader::class) + ->onlyMethods(['create_client']) + ->getMock(); + + $file_downloader->method('create_client') + ->willReturn($this->guzzle_mock); + + $this->expectException(\phpbb\exception\runtime_exception::class); + $this->expectExceptionMessage('FILE_NOT_FOUND'); + + $file_downloader->get('foo.com', 'bar', 'foo.txt'); + } + + public function test_file_downloader_exception_moved() + { + $this->guzzle_mock = $this->getMockBuilder('\GuzzleHttp\Client') + ->onlyMethods(['request']) + ->getMock(); + + $this->guzzle_mock->method('request') + ->will($this->returnCallback(function($method, $uri) + { + $request = new \GuzzleHttp\Psr7\Request('GET', $uri); + $response = new \GuzzleHttp\Psr7\Response(302, [], ''); + throw new RequestException('FILE_MOVED', $request, $response); + } + )); + + $file_downloader = $this->getMockBuilder(\phpbb\file_downloader::class) + ->onlyMethods(['create_client']) + ->getMock(); + + $file_downloader->method('create_client') + ->willReturn($this->guzzle_mock); + + $this->assertFalse($file_downloader->get('foo.com', 'bar', 'foo.txt')); + $this->assertEquals(302, $file_downloader->get_error_number()); + $this->assertEquals('FILE_MOVED', $file_downloader->get_error_string()); + } + + public function test_file_downloader_exception_timeout() + { + $this->guzzle_mock = $this->getMockBuilder('\GuzzleHttp\Client') + ->onlyMethods(['request']) + ->getMock(); + + $this->guzzle_mock->method('request') + ->will($this->returnCallback(function($method, $uri) + { + $request = new \GuzzleHttp\Psr7\Request('GET', $uri); + throw new RequestException('FILE_NOT_FOUND', $request); + } + )); + + $file_downloader = $this->getMockBuilder(\phpbb\file_downloader::class) + ->onlyMethods(['create_client']) + ->getMock(); + + $file_downloader->method('create_client') + ->willReturn($this->guzzle_mock); + + $this->expectException(\phpbb\exception\runtime_exception::class); + $this->expectExceptionMessage('FSOCK_TIMEOUT'); + + $file_downloader->get('foo.com', 'bar', 'foo.txt'); + } + + public function test_file_downloader_exception_other() + { + $this->guzzle_mock = $this->getMockBuilder('\GuzzleHttp\Client') + ->onlyMethods(['request']) + ->getMock(); + + $this->guzzle_mock->method('request') + ->will($this->returnCallback(function($method, $uri) + { + throw new \RuntimeException('FSOCK_NOT_SUPPORTED'); + } + )); + + $file_downloader = $this->getMockBuilder(\phpbb\file_downloader::class) + ->onlyMethods(['create_client']) + ->getMock(); + + $file_downloader->method('create_client') + ->willReturn($this->guzzle_mock); + + $this->expectException(\phpbb\exception\runtime_exception::class); + $this->expectExceptionMessage('FSOCK_DISABLED'); + + $file_downloader->get('foo.com', 'bar', 'foo.txt'); + } }