diff --git a/.github/workflows/commit-built-file-changes.yml b/.github/workflows/commit-built-file-changes.yml index f93cd4bd662ec..b6ba9935ba675 100644 --- a/.github/workflows/commit-built-file-changes.yml +++ b/.github/workflows/commit-built-file-changes.yml @@ -131,11 +131,12 @@ jobs: path: 'pr-repo' show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} token: ${{ env.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 + run: git apply "$GITHUB_WORKSPACE/changes.diff" - name: Display changes to versioned files if: ${{ steps.artifact-check.outputs.exists == 'true' }} @@ -149,7 +150,7 @@ jobs: GH_APP_ID: ${{ secrets.GH_PR_BUILT_FILES_APP_ID }} run: | git config user.name "wordpress-develop-pr-bot[bot]" - git config user.email ${{ env.GH_APP_ID }}+wordpress-develop-pr-bot[bot]@users.noreply.github.com + 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' }} diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index b397a2241947e..4375091546dd7 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -53,7 +53,7 @@ permissions: {} env: LOCAL_DIR: build - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + PUPPETEER_SKIP_DOWNLOAD: true jobs: # Runs the end-to-end test suite. diff --git a/.github/workflows/install-testing.yml b/.github/workflows/install-testing.yml index f15d6e4830268..8da6a84f1caeb 100644 --- a/.github/workflows/install-testing.yml +++ b/.github/workflows/install-testing.yml @@ -49,7 +49,6 @@ jobs: uses: ./.github/workflows/reusable-support-json-reader-v1.yml permissions: contents: read - secrets: inherit if: ${{ github.repository == 'WordPress/wordpress-develop' }} with: wp-version: ${{ inputs.wp-version }} @@ -118,7 +117,7 @@ jobs: steps: - name: Set up PHP ${{ matrix.php }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0 with: php-version: '${{ matrix.php }}' coverage: none diff --git a/.github/workflows/local-docker-environment.yml b/.github/workflows/local-docker-environment.yml index c9dbae312595a..d42bba623ec64 100644 --- a/.github/workflows/local-docker-environment.yml +++ b/.github/workflows/local-docker-environment.yml @@ -79,7 +79,6 @@ jobs: uses: ./.github/workflows/reusable-support-json-reader-v1.yml permissions: contents: read - secrets: inherit if: ${{ github.repository == 'WordPress/wordpress-develop' }} with: wp-version: ${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }} diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml index de36d5a505187..74dfc220c04a6 100644 --- a/.github/workflows/phpunit-tests.yml +++ b/.github/workflows/phpunit-tests.yml @@ -66,7 +66,9 @@ jobs: uses: ./.github/workflows/reusable-phpunit-tests-v3.yml permissions: contents: read - secrets: inherit + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + WPT_REPORT_API_KEY: ${{ secrets.WPT_REPORT_API_KEY }} if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: fail-fast: false @@ -143,7 +145,9 @@ jobs: uses: ./.github/workflows/reusable-phpunit-tests-v3.yml permissions: contents: read - secrets: inherit + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + WPT_REPORT_API_KEY: ${{ secrets.WPT_REPORT_API_KEY }} if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: fail-fast: false @@ -177,7 +181,7 @@ jobs: multisite: ${{ matrix.multisite }} memcached: ${{ matrix.memcached }} phpunit-config: ${{ matrix.multisite && 'tests/phpunit/multisite.xml' || 'phpunit.xml.dist' }} - report: ${{ false }} + report: false # # Creates PHPUnit test jobs to test MariaDB and MySQL innovation releases. @@ -195,7 +199,9 @@ jobs: uses: ./.github/workflows/reusable-phpunit-tests-v3.yml permissions: contents: read - secrets: inherit + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + WPT_REPORT_API_KEY: ${{ secrets.WPT_REPORT_API_KEY }} if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: fail-fast: false @@ -223,7 +229,7 @@ jobs: multisite: ${{ matrix.multisite }} memcached: ${{ matrix.memcached }} phpunit-config: ${{ matrix.multisite && 'tests/phpunit/multisite.xml' || 'phpunit.xml.dist' }} - report: ${{ false }} + report: false # # Runs the HTML API test group. @@ -238,7 +244,9 @@ jobs: uses: ./.github/workflows/reusable-phpunit-tests-v3.yml permissions: contents: read - secrets: inherit + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + WPT_REPORT_API_KEY: ${{ secrets.WPT_REPORT_API_KEY }} if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: fail-fast: false @@ -267,7 +275,9 @@ jobs: uses: ./.github/workflows/reusable-phpunit-tests-v3.yml permissions: contents: read - secrets: inherit + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + WPT_REPORT_API_KEY: ${{ secrets.WPT_REPORT_API_KEY }} if: ${{ ! startsWith( github.repository, 'WordPress/' ) && github.event_name == 'pull_request' }} strategy: fail-fast: false diff --git a/.github/workflows/reusable-check-built-files.yml b/.github/workflows/reusable-check-built-files.yml index 11d97639a30fc..290161c485324 100644 --- a/.github/workflows/reusable-check-built-files.yml +++ b/.github/workflows/reusable-check-built-files.yml @@ -40,6 +40,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + persist-credentials: false - name: Set up Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 @@ -56,7 +57,7 @@ jobs: # 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 # v4.0.0 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: custom-cache-suffix: ${{ steps.get-date.outputs.date }} diff --git a/.github/workflows/reusable-cleanup-pull-requests.yml b/.github/workflows/reusable-cleanup-pull-requests.yml index 9dae63cb213d3..cdce56001d16b 100644 --- a/.github/workflows/reusable-cleanup-pull-requests.yml +++ b/.github/workflows/reusable-cleanup-pull-requests.yml @@ -19,7 +19,7 @@ jobs: # - Parse fixed ticket numbers from the commit message. # - Parse the SVN revision from the commit message. # - Searches for pull requests referencing any fixed tickets. - # - Leaves a comment on each PR before closing. +# - Comments on pull requests referencing any fixed tickets before closing. close-prs: name: Find and close PRs runs-on: ubuntu-24.04 @@ -43,13 +43,17 @@ jobs: COMMIT_MESSAGE="$(echo "$COMMIT_MSG_RAW" | sed -n '$p')" echo "svn_revision_number=$(echo "$COMMIT_MESSAGE" | sed -n 's/.*git-svn-id: https:\/\/develop.svn.wordpress.org\/[^@]*@\([0-9]*\) .*/\1/p')" >> "$GITHUB_OUTPUT" - - name: Find pull requests - id: linked-prs + - name: Find, comment on, and close pull requests if: ${{ steps.trac-tickets.outputs.fixed_list != '' && steps.git-svn-id.outputs.svn_revision_number != '' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + FIXED_LIST: ${{ steps.trac-tickets.outputs.fixed_list }} + SVN_REVISION_NUMBER: ${{ steps.git-svn-id.outputs.svn_revision_number }} with: script: | - const fixedList = "${{ steps.trac-tickets.outputs.fixed_list }}".split(' ').filter(Boolean); + const fixedList = process.env.FIXED_LIST.split(' ').filter(Boolean); + const svnRevisionNumber = process.env.SVN_REVISION_NUMBER; + const githubSha = process.env.GITHUB_SHA; let prNumbers = []; for (const ticket of fixedList) { @@ -86,19 +90,10 @@ jobs: prNumbers.push(...matchingPRs); } - return prNumbers; - - - name: Comment and close pull requests - if: ${{ steps.trac-tickets.outputs.fixed_list != '' && steps.git-svn-id.outputs.svn_revision_number != '' }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const prNumbers = ${{ steps.linked-prs.outputs.result }}; - const commentBody = `A commit was made that fixes the Trac ticket referenced in the description of this pull request. - SVN changeset: [${{ steps.git-svn-id.outputs.svn_revision_number }}](https://core.trac.wordpress.org/changeset/${{ steps.git-svn-id.outputs.svn_revision_number }}) - GitHub commit: https://github.com/WordPress/wordpress-develop/commit/${{ github.sha }} + SVN changeset: [${svnRevisionNumber}](https://core.trac.wordpress.org/changeset/${svnRevisionNumber}) + GitHub commit: https://github.com/WordPress/wordpress-develop/commit/${githubSha} This PR will be closed, but please confirm the accuracy of this and reopen if there is more work to be done.`; diff --git a/.github/workflows/reusable-coding-standards-javascript.yml b/.github/workflows/reusable-coding-standards-javascript.yml index 5c9a0c1ec0d03..6d776aabf1e27 100644 --- a/.github/workflows/reusable-coding-standards-javascript.yml +++ b/.github/workflows/reusable-coding-standards-javascript.yml @@ -7,7 +7,7 @@ on: workflow_call: env: - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + PUPPETEER_SKIP_DOWNLOAD: true # Disable permissions for all available scopes by default. # Any needed permissions should be configured at the job level. diff --git a/.github/workflows/reusable-coding-standards-php.yml b/.github/workflows/reusable-coding-standards-php.yml index 1213ccb6baa6f..99683e0850d64 100644 --- a/.github/workflows/reusable-coding-standards-php.yml +++ b/.github/workflows/reusable-coding-standards-php.yml @@ -52,7 +52,7 @@ jobs: persist-credentials: false - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0 with: php-version: ${{ inputs.php-version }} coverage: none @@ -75,7 +75,7 @@ jobs: # 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 # v4.0.0 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: custom-cache-suffix: ${{ steps.get-date.outputs.date }} diff --git a/.github/workflows/reusable-performance-test-v2.yml b/.github/workflows/reusable-performance-test-v2.yml index f572060e26d63..691e79c39508a 100644 --- a/.github/workflows/reusable-performance-test-v2.yml +++ b/.github/workflows/reusable-performance-test-v2.yml @@ -49,7 +49,7 @@ on: required: false env: - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + PUPPETEER_SKIP_DOWNLOAD: true # Prevent wp-scripts from downloading extra Playwright browsers, # since Chromium will be installed in its dedicated step already. diff --git a/.github/workflows/reusable-performance.yml b/.github/workflows/reusable-performance.yml index 923b472f609c6..3ebe31ff8e38f 100644 --- a/.github/workflows/reusable-performance.yml +++ b/.github/workflows/reusable-performance.yml @@ -37,7 +37,7 @@ on: required: false env: - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + PUPPETEER_SKIP_DOWNLOAD: true # Prevent wp-scripts from downloading extra Playwright browsers, # since Chromium will be installed in its dedicated step already. diff --git a/.github/workflows/reusable-php-compatibility.yml b/.github/workflows/reusable-php-compatibility.yml index fee371fbdf7a0..a00c952bae6d1 100644 --- a/.github/workflows/reusable-php-compatibility.yml +++ b/.github/workflows/reusable-php-compatibility.yml @@ -46,7 +46,7 @@ jobs: persist-credentials: false - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0 with: php-version: ${{ inputs.php-version }} coverage: none @@ -71,7 +71,7 @@ jobs: # 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 # v4.0.0 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: custom-cache-suffix: ${{ steps.get-date.outputs.date }} diff --git a/.github/workflows/reusable-phpstan-static-analysis-v1.yml b/.github/workflows/reusable-phpstan-static-analysis-v1.yml index bbf1b78589a8c..d1ac9f9799792 100644 --- a/.github/workflows/reusable-phpstan-static-analysis-v1.yml +++ b/.github/workflows/reusable-phpstan-static-analysis-v1.yml @@ -52,7 +52,7 @@ jobs: cache: npm - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0 with: php-version: ${{ inputs.php-version }} coverage: none @@ -73,7 +73,7 @@ jobs: # 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 # v4.0.0 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: custom-cache-suffix: ${{ steps.get-date.outputs.date }} diff --git a/.github/workflows/reusable-phpunit-tests-v1.yml b/.github/workflows/reusable-phpunit-tests-v1.yml index bcb0451d7134b..b55e9f58fcf17 100644 --- a/.github/workflows/reusable-phpunit-tests-v1.yml +++ b/.github/workflows/reusable-phpunit-tests-v1.yml @@ -50,13 +50,13 @@ on: type: boolean default: false env: - COMPOSER_INSTALL: ${{ false }} + COMPOSER_INSTALL: false LOCAL_PHP: ${{ inputs.php }}-fpm LOCAL_PHPUNIT: ${{ inputs.phpunit && inputs.phpunit || inputs.php }}-fpm LOCAL_PHP_MEMCACHED: ${{ inputs.memcached }} PHPUNIT_CONFIG: ${{ inputs.phpunit-config }} PHPUNIT_SCRIPT: php - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + PUPPETEER_SKIP_DOWNLOAD: true SLOW_TESTS: 'external-http,media' # Disable permissions for all available scopes by default. diff --git a/.github/workflows/reusable-phpunit-tests-v2.yml b/.github/workflows/reusable-phpunit-tests-v2.yml index 4e7b6716ebef1..36d5927976505 100644 --- a/.github/workflows/reusable-phpunit-tests-v2.yml +++ b/.github/workflows/reusable-phpunit-tests-v2.yml @@ -58,7 +58,7 @@ env: LOCAL_PHP: ${{ inputs.php }}-fpm LOCAL_PHP_MEMCACHED: ${{ inputs.memcached }} PHPUNIT_CONFIG: ${{ inputs.phpunit-config }} - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + PUPPETEER_SKIP_DOWNLOAD: true # Controls which npm script to use for running PHPUnit tests. Options ar `php` and `php-composer`. PHPUNIT_SCRIPT: php SLOW_TESTS: 'external-http,media' diff --git a/.github/workflows/reusable-phpunit-tests-v3.yml b/.github/workflows/reusable-phpunit-tests-v3.yml index da0372f8538be..793fac8adfc4f 100644 --- a/.github/workflows/reusable-phpunit-tests-v3.yml +++ b/.github/workflows/reusable-phpunit-tests-v3.yml @@ -89,7 +89,7 @@ env: LOCAL_PHP_MEMCACHED: ${{ inputs.memcached }} LOCAL_WP_TESTS_DOMAIN: ${{ inputs.tests-domain }} PHPUNIT_CONFIG: ${{ inputs.phpunit-config }} - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + PUPPETEER_SKIP_DOWNLOAD: true # Disable permissions for all available scopes by default. # Any needed permissions should be configured at the job level. @@ -150,7 +150,7 @@ jobs: # dependency versions are installed and cached. ## - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0 with: php-version: '${{ inputs.php }}' coverage: none @@ -158,7 +158,7 @@ jobs: # 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 # v4.0.0 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") diff --git a/.github/workflows/reusable-test-core-build-process.yml b/.github/workflows/reusable-test-core-build-process.yml index fbb6a08b15820..b5c40eb040e64 100644 --- a/.github/workflows/reusable-test-core-build-process.yml +++ b/.github/workflows/reusable-test-core-build-process.yml @@ -38,7 +38,7 @@ on: default: false env: - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + PUPPETEER_SKIP_DOWNLOAD: true NODE_OPTIONS: --max-old-space-size=4096 # Disable permissions for all available scopes by default. @@ -86,7 +86,7 @@ jobs: # passing a custom cache suffix ensures that the cache is flushed at least once per week. - name: Install Composer dependencies if: ${{ inputs.test-certificates }} - uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # v4.0.0 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: custom-cache-suffix: ${{ steps.get-date.outputs.date }} diff --git a/.github/workflows/reusable-test-gutenberg-build-process.yml b/.github/workflows/reusable-test-gutenberg-build-process.yml index 6fff07a842bf2..772b8ee577d7f 100644 --- a/.github/workflows/reusable-test-gutenberg-build-process.yml +++ b/.github/workflows/reusable-test-gutenberg-build-process.yml @@ -19,7 +19,7 @@ on: env: GUTENBERG_DIRECTORY: ${{ inputs.directory == 'build' && 'build' || 'src' }}/wp-content/plugins/gutenberg - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + PUPPETEER_SKIP_DOWNLOAD: true NODE_OPTIONS: '--max-old-space-size=8192' # Disable permissions for all available scopes by default. diff --git a/.github/workflows/reusable-test-local-docker-environment-v1.yml b/.github/workflows/reusable-test-local-docker-environment-v1.yml index 9aa0fb124a22e..370b88c6c0231 100644 --- a/.github/workflows/reusable-test-local-docker-environment-v1.yml +++ b/.github/workflows/reusable-test-local-docker-environment-v1.yml @@ -45,7 +45,7 @@ env: LOCAL_DB_VERSION: ${{ inputs.db-version }} LOCAL_PHP_MEMCACHED: ${{ inputs.memcached }} LOCAL_WP_TESTS_DOMAIN: ${{ inputs.tests-domain }} - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + PUPPETEER_SKIP_DOWNLOAD: true # Disable permissions for all available scopes by default. # Any needed permissions should be configured at the job level. @@ -105,7 +105,7 @@ jobs: # dependency versions are installed and cached. ## - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0 with: php-version: '${{ inputs.php }}' coverage: none @@ -113,7 +113,7 @@ jobs: # 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 # v4.0.0 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") diff --git a/.github/workflows/reusable-upgrade-testing.yml b/.github/workflows/reusable-upgrade-testing.yml index 60d5523a9e3b6..372b6ae0c3e60 100644 --- a/.github/workflows/reusable-upgrade-testing.yml +++ b/.github/workflows/reusable-upgrade-testing.yml @@ -78,7 +78,7 @@ jobs: steps: - name: Set up PHP ${{ inputs.php }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # 2.37.0 with: php-version: '${{ inputs.php }}' coverage: none diff --git a/.github/workflows/reusable-workflow-lint.yml b/.github/workflows/reusable-workflow-lint.yml index 3a538a8a99690..13fcde47f5731 100644 --- a/.github/workflows/reusable-workflow-lint.yml +++ b/.github/workflows/reusable-workflow-lint.yml @@ -7,12 +7,10 @@ permissions: {} jobs: # Runs the actionlint GitHub Action workflow file linter. # + # See https://github.com/rhysd/actionlint. + # # This helps guard against common mistakes including strong type checking for expressions (${{ }}), security checks, # `run:` script checking, glob syntax validation, and more. - # - # Performs the following steps: - # - Checks out the repository. - # - Runs actionlint. actionlint: name: Run actionlint runs-on: ubuntu-24.04 @@ -26,9 +24,46 @@ jobs: persist-credentials: false show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - # actionlint is static checker for GitHub Actions workflow files. - # See https://github.com/rhysd/actionlint. - name: Run actionlint - uses: docker://rhysd/actionlint@sha256:887a259a5a534f3c4f36cb02dca341673c6089431057242cdc931e9f133147e9 # v1.7.7 + uses: docker://rhysd/actionlint@sha256:5457037ba91acd225478edac3d4b32e45cf6c10291e0dabbfd2491c63129afe1 # v1.7.11 with: args: "-color -verbose" + + # Runs the Zizmor GitHub Action workflow file linter. + # + # See https://github.com/zizmorcore/zizmor + # + # This helps guard against supply chain attacks, unpinned dependencies, excessive permissions, + # dangerous triggers, credential leaks, and sophisticated security vulnerabilities. + # + # Performs the following steps: + # - Checks out the repository. + # - Installs and configures uv. + # - Runs a zizmor scan. + # - Uploads the SARIF file to GitHub. + zizmor: + name: Zizmor + runs-on: ubuntu-24.04 + permissions: + security-events: write + actions: read + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install the latest version of uv + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + + - name: Run zizmor + run: uvx zizmor@1.24.1 --persona=regular --format=sarif --strict-collection . > results.sarif + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0 + with: + sarif_file: results.sarif + category: zizmor diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index deb190eba9e9b..f2b0afce3256f 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -40,8 +40,8 @@ permissions: {} env: LOCAL_PHP_XDEBUG: true LOCAL_PHP_XDEBUG_MODE: 'coverage' - LOCAL_PHP_MEMCACHED: ${{ false }} - PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + LOCAL_PHP_MEMCACHED: false + PUPPETEER_SKIP_DOWNLOAD: true jobs: # diff --git a/Gruntfile.js b/Gruntfile.js index 5f9109fac3cb0..8863d030627b8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -397,6 +397,55 @@ module.exports = function(grunt) { 'suggest*' ], dest: WORKING_DIR + 'wp-includes/js/jquery/' + }, + { + [ WORKING_DIR + 'wp-includes/js/dist/vendor/lodash.js' ]: [ './node_modules/lodash/lodash.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/lodash.min.js' ]: [ './node_modules/lodash/lodash.min.js' ], + }, + { + [ WORKING_DIR + 'wp-includes/js/dist/vendor/moment.js' ]: [ './node_modules/moment/moment.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/moment.min.js' ]: [ './node_modules/moment/min/moment.min.js' ], + }, + { + [ WORKING_DIR + 'wp-includes/js/dist/vendor/regenerator-runtime.js' ]: [ './node_modules/regenerator-runtime/runtime.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/regenerator-runtime.min.js' ]: [ './node_modules/regenerator-runtime/runtime.js' ], + }, + // React libraries: react, react-dom + { + [ WORKING_DIR + 'wp-includes/js/dist/vendor/react.js' ]: [ './node_modules/react/umd/react.development.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/react.min.js' ]: [ './node_modules/react/umd/react.production.min.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/react-dom.js' ]: [ './node_modules/react-dom/umd/react-dom.development.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/react-dom.min.js' ]: [ './node_modules/react-dom/umd/react-dom.production.min.js' ], + }, + // Polyfills + { + // @wordpress/babel-preset-default + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill.js' ]: [ './node_modules/@wordpress/babel-preset-default/build/polyfill.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill.min.js' ]: [ './node_modules/@wordpress/babel-preset-default/build/polyfill.min.js' ], + // polyfill-library (DOMRect) + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-dom-rect.js' ]: [ './node_modules/polyfill-library/polyfills/__dist/DOMRect/raw.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-dom-rect.min.js' ]: [ './node_modules/polyfill-library/polyfills/__dist/DOMRect/min.js' ], + // element-closest + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-element-closest.js' ]: [ './node_modules/element-closest/browser.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-element-closest.min.js' ]: [ './node_modules/element-closest/browser.js' ], + // whatwg-fetch + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-fetch.js' ]: [ './node_modules/whatwg-fetch/dist/fetch.umd.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-fetch.min.js' ]: [ './node_modules/whatwg-fetch/dist/fetch.umd.js' ], + // formdata-polyfill + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-formdata.js' ]: [ './node_modules/formdata-polyfill/FormData.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-formdata.min.js' ]: [ './node_modules/formdata-polyfill/formdata.min.js' ], + // wicg-inert + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-inert.js' ]: [ './node_modules/wicg-inert/dist/inert.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-inert.min.js' ]: [ './node_modules/wicg-inert/dist/inert.min.js' ], + // polyfill-library (Node.prototype.contains) + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-node-contains.js' ]: [ './node_modules/polyfill-library/polyfills/__dist/Node.prototype.contains/raw.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-node-contains.min.js' ]: [ './node_modules/polyfill-library/polyfills/__dist/Node.prototype.contains/min.js' ], + // objectFitPolyfill + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-object-fit.js' ]: [ './node_modules/objectFitPolyfill/src/objectFitPolyfill.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-object-fit.min.js' ]: [ './node_modules/objectFitPolyfill/dist/objectFitPolyfill.min.js' ], + // core-js-url-browser + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-url.js' ]: [ './node_modules/core-js-url-browser/url.js' ], + [ WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-url.min.js' ]: [ './node_modules/core-js-url-browser/url.min.js' ], } ].concat( // Copy tinymce.js only when building to /src. @@ -1107,6 +1156,14 @@ module.exports = function(grunt) { src: WORKING_DIR + 'wp-includes/js/dist/vendor/moment.js', dest: WORKING_DIR + 'wp-includes/js/dist/vendor/moment.min.js' }, + 'regenerator-runtime': { + src: WORKING_DIR + 'wp-includes/js/dist/vendor/regenerator-runtime.js', + dest: WORKING_DIR + 'wp-includes/js/dist/vendor/regenerator-runtime.min.js' + }, + 'wp-polyfill-fetch': { + src: WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-fetch.js', + dest: WORKING_DIR + 'wp-includes/js/dist/vendor/wp-polyfill-fetch.min.js' + }, dynamic: { expand: true, cwd: WORKING_DIR, @@ -1635,18 +1692,6 @@ module.exports = function(grunt) { } ); } ); - grunt.registerTask( 'copy-vendor-scripts', 'Copies vendor scripts from node_modules to wp-includes/js/dist/vendor/.', function() { - const done = this.async(); - const buildDir = grunt.option( 'dev' ) ? 'src' : 'build'; - grunt.util.spawn( { - cmd: 'node', - args: [ 'tools/vendors/copy-vendors.js', `--build-dir=${ buildDir }` ], - opts: { stdio: 'inherit' } - }, function( error ) { - done( ! error ); - } ); - } ); - grunt.renameTask( 'watch', '_watch' ); grunt.registerTask( 'watch', function() { @@ -1675,6 +1720,8 @@ module.exports = function(grunt) { 'uglify:imgareaselect', 'uglify:jqueryform', 'uglify:moment', + 'uglify:regenerator-runtime', + 'uglify:wp-polyfill-fetch', 'qunit:compiled' ] ); @@ -1817,7 +1864,9 @@ module.exports = function(grunt) { 'uglify:jquery-ui', 'uglify:imgareaselect', 'uglify:jqueryform', - 'uglify:moment' + 'uglify:moment', + 'uglify:regenerator-runtime', + 'uglify:wp-polyfill-fetch' ] ); grunt.registerTask( 'build:codemirror', [ @@ -1837,7 +1886,6 @@ module.exports = function(grunt) { 'clean:js', 'build:webpack', 'copy:js', - 'copy-vendor-scripts', 'file_append', 'uglify:all', 'concat:tinymce', diff --git a/README.md b/README.md index 4c27999495f55..5201a5180c1da 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,29 @@ npm run test:php -- --filter npm run test:php -- --group ``` +#### To lint the workflow files + +GitHub Actions workflows operate in a privileged software supply chain environment, therefore all workflow files must adhere to a high degree of quality and security standards. + +All YAML workflow files within the `.github/workflows` directory are statically scanned when modified using [Actionlint](https://github.com/rhysd/actionlint) and [Zizmor](https://github.com/zizmorcore/zizmor). It's recommended that you install both of these tools locally using a package manager to run prior to submitting changes to workflow files. + +- [Actionlint installations instructions](https://github.com/rhysd/actionlint/blob/main/docs/install.md) +- [Zizmor installation instructions](https://docs.zizmor.sh/installation/) + +To run Actionlint: + +``` +actionlint +``` + +To run Zizmor for all workflow files (note the trailing period): + +``` +zizmor . +``` + +**Note:** A workflow run failure will not occur when issues are detected by Zizmor. Instead, the generated report is submitted to GitHub Code Scanning and surfaced through a status check. Some locally reported issues may be ignored based on the repository's configured Code Scanning settings. + #### Generating a code coverage report PHP code coverage reports are [generated daily](https://github.com/WordPress/wordpress-develop/actions/workflows/test-coverage.yml) and [submitted to Codecov.io](https://app.codecov.io/gh/WordPress/wordpress-develop). diff --git a/composer.json b/composer.json index ee5c5d0c0aa03..59df0a7af3770 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "ext-dom": "*" }, "require-dev": { - "composer/ca-bundle": "1.5.10", + "composer/ca-bundle": "1.5.11", "squizlabs/php_codesniffer": "3.13.5", "wp-coding-standards/wpcs": "~3.3.0", "phpcompatibility/phpcompatibility-wp": "~2.1.3", diff --git a/package.json b/package.json index 4d0b8110e0a9f..6f3cd1fbe6c3e 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,6 @@ "typecheck:php": "node ./tools/local-env/scripts/docker.js run --rm php composer phpstan", "gutenberg:copy": "node tools/gutenberg/copy.js", "gutenberg:verify": "node tools/gutenberg/utils.js", - "gutenberg:download": "node tools/gutenberg/download.js && grunt build:gutenberg --dev", - "vendor:copy": "node tools/vendors/copy-vendors.js" + "gutenberg:download": "node tools/gutenberg/download.js && grunt build:gutenberg --dev" } } diff --git a/src/wp-admin/menu.php b/src/wp-admin/menu.php index dc8c4271e9aad..57d94c75e26f2 100644 --- a/src/wp-admin/menu.php +++ b/src/wp-admin/menu.php @@ -72,7 +72,7 @@ $menu[10] = array( __( 'Media' ), 'upload_files', 'upload.php', '', 'menu-top menu-icon-media', 'menu-media', 'dashicons-admin-media' ); - $submenu['upload.php'][5] = array( __( 'Library' ), 'upload_files', 'upload.php' ); + $submenu['upload.php'][5] = array( _x( 'Library', 'media library menu item' ), 'upload_files', 'upload.php' ); $submenu['upload.php'][10] = array( __( 'Add Media File' ), 'upload_files', 'media-new.php' ); $submenu_index = 15; diff --git a/src/wp-includes/ai-client.php b/src/wp-includes/ai-client.php index 4fc20166fb8bb..b38c7b721416d 100644 --- a/src/wp-includes/ai-client.php +++ b/src/wp-includes/ai-client.php @@ -8,6 +8,8 @@ */ use WordPress\AiClient\AiClient; +use WordPress\AiClient\Messages\DTO\Message; +use WordPress\AiClient\Messages\DTO\MessagePart; /** * Returns whether AI features are supported in the current environment. @@ -55,6 +57,6 @@ function wp_supports_ai(): bool { * conversations. Default null. * @return WP_AI_Client_Prompt_Builder The prompt builder instance. */ -function wp_ai_client_prompt( $prompt = null ) { +function wp_ai_client_prompt( $prompt = null ): WP_AI_Client_Prompt_Builder { return new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry(), $prompt ); } diff --git a/src/wp-includes/ai-client/adapters/class-wp-ai-client-cache.php b/src/wp-includes/ai-client/adapters/class-wp-ai-client-cache.php index 18d85eee6c9e6..45504897485f7 100644 --- a/src/wp-includes/ai-client/adapters/class-wp-ai-client-cache.php +++ b/src/wp-includes/ai-client/adapters/class-wp-ai-client-cache.php @@ -104,7 +104,7 @@ public function clear(): bool { * @param mixed $default_value Default value to return for keys that do not exist. * @return array A list of key => value pairs. */ - public function getMultiple( $keys, $default_value = null ) { + public function getMultiple( $keys, $default_value = null ): array { /** * Keys array. * diff --git a/src/wp-includes/ai-client/adapters/class-wp-ai-client-http-client.php b/src/wp-includes/ai-client/adapters/class-wp-ai-client-http-client.php index f1827db0e437c..f6c6dea441d1c 100644 --- a/src/wp-includes/ai-client/adapters/class-wp-ai-client-http-client.php +++ b/src/wp-includes/ai-client/adapters/class-wp-ai-client-http-client.php @@ -32,17 +32,15 @@ class WP_AI_Client_HTTP_Client implements ClientInterface, ClientWithOptionsInte * Response factory instance. * * @since 7.0.0 - * @var ResponseFactoryInterface */ - private $response_factory; + private ResponseFactoryInterface $response_factory; /** * Stream factory instance. * * @since 7.0.0 - * @var StreamFactoryInterface */ - private $stream_factory; + private StreamFactoryInterface $stream_factory; /** * Constructor. diff --git a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php index d1f2271bd47d3..da7858dd76555 100644 --- a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php +++ b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php @@ -190,14 +190,29 @@ public function __construct( ProviderRegistry $registry, $prompt = null ) { $this->error = $this->exception_to_wp_error( $e ); } + $default_timeout = 30.0; + /** * Filters the default request timeout in seconds for AI Client HTTP requests. * * @since 7.0.0 * - * @param int $default_timeout The default timeout in seconds. + * @param float $default_timeout The default timeout in seconds. */ - $default_timeout = (int) apply_filters( 'wp_ai_client_default_request_timeout', 30 ); + $filtered_default_timeout = apply_filters( 'wp_ai_client_default_request_timeout', $default_timeout ); + if ( is_numeric( $filtered_default_timeout ) && (float) $filtered_default_timeout >= 0.0 ) { + $default_timeout = (float) $filtered_default_timeout; + } else { + _doing_it_wrong( + __METHOD__, + sprintf( + /* translators: %s: wp_ai_client_default_request_timeout */ + __( 'The %s filter must return a non-negative number.' ), + 'wp_ai_client_default_request_timeout' + ), + '7.0.0' + ); + } $this->builder->usingRequestOptions( RequestOptions::fromArray( diff --git a/src/wp-includes/block-supports/custom-css.php b/src/wp-includes/block-supports/custom-css.php index 9d5b13426f4ef..b931acf47bc02 100644 --- a/src/wp-includes/block-supports/custom-css.php +++ b/src/wp-includes/block-supports/custom-css.php @@ -124,6 +124,158 @@ function wp_register_custom_css_support( $block_type ) { } } +/** + * Strips `style.css` attributes from all blocks in post content. + * + * Uses {@see WP_Block_Parser::next_token()} to scan block tokens and surgically + * replace only the attribute JSON that changed — no parse_blocks() + + * serialize_blocks() round-trip needed. + * + * @since 7.0.0 + * @access private + * + * @param string $content Post content to filter, expected to be escaped with slashes. + * @return string Filtered post content with block custom CSS removed. + */ +function wp_strip_custom_css_from_blocks( $content ) { + if ( ! has_blocks( $content ) ) { + return $content; + } + + $unslashed = stripslashes( $content ); + + $parser = new WP_Block_Parser(); + $parser->document = $unslashed; + $parser->offset = 0; + $end = strlen( $unslashed ); + $replacements = array(); + + while ( $parser->offset < $end ) { + $next_token = $parser->next_token(); + + if ( 'no-more-tokens' === $next_token[0] ) { + break; + } + + list( $token_type, , $attrs, $start_offset, $token_length ) = $next_token; + + $parser->offset = $start_offset + $token_length; + + if ( 'block-opener' !== $token_type && 'void-block' !== $token_type ) { + continue; + } + + if ( ! isset( $attrs['style']['css'] ) ) { + continue; + } + + // Remove css and clean up empty style. + unset( $attrs['style']['css'] ); + if ( empty( $attrs['style'] ) ) { + unset( $attrs['style'] ); + } + + // Locate the JSON portion within the token. + $token_string = substr( $unslashed, $start_offset, $token_length ); + $json_rel_start = strcspn( $token_string, '{' ); + $json_rel_end = strrpos( $token_string, '}' ); + + $json_start = $start_offset + $json_rel_start; + $json_length = $json_rel_end - $json_rel_start + 1; + + // Re-encode attributes. If attrs is now empty, remove JSON and trailing space. + if ( empty( $attrs ) ) { + // Remove the trailing space after JSON. + $replacements[] = array( $json_start, $json_length + 1, '' ); + } else { + $replacements[] = array( $json_start, $json_length, serialize_block_attributes( $attrs ) ); + } + } + + if ( empty( $replacements ) ) { + return $content; + } + + // Build the result by splicing replacements into the original string. + $result = ''; + $was_at = 0; + + foreach ( $replacements as $replacement ) { + list( $offset, $length, $new_json ) = $replacement; + $result .= substr( $unslashed, $was_at, $offset - $was_at ) . $new_json; + $was_at = $offset + $length; + } + + if ( $was_at < $end ) { + $result .= substr( $unslashed, $was_at ); + } + + return addslashes( $result ); +} + +/** + * Adds the filters to strip custom CSS from block content on save. + * Priority of 8 to run before wp_filter_global_styles_post (priority 9) and wp_filter_post_kses (priority 10). + * + * @since 7.0.0 + * @access private + */ +function wp_custom_css_kses_init_filters() { + add_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks', 8 ); + add_filter( 'content_filtered_save_pre', 'wp_strip_custom_css_from_blocks', 8 ); +} + +/** + * Removes the filters that strip custom CSS from block content on save. + * Priority of 8 to run before wp_filter_global_styles_post (priority 9) and wp_filter_post_kses (priority 10). + * + * @since 7.0.0 + * @access private + */ +function wp_custom_css_remove_filters() { + remove_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks', 8 ); + remove_filter( 'content_filtered_save_pre', 'wp_strip_custom_css_from_blocks', 8 ); +} + +/** + * Registers the custom CSS content filters if the user does not have the edit_css capability. + * + * @since 7.0.0 + * @access private + */ +function wp_custom_css_kses_init() { + wp_custom_css_remove_filters(); + if ( ! current_user_can( 'edit_css' ) ) { + wp_custom_css_kses_init_filters(); + } +} + +/** + * Initializes custom CSS content filters when imported data should be filtered. + * + * Runs at priority 999 on {@see 'force_filtered_html_on_import'} to ensure it + * fires after general KSES initialization, independently of user capabilities. + * If the input of the filter is true it means we are in an import situation and should + * enable the custom CSS filters, independently of the user capabilities. + * + * @since 7.0.0 + * @access private + * + * @param mixed $arg Input argument of the filter. + * @return mixed Input argument of the filter. + */ +function wp_custom_css_force_filtered_html_on_import_filter( $arg ) { + if ( $arg ) { + wp_custom_css_kses_init_filters(); + } + return $arg; +} + +// Run before wp_filter_global_styles_post (priority 9) and wp_filter_post_kses (priority 10). +add_action( 'init', 'wp_custom_css_kses_init', 20 ); +add_action( 'set_current_user', 'wp_custom_css_kses_init' ); +add_filter( 'force_filtered_html_on_import', 'wp_custom_css_force_filtered_html_on_import_filter', 999 ); + // Register the block support. WP_Block_Supports::get_instance()->register( 'custom-css', diff --git a/src/wp-includes/certificates/ca-bundle.crt b/src/wp-includes/certificates/ca-bundle.crt index 65be891eea878..a78e1dd471fa9 100644 --- a/src/wp-includes/certificates/ca-bundle.crt +++ b/src/wp-includes/certificates/ca-bundle.crt @@ -1,7 +1,7 @@ ## ## Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Tue Dec 2 04:12:02 2025 GMT +## Certificate data from Mozilla last updated on: Wed Feb 11 18:26:30 2026 GMT ## ## Find updated versions here: https://curl.se/docs/caextract.html ## @@ -15,8 +15,8 @@ ## an Apache+mod_ssl webserver for SSL client authentication. ## Just configure this file as the SSLCACertificateFile. ## -## Conversion done with mk-ca-bundle.pl version 1.30. -## SHA256: a903b3cd05231e39332515ef7ebe37e697262f39515a52015c23c62805b73cd0 +## Conversion done with mk-ca-bundle.pl version 1.32. +## SHA256: 3b98d4e3ff57a326d9587c33633039c8c3a9cf0b55f7ca581d7598ff329eb1f3 ## @@ -3480,8 +3480,8 @@ SM49BAMDA2kAMGYCMQCpKjAd0MKfkFFRQD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxg ZzFDJe0CMQCSia7pXGKDYmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= -----END CERTIFICATE----- - OISTE Server Root RSA G1 -========================= +OISTE Server Root RSA G1 +======================== -----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBLMQswCQYDVQQG EwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJv @@ -3509,3 +3509,21 @@ msuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3J8tRd/iWkx7P8nd9H0aT olkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2wq1yVAb+axj5d9spLFKebXd7Yv0PTY6Y MjAwcRLWJTXjn/hvnLXrahut6hDTlhZyBiElxky8j3C7DOReIoMt0r7+hVu05L0= -----END CERTIFICATE----- + +e-Szigno TLS Root CA 2023 +========================= +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xFzAVBgNVBGEMDlZBVEhV +LTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBUTFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0 +MDAwMFoXDTM4MDcxNzE0MDAwMFowdTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYw +FAYDVQQKDA1NaWNyb3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZ +ZS1Temlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAGgP36J8 +PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFSAL/fjO1ZrTJlqwlZULUZ +wmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/vSzUaQ49CE0y5LBqcvjC2xN7cS53kpDzL +Ltmt3999Cd8ukv+ho2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E +FgQUWYQCYlpGePVd3I8KECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0Uw +CgYIKoZIzj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpty7Ve +7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZlC9p2x1L/Cx6AcCIw +wzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6uWWL +-----END CERTIFICATE----- diff --git a/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php b/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php index 3630b0bab403a..e758a6868aa42 100644 --- a/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php +++ b/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php @@ -188,19 +188,25 @@ public function test_constructor_sets_default_request_timeout() { $request_options = $this->get_wrapped_prompt_builder_property_value( $builder, 'requestOptions' ); $this->assertInstanceOf( RequestOptions::class, $request_options ); - $this->assertEquals( 30, $request_options->getTimeout() ); + $this->assertSame( 30.0, $request_options->getTimeout() ); } /** - * Test that the constructor allows overriding the default request timeout. + * Test that the constructor allows overriding the default request timeout with a valid value. * * @ticket 64591 + * @ticket 65094 + * + * @dataProvider data_valid_request_timeout_overrides + * + * @param mixed $input The timeout value returned by the filter. + * @param float $expected The expected timeout stored on the request options. */ - public function test_constructor_allows_overriding_request_timeout() { + public function test_constructor_allows_overriding_request_timeout_with_valid_timeout( $input, float $expected ) { add_filter( 'wp_ai_client_default_request_timeout', - static function () { - return 45; + static function () use ( $input ) { + return $input; } ); @@ -210,7 +216,63 @@ static function () { $request_options = $this->get_wrapped_prompt_builder_property_value( $builder, 'requestOptions' ); $this->assertInstanceOf( RequestOptions::class, $request_options ); - $this->assertEquals( 45, $request_options->getTimeout() ); + $this->assertSame( $expected, $request_options->getTimeout() ); + } + + /** + * Data provider for {@see self::test_constructor_allows_overriding_request_timeout_with_valid_timeout()}. + * + * @return array + */ + public function data_valid_request_timeout_overrides(): array { + return array( + 'float' => array( 45.5, 45.5 ), + 'integer' => array( 67, 67.0 ), + 'string' => array( '20', 20.0 ), + 'infinity' => array( INF, INF ), + 'zero' => array( 0.0, 0.0 ), + ); + } + + /** + * Test that the constructor disallows overriding the default request timeout with an invalid value. + * + * @ticket 65094 + * + * @dataProvider data_invalid_request_timeouts + * + * @expectedIncorrectUsage WP_AI_Client_Prompt_Builder::__construct + * + * @param mixed $timeout The invalid timeout value returned by the filter. + */ + public function test_constructor_disallows_overriding_with_invalid_request_timeout( $timeout ) { + add_filter( + 'wp_ai_client_default_request_timeout', + static function () use ( $timeout ) { + return $timeout; + } + ); + + $builder = new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry() ); + + /** @var RequestOptions $request_options */ + $request_options = $this->get_wrapped_prompt_builder_property_value( $builder, 'requestOptions' ); + + $this->assertInstanceOf( RequestOptions::class, $request_options ); + $this->assertSame( 30.0, $request_options->getTimeout() ); + } + + /** + * Data provider for {@see self::test_constructor_disallows_overriding_with_invalid_request_timeout()}. + * + * @return array + */ + public function data_invalid_request_timeouts(): array { + return array( + 'negative number' => array( -1 ), + 'array' => array( array() ), + 'null' => array( null ), + ); } /** @@ -401,7 +463,7 @@ public function test_constructor_with_string_prompt() { $this->assertCount( 1, $messages ); $this->assertInstanceOf( Message::class, $messages[0] ); - $this->assertEquals( 'Hello, world!', $messages[0]->getParts()[0]->getText() ); + $this->assertSame( 'Hello, world!', $messages[0]->getParts()[0]->getText() ); } /** @@ -418,7 +480,7 @@ public function test_constructor_with_message_part_prompt() { $this->assertCount( 1, $messages ); $this->assertInstanceOf( Message::class, $messages[0] ); - $this->assertEquals( 'Test message', $messages[0]->getParts()[0]->getText() ); + $this->assertSame( 'Test message', $messages[0]->getParts()[0]->getText() ); } /** @@ -479,7 +541,7 @@ public function test_constructor_with_message_array_shape() { $this->assertCount( 1, $messages ); $this->assertInstanceOf( Message::class, $messages[0] ); - $this->assertEquals( 'Hello from array', $messages[0]->getParts()[0]->getText() ); + $this->assertSame( 'Hello from array', $messages[0]->getParts()[0]->getText() ); } /** @@ -497,7 +559,7 @@ public function test_with_text() { $messages = $this->get_wrapped_prompt_builder_property_value( $builder, 'messages' ); $this->assertCount( 1, $messages ); - $this->assertEquals( 'Some text', $messages[0]->getParts()[0]->getText() ); + $this->assertSame( 'Some text', $messages[0]->getParts()[0]->getText() ); } /** @@ -515,8 +577,8 @@ public function test_with_text_appends_to_existing_user_message() { $this->assertCount( 1, $messages ); $parts = $messages[0]->getParts(); $this->assertCount( 2, $parts ); - $this->assertEquals( 'Initial text', $parts[0]->getText() ); - $this->assertEquals( ' Additional text', $parts[1]->getText() ); + $this->assertSame( 'Initial text', $parts[0]->getText() ); + $this->assertSame( ' Additional text', $parts[1]->getText() ); } /** @@ -537,8 +599,8 @@ public function test_with_inline_file() { $this->assertCount( 1, $messages ); $file = $messages[0]->getParts()[0]->getFile(); $this->assertInstanceOf( File::class, $file ); - $this->assertEquals( 'data:image/png;base64,' . $base64, $file->getDataUri() ); - $this->assertEquals( 'image/png', $file->getMimeType() ); + $this->assertSame( 'data:image/png;base64,' . $base64, $file->getDataUri() ); + $this->assertSame( 'image/png', $file->getMimeType() ); } /** @@ -558,8 +620,8 @@ public function test_with_remote_file() { $this->assertCount( 1, $messages ); $file = $messages[0]->getParts()[0]->getFile(); $this->assertInstanceOf( File::class, $file ); - $this->assertEquals( 'https://example.com/image.jpg', $file->getUrl() ); - $this->assertEquals( 'image/jpeg', $file->getMimeType() ); + $this->assertSame( 'https://example.com/image.jpg', $file->getUrl() ); + $this->assertSame( 'image/jpeg', $file->getMimeType() ); } /** @@ -580,7 +642,7 @@ public function test_with_inline_file_data_uri() { $this->assertCount( 1, $messages ); $file = $messages[0]->getParts()[0]->getFile(); $this->assertInstanceOf( File::class, $file ); - $this->assertEquals( 'image/jpeg', $file->getMimeType() ); + $this->assertSame( 'image/jpeg', $file->getMimeType() ); } /** @@ -600,8 +662,8 @@ public function test_with_remote_file_without_mime_type() { $this->assertCount( 1, $messages ); $file = $messages[0]->getParts()[0]->getFile(); $this->assertInstanceOf( File::class, $file ); - $this->assertEquals( 'https://example.com/audio.mp3', $file->getUrl() ); - $this->assertEquals( 'audio/mpeg', $file->getMimeType() ); + $this->assertSame( 'https://example.com/audio.mp3', $file->getUrl() ); + $this->assertSame( 'audio/mpeg', $file->getMimeType() ); } /** @@ -644,9 +706,9 @@ public function test_with_message_parts() { $this->assertCount( 1, $messages ); $parts = $messages[0]->getParts(); $this->assertCount( 3, $parts ); - $this->assertEquals( 'Part 1', $parts[0]->getText() ); - $this->assertEquals( 'Part 2', $parts[1]->getText() ); - $this->assertEquals( 'Part 3', $parts[2]->getText() ); + $this->assertSame( 'Part 1', $parts[0]->getText() ); + $this->assertSame( 'Part 2', $parts[1]->getText() ); + $this->assertSame( 'Part 3', $parts[2]->getText() ); } /** @@ -670,9 +732,9 @@ public function test_with_history() { $messages = $this->get_wrapped_prompt_builder_property_value( $builder, 'messages' ); $this->assertCount( 3, $messages ); - $this->assertEquals( 'User 1', $messages[0]->getParts()[0]->getText() ); - $this->assertEquals( 'Model 1', $messages[1]->getParts()[0]->getText() ); - $this->assertEquals( 'User 2', $messages[2]->getParts()[0]->getText() ); + $this->assertSame( 'User 1', $messages[0]->getParts()[0]->getText() ); + $this->assertSame( 'Model 1', $messages[1]->getParts()[0]->getText() ); + $this->assertSame( 'User 2', $messages[2]->getParts()[0]->getText() ); } /** @@ -710,9 +772,9 @@ public function test_constructor_with_string_parts_list() { $this->assertInstanceOf( Message::class, $messages[0] ); $parts = $messages[0]->getParts(); $this->assertCount( 3, $parts ); - $this->assertEquals( 'Part 1', $parts[0]->getText() ); - $this->assertEquals( 'Part 2', $parts[1]->getText() ); - $this->assertEquals( 'Part 3', $parts[2]->getText() ); + $this->assertSame( 'Part 1', $parts[0]->getText() ); + $this->assertSame( 'Part 2', $parts[1]->getText() ); + $this->assertSame( 'Part 3', $parts[2]->getText() ); } /** @@ -735,9 +797,9 @@ public function test_constructor_with_mixed_parts_list() { $this->assertCount( 1, $messages ); $parts = $messages[0]->getParts(); $this->assertCount( 3, $parts ); - $this->assertEquals( 'String part', $parts[0]->getText() ); - $this->assertEquals( 'Part 1', $parts[1]->getText() ); - $this->assertEquals( 'Part 2', $parts[2]->getText() ); + $this->assertSame( 'String part', $parts[0]->getText() ); + $this->assertSame( 'Part 1', $parts[1]->getText() ); + $this->assertSame( 'Part 2', $parts[2]->getText() ); } /** @@ -775,13 +837,13 @@ public function test_method_chaining() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 'Be helpful', $config->getSystemInstruction() ); - $this->assertEquals( 500, $config->getMaxTokens() ); - $this->assertEquals( 0.8, $config->getTemperature() ); - $this->assertEquals( 0.95, $config->getTopP() ); - $this->assertEquals( 50, $config->getTopK() ); - $this->assertEquals( 2, $config->getCandidateCount() ); - $this->assertEquals( 'application/json', $config->getOutputMimeType() ); + $this->assertSame( 'Be helpful', $config->getSystemInstruction() ); + $this->assertSame( 500, $config->getMaxTokens() ); + $this->assertSame( 0.8, $config->getTemperature() ); + $this->assertSame( 0.95, $config->getTopP() ); + $this->assertSame( 50, $config->getTopK() ); + $this->assertSame( 2, $config->getCandidateCount() ); + $this->assertSame( 'application/json', $config->getOutputMimeType() ); } /** @@ -1001,11 +1063,11 @@ public function test_using_model_config() { /** @var ModelConfig $merged_config */ $merged_config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 'Builder instruction', $merged_config->getSystemInstruction() ); - $this->assertEquals( 500, $merged_config->getMaxTokens() ); - $this->assertEquals( 0.5, $merged_config->getTemperature() ); - $this->assertEquals( 0.9, $merged_config->getTopP() ); - $this->assertEquals( 40, $merged_config->getTopK() ); + $this->assertSame( 'Builder instruction', $merged_config->getSystemInstruction() ); + $this->assertSame( 500, $merged_config->getMaxTokens() ); + $this->assertSame( 0.5, $merged_config->getTemperature() ); + $this->assertSame( 0.9, $merged_config->getTopP() ); + $this->assertSame( 40, $merged_config->getTopK() ); } /** @@ -1028,22 +1090,22 @@ public function test_using_model_config_with_custom_options() { $this->assertArrayHasKey( 'stopSequences', $custom_options ); $this->assertIsArray( $custom_options['stopSequences'] ); - $this->assertEquals( array( 'CONFIG_STOP' ), $custom_options['stopSequences'] ); + $this->assertSame( array( 'CONFIG_STOP' ), $custom_options['stopSequences'] ); $this->assertArrayHasKey( 'otherOption', $custom_options ); - $this->assertEquals( 'value', $custom_options['otherOption'] ); + $this->assertSame( 'value', $custom_options['otherOption'] ); $builder->using_stop_sequences( 'STOP' ); /** @var ModelConfig $merged_config */ $merged_config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( array( 'STOP' ), $merged_config->getStopSequences() ); + $this->assertSame( array( 'STOP' ), $merged_config->getStopSequences() ); $custom_options = $merged_config->getCustomOptions(); $this->assertArrayHasKey( 'stopSequences', $custom_options ); - $this->assertEquals( array( 'CONFIG_STOP' ), $custom_options['stopSequences'] ); + $this->assertSame( array( 'CONFIG_STOP' ), $custom_options['stopSequences'] ); $this->assertArrayHasKey( 'otherOption', $custom_options ); - $this->assertEquals( 'value', $custom_options['otherOption'] ); + $this->assertSame( 'value', $custom_options['otherOption'] ); } /** @@ -1058,7 +1120,7 @@ public function test_using_provider() { $this->assertSame( $builder, $result ); $actual_provider = $this->get_wrapped_prompt_builder_property_value( $builder, 'providerIdOrClassName' ); - $this->assertEquals( 'test-provider', $actual_provider ); + $this->assertSame( 'test-provider', $actual_provider ); } /** @@ -1075,7 +1137,7 @@ public function test_using_system_instruction() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 'You are a helpful assistant.', $config->getSystemInstruction() ); + $this->assertSame( 'You are a helpful assistant.', $config->getSystemInstruction() ); } /** @@ -1092,7 +1154,7 @@ public function test_using_max_tokens() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 1000, $config->getMaxTokens() ); + $this->assertSame( 1000, $config->getMaxTokens() ); } /** @@ -1109,7 +1171,7 @@ public function test_using_temperature() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 0.7, $config->getTemperature() ); + $this->assertSame( 0.7, $config->getTemperature() ); } /** @@ -1126,7 +1188,7 @@ public function test_using_top_p() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 0.9, $config->getTopP() ); + $this->assertSame( 0.9, $config->getTopP() ); } /** @@ -1143,7 +1205,7 @@ public function test_using_top_k() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 40, $config->getTopK() ); + $this->assertSame( 40, $config->getTopK() ); } /** @@ -1160,7 +1222,7 @@ public function test_using_stop_sequences() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( array( 'STOP', 'END', '###' ), $config->getStopSequences() ); + $this->assertSame( array( 'STOP', 'END', '###' ), $config->getStopSequences() ); } /** @@ -1177,7 +1239,7 @@ public function test_using_candidate_count() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 3, $config->getCandidateCount() ); + $this->assertSame( 3, $config->getCandidateCount() ); } /** @@ -1194,7 +1256,7 @@ public function test_using_output_mime() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 'application/json', $config->getOutputMimeType() ); + $this->assertSame( 'application/json', $config->getOutputMimeType() ); } /** @@ -1218,7 +1280,7 @@ public function test_using_output_schema() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( $schema, $config->getOutputSchema() ); + $this->assertSame( $schema, $config->getOutputSchema() ); } /** @@ -1258,7 +1320,7 @@ public function test_as_json_response() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 'application/json', $config->getOutputMimeType() ); + $this->assertSame( 'application/json', $config->getOutputMimeType() ); } /** @@ -1276,8 +1338,8 @@ public function test_as_json_response_with_schema() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 'application/json', $config->getOutputMimeType() ); - $this->assertEquals( $schema, $config->getOutputSchema() ); + $this->assertSame( 'application/json', $config->getOutputMimeType() ); + $this->assertSame( $schema, $config->getOutputSchema() ); } /** @@ -1824,14 +1886,14 @@ public function test_generate_texts() { $texts = $builder->generate_texts( 3 ); $this->assertCount( 3, $texts ); - $this->assertEquals( 'Text 1', $texts[0] ); - $this->assertEquals( 'Text 2', $texts[1] ); - $this->assertEquals( 'Text 3', $texts[2] ); + $this->assertSame( 'Text 1', $texts[0] ); + $this->assertSame( 'Text 2', $texts[1] ); + $this->assertSame( 'Text 3', $texts[2] ); /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 3, $config->getCandidateCount() ); + $this->assertSame( 3, $config->getCandidateCount() ); } /** @@ -2255,7 +2317,7 @@ public function test_as_output_media_aspect_ratio() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( '16:9', $config->getOutputMediaAspectRatio() ); + $this->assertSame( '16:9', $config->getOutputMediaAspectRatio() ); } /** @@ -2272,7 +2334,7 @@ public function test_as_output_speech_voice() { /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 'alloy', $config->getOutputSpeechVoice() ); + $this->assertSame( 'alloy', $config->getOutputSpeechVoice() ); } /** @@ -2290,8 +2352,8 @@ public function test_using_ability_with_string() { $this->assertNotNull( $declarations ); $this->assertCount( 1, $declarations ); - $this->assertEquals( 'wpab__wpaiclienttests__simple', $declarations[0]->getName() ); - $this->assertEquals( 'A simple test ability with no parameters.', $declarations[0]->getDescription() ); + $this->assertSame( 'wpab__wpaiclienttests__simple', $declarations[0]->getName() ); + $this->assertSame( 'A simple test ability with no parameters.', $declarations[0]->getDescription() ); } /** @@ -2311,8 +2373,8 @@ public function test_using_ability_with_wp_ability_object() { $this->assertNotNull( $declarations ); $this->assertCount( 1, $declarations ); - $this->assertEquals( 'wpab__wpaiclienttests__with-params', $declarations[0]->getName() ); - $this->assertEquals( 'A test ability that accepts parameters.', $declarations[0]->getDescription() ); + $this->assertSame( 'wpab__wpaiclienttests__with-params', $declarations[0]->getName() ); + $this->assertSame( 'A test ability that accepts parameters.', $declarations[0]->getDescription() ); $params = $declarations[0]->getParameters(); $this->assertNotNull( $params ); @@ -2339,9 +2401,9 @@ public function test_using_ability_with_multiple_abilities() { $this->assertNotNull( $declarations ); $this->assertCount( 3, $declarations ); - $this->assertEquals( 'wpab__wpaiclienttests__simple', $declarations[0]->getName() ); - $this->assertEquals( 'wpab__wpaiclienttests__with-params', $declarations[1]->getName() ); - $this->assertEquals( 'wpab__wpaiclienttests__returns-error', $declarations[2]->getName() ); + $this->assertSame( 'wpab__wpaiclienttests__simple', $declarations[0]->getName() ); + $this->assertSame( 'wpab__wpaiclienttests__with-params', $declarations[1]->getName() ); + $this->assertSame( 'wpab__wpaiclienttests__returns-error', $declarations[2]->getName() ); } /** @@ -2367,8 +2429,8 @@ public function test_using_ability_skips_nonexistent_abilities() { $this->assertNotNull( $declarations ); $this->assertCount( 2, $declarations ); - $this->assertEquals( 'wpab__wpaiclienttests__simple', $declarations[0]->getName() ); - $this->assertEquals( 'wpab__wpaiclienttests__with-params', $declarations[1]->getName() ); + $this->assertSame( 'wpab__wpaiclienttests__simple', $declarations[0]->getName() ); + $this->assertSame( 'wpab__wpaiclienttests__with-params', $declarations[1]->getName() ); } /** @@ -2407,8 +2469,8 @@ public function test_using_ability_with_mixed_types() { $this->assertNotNull( $declarations ); $this->assertCount( 2, $declarations ); - $this->assertEquals( 'wpab__wpaiclienttests__simple', $declarations[0]->getName() ); - $this->assertEquals( 'wpab__wpaiclienttests__with-params', $declarations[1]->getName() ); + $this->assertSame( 'wpab__wpaiclienttests__simple', $declarations[0]->getName() ); + $this->assertSame( 'wpab__wpaiclienttests__with-params', $declarations[1]->getName() ); } /** @@ -2426,7 +2488,7 @@ public function test_using_ability_with_hyphenated_name() { $this->assertNotNull( $declarations ); $this->assertCount( 1, $declarations ); - $this->assertEquals( 'wpab__wpaiclienttests__hyphen-test', $declarations[0]->getName() ); + $this->assertSame( 'wpab__wpaiclienttests__hyphen-test', $declarations[0]->getName() ); } /** @@ -2448,13 +2510,13 @@ public function test_using_ability_method_chaining() { $this->assertNotNull( $declarations ); $this->assertCount( 1, $declarations ); - $this->assertEquals( 'wpab__wpaiclienttests__simple', $declarations[0]->getName() ); + $this->assertSame( 'wpab__wpaiclienttests__simple', $declarations[0]->getName() ); /** @var ModelConfig $config */ $config = $this->get_wrapped_prompt_builder_property_value( $builder, 'modelConfig' ); - $this->assertEquals( 'You are a helpful assistant', $config->getSystemInstruction() ); - $this->assertEquals( 500, $config->getMaxTokens() ); + $this->assertSame( 'You are a helpful assistant', $config->getSystemInstruction() ); + $this->assertSame( 500, $config->getMaxTokens() ); } /** diff --git a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php new file mode 100644 index 0000000000000..1b6076ee185a4 --- /dev/null +++ b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php @@ -0,0 +1,238 @@ +assertArrayNotHasKey( 'css', $blocks[0]['attrs']['style'] ?? array(), $message ); + $this->assertArrayNotHasKey( 'style', $blocks[0]['attrs'] ?? array(), 'style key should be fully removed when css was the only property.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_strips_css_from_blocks() { + return array( + 'single block' => array( + 'content' => '

Hello

', + 'message' => 'style.css should be stripped from block attributes.', + ), + ); + } + + /** + * Tests that style.css is stripped from nested inner blocks. + * + * @covers ::wp_strip_custom_css_from_blocks + * @ticket 64771 + */ + public function test_strips_css_from_inner_blocks() { + $content = '

Hello

'; + + $result = wp_unslash( wp_strip_custom_css_from_blocks( $content ) ); + $blocks = parse_blocks( $result ); + + $inner_block = $blocks[0]['innerBlocks'][0]; + $this->assertArrayNotHasKey( 'css', $inner_block['attrs']['style'] ?? array(), 'style.css should be stripped from inner block attributes.' ); + } + + /** + * Tests that content without blocks is returned unchanged. + * + * @covers ::wp_strip_custom_css_from_blocks + * @ticket 64771 + */ + public function test_returns_non_block_content_unchanged() { + $content = '

This is plain HTML content with no blocks.

'; + + $result = wp_strip_custom_css_from_blocks( $content ); + + $this->assertSame( $content, $result, 'Non-block content should be returned unchanged.' ); + } + + /** + * Tests that content without style.css attributes is returned unchanged. + * + * @covers ::wp_strip_custom_css_from_blocks + * @ticket 64771 + */ + public function test_returns_unchanged_when_no_css_attributes() { + $content = '

Hello

'; + + $result = wp_strip_custom_css_from_blocks( $content ); + + $this->assertSame( $content, $result, 'Content without style.css attributes should be returned unchanged.' ); + } + + /** + * Tests that other style properties are preserved when css is stripped. + * + * @covers ::wp_strip_custom_css_from_blocks + * @ticket 64771 + */ + public function test_preserves_other_style_properties() { + $content = '

Hello

'; + + $result = wp_unslash( wp_strip_custom_css_from_blocks( $content ) ); + $blocks = parse_blocks( $result ); + + $this->assertArrayNotHasKey( 'css', $blocks[0]['attrs']['style'], 'style.css should be stripped.' ); + $this->assertSame( '#ff0000', $blocks[0]['attrs']['style']['color']['text'], 'Other style properties should be preserved.' ); + } + + /** + * Tests that empty style object is cleaned up after stripping css. + * + * @covers ::wp_strip_custom_css_from_blocks + * @ticket 64771 + */ + public function test_cleans_up_empty_style_object() { + $content = '

Hello

'; + + $result = wp_unslash( wp_strip_custom_css_from_blocks( $content ) ); + $blocks = parse_blocks( $result ); + + $this->assertArrayNotHasKey( 'style', $blocks[0]['attrs'], 'Empty style object should be cleaned up after stripping css.' ); + } + + /** + * Tests that slashed content is handled correctly. + * + * @covers ::wp_strip_custom_css_from_blocks + * @ticket 64771 + */ + public function test_handles_slashed_content() { + $content = '

Hello

'; + $slashed = wp_slash( $content ); + + $result = wp_strip_custom_css_from_blocks( $slashed ); + $blocks = parse_blocks( wp_unslash( $result ) ); + + $this->assertArrayNotHasKey( 'css', $blocks[0]['attrs']['style'] ?? array(), 'style.css should be stripped even from slashed content.' ); + } + + /** + * Tests that the content_save_pre filter is added for a user without edit_css. + * + * @ticket 64771 + * + * @covers ::wp_custom_css_kses_init + * @covers ::wp_custom_css_kses_init_filters + */ + public function test_filter_added_for_user_without_edit_css() { + $author_id = self::factory()->user->create( array( 'role' => 'author' ) ); + wp_set_current_user( $author_id ); + wp_custom_css_kses_init(); + + $this->assertSame( 8, has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'content_save_pre filter should be added at priority 8 for users without edit_css.' ); + $this->assertSame( 8, has_filter( 'content_filtered_save_pre', 'wp_strip_custom_css_from_blocks' ), 'content_filtered_save_pre filter should be added at priority 8 for users without edit_css.' ); + + wp_set_current_user( 0 ); + wp_custom_css_remove_filters(); + } + + /** + * Tests that the content_save_pre filter is not added for a user with edit_css. + * + * @ticket 64771 + * + * @covers ::wp_custom_css_kses_init + * @covers ::wp_custom_css_remove_filters + */ + public function test_filter_not_added_for_user_with_edit_css() { + $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + if ( is_multisite() ) { + grant_super_admin( $admin_id ); + } + wp_set_current_user( $admin_id ); + wp_custom_css_kses_init(); + + $this->assertFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'content_save_pre filter should not be added for users with edit_css.' ); + $this->assertFalse( has_filter( 'content_filtered_save_pre', 'wp_strip_custom_css_from_blocks' ), 'content_filtered_save_pre filter should not be added for users with edit_css.' ); + + if ( is_multisite() ) { + revoke_super_admin( $admin_id ); + } + wp_set_current_user( 0 ); + wp_custom_css_remove_filters(); + } + + /** + * Tests that switching to a user with edit_css removes the filter via the set_current_user action. + * + * wp_custom_css_kses_init() is hooked to set_current_user, so wp_set_current_user() + * alone should update the filter state without a manual call. + * + * @ticket 64771 + * + * @covers ::wp_custom_css_kses_init + */ + public function test_set_current_user_action_triggers_reinit() { + $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + $author_id = self::factory()->user->create( array( 'role' => 'author' ) ); + if ( is_multisite() ) { + grant_super_admin( $admin_id ); + } + + // Switching to a user without edit_css should add the filter via the set_current_user action. + wp_set_current_user( $author_id ); + $this->assertNotFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should be active for user without edit_css.' ); + + // Switching to a user with edit_css should remove the filter via the set_current_user action. + wp_set_current_user( $admin_id ); + $this->assertFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should be removed after switching to a user with edit_css.' ); + + if ( is_multisite() ) { + revoke_super_admin( $admin_id ); + } + wp_set_current_user( 0 ); + wp_custom_css_remove_filters(); + } + + /** + * Tests that the filter is enabled during import regardless of user capability. + * + * @ticket 64771 + * + * @covers ::wp_custom_css_force_filtered_html_on_import_filter + */ + public function test_force_filtered_html_on_import_enables_filter_for_privileged_user() { + $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + if ( is_multisite() ) { + grant_super_admin( $admin_id ); + } + wp_set_current_user( $admin_id ); + wp_custom_css_kses_init(); + + $this->assertFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should not be active for admin before import.' ); + + apply_filters( 'force_filtered_html_on_import', true ); + + $this->assertNotFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should be enabled during import regardless of user capability.' ); + + if ( is_multisite() ) { + revoke_super_admin( $admin_id ); + } + wp_set_current_user( 0 ); + wp_custom_css_remove_filters(); + } +} diff --git a/tests/phpunit/tests/blocks/wpBlockTypeRegistry.php b/tests/phpunit/tests/blocks/wpBlockTypeRegistry.php index c97bdc95d43a1..97f35f2e8ac7a 100644 --- a/tests/phpunit/tests/blocks/wpBlockTypeRegistry.php +++ b/tests/phpunit/tests/blocks/wpBlockTypeRegistry.php @@ -7,6 +7,8 @@ * @since 5.0.0 * * @group blocks + * + * @coversDefaultClass WP_Block_Type_Registry */ class Tests_Blocks_wpBlockTypeRegistry extends WP_UnitTestCase { @@ -41,57 +43,42 @@ public function tear_down() { } /** - * Should reject numbers + * Should reject invalid block names. * * @ticket 45097 * - * @expectedIncorrectUsage WP_Block_Type_Registry::register - */ - public function test_invalid_non_string_names() { - $result = $this->registry->register( 1, array() ); - $this->assertFalse( $result ); - } - - /** - * Should reject blocks without a namespace + * @covers ::register * - * @ticket 45097 + * @dataProvider data_invalid_block_names * * @expectedIncorrectUsage WP_Block_Type_Registry::register */ - public function test_invalid_names_without_namespace() { - $result = $this->registry->register( 'paragraph', array() ); + public function test_invalid_block_names( $name ) { + $result = $this->registry->register( $name, array() ); $this->assertFalse( $result ); } /** - * Should reject blocks with invalid characters - * - * @ticket 45097 + * Data provider for test_invalid_block_names(). * - * @expectedIncorrectUsage WP_Block_Type_Registry::register + * @return array */ - public function test_invalid_characters() { - $result = $this->registry->register( 'still/_doing_it_wrong', array() ); - $this->assertFalse( $result ); + public function data_invalid_block_names(): array { + return array( + 'non-string name' => array( 1 ), + 'no namespace' => array( 'paragraph' ), + 'invalid characters' => array( 'still/_doing_it_wrong' ), + 'uppercase characters' => array( 'Core/Paragraph' ), + ); } /** - * Should reject blocks with uppercase characters + * Should accept valid block names. * * @ticket 45097 * - * @expectedIncorrectUsage WP_Block_Type_Registry::register - */ - public function test_uppercase_characters() { - $result = $this->registry->register( 'Core/Paragraph', array() ); - $this->assertFalse( $result ); - } - - /** - * Should accept valid block names - * - * @ticket 45097 + * @covers ::register + * @covers ::get_registered */ public function test_register_block_type() { $name = 'core/paragraph'; @@ -106,10 +93,12 @@ public function test_register_block_type() { } /** - * Should fail to re-register the same block + * Should fail to re-register the same block. * * @ticket 45097 * + * @covers ::register + * * @expectedIncorrectUsage WP_Block_Type_Registry::register */ public function test_register_block_type_twice() { @@ -125,9 +114,11 @@ public function test_register_block_type_twice() { } /** - * Should accept a WP_Block_Type instance + * Should accept a WP_Block_Type instance. * * @ticket 45097 + * + * @covers ::register */ public function test_register_block_type_instance() { $block_type = new WP_Fake_Block_Type( 'core/fake' ); @@ -137,10 +128,12 @@ public function test_register_block_type_instance() { } /** - * Unregistering should fail if a block is not registered + * Unregistering should fail if a block is not registered. * * @ticket 45097 * + * @covers ::unregister + * * @expectedIncorrectUsage WP_Block_Type_Registry::unregister */ public function test_unregister_not_registered_block() { @@ -149,9 +142,12 @@ public function test_unregister_not_registered_block() { } /** - * Should unregister existing blocks + * Should unregister existing blocks. * * @ticket 45097 + * + * @covers ::unregister + * @covers ::is_registered */ public function test_unregister_block_type() { $name = 'core/paragraph'; @@ -168,6 +164,8 @@ public function test_unregister_block_type() { /** * @ticket 45097 + * + * @covers ::get_all_registered */ public function test_get_all_registered() { $names = array( 'core/paragraph', 'core/image', 'core/blockquote' ); diff --git a/tests/phpunit/tests/formatting/isEmail.php b/tests/phpunit/tests/formatting/isEmail.php index d79647885ceba..b793af2c4a70d 100644 --- a/tests/phpunit/tests/formatting/isEmail.php +++ b/tests/phpunit/tests/formatting/isEmail.php @@ -122,7 +122,60 @@ public static function data_invalid_email_provider() { ); foreach ( $invalid_emails as $email ) { - yield $email => array( $email ); + yield self::invalid_utf8_as_ascii( $email ) => array( $email ); + } + } + + /** + * Transforms invalid byte sequences in UTF-8 into representations of + * each byte value, according to the maximal subpart rule. + * + * Example: + * + * // For valid UTF-8 the output is the input. + * 'test' === invalid_utf8_as_ascii( 'test' ); + * + * // Invalid bytes are represented with their hex value. + * 'a(0x80)b' === invalid_utf8_as_ascii( "a\x80b" ); + * + * // Invalid byte sequences form maximal subparts. + * '(0xC2)(0xEF 0xBF)' === invalid_utf8_as_ascii( "\xC2\xEF\xBF" ); + * + * @param string $text + * @return string + */ + private static function invalid_utf8_as_ascii( string $text ): string { + $output = ''; + $at = 0; + $was_at = 0; + $end = strlen( $text ); + $invalid_bytes = 0; + + while ( $at < $end ) { + if ( 0 === _wp_scan_utf8( $text, $at, $invalid_bytes ) && 0 === $invalid_bytes ) { + break; + } + + if ( $at > $was_at ) { + $output .= substr( $text, $was_at, $at - $was_at ); + } + + if ( $invalid_bytes > 0 ) { + $output .= '('; + + for ( $i = 0; $i < $invalid_bytes; $i++ ) { + $space = $i > 0 ? ' ' : ''; + $as_hex = bin2hex( $text[ $at + $i ] ); + $output .= "{$space}0x{$as_hex}"; + } + + $output .= ')'; + } + + $at += $invalid_bytes; + $was_at = $at; } + + return $output; } } diff --git a/tests/phpunit/tests/formatting/sanitizeEmail.php b/tests/phpunit/tests/formatting/sanitizeEmail.php index 6ca396f42dc26..5490374d0a5e7 100644 --- a/tests/phpunit/tests/formatting/sanitizeEmail.php +++ b/tests/phpunit/tests/formatting/sanitizeEmail.php @@ -17,11 +17,21 @@ class Tests_Formatting_SanitizeEmail extends WP_UnitTestCase { * @param string $expected The expected sanitized email address. */ public function test_returns_stripped_email_address( $address, $expected ) { - $this->assertSame( - $expected, - sanitize_email( $address ), - 'Should have produced the known sanitized form of the email.' - ); + $sanitized = sanitize_email( $address ); + + if ( $expected === $sanitized ) { + $this->assertSame( + $expected, + $sanitized, + 'Should have produced the known sanitized form of the email.' + ); + } else { + $this->assertSame( + $expected, + self::invalid_utf8_as_ascii( $sanitized ), + 'Should have produced the known sanitized form of the email.' + ); + } } /** @@ -39,4 +49,57 @@ public function data_sanitized_email_pairs() { 'all subdomains invalid utf8' => array( "abc@\x80.org", '' ), ); } + + /** + * Transforms invalid byte sequences in UTF-8 into representations of + * each byte value, according to the maximal subpart rule. + * + * Example: + * + * // For valid UTF-8 the output is the input. + * 'test' === invalid_utf8_as_ascii( 'test' ); + * + * // Invalid bytes are represented with their hex value. + * 'a(0x80)b' === invalid_utf8_as_ascii( "a\x80b" ); + * + * // Invalid byte sequences form maximal subparts. + * '(0xC2)(0xEF 0xBF)' === invalid_utf8_as_ascii( "\xC2\xEF\xBF" ); + * + * @param string $text + * @return string + */ + private static function invalid_utf8_as_ascii( string $text ): string { + $output = ''; + $at = 0; + $was_at = 0; + $end = strlen( $text ); + $invalid_bytes = 0; + + while ( $at < $end ) { + if ( 0 === _wp_scan_utf8( $text, $at, $invalid_bytes ) && 0 === $invalid_bytes ) { + break; + } + + if ( $at > $was_at ) { + $output .= substr( $text, $was_at, $at - $was_at ); + } + + if ( $invalid_bytes > 0 ) { + $output .= '('; + + for ( $i = 0; $i < $invalid_bytes; $i++ ) { + $space = $i > 0 ? ' ' : ''; + $as_hex = bin2hex( $text[ $at + $i ] ); + $output .= "{$space}0x{$as_hex}"; + } + + $output .= ')'; + } + + $at += $invalid_bytes; + $was_at = $at; + } + + return $output; + } } diff --git a/tests/phpunit/tests/link/getAdjacentPost.php b/tests/phpunit/tests/link/getAdjacentPost.php index 7fdd06ec75ead..e10ff82c099dc 100644 --- a/tests/phpunit/tests/link/getAdjacentPost.php +++ b/tests/phpunit/tests/link/getAdjacentPost.php @@ -477,14 +477,14 @@ public function test_get_adjacent_post_term_array_processing_order() { // Should find post_one (previous post that shares term1). $this->assertInstanceOf( WP_Post::class, $result ); - $this->assertEquals( $post1_id, $result->ID ); + $this->assertSame( $post1_id, $result->ID ); // Test next post. $result = get_adjacent_post( true, array( $term2_id ), false, 'wptests_tax' ); // Should find post_three (next post that shares term1). $this->assertInstanceOf( WP_Post::class, $result ); - $this->assertEquals( $post3_id, $result->ID ); + $this->assertSame( $post3_id, $result->ID ); } /** @@ -614,12 +614,12 @@ public function test_get_adjacent_post_with_identical_dates() { // Previous post should be the 2nd post (lower ID, same date). $previous = get_adjacent_post( false, '', true ); $this->assertInstanceOf( 'WP_Post', $previous ); - $this->assertEquals( $post_ids[1], $previous->ID ); + $this->assertSame( $post_ids[1], $previous->ID ); // Next post should be the 4th post (higher ID, same date). $next = get_adjacent_post( false, '', false ); $this->assertInstanceOf( 'WP_Post', $next ); - $this->assertEquals( $post_ids[3], $next->ID ); + $this->assertSame( $post_ids[3], $next->ID ); } /** @@ -661,12 +661,12 @@ public function test_get_adjacent_post_mixed_dates_with_identical_groups() { // Previous should be the early post (different date). $previous = get_adjacent_post( false, '', true ); $this->assertInstanceOf( 'WP_Post', $previous ); - $this->assertEquals( $post_early, $previous->ID ); + $this->assertSame( $post_early, $previous->ID ); // Next should be the second identical post (same date, higher ID). $next = get_adjacent_post( false, '', false ); $this->assertInstanceOf( 'WP_Post', $next ); - $this->assertEquals( $post_ids[1], $next->ID ); + $this->assertSame( $post_ids[1], $next->ID ); // Test from middle identical post. $this->go_to( get_permalink( $post_ids[1] ) ); @@ -674,12 +674,12 @@ public function test_get_adjacent_post_mixed_dates_with_identical_groups() { // Previous should be the first identical post (same date, lower ID). $previous = get_adjacent_post( false, '', true ); $this->assertInstanceOf( 'WP_Post', $previous ); - $this->assertEquals( $post_ids[0], $previous->ID ); + $this->assertSame( $post_ids[0], $previous->ID ); // Next should be the third identical post (same date, higher ID). $next = get_adjacent_post( false, '', false ); $this->assertInstanceOf( 'WP_Post', $next ); - $this->assertEquals( $post_ids[2], $next->ID ); + $this->assertSame( $post_ids[2], $next->ID ); // Test from last identical post. $this->go_to( get_permalink( $post_ids[2] ) ); @@ -687,12 +687,12 @@ public function test_get_adjacent_post_mixed_dates_with_identical_groups() { // Previous should be the second identical post (same date, lower ID). $previous = get_adjacent_post( false, '', true ); $this->assertInstanceOf( 'WP_Post', $previous ); - $this->assertEquals( $post_ids[1], $previous->ID ); + $this->assertSame( $post_ids[1], $previous->ID ); // Next should be the late post (different date). $next = get_adjacent_post( false, '', false ); $this->assertInstanceOf( 'WP_Post', $next ); - $this->assertEquals( $post_late, $next->ID ); + $this->assertSame( $post_late, $next->ID ); } /** @@ -719,26 +719,26 @@ public function test_get_adjacent_post_navigation_through_identical_dates() { // From post 1, next should be post 2. $next = get_adjacent_post( false, '', false ); - $this->assertEquals( $post_ids[1], $next->ID ); + $this->assertSame( $post_ids[1], $next->ID ); // From post 2, previous should be post 1, next should be post 3. $this->go_to( get_permalink( $post_ids[1] ) ); $previous = get_adjacent_post( false, '', true ); - $this->assertEquals( $post_ids[0], $previous->ID ); + $this->assertSame( $post_ids[0], $previous->ID ); $next = get_adjacent_post( false, '', false ); - $this->assertEquals( $post_ids[2], $next->ID ); + $this->assertSame( $post_ids[2], $next->ID ); // From post 3, previous should be post 2, next should be post 4. $this->go_to( get_permalink( $post_ids[2] ) ); $previous = get_adjacent_post( false, '', true ); - $this->assertEquals( $post_ids[1], $previous->ID ); + $this->assertSame( $post_ids[1], $previous->ID ); $next = get_adjacent_post( false, '', false ); - $this->assertEquals( $post_ids[3], $next->ID ); + $this->assertSame( $post_ids[3], $next->ID ); // From post 4, previous should be post 3. $this->go_to( get_permalink( $post_ids[3] ) ); $previous = get_adjacent_post( false, '', true ); - $this->assertEquals( $post_ids[2], $previous->ID ); + $this->assertSame( $post_ids[2], $previous->ID ); } /** @@ -777,6 +777,6 @@ public function test_get_adjacent_post_identical_dates_with_category() { $next = get_adjacent_post( true, '', false, 'category' ); $this->assertInstanceOf( 'WP_Post', $next ); - $this->assertEquals( $post_ids[3], $next->ID ); // Post 4 (in category) + $this->assertSame( $post_ids[3], $next->ID ); // Post 4 (in category) } } diff --git a/tests/phpunit/tests/rewrite/addRewriteEndpoint.php b/tests/phpunit/tests/rewrite/addRewriteEndpoint.php index 7b3ada5febe7d..6527f32929917 100644 --- a/tests/phpunit/tests/rewrite/addRewriteEndpoint.php +++ b/tests/phpunit/tests/rewrite/addRewriteEndpoint.php @@ -2,6 +2,8 @@ /** * @group rewrite + * + * @covers ::add_rewrite_endpoint */ class Tests_Rewrite_AddRewriteEndpoint extends WP_UnitTestCase { private $qvs; diff --git a/tests/phpunit/tests/rewrite/addRewriteRule.php b/tests/phpunit/tests/rewrite/addRewriteRule.php index 02efc7bd0aa37..d667f58ceafa9 100644 --- a/tests/phpunit/tests/rewrite/addRewriteRule.php +++ b/tests/phpunit/tests/rewrite/addRewriteRule.php @@ -2,6 +2,8 @@ /** * @group rewrite + * + * @covers ::add_rewrite_rule */ class Tests_Rewrite_AddRewriteRule extends WP_UnitTestCase { diff --git a/tests/phpunit/tests/rewrite/permastructs.php b/tests/phpunit/tests/rewrite/permastructs.php index 4e2bc0594b216..ce98e06a68bb3 100644 --- a/tests/phpunit/tests/rewrite/permastructs.php +++ b/tests/phpunit/tests/rewrite/permastructs.php @@ -2,6 +2,9 @@ /** * @group rewrite + * + * @covers ::add_permastruct + * @covers ::remove_permastruct */ class Tests_Rewrite_Permastructs extends WP_UnitTestCase { diff --git a/tools/vendors/copy-vendors.js b/tools/vendors/copy-vendors.js deleted file mode 100644 index 12660fc639645..0000000000000 --- a/tools/vendors/copy-vendors.js +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env node - -/** - * Copy Vendor Scripts - * - * This script copies vendor dependencies from node_modules to wp-includes/js/dist/vendor/. - * These are Core's own dependencies (moment, lodash, regenerator-runtime, polyfills, etc.) - * separate from Gutenberg packages. - * - * @package WordPress - */ - -const fs = require( 'fs' ); -const path = require( 'path' ); - -// Paths -const rootDir = path.resolve( __dirname, '../..' ); -const nodeModulesDir = path.join( rootDir, 'node_modules' ); - -// Parse command line arguments -const args = process.argv.slice( 2 ); -const buildDirArg = args.find( arg => arg.startsWith( '--build-dir=' ) ); -const buildTarget = buildDirArg - ? buildDirArg.split( '=' )[1] - : ( args.includes( '--dev' ) ? 'src' : 'build' ); - -const vendorDir = path.join( rootDir, buildTarget, 'wp-includes/js/dist/vendor' ); - -/** - * Vendor files to copy from node_modules. - */ -const VENDOR_FILES = { - // Moment.js - 'moment': { - files: [ - { from: 'moment/moment.js', to: 'moment.js' }, - { from: 'moment/min/moment.min.js', to: 'moment.min.js' }, - ], - }, - - // Lodash - 'lodash': { - files: [ - { from: 'lodash/lodash.js', to: 'lodash.js' }, - { from: 'lodash/lodash.min.js', to: 'lodash.min.js' }, - ], - }, - - // Regenerator Runtime - 'regenerator-runtime': { - files: [ - { from: 'regenerator-runtime/runtime.js', to: 'regenerator-runtime.js' }, - { from: 'regenerator-runtime/runtime.js', to: 'regenerator-runtime.min.js' }, - ], - }, - - // React (UMD builds from node_modules) - 'react': { - files: [ - { from: 'react/umd/react.development.js', to: 'react.js' }, - { from: 'react/umd/react.production.min.js', to: 'react.min.js' }, - ], - }, - - // React DOM (UMD builds from node_modules) - 'react-dom': { - files: [ - { from: 'react-dom/umd/react-dom.development.js', to: 'react-dom.js' }, - { from: 'react-dom/umd/react-dom.production.min.js', to: 'react-dom.min.js' }, - ], - }, - - // Main Polyfill bundle - 'wp-polyfill': { - files: [ - { from: '@wordpress/babel-preset-default/build/polyfill.js', to: 'wp-polyfill.js' }, - { from: '@wordpress/babel-preset-default/build/polyfill.min.js', to: 'wp-polyfill.min.js' }, - ], - }, - - // Polyfills - Fetch (same source for both - was minified by webpack) - 'wp-polyfill-fetch': { - files: [ - { from: 'whatwg-fetch/dist/fetch.umd.js', to: 'wp-polyfill-fetch.js' }, - { from: 'whatwg-fetch/dist/fetch.umd.js', to: 'wp-polyfill-fetch.min.js' }, - ], - }, - - // Polyfills - FormData - 'wp-polyfill-formdata': { - files: [ - { from: 'formdata-polyfill/FormData.js', to: 'wp-polyfill-formdata.js' }, - { from: 'formdata-polyfill/formdata.min.js', to: 'wp-polyfill-formdata.min.js' }, - ], - }, - - // Polyfills - Element Closest (same for both) - 'wp-polyfill-element-closest': { - files: [ - { from: 'element-closest/browser.js', to: 'wp-polyfill-element-closest.js' }, - { from: 'element-closest/browser.js', to: 'wp-polyfill-element-closest.min.js' }, - ], - }, - - // Polyfills - Object Fit - 'wp-polyfill-object-fit': { - files: [ - { from: 'objectFitPolyfill/src/objectFitPolyfill.js', to: 'wp-polyfill-object-fit.js' }, - { from: 'objectFitPolyfill/dist/objectFitPolyfill.min.js', to: 'wp-polyfill-object-fit.min.js' }, - ], - }, - - // Polyfills - Inert - 'wp-polyfill-inert': { - files: [ - { from: 'wicg-inert/dist/inert.js', to: 'wp-polyfill-inert.js' }, - { from: 'wicg-inert/dist/inert.min.js', to: 'wp-polyfill-inert.min.js' }, - ], - }, - - // Polyfills - URL - 'wp-polyfill-url': { - files: [ - { from: 'core-js-url-browser/url.js', to: 'wp-polyfill-url.js' }, - { from: 'core-js-url-browser/url.min.js', to: 'wp-polyfill-url.min.js' }, - ], - }, - - // Polyfills - DOMRect (same source for both - was minified by webpack) - 'wp-polyfill-dom-rect': { - files: [ - { from: 'polyfill-library/polyfills/__dist/DOMRect/raw.js', to: 'wp-polyfill-dom-rect.js' }, - { from: 'polyfill-library/polyfills/__dist/DOMRect/raw.js', to: 'wp-polyfill-dom-rect.min.js' }, - ], - }, - - // Polyfills - Node.contains (same source for both - was minified by webpack) - 'wp-polyfill-node-contains': { - files: [ - { from: 'polyfill-library/polyfills/__dist/Node.prototype.contains/raw.js', to: 'wp-polyfill-node-contains.js' }, - { from: 'polyfill-library/polyfills/__dist/Node.prototype.contains/raw.js', to: 'wp-polyfill-node-contains.min.js' }, - ], - }, -}; - -/** - * Main execution function. - */ -async function main() { - console.log( 'šŸ“¦ Copying vendor scripts from node_modules...' ); - console.log( ` Build target: ${ buildTarget }/` ); - - // Create vendor directory - fs.mkdirSync( vendorDir, { recursive: true } ); - - let copied = 0; - let skipped = 0; - - for ( const [ vendor, config ] of Object.entries( VENDOR_FILES ) ) { - for ( const file of config.files ) { - const srcPath = path.join( nodeModulesDir, file.from ); - const destPath = path.join( vendorDir, file.to ); - - if ( fs.existsSync( srcPath ) ) { - fs.copyFileSync( srcPath, destPath ); - copied++; - } else { - console.log( ` āš ļø Skipping ${ file.to }: source not found` ); - skipped++; - } - } - } - - console.log( `\nāœ… Vendor scripts copied!` ); - console.log( ` Copied: ${ copied } files` ); - if ( skipped > 0 ) { - console.log( ` Skipped: ${ skipped } files` ); - } -} - -// Run main function -main().catch( ( error ) => { - console.error( 'āŒ Unexpected error:', error ); - process.exit( 1 ); -} );