diff --git a/.env.example b/.env.example index 6974d8554611a..dddbb4be4c260 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,10 @@ LOCAL_PORT=8889 # Where to run WordPress from. Valid options are 'src' and 'build'. LOCAL_DIR=src +# Whether or not to enable text compression (gzip) in the web server. +# Valid options are 'on' and 'off'. +LOCAL_NGINX_COMPRESSION=off + # The PHP version to use. Valid options are 'latest', and '{version}-fpm'. LOCAL_PHP=latest diff --git a/.github/workflows/check-built-files.yml b/.github/workflows/check-built-files.yml deleted file mode 100644 index 01a239c4eb3b0..0000000000000 --- a/.github/workflows/check-built-files.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Checks for uncommitted changes to built files in pull requests. -name: Check Built Files (PRs) - -on: - # Because all commits happen through SVN and should always be manually reviewed by a committer, this workflow only - # runs for pull requests. - # - # Other workflows that run for the push event will detect changes to versioned files and fail. - pull_request: - branches: - - trunk - - '6.[8-9]' - - '[7-9].[0-9]' - paths: - # Any change to a CSS, JavaScript, JSON, or SASS file should run checks. - - '**.css' - - '**.js' - - '**.json' - - '**.sass' - # These files configure npm and the task runner. Changes could affect the outcome. - - 'package*.json' - - '.npmrc' - - '.nvmrc' - - 'Gruntfile.js' - - 'webpack.config.js' - - 'tools/gutenberg/**' - - 'tools/vendors/**' - - 'tools/webpack/**' - # These files configure Composer. Changes could affect the outcome. - - 'composer.*' - # Confirm any changes to relevant workflow files. - - '.github/workflows/check-built-files.yml' - - '.github/workflows/reusable-check-built-files.yml' - # Changes to the default themes should be handled by the themes workflows. - - '!src/wp-content/themes/twenty**' - -# Cancels all previous workflow runs for pull requests that have not completed. -concurrency: - # The concurrency group contains the workflow name and the branch name for pull requests - # or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} - cancel-in-progress: true - -# Disable permissions for all available scopes by default. -# Any needed permissions should be configured at the job level. -permissions: {} - -jobs: - check-for-built-file-changes: - name: Check built files - if: ${{ github.repository == 'wordpress/wordpress-develop' }} - uses: ./.github/workflows/reusable-check-built-files.yml - permissions: - contents: read diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 8f1e9fe701330..d579368eeb079 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -89,6 +89,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/commit-built-file-changes.yml b/.github/workflows/commit-built-file-changes.yml deleted file mode 100644 index 9a959187505c9..0000000000000 --- a/.github/workflows/commit-built-file-changes.yml +++ /dev/null @@ -1,166 +0,0 @@ -# Commits all missed changes to built files back to pull request branches. -name: Commit Built File Changes (PRs) - -on: - workflow_run: - workflows: - - 'Check Built Files (PRs)' - - 'Test Default Themes & Create ZIPs' - types: - - completed - -# Cancels all previous workflow runs for pull requests that have not completed. -concurrency: - # The concurrency group contains the workflow name and the branch name for pull requests - # or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'workflow_run' && format( '{0}-{1}', github.event.workflow_run.head_branch, github.event.workflow_run.head_repository.name ) || github.sha }} - -# Disable permissions for all available scopes by default. -# Any needed permissions should be configured at the job level. -permissions: {} - -jobs: - # Checks a PR for uncommitted changes to built files. - # - # Performs the following steps: - # - Attempts to download the artifact containing the PR diff. - # - Checks for the existence of an artifact. - # - Unzips the artifact. - # - Generates a token for authenticating with the GitHub App. - # - Checks out the repository. - # - Applies the patch file. - # - Displays the result of git diff. - # - Configures the Git author. - # - Stages changes. - # - Commits changes. - # - Pushes changes. - update-built-files: - name: Check and update built files - runs-on: ubuntu-24.04 - if: ${{ github.repository == 'wordpress/wordpress-develop' }} - timeout-minutes: 10 - permissions: - # The actual `git push` is authenticated via a dedicated GitHub App installation token - # generated below, so `GITHUB_TOKEN` only needs read access to the triggering workflow's artifacts. - actions: read # Required to list and download the artifact uploaded by the triggering workflow run. - steps: - - name: Download artifact - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const artifacts = await github.rest.actions.listWorkflowRunArtifacts( { - owner: context.repo.owner, - repo: context.repo.repo, - run_id: process.env.RUN_ID, - } ); - - const matchArtifact = artifacts.data.artifacts.filter( ( artifact ) => { - return artifact.name === 'pr-built-file-changes' - } )[0]; - - if ( ! matchArtifact ) { - core.info( 'No artifact found!' ); - return; - } - - const download = await github.rest.actions.downloadArtifact( { - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - } ); - - const fs = require( 'fs' ); - fs.writeFileSync( '${{ github.workspace }}/pr-built-file-changes.zip', Buffer.from( download.data ) ) - env: - RUN_ID: ${{ github.event.workflow_run.id }} - - - name: Check for artifact - id: artifact-check - run: | - if [ -f "pr-built-file-changes.zip" ]; then - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "exists=false" >> "$GITHUB_OUTPUT" - fi - - - name: Unzip the artifact containing the PR data - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - run: unzip pr-built-file-changes.zip - - - name: Generate Installation Token - id: generate_token - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - env: - GH_APP_ID: ${{ vars.GH_PR_BUILT_FILES_APP_ID }} - GH_APP_PRIVATE_KEY: ${{ secrets.GH_PR_BUILT_FILES_PRIVATE_KEY }} - run: | - # Generate JWT - JWT=$(python3 - <> "$GITHUB_OUTPUT" - - - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - with: - repository: ${{ github.event.workflow_run.head_repository.full_name }} - ref: ${{ github.event.workflow_run.head_branch }} - path: 'pr-repo' - show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - token: ${{ steps.generate_token.outputs.access-token }} - persist-credentials: true - - - name: Apply patch - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - run: git apply "$GITHUB_WORKSPACE/changes.diff" - - - name: Display changes to versioned files - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - run: git diff - - - name: Configure git user name and email - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - env: - GH_APP_ID: ${{ vars.GH_PR_BUILT_FILES_APP_ID }} - run: | - git config user.name "wordpress-develop-pr-bot[bot]" - git config user.email "${GH_APP_ID}+wordpress-develop-pr-bot[bot]@users.noreply.github.com" - - - name: Stage changes - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - run: git add . - - - name: Commit changes - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - run: | - git commit -m "Automation: Updating built files with changes." - - - name: Push changes - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - run: git push diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index d91b6512f4b49..f2eb5abb83ddb 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -89,6 +89,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/install-testing.yml b/.github/workflows/install-testing.yml index 140e5f679701a..d69dc103d2d3a 100644 --- a/.github/workflows/install-testing.yml +++ b/.github/workflows/install-testing.yml @@ -152,6 +152,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/javascript-tests.yml b/.github/workflows/javascript-tests.yml index 8b1241038773d..08a118e4a4056 100644 --- a/.github/workflows/javascript-tests.yml +++ b/.github/workflows/javascript-tests.yml @@ -77,6 +77,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/javascript-type-checking.yml b/.github/workflows/javascript-type-checking.yml index 65561cabbb1e1..4983e0c619ec8 100644 --- a/.github/workflows/javascript-type-checking.yml +++ b/.github/workflows/javascript-type-checking.yml @@ -70,6 +70,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/local-docker-environment.yml b/.github/workflows/local-docker-environment.yml index 856db171726b6..0f88d54ff4eb8 100644 --- a/.github/workflows/local-docker-environment.yml +++ b/.github/workflows/local-docker-environment.yml @@ -135,6 +135,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index ef02c51101557..22a3a27392859 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -153,6 +153,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/php-compatibility.yml b/.github/workflows/php-compatibility.yml index 4bae7e0a381b0..37de91a73b24b 100644 --- a/.github/workflows/php-compatibility.yml +++ b/.github/workflows/php-compatibility.yml @@ -66,6 +66,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/phpstan-static-analysis.yml b/.github/workflows/phpstan-static-analysis.yml index 6a26c75b16525..a07ee13e16467 100644 --- a/.github/workflows/phpstan-static-analysis.yml +++ b/.github/workflows/phpstan-static-analysis.yml @@ -66,6 +66,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml index f0f1282f73a93..30cf72bc606ae 100644 --- a/.github/workflows/phpunit-tests.yml +++ b/.github/workflows/phpunit-tests.yml @@ -411,6 +411,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/reusable-check-built-files.yml b/.github/workflows/reusable-check-built-files.yml deleted file mode 100644 index 043c24581cc36..0000000000000 --- a/.github/workflows/reusable-check-built-files.yml +++ /dev/null @@ -1,104 +0,0 @@ -## -# A reusable workflow that checks for uncommitted changes to built files in pull requests. -## -name: Check Built Files (PRs) - -on: - workflow_call: - -permissions: {} - -jobs: - # Checks a PR for uncommitted changes to built files. - # - # When changes are detected, the patch is stored as an artifact for processing by the Commit Built File Changes - # workflow. - # - # Performs the following steps: - # - Checks out the repository. - # - Sets up Node.js. - # - Installs Composer dependencies. - # - Logs general debug information about the runner. - # - Installs npm dependencies. - # - Builds CSS file using SASS. - # - Builds Emoji files. - # - Builds bundled Root Certificate files. - # - Builds WordPress. - # - Checks for changes to versioned files. - # - Displays the result of git diff for debugging purposes. - # - Saves the diff to a patch file. - # - Uploads the patch file as an artifact. - update-built-files: - name: Check and update built files - runs-on: ubuntu-24.04 - timeout-minutes: 10 - permissions: - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - persist-credentials: false - - - name: Set up Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version-file: '.nvmrc' - cache: npm - - # Since Composer dependencies are installed using `composer update` and no lock file is in version control, - # passing a custom cache suffix ensures that the cache is flushed at least once per week. - - name: Install Composer dependencies - uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 - with: - custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") - - - name: Log debug information - run: | - npm --version - node --version - curl --version - git --version - - - name: Install npm Dependencies - run: npm ci - - - name: Run SASS precommit tasks - run: npm run grunt precommit:css - - - name: Run Emoji precommit task - run: npm run grunt precommit:emoji - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Run certificate tasks - run: npm run grunt copy:certificates - - - name: Build WordPress - run: npm run build:dev - - - name: Check for changes to versioned files - id: built-file-check - run: | - if git diff --quiet; then - echo "uncommitted_changes=false" >> "$GITHUB_OUTPUT" - else - echo "uncommitted_changes=true" >> "$GITHUB_OUTPUT" - fi - - - name: Display changes to versioned files - if: ${{ steps.built-file-check.outputs.uncommitted_changes == 'true' }} - run: git diff - - - name: Save diff to a file - if: ${{ steps.built-file-check.outputs.uncommitted_changes == 'true' }} - run: git diff > ./changes.diff - - # Uploads the diff file as an artifact. - - name: Upload diff file as artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: ${{ steps.built-file-check.outputs.uncommitted_changes == 'true' }} - with: - name: pr-built-file-changes - path: changes.diff diff --git a/.github/workflows/reusable-end-to-end-tests.yml b/.github/workflows/reusable-end-to-end-tests.yml index 36a71d1711d2f..9b5586973aacf 100644 --- a/.github/workflows/reusable-end-to-end-tests.yml +++ b/.github/workflows/reusable-end-to-end-tests.yml @@ -100,7 +100,7 @@ jobs: - name: Install Playwright browsers if: ${{ inputs.install-playwright }} - run: npx playwright install --with-deps chromium + run: npm exec --no -- playwright install --with-deps chromium - name: Build WordPress run: npm run build diff --git a/.github/workflows/reusable-performance-test-v2.yml b/.github/workflows/reusable-performance-test-v2.yml index a9b911e718cba..b92893f52f7f1 100644 --- a/.github/workflows/reusable-performance-test-v2.yml +++ b/.github/workflows/reusable-performance-test-v2.yml @@ -148,7 +148,7 @@ jobs: run: npm ci - name: Install Playwright browsers - run: npx playwright install --with-deps chromium + run: npm exec --no -- playwright install --with-deps chromium - name: Start Docker environment run: npm run env:start diff --git a/.github/workflows/reusable-test-core-build-process.yml b/.github/workflows/reusable-test-core-build-process.yml index 0fe5dfe6d564b..1a41257418dec 100644 --- a/.github/workflows/reusable-test-core-build-process.yml +++ b/.github/workflows/reusable-test-core-build-process.yml @@ -112,6 +112,10 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run CSS precommit tasks + if: ${{ inputs.directory == 'src' }} + run: npm run grunt precommit:css + - name: Ensure certificates files are updated if: ${{ inputs.test-certificates }} run: npm run grunt copy:certificates && npm run grunt build:certificates diff --git a/.github/workflows/slack-notifications.yml b/.github/workflows/slack-notifications.yml index 0877405409e83..6a2f23714e291 100644 --- a/.github/workflows/slack-notifications.yml +++ b/.github/workflows/slack-notifications.yml @@ -25,6 +25,9 @@ on: SLACK_GHA_FAILURE_WEBHOOK: description: 'The Slack webhook URL for a failed build.' required: true + SLACK_GHA_TIMEOUT_WEBHOOK: + description: 'The Slack webhook URL for a timed out build.' + required: false # Disable permissions for all available scopes by default. # Any needed permissions should be configured at the job level. @@ -55,6 +58,7 @@ jobs: if: ${{ github.repository == 'WordPress/wordpress-develop' && github.event.workflow_run.event != 'pull_request' }} outputs: previous_conclusion: ${{ steps.previous-attempt-result.outputs.result }} + timed_out: ${{ steps.timeout-check.outputs.result }} payload: ${{ steps.create-payload.outputs.payload }} steps: @@ -128,6 +132,25 @@ jobs: env: CALLING_STATUS: ${{ inputs.calling_status }} + # The REST API exposes a distinct `timed_out` job conclusion that is unavailable through workflow contexts. + - name: Determine whether the workflow timed out + id: timeout-check + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + if: ${{ inputs.calling_status == 'cancelled' }} + with: + retries: 2 + retry-exempt-status-codes: 418 + result-encoding: string + script: | + const jobs = await github.paginate( github.rest.actions.listJobsForWorkflowRunAttempt, { + owner: context.repo.owner, + repo: context.repo.repo, + run_id: `${context.runId}`, + attempt_number: Number( process.env.GITHUB_RUN_ATTEMPT ), + }, ( response ) => response.data.jobs ); + + return jobs.some( ( job ) => job.conclusion === 'timed_out' ) ? 'true' : 'false'; + - name: Get the commit message id: current-commit-message uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -211,14 +234,13 @@ jobs: webhook: ${{ secrets.SLACK_GHA_SUCCESS_WEBHOOK }} payload: ${{ needs.prepare.outputs.payload }} - # Posts notifications when a workflow is cancelled. cancelled: name: Cancelled notifications permissions: {} runs-on: ubuntu-24.04 timeout-minutes: 20 needs: [ prepare ] - if: ${{ inputs.calling_status == 'cancelled' && github.run_attempt == 2 || cancelled() }} + if: ${{ ( inputs.calling_status == 'cancelled' && github.run_attempt == 2 && needs.prepare.outputs.timed_out != 'true' ) || cancelled() }} steps: - name: Post cancelled notifications to Slack @@ -227,3 +249,19 @@ jobs: webhook-type: webhook-trigger webhook: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} payload: ${{ needs.prepare.outputs.payload }} + + timeout: + name: Timeout notifications + permissions: {} + runs-on: ubuntu-24.04 + timeout-minutes: 20 + needs: [ prepare ] + if: ${{ inputs.calling_status == 'cancelled' && github.run_attempt == 2 && needs.prepare.outputs.timed_out == 'true' }} + + steps: + - name: Post timeout notifications to Slack + uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 + with: + webhook-type: webhook-trigger + webhook: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} + payload: ${{ needs.prepare.outputs.payload }} diff --git a/.github/workflows/test-and-zip-default-themes.yml b/.github/workflows/test-and-zip-default-themes.yml index 30c15408f9736..0464a1652d36b 100644 --- a/.github/workflows/test-and-zip-default-themes.yml +++ b/.github/workflows/test-and-zip-default-themes.yml @@ -284,6 +284,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/test-build-processes.yml b/.github/workflows/test-build-processes.yml index cd32d99cacb1f..ab3bea392cef8 100644 --- a/.github/workflows/test-build-processes.yml +++ b/.github/workflows/test-build-processes.yml @@ -118,6 +118,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 191445ba7167c..94f57628254c3 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -97,6 +97,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/test-old-branches.yml b/.github/workflows/test-old-branches.yml index b5bef1b88c0bc..7094550485c4f 100644 --- a/.github/workflows/test-old-branches.yml +++ b/.github/workflows/test-old-branches.yml @@ -158,3 +158,4 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} diff --git a/.github/workflows/upgrade-develop-testing.yml b/.github/workflows/upgrade-develop-testing.yml index f14736af874a1..4147d3f054b8b 100644 --- a/.github/workflows/upgrade-develop-testing.yml +++ b/.github/workflows/upgrade-develop-testing.yml @@ -140,6 +140,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/upgrade-testing.yml b/.github/workflows/upgrade-testing.yml index 9a9c239e3093d..0d89c062f3b3b 100644 --- a/.github/workflows/upgrade-testing.yml +++ b/.github/workflows/upgrade-testing.yml @@ -206,6 +206,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/workflow-lint.yml b/.github/workflows/workflow-lint.yml index b2d5c2638e7b4..1ef050f1b9b92 100644 --- a/.github/workflows/workflow-lint.yml +++ b/.github/workflows/workflow-lint.yml @@ -61,6 +61,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/Gruntfile.js b/Gruntfile.js index 61f18481e23a8..ab40643d05a80 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2279,8 +2279,8 @@ module.exports = function(grunt) { grunt.registerTask( 'qunit', 'Runs QUnit tests.', function() { var done = this.async(); grunt.util.spawn( { - cmd: 'npx', - args: [ 'playwright', 'test', '--config', 'tests/qunit/playwright.config.js' ], + cmd: 'npm', + args: [ 'exec', '--no', '--', 'playwright', 'test', '--config', 'tests/qunit/playwright.config.js' ], opts: { stdio: 'inherit' } }, function( error, result, code ) { if ( code !== 0 ) { @@ -2363,15 +2363,15 @@ module.exports = function(grunt) { grunt.registerTask( 'wp-packages:update', 'Update WordPress packages', function() { const distTag = grunt.option('dist-tag') || 'latest'; grunt.log.writeln( `Updating WordPress packages (--dist-tag=${distTag})` ); - spawn( 'npx', [ 'wp-scripts', 'packages-update', `--dist-tag=${distTag}` ], { + spawn( 'npm', [ 'exec', '--no', '--', 'wp-scripts', 'packages-update', `--dist-tag=${distTag}` ], { cwd: __dirname, stdio: 'inherit', } ); } ); grunt.registerTask( 'browserslist:update', 'Update the local database of browser supports', function() { - grunt.log.writeln( `Updating browsers list` ); - spawn( 'npx', [ 'update-browserslist-db@latest' ], { + grunt.log.writeln( 'Updating browsers list' ); + spawn( 'npm', [ 'exec', '--no', '--', 'update-browserslist-db' ], { cwd: __dirname, stdio: 'inherit', } ); diff --git a/docker-compose.yml b/docker-compose.yml index 7ab6ae8c9b4d5..02cd542c245db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,13 +14,14 @@ services: environment: LOCAL_DIR: ${LOCAL_DIR-src} + LOCAL_NGINX_COMPRESSION: ${LOCAL_NGINX_COMPRESSION:-off} volumes: - ./tools/local-env/default.template:/etc/nginx/conf.d/default.template - ./:/var/www # Load our config file, substituting environment variables into the config. - command: /bin/sh -c "envsubst '$$LOCAL_DIR' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'" + command: /bin/sh -c "envsubst '$$LOCAL_DIR $$LOCAL_NGINX_COMPRESSION' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'" depends_on: php: diff --git a/package-lock.json b/package-lock.json index fe92d5381d923..f1d7ebd89b2c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,6 +89,7 @@ "sinon-test": "~3.1.6", "source-map-loader": "5.0.0", "typescript": "6.0.3", + "update-browserslist-db": "1.3.1", "uuid": "14.0.1", "wait-on": "9.0.10", "webpack": "5.108.4" @@ -32266,9 +32267,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index e87e95546a204..9bb1fd5d0af5b 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "sinon-test": "~3.1.6", "source-map-loader": "5.0.0", "typescript": "6.0.3", + "update-browserslist-db": "1.3.1", "uuid": "14.0.1", "wait-on": "9.0.10", "webpack": "5.108.4" diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e2e72fb367482..248623a5c01d6 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -79,7 +79,6 @@ includes: - tests/phpstan/baselines/property.phpDocType.neon - tests/phpstan/baselines/property.private.neon - tests/phpstan/baselines/property.protected.neon - - tests/phpstan/baselines/return.empty.neon - tests/phpstan/baselines/return.missing.neon - tests/phpstan/baselines/return.type.neon - tests/phpstan/baselines/return.unusedType.neon diff --git a/src/wp-admin/css/admin-menu.css b/src/wp-admin/css/admin-menu.css index aa6a8bc1414aa..3747613282be1 100644 --- a/src/wp-admin/css/admin-menu.css +++ b/src/wp-admin/css/admin-menu.css @@ -800,10 +800,12 @@ li#wp-admin-bar-menu-toggle { display: block; padding: 0; overflow: hidden; + outline: none; text-decoration: none; + border: 1px solid transparent; background: none; - height: 46px; - box-sizing: border-box; + height: 44px; + margin-left: -1px; } .wp-responsive-open #wpadminbar #wp-admin-bar-menu-toggle a { @@ -814,17 +816,22 @@ li#wp-admin-bar-menu-toggle { display: block; } + #wpadminbar #wp-admin-bar-menu-toggle a:hover { + border: 1px solid transparent; + } + #wpadminbar #wp-admin-bar-menu-toggle .ab-icon:before { content: "\f228"; display: inline-block; float: left; - font: normal 40px/46px dashicons; + font: normal 40px/45px dashicons; vertical-align: middle; + outline: none; margin: 0; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - height: 46px; - width: 52px; + height: 44px; + width: 50px; padding: 0; border: none; text-align: center; diff --git a/src/wp-admin/css/colors/_admin.scss b/src/wp-admin/css/colors/_admin.scss index 91d6a4b66bc8c..366f1b86b4f16 100644 --- a/src/wp-admin/css/colors/_admin.scss +++ b/src/wp-admin/css/colors/_admin.scss @@ -416,11 +416,9 @@ ul#adminmenu > li.current > a.current:after { background: variables.$menu-submenu-background; } -#wpadminbar > #wp-toolbar li:hover span.ab-label, -#wpadminbar > #wp-toolbar li.hover span.ab-label, -/* The adminbar menu may output either links or focusable div elements. */ -/* As such, we target the focus state without specifying the element type. */ -#wpadminbar > #wp-toolbar :focus span.ab-label { +#wpadminbar:not(.mobile) > #wp-toolbar li:hover span.ab-label, +#wpadminbar:not(.mobile) > #wp-toolbar li.hover span.ab-label, +#wpadminbar:not(.mobile) > #wp-toolbar a:focus span.ab-label { color: variables.$menu-submenu-focus-text; } @@ -451,9 +449,7 @@ ul#adminmenu > li.current > a.current:after { } #wpadminbar .quicklinks li .blavatar, -#wpadminbar .menupop .menupop > .ab-item:before, -#wpadminbar.mobile .quicklinks .ab-icon:before, -#wpadminbar.mobile .quicklinks .ab-item:before { +#wpadminbar .menupop .menupop > .ab-item:before { color: variables.$menu-icon; } @@ -478,17 +474,21 @@ ul#adminmenu > li.current > a.current:after { color: variables.$menu-submenu-focus-text; } -// Note that the icon of the site-name item is not wrapped within a span with class ab-icon like other items. #wpadminbar .quicklinks li a:hover .blavatar, #wpadminbar .quicklinks li a:focus .blavatar, #wpadminbar .quicklinks .ab-sub-wrapper .menupop.hover > a .blavatar, #wpadminbar .menupop .menupop > .ab-item:hover:before, #wpadminbar.mobile .quicklinks .hover .ab-icon:before, -#wpadminbar.mobile .quicklinks .hover .ab-item:before, -#wpadminbar.mobile .quicklinks .ab-item:focus:before { +#wpadminbar.mobile .quicklinks .hover .ab-item:before { color: variables.$menu-submenu-focus-text; } +#wpadminbar.mobile .quicklinks .ab-icon:before, +#wpadminbar.mobile .quicklinks .ab-item:before { + color: variables.$menu-icon; +} + + /* Admin Bar: search */ #wpadminbar #adminbarsearch:before { @@ -531,16 +531,14 @@ ul#adminmenu > li.current > a.current:after { color: variables.$menu-text; } +#wpadminbar #wp-admin-bar-user-info a:hover .display-name { + color: variables.$menu-submenu-focus-text; +} + #wpadminbar #wp-admin-bar-user-info .username { color: variables.$menu-submenu-text; } -#wpadminbar #wp-admin-bar-user-info a:hover .display-name, -#wpadminbar #wp-admin-bar-user-info a:focus .display-name, -#wpadminbar #wp-admin-bar-user-info a:hover .username, -#wpadminbar #wp-admin-bar-user-info a:focus .username { - color: variables.$menu-submenu-focus-text; -} /* Pointers */ diff --git a/src/wp-content/themes/twentytwenty/inc/custom-css.php b/src/wp-content/themes/twentytwenty/inc/custom-css.php index 25f8525d8511a..50af2e6e72c0e 100644 --- a/src/wp-content/themes/twentytwenty/inc/custom-css.php +++ b/src/wp-content/themes/twentytwenty/inc/custom-css.php @@ -23,15 +23,11 @@ * @return string Generated CSS. */ function twentytwenty_generate_css( $selector, $style, $value, $prefix = '', $suffix = '', $display = true ) { - - $return = ''; - /* * Bail early if we have no $selector elements or properties and $value. */ if ( ! $value || ! $selector ) { - - return; + return ''; } $return = sprintf( '%s { %s: %s; }', $selector, $style, $prefix . $value . $suffix ); diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index f08c934864916..1e6d60f637592 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -109,7 +109,6 @@ function get_block_categories( $post_or_block_editor_context ) { * @since 5.8.0 * * @param WP_Block_Editor_Context $block_editor_context The current block editor context. - * * @return bool|string[] Array of block type slugs, or boolean to enable/disable all. */ function get_allowed_block_types( $block_editor_context ) { @@ -484,7 +483,6 @@ function wp_get_post_content_block_attributes() { * * @param array $custom_settings Custom settings to use with the given editor type. * @param WP_Block_Editor_Context $block_editor_context The current block editor context. - * * @return array The contextualized block editor settings. */ function get_block_editor_settings( array $custom_settings, $block_editor_context ) { diff --git a/src/wp-includes/block-supports/aria-label.php b/src/wp-includes/block-supports/aria-label.php index b51cd3499e0d4..c44333221101a 100644 --- a/src/wp-includes/block-supports/aria-label.php +++ b/src/wp-includes/block-supports/aria-label.php @@ -40,7 +40,6 @@ function wp_register_aria_label_support( $block_type ) { * * @param WP_Block_Type $block_type Block Type. * @param array $block_attributes Block attributes. - * * @return array Block aria-label. */ function wp_apply_aria_label_support( $block_type, $block_attributes ) { diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index 5daccd629c055..a808ea3d74ecf 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -13,7 +13,6 @@ * @since 6.6.0 * * @param string $class_string CSS class string to look for a variation in. - * * @return array|null The block style variation name if found. */ function wp_get_block_style_variation_name_from_class( $class_string ) { @@ -75,7 +74,6 @@ function wp_resolve_block_style_variation_ref_values( &$variation_data, $theme_j * @access private * * @param array $parsed_block The parsed block. - * * @return array The parsed block with block style variation classname added. */ function wp_render_block_style_variation_support_styles( $parsed_block ) { @@ -215,7 +213,6 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { * * @param string $block_content Rendered block content. * @param array $block Block object. - * * @return string Filtered block content. */ function wp_render_block_style_variation_class_name( $block_content, $block ) { diff --git a/src/wp-includes/block-supports/colors.php b/src/wp-includes/block-supports/colors.php index 18da234e55e37..330f32e352b36 100644 --- a/src/wp-includes/block-supports/colors.php +++ b/src/wp-includes/block-supports/colors.php @@ -78,7 +78,6 @@ function wp_register_colors_support( $block_type ) { * * @param WP_Block_Type $block_type Block type. * @param array $block_attributes Block attributes. - * * @return array Colors CSS classes and inline styles. */ function wp_apply_colors_support( $block_type, $block_attributes ) { diff --git a/src/wp-includes/block-supports/custom-classname.php b/src/wp-includes/block-supports/custom-classname.php index 1889918644fc9..3c1215723d30b 100644 --- a/src/wp-includes/block-supports/custom-classname.php +++ b/src/wp-includes/block-supports/custom-classname.php @@ -38,7 +38,6 @@ function wp_register_custom_classname_support( $block_type ) { * * @param WP_Block_Type $block_type Block Type. * @param array $block_attributes Block attributes. - * * @return array Block CSS classes and inline styles. */ function wp_apply_custom_classname_support( $block_type, $block_attributes ) { diff --git a/src/wp-includes/block-supports/settings.php b/src/wp-includes/block-supports/settings.php index 59c3cbd9a28de..2af6f865f76b3 100644 --- a/src/wp-includes/block-supports/settings.php +++ b/src/wp-includes/block-supports/settings.php @@ -71,7 +71,6 @@ function _wp_add_block_level_presets_class( $block_content, $block ) { * * @param string|null $pre_render The pre-rendered content. Default null. * @param array $block The block being rendered. - * * @return null */ function _wp_add_block_level_preset_styles( $pre_render, $block ) { diff --git a/src/wp-includes/block-supports/utils.php b/src/wp-includes/block-supports/utils.php index 7c5d7db48a150..c668586674de4 100644 --- a/src/wp-includes/block-supports/utils.php +++ b/src/wp-includes/block-supports/utils.php @@ -17,7 +17,6 @@ * @param WP_Block_Type $block_type Block type. * @param string $feature_set Name of block support feature set.. * @param string $feature Optional name of individual feature to check. - * * @return bool Whether to serialize block support styles & classes. */ function wp_should_skip_block_supports_serialization( $block_type, $feature_set, $feature = null ) { diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index a7d5d4aa1141d..2d23f8545e87f 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -31,7 +31,6 @@ * @since 5.9.0 * * @param string $theme_stylesheet The stylesheet. Default is to leverage the main theme root. - * * @return string[] { * Folder names used by block themes. * diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 85ad05cfc91ae..8c34b05d016ae 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -431,7 +431,6 @@ function _strip_template_file_suffix( $template_file ) { * @since 5.8.0 * * @param array $context Default context. - * * @return array Filtered context. */ function _block_template_render_without_post_block_context( $context ) { diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index f2df7226a6d78..4a048c80baf3e 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -623,7 +623,6 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { * @param array $attributes Block attributes. * @param string $content Block default content. * @param WP_Block $block Block instance. - * * @return string Returns the block content. */ $settings['render_callback'] = static function ( $attributes, $content, $block ) use ( $template_path ) { @@ -1929,7 +1928,6 @@ function traverse_and_serialize_block( $block, $pre_callback = null, $post_callb * @since 7.0.0 Adds metadata to attributes of single-pattern container blocks. * * @param array $blocks An array blocks. - * * @return array An array of blocks with patterns replaced by their content. */ function resolve_pattern_blocks( $blocks ) { @@ -2814,7 +2812,6 @@ function wp_migrate_old_typography_shape( $metadata ) { * * @param WP_Block $block Block instance. * @param int $page Current query's page. - * * @return array Returns the constructed WP_Query arguments. */ function build_query_vars_from_query_block( $block, $page ) { diff --git a/src/wp-includes/class-wp-block-type.php b/src/wp-includes/class-wp-block-type.php index 86f0ea21a2a3c..d06279879c1f8 100644 --- a/src/wp-includes/class-wp-block-type.php +++ b/src/wp-includes/class-wp-block-type.php @@ -357,7 +357,6 @@ public function __construct( $block_type, $args = array() ) { * @since 6.1.0 * * @param string $name Deprecated property name. - * * @return string|string[]|null The value read from the new property if the first item in the array provided, * null when value not found or when unknown property name provided. */ @@ -393,7 +392,6 @@ public function __get( $name ) { * @since 6.1.0 * * @param string $name Deprecated property name. - * * @return bool Returns true when for the new property the first item in the array exists, * or false otherwise. */ diff --git a/src/wp-includes/class-wp-term-query.php b/src/wp-includes/class-wp-term-query.php index a30d887aa56d1..909da85433a51 100644 --- a/src/wp-includes/class-wp-term-query.php +++ b/src/wp-includes/class-wp-term-query.php @@ -973,7 +973,6 @@ protected function parse_orderby( $orderby_raw ) { * * @param WP_Term[] $term_objects Array of term objects. * @param string $_fields Field to format. - * * @return WP_Term[]|int[]|string[] Array of terms / strings / ints depending on field requested. */ protected function format_terms( $term_objects, $_fields ) { @@ -1156,7 +1155,6 @@ protected function populate_terms( $terms ) { * * @param array $args WP_Term_Query arguments. * @param string $sql SQL statement. - * * @return string Cache key. */ protected function generate_cache_key( array $args, $sql ) { diff --git a/src/wp-includes/class-wp-textdomain-registry.php b/src/wp-includes/class-wp-textdomain-registry.php index bb2135e3650a2..c62e751b6e47b 100644 --- a/src/wp-includes/class-wp-textdomain-registry.php +++ b/src/wp-includes/class-wp-textdomain-registry.php @@ -87,7 +87,6 @@ public function init() { * * @param string $domain Text domain. * @param string $locale Locale. - * * @return string|false Languages directory path or false if there is none available. */ public function get( $domain, $locale ) { diff --git a/src/wp-includes/class-wp-theme-json-data.php b/src/wp-includes/class-wp-theme-json-data.php index 9a9e73ecffdbf..a1bf64b1d8232 100644 --- a/src/wp-includes/class-wp-theme-json-data.php +++ b/src/wp-includes/class-wp-theme-json-data.php @@ -50,7 +50,6 @@ public function __construct( $data = array( 'version' => WP_Theme_JSON::LATEST_S * @since 6.1.0 * * @param array $new_data Array following the theme.json specification. - * * @return WP_Theme_JSON_Data The own instance with access to the modified data. */ public function update_with( $new_data ) { diff --git a/src/wp-includes/class-wp-theme-json-schema.php b/src/wp-includes/class-wp-theme-json-schema.php index aaa2f968a6395..47c0f016ec3f3 100644 --- a/src/wp-includes/class-wp-theme-json-schema.php +++ b/src/wp-includes/class-wp-theme-json-schema.php @@ -73,7 +73,6 @@ public static function migrate( $theme_json, $origin = 'theme' ) { * @since 5.9.0 * * @param array $old Data to migrate. - * * @return array Data without the custom prefixes. */ private static function migrate_v1_to_v2( $old ) { @@ -169,7 +168,6 @@ private static function migrate_v2_to_v3( $old, $origin ) { * * @param array $settings Array to process. * @param array $paths_to_rename Paths to rename. - * * @return array The settings in the new format. */ private static function rename_paths( $settings, $paths_to_rename ) { diff --git a/src/wp-includes/class-wp-token-map.php b/src/wp-includes/class-wp-token-map.php index fc223b187f8c5..317a9830cba85 100644 --- a/src/wp-includes/class-wp-token-map.php +++ b/src/wp-includes/class-wp-token-map.php @@ -528,7 +528,6 @@ public function contains( string $word, string $case_sensitivity = 'case-sensiti * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. * @param int|null &$matched_token_byte_length Optional. Holds byte-length of found token matched, otherwise not set. Default null. * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. - * * @return string|null Mapped value of lookup key if found, otherwise `null`. */ public function read_token( string $text, int $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ): ?string { @@ -589,7 +588,6 @@ public function read_token( string $text, int $offset = 0, &$matched_token_byte_ * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. * @param int|null &$matched_token_byte_length Optional. Holds byte-length of found lookup key if matched, otherwise not set. Default null. * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. - * * @return string|null Mapped value of lookup key if found, otherwise `null`. */ private function read_small_token( string $text, int $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ): ?string { diff --git a/src/wp-includes/css/admin-bar.css b/src/wp-includes/css/admin-bar.css index e0411701a28e8..77e196525657a 100644 --- a/src/wp-includes/css/admin-bar.css +++ b/src/wp-includes/css/admin-bar.css @@ -77,28 +77,12 @@ html:lang(he-il) .rtl #wpadminbar * { box-shadow: none; } -#wpadminbar a:focus, -#wpadminbar .ab-item[tabindex="0"]:focus { - outline-offset: -2px; +#wpadminbar a:focus { + outline-offset: -1px; /* Only visible in Windows High Contrast mode */ outline: 2px solid transparent; } -#wpadminbar a:hover, -#wpadminbar a:focus, -#wpadminbar .ab-item[tabindex="0"]:hover, -#wpadminbar .ab-item[tabindex="0"]:focus { - box-shadow: inset 0 -4px 0 0 currentColor; - transition: box-shadow 0.1s linear; -} - -#wpadminbar .ab-submenu a:hover, -#wpadminbar .ab-submenu a:focus, -#wpadminbar .ab-submenu .ab-item[tabindex="0"]:hover, -#wpadminbar .ab-submenu .ab-item[tabindex="0"]:focus { - box-shadow: inset 4px 0 0 0 currentColor; -} - #wpadminbar { direction: ltr; color: #c3c4c7; @@ -239,11 +223,9 @@ html:lang(he-il) .rtl #wpadminbar * { color: #72aee6; } -#wpadminbar > #wp-toolbar li:hover span.ab-label, +#wpadminbar:not(.mobile) > #wp-toolbar li:hover span.ab-label, #wpadminbar > #wp-toolbar li.hover span.ab-label, -/* The adminbar menu may output either links or focusable div elements. */ -/* As such, we target the focus state without specifying the element type. */ -#wpadminbar > #wp-toolbar :focus span.ab-label { +#wpadminbar:not(.mobile) > #wp-toolbar a:focus span.ab-label { color: #72aee6; } @@ -312,8 +294,7 @@ html:lang(he-il) .rtl #wpadminbar * { #wpadminbar li.hover .ab-icon:before, #wpadminbar li.hover .ab-item:before, #wpadminbar li:hover #adminbarsearch:before, -#wpadminbar li #adminbarsearch.adminbar-focused:before, -#wpadminbar.mobile .quicklinks .ab-item:focus:before { +#wpadminbar li #adminbarsearch.adminbar-focused:before { color: #72aee6; } @@ -398,6 +379,11 @@ html:lang(he-il) .rtl #wpadminbar * { float: right; } +#wpadminbar ul li:last-child, +#wpadminbar ul li:last-child .ab-item { + box-shadow: none; +} + /** * Recovery Mode */ @@ -466,7 +452,7 @@ html:lang(he-il) .rtl #wpadminbar * { #wp-admin-bar-user-info .avatar { position: absolute; left: -72px; - top: 0; + top: 4px; width: 64px; height: 64px; border-radius: 50%; @@ -480,7 +466,7 @@ html:lang(he-il) .rtl #wpadminbar * { #wpadminbar #wp-admin-bar-user-info span { background: none; padding: 0; - line-height: 1.38461538; + height: 18px; } #wpadminbar #wp-admin-bar-user-info .display-name, @@ -489,6 +475,7 @@ html:lang(he-il) .rtl #wpadminbar * { } #wpadminbar #wp-admin-bar-user-info .username { + color: #a7aaad; font-size: 11px; } @@ -924,6 +911,7 @@ html:lang(he-il) .rtl #wpadminbar * { overflow: hidden; width: 52px; padding: 0; + color: #a7aaad; /* @todo not needed? this text is hidden */ position: relative; } @@ -1043,6 +1031,16 @@ html:lang(he-il) .rtl #wpadminbar * { height: auto; font-size: 16px; line-height: 1.5; + color: #f0f0f1; + } + + #wpadminbar #wp-admin-bar-user-info a { + padding-top: 4px; + } + + #wpadminbar #wp-admin-bar-user-info .username { + line-height: 0.8 !important; + margin-bottom: -2px; } /* Show only default top level items */ diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index 14f5c24aec914..3b78d1610fdad 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -6394,7 +6394,6 @@ function wp_enqueue_global_styles_custom_css() { * * @param array $block Block object. * @param string $variation Slug for the block style variation. - * * @return string The unique variation name. */ function wp_create_block_style_variation_instance_name( $block, $variation ) { diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index f5002a45de1e8..a60bb57df7178 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -5300,7 +5300,6 @@ function _wp_array_set( &$input_array, $path, $value = null ) { * @link https://github.com/lodash-php/lodash-php/blob/master/src/internal/unicodeWords.php * * @param string $input_string The string to kebab-case. - * * @return string kebab-cased-string. */ function _wp_to_kebab_case( $input_string ) { @@ -6359,11 +6358,20 @@ function iis7_supports_permalinks() { * * A return value of `1` means the file path contains directory traversal. * - * A return value of `2` means the file path contains a Windows drive path. + * A return value of `2` means the file path contains an absolute Windows path. + * This covers drive paths such as `C:/WINDOWS`, UNC network share paths such as + * `//server/share`, and the Windows device namespaces `//./` and `//?/`. * * A return value of `3` means the file is not in the allowed files list. * + * Note that absolute POSIX paths such as `/etc/passwd` are *not* rejected, and + * never have been. Callers that must reject them are responsible for their own + * check. The convention in core is to concatenate the validated value onto a + * trusted base directory and then confirm the result exists, rather than to + * treat this function as an absolute-path guard. + * * @since 1.2.0 + * @since 7.2.0 A return value of `2` also covers UNC and Windows device paths. * * @param string $file File path. * @param string[] $allowed_files Optional. Array of allowed files. Default empty array. @@ -6399,8 +6407,21 @@ function validate_file( $file, $allowed_files = array() ) { return 3; } - // Absolute Windows drive paths are not allowed: - if ( ':' === substr( $file, 1, 1 ) ) { + /* + * Absolute Windows paths are not allowed. + * + * The drive-letter test predates validate_file() itself, arriving from + * b2/cafelog by way of a long series of moves. It only ever matched the + * `X:` form, which left UNC and device paths accepted: wp_normalize_path() + * above has already folded backslashes to forward slashes, and it + * deliberately preserves a leading `//` for network shares, so those + * paths arrive with no colon in the second byte. + * + * Anchoring the second test to the start of the string is what keeps + * stream wrappers working. A registered wrapper keeps its `://` through + * wp_normalize_path(), placing those slashes past the second byte. + */ + if ( ':' === substr( $file, 1, 1 ) || str_starts_with( $file, '//' ) ) { return 2; } @@ -8699,7 +8720,6 @@ function wp_get_default_update_php_url() { * @param string $before Markup to output before the annotation. Default `

`. * @param string $after Markup to output after the annotation. Default `

`. * @param bool $display Whether to echo or return the markup. Default `true` for echo. - * * @return string|null Update PHP page annotation if available and $display is false, null otherwise. */ function wp_update_php_annotation( $before = '

', $after = '

', $display = true ) { diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index 4b08301f9c7db..0534d8b0dee58 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -498,7 +498,6 @@ function wp_get_theme_data_template_parts() { * @param WP_Block_Type $block_type The block's type. * @param string|array $target The desired selector's target, `root` or array path. * @param boolean $fallback Whether to fall back to broader selector. - * * @return string|null CSS selector or `null` if no selector available. */ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = false ) { diff --git a/src/wp-includes/html-api/class-wp-html-doctype-info.php b/src/wp-includes/html-api/class-wp-html-doctype-info.php index f448aabde3ad9..ce6004a940cf3 100644 --- a/src/wp-includes/html-api/class-wp-html-doctype-info.php +++ b/src/wp-includes/html-api/class-wp-html-doctype-info.php @@ -410,7 +410,6 @@ private function __construct( * @since 6.7.0 * * @param string $doctype_html The complete raw DOCTYPE HTML string, e.g. ``. - * * @return WP_HTML_Doctype_Info|null A WP_HTML_Doctype_Info instance will be returned if the * provided DOCTYPE HTML is a valid DOCTYPE. Otherwise, null. */ diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index b4c590ef5141a..9e18d0e9b7121 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -1267,7 +1267,6 @@ public function get_current_depth(): int { * @since 6.7.0 * * @param string $html Input HTML to normalize. - * * @return string|null Normalized output, or `null` if unable to normalize. */ public static function normalize( string $html ): ?string { diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index dd99a8324b5c6..88487fb068d11 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -3233,7 +3233,6 @@ public function get_qualified_tag_name(): ?string { * @since 6.7.0 * * @param string $attribute_name Which attribute to adjust. - * * @return string|null */ public function get_qualified_attribute_name( $attribute_name ): ?string { diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 9cd0f50f22487..9394b75989912 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -1390,7 +1390,6 @@ function _wp_kses_split_callback( $matches ) { * or a context name such as 'post'. See wp_kses_allowed_html() * for the list of accepted context names. * @param string[] $allowed_protocols Array of allowed URL protocols. - * * @return string Fixed HTML element */ function wp_kses_split2( $content, $allowed_html, $allowed_protocols ) { diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 2f27c2180037a..0f9dd0d4016a0 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -1735,7 +1735,7 @@ function wp_get_l10n_php_file_data( $php_file ) { * @type bool $explicit_option_en_us Whether the English (United States) option uses an explicit value of en_US * instead of an empty value. Default false. * } - * @return string HTML dropdown list of languages. + * @return string|void HTML dropdown list of languages. */ function wp_dropdown_languages( $args = array() ) { @@ -1993,7 +1993,6 @@ function is_locale_switched() { * @param string|string[]|array[]|object $i18n_schema I18n schema for the setting. * @param string|string[]|array[] $settings Value for the settings. * @param string $textdomain Textdomain to use with translations. - * * @return string|string[]|array[] Translated settings. */ function translate_settings_using_i18n_schema( $i18n_schema, $settings, $textdomain ) { diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index d54cee18c5b39..892716b5d69e0 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -3458,7 +3458,6 @@ function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::C * @since 5.7.0 * * @param WP_Error $error WP_Error instance. - * * @return WP_REST_Response List of associative arrays with code and message keys. */ function rest_convert_error_to_response( $error ) { diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php index c331d73363541..c7ffb5b7677d0 100644 --- a/src/wp-includes/rest-api/class-wp-rest-server.php +++ b/src/wp-includes/rest-api/class-wp-rest-server.php @@ -245,7 +245,6 @@ protected function json_error( $code, $message, $status = null ) { * @since 6.1.0 * * @param \WP_REST_Request $request The current request object. - * * @return int The JSON encode options. */ protected function get_json_encode_options( WP_REST_Request $request ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index e8e81c3617fc4..ed8195cbb91b7 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -626,7 +626,6 @@ public function get_theme_items_permissions_check( $request ) { * @since 6.6.0 Added custom relative theme file URIs to `_links` for each item. * * @param WP_REST_Request $request The request instance. - * * @return WP_REST_Response|WP_Error */ public function get_theme_items( $request ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php index dd72bc1c15210..396d657fd2546 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php @@ -337,7 +337,6 @@ public function delete_item( $request ) { * @since 5.9.0 * * @param WP_REST_Request $request Request object. - * * @return object|WP_Error */ protected function prepare_item_for_database( $request ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php index 1d07c27251c3b..a36fe356e9070 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php @@ -604,7 +604,6 @@ private function get_widget_form( $widget_object, $instance ) { * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. - * * @return array An array with rendered Legacy Widget HTML. */ public function render( $request ) { @@ -623,7 +622,6 @@ public function render( $request ) { * * @param string $id_base The id base of the requested widget. * @param array $instance The widget instance attributes. - * * @return string Rendered Legacy Widget block preview. */ private function render_legacy_widget_preview_iframe( $id_base, $instance ) { diff --git a/src/wp-includes/rewrite.php b/src/wp-includes/rewrite.php index 0a6b84b75e534..976d2a014b9db 100644 --- a/src/wp-includes/rewrite.php +++ b/src/wp-includes/rewrite.php @@ -502,7 +502,8 @@ function url_to_postid( $url ) { $url_host = parse_url( $url, PHP_URL_HOST ); if ( is_string( $url_host ) ) { - $url_host = str_replace( 'www.', '', $url_host ); + // Only a leading 'www.' is optional. Removing it anywhere else would match a different host. + $url_host = preg_replace( '|^www\.|', '', $url_host ); } else { $url_host = ''; } @@ -510,7 +511,7 @@ function url_to_postid( $url ) { $home_url_host = parse_url( home_url(), PHP_URL_HOST ); if ( is_string( $home_url_host ) ) { - $home_url_host = str_replace( 'www.', '', $home_url_host ); + $home_url_host = preg_replace( '|^www\.|', '', $home_url_host ); } else { $home_url_host = ''; } diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index a364439f0abbb..7d5ba24e5617d 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -3212,7 +3212,6 @@ static function ( $a, $b ) { * * @param string $css The CSS to make URLs relative to the WordPress installation. * @param string $stylesheet_url The URL to the stylesheet. - * * @return string The CSS with URLs made relative to the WordPress installation. */ function _wp_normalize_relative_css_links( $css, $stylesheet_url ) { diff --git a/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php b/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php index 7436f5ff0083b..95bd10c5e4947 100644 --- a/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php +++ b/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php @@ -58,7 +58,6 @@ public function get_object_subtypes() { * * @param int $page_num Page of results. * @param string $object_subtype Optional. Post type name. Default empty. - * * @return array[] Array of URL information for a sitemap. */ public function get_url_list( $page_num, $object_subtype = '' ) { diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php index 07869ae61d0ff..3262ebde134f2 100644 --- a/src/wp-includes/theme.php +++ b/src/wp-includes/theme.php @@ -4369,7 +4369,6 @@ function wp_is_block_theme() { * @since 6.1.0 * * @param string $element The name of the element. - * * @return string The name of the class. */ function wp_theme_get_element_class_name( $element ) { diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 539240a42e9d2..1a1ce8359ccbe 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -853,7 +853,6 @@ function delete_user_option( $user_id, $option_name, $is_global = false ) { * @since 6.7.0 * * @param int $user_id User ID. - * * @return WP_User|false WP_User object on success, false on failure. */ function get_user( $user_id ) { diff --git a/tests/phpstan/baselines/return.empty.neon b/tests/phpstan/baselines/return.empty.neon index 5abf2badb5636..e69de29bb2d1d 100644 --- a/tests/phpstan/baselines/return.empty.neon +++ b/tests/phpstan/baselines/return.empty.neon @@ -1,30 +0,0 @@ -# PHPStan baseline for the `return.empty` errors in WordPress core. -# -# https://phpstan.org/error-identifiers/return.empty -# -# Each entry is scoped to a single file and carries an exact occurrence count, -# so that a new instance is reported as a new error rather than being absorbed -# silently. Fixing an occurrence therefore means decrementing or removing its -# entry here as part of the same change. -# -# The goal is to empty this file and delete it, along with the `includes` entry -# for it in phpstan.neon.dist. -# -# Generated by `composer phpstan:baselines`. Do not edit by hand; regenerate with -# -# composer phpstan:baselines -- --identifier=return.empty -# -# which reruns the analysis with this file suppressed so the errors surface again. - -parameters: - ignoreErrors: - - - message: '#^Function twentytwenty_generate_css\(\) should return string but empty return statement found\.$#' - identifier: return.empty - count: 1 - path: ../../../src/wp-content/themes/twentytwenty/inc/custom-css.php - - - message: '#^Function wp_dropdown_languages\(\) should return string but empty return statement found\.$#' - identifier: return.empty - count: 1 - path: ../../../src/wp-includes/l10n.php diff --git a/tests/phpunit/tests/customize/widgets.php b/tests/phpunit/tests/customize/widgets.php index 5b64f85ce3392..c2ed6dbc661e2 100644 --- a/tests/phpunit/tests/customize/widgets.php +++ b/tests/phpunit/tests/customize/widgets.php @@ -397,8 +397,8 @@ public function test_get_setting_args() { foreach ( $default_args as $key => $default_value ) { $this->assertSame( $default_value, $args[ $key ] ); } - $this->assertTrue( is_callable( $args['sanitize_callback'] ), 'sanitize_callback is callable' ); - $this->asserttrue( is_callable( $args['sanitize_js_callback'] ), 'sanitize_js_callback is callable' ); + $this->assertIsCallable( $args['sanitize_callback'], 'sanitize_callback is callable' ); + $this->assertIsCallable( $args['sanitize_js_callback'], 'sanitize_js_callback is callable' ); $this->assertSame( 'WIDGET_FOO[2]', $args['uppercase_id_set_by_filter'] ); $default_args = array( @@ -411,8 +411,8 @@ public function test_get_setting_args() { foreach ( $default_args as $key => $default_value ) { $this->assertSame( $default_value, $args[ $key ] ); } - $this->assertTrue( is_callable( $args['sanitize_callback'] ), 'sanitize_callback is callable' ); - $this->asserttrue( is_callable( $args['sanitize_js_callback'] ), 'sanitize_js_callback is callable' ); + $this->assertIsCallable( $args['sanitize_callback'], 'sanitize_callback is callable' ); + $this->assertIsCallable( $args['sanitize_js_callback'], 'sanitize_js_callback is callable' ); remove_theme_support( 'customize-selective-refresh-widgets' ); $args = $this->manager->widgets->get_setting_args( 'widget_search[2]' ); @@ -443,8 +443,8 @@ public function test_get_setting_args() { foreach ( $default_args as $key => $default_value ) { $this->assertSame( $default_value, $args[ $key ] ); } - $this->assertTrue( is_callable( $args['sanitize_callback'] ), 'sanitize_callback is callable' ); - $this->asserttrue( is_callable( $args['sanitize_js_callback'] ), 'sanitize_js_callback is callable' ); + $this->assertIsCallable( $args['sanitize_callback'], 'sanitize_callback is callable' ); + $this->assertIsCallable( $args['sanitize_js_callback'], 'sanitize_js_callback is callable' ); $this->assertSame( 'SIDEBARS_WIDGETS[SIDEBAR-1]', $args['uppercase_id_set_by_filter'] ); $override_args = array( diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 19c721aeaa46a..3c709f3ea7090 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -1984,6 +1984,121 @@ public function data_validate_file() { 0, ), + /* + * Windows UNC and device paths. + * + * wp_normalize_path() folds backslashes to forward slashes but + * deliberately preserves a leading '//' for network shares, so + * these arrive with no colon at offset 1 and must be matched on + * the '//' prefix instead. + */ + array( + '//system07/C$/', + array(), + 2, + ), + array( + '//Server2/Share/Test/Foo.txt', + array(), + 2, + ), + array( + '//127.0.0.1/c$/temp/test-file.txt', + array(), + 2, + ), + array( + '//./c:/temp/test-file.txt', + array(), + 2, + ), + array( + '//?/c:/temp/test-file.txt', + array(), + 2, + ), + array( + '//./UNC/LOCALHOST/c$/temp/test-file.txt', + array(), + 2, + ), + // The backslash form a Windows caller actually supplies. + array( + '\\\\system07\\C$\\', + array(), + 2, + ), + // Shortest matching input. + array( + '//', + array(), + 2, + ), + // A UNC path is rejected even when explicitly allowed, matching + // the precedence the drive-letter check already has. + array( + '//system07/C$/foo.php', + array( '//system07/C$/foo.php' ), + 2, + ), + + /* + * Absolute POSIX paths are NOT rejected. validate_file() has never + * screened them and does not begin to here. Callers needing that + * must check separately; core's convention is to concatenate onto + * a trusted base directory and stat the result. + */ + array( + '/etc/passwd', + array(), + 0, + ), + array( + '/Server2/Share/Test/Foo.txt', + array(), + 0, + ), + array( + '/', + array(), + 0, + ), + /* + * A deliberate exception. POSIX.1 leaves a pathname beginning with + * two successive slashes implementation-defined, and Linux resolves + * it as a single slash, so this is a valid POSIX path. It is still + * rejected: after normalization it cannot be told apart from a UNC + * path, and wp_normalize_path() already preserves a leading '//' on + * the assumption that it denotes a network share. + */ + array( + '//home/user/file.php', + array(), + 2, + ), + + /* + * Stream wrappers stay allowed via two different mechanisms. + * A registered wrapper keeps its '://' through wp_normalize_path()'s + * scheme split; an unregistered one has its '//' collapsed. Neither + * ends up with a leading '//'. + */ + array( + 'php://memory', + array(), + 0, + ), + array( + 'file:///tmp/test-file.txt', + array(), + 0, + ), + array( + 'myapp://foo/bar', + array(), + 0, + ), + // Disallowed files: array( 'foo.ext', diff --git a/tests/phpunit/tests/multisite/wpCacheSwitchToBlogFallback.php b/tests/phpunit/tests/multisite/wpCacheSwitchToBlogFallback.php index 35083088017b0..ffc3e1f263f6d 100644 --- a/tests/phpunit/tests/multisite/wpCacheSwitchToBlogFallback.php +++ b/tests/phpunit/tests/multisite/wpCacheSwitchToBlogFallback.php @@ -121,8 +121,8 @@ public function test_wp_cache_switch_to_blog_function_exists() { $this->assertTrue( function_exists( 'wp_cache_switch_to_blog_fallback' ), 'wp_cache_switch_to_blog_fallback() should always exist' ); // Both should be callable. - $this->assertTrue( is_callable( 'wp_cache_switch_to_blog' ) ); - $this->assertTrue( is_callable( 'wp_cache_switch_to_blog_fallback' ) ); + $this->assertIsCallable( 'wp_cache_switch_to_blog' ); + $this->assertIsCallable( 'wp_cache_switch_to_blog_fallback' ); } /** diff --git a/tests/phpunit/tests/rewrite.php b/tests/phpunit/tests/rewrite.php index 24c7e4e1459fc..085998e4b7eec 100644 --- a/tests/phpunit/tests/rewrite.php +++ b/tests/phpunit/tests/rewrite.php @@ -262,6 +262,48 @@ public function test_url_to_postid_url_has_only_path() { $this->assertSame( 0, url_to_postid( '/example/' ) ); } + /** + * Only a leading 'www.' is optional when comparing the URL's host to the site's. + * + * A 'www.' elsewhere in the host belongs to a different domain, which an attacker + * can register: stripping it everywhere makes 'exwww.ample.com' match 'example.com'. + * + * @ticket 65016 + * + * @covers ::url_to_postid + * + * @dataProvider data_url_to_postid_host_matching + * + * @param string $host Host of the URL to resolve. + * @param bool $is_local Whether the host should be treated as this site. + */ + public function test_url_to_postid_matches_www_prefix_only( $host, $is_local ) { + update_option( 'home', 'https://example.com' ); + update_option( 'siteurl', 'https://example.com' ); + + $post_id = self::factory()->post->create(); + + $expected = $is_local ? $post_id : 0; + + $this->assertSame( $expected, url_to_postid( "https://$host/?p=$post_id" ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_url_to_postid_host_matching() { + return array( + 'the site host' => array( 'example.com', true ), + 'the site host with www' => array( 'www.example.com', true ), + 'www inside the domain' => array( 'exwww.ample.com', false ), + 'www inside the TLD' => array( 'example.cwww.om', false ), + 'an unrelated host' => array( 'evil.com', false ), + 'the site host as subdomain' => array( 'example.com.evil.com', false ), + ); + } + /** * @covers ::url_to_postid */ diff --git a/tests/qunit/playwright.config.js b/tests/qunit/playwright.config.js index fc6651e0917ac..9e49301727a6a 100644 --- a/tests/qunit/playwright.config.js +++ b/tests/qunit/playwright.config.js @@ -12,7 +12,7 @@ module.exports = defineConfig( { workers: 1, use: { headless: true, - /* This avoids the need to run `npx playwright install` in CI. */ + /* The system Chrome channel avoids a browser download in CI. */ channel: process.env.CI ? 'chrome' : undefined, }, reporter: process.env.CI ? 'github' : 'list', diff --git a/tools/local-env/default.template b/tools/local-env/default.template index 995913fb453a2..4d8dec3c131fc 100644 --- a/tools/local-env/default.template +++ b/tools/local-env/default.template @@ -15,6 +15,21 @@ server { absolute_redirect off; + gzip ${LOCAL_NGINX_COMPRESSION}; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 5; + gzip_min_length 256; + gzip_types + application/javascript + text/javascript + application/json + application/xml + application/rss+xml + text/css + text/plain + image/svg+xml; + if (!-e $request_filename) { rewrite /wp-admin$ $scheme://$host$request_uri/ permanent; rewrite ^(/[^/]+)?(/wp-.*) $2 last;