From 17bcbf682de5740d346990171916173841a81e96 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Fri, 1 Sep 2023 23:40:32 +0100 Subject: [PATCH 01/60] Introduce a method for inserting multiple rows into a table at once. --- src/wp-includes/class-wpdb.php | 94 ++++++++++++++++++++++++++++++++++ tests/phpunit/tests/db.php | 34 ++++++++++++ 2 files changed, 128 insertions(+) diff --git a/src/wp-includes/class-wpdb.php b/src/wp-includes/class-wpdb.php index 1510d82a9f190..5a2e42dfabb95 100644 --- a/src/wp-includes/class-wpdb.php +++ b/src/wp-includes/class-wpdb.php @@ -2488,6 +2488,100 @@ public function insert( $table, $data, $format = null ) { return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' ); } + /** + * Inserts multiple rows into the table in one query. + * + * Examples: + * + * $wpdb->insert_multiple( + * 'table', + * array( + * 'column1', + * 'column2', + * ), + * array( + * array( + * 'column 1 value 1', + * 'column 2 value 1', + * ), + * array( + * 'column 1 value 2', + * 'column 2 value 2', + * ), + * array( + * 'column 1 value 3', + * 'column 2 value 3', + * ), + * ) + * ); + * $wpdb->insert_multiple( + * 'table', + * array( + * 'column1', + * 'column2', + * ), + * array( + * array( + * 'column 1 value 1', + * 1, + * ), + * array( + * 'column 1 value 2', + * 2, + * ), + * array( + * 'column 1 value 3', + * 3, + * ), + * ), + * array( + * '%s', + * '%d', + * ) + * ); + * + * @since x.y.z + * + * @param string $table Table name. + * @todo + * @return int|false The number of rows inserted, or false on error. + */ + public function insert_multiple( $table, array $columns, array $datas, array $format = null ) { + $this->insert_id = 0; + + $values_sql = array(); + $values = array( + $table, + ); + + foreach ( $datas as $data ) { + $data = $this->process_fields( $table, $data, $format ); + if ( false === $data ) { + return false; + } + + $formats = array(); + foreach ( $data as $value ) { + if ( is_null( $value['value'] ) ) { + $formats[] = 'NULL'; + continue; + } + + $formats[] = $value['format']; + $values[] = $value['value']; + } + + $values_sql[] = '(' . implode( ', ', $formats ) . ')'; + } + + $all_formats = implode( ', ', $values_sql ); + $fields = implode( '`, `', $columns ); + $sql = "INSERT INTO %i (`$fields`) VALUES $all_formats"; + + $this->check_current_query = false; + return $this->query( $this->prepare( $sql, $values ) ); + } + /** * Replaces a row in the table or inserts it if it does not exist, based on a PRIMARY KEY or a UNIQUE index. * diff --git a/tests/phpunit/tests/db.php b/tests/phpunit/tests/db.php index 21c34cbadb856..a436dd68d2e37 100644 --- a/tests/phpunit/tests/db.php +++ b/tests/phpunit/tests/db.php @@ -2469,4 +2469,38 @@ public function data_parse_db_host() { ), ); } + + /** + * @dataProvider data_insert_multiple + * @ticket nnnnn + */ + public function test_insert_multiple( $table, $columns, $datas, $format ) { + global $wpdb; + + $result = $wpdb->insert_multiple( + $table, + $columns, + $datas, + $format + ); + + $this->assertSame( 3, $result ); + } + + public function data_insert_multiple() { + global $wpdb; + + return array( + array( + $wpdb->postmeta, + array( 'post_id', 'meta_key', 'meta_value' ), + array( + array( 1, 'foo', 'bar' ), + array( 2, 'foo', 'bar' ), + array( 3, 'foo', 'bar' ), + ), + array( '%d', '%s', '%s' ), + ), + ); + } } From 986ec5277b5438aa78b6d10292ac8c7358351d51 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Fri, 1 Sep 2023 23:42:32 +0100 Subject: [PATCH 02/60] Introduce functions for bulk inserting metadata. --- src/wp-includes/meta.php | 115 +++++++++++++++++++++++++++++++++++++++ src/wp-includes/post.php | 18 ++++++ 2 files changed, 133 insertions(+) diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 0b9f08544e498..8dd256888ca4c 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -158,6 +158,121 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = return $mid; } +/** + * Adds multiple items of metadata for the specified object. + * + * For historial reasons the data should be slashed. + * + * This is more performant than calling `add_metadata()` multiple times because it + * only queries the database once and only clears the meta cache once. + * + * This function will always insert all of the provided metadata even if meta data with + * matching keys already exist. If the insert fails, no metadata will be inserted. It's + * not possible for some rows to be inserted and not others. + * + * @todo: + * + * [X] change this function so it returns the array of mids instead of true + * [ ] need to account for the return value of "add_{$meta_type}_metadata" for each key + * [ ] need tests to cover when the "add_{$meta_type}_metadata" filter returns a value for a key + * [ ] confirm that the method of getting the mids via `range()` is reliable + * + * @since x.y.z + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', + * or any other object type with an associated meta table. + * @param int $object_id ID of the object metadata is for. + * @param array $meta_fields Metadata values keyed by their meta key. + * @return int[]|false Array of meta IDs on success, false on failure. + */ +function bulk_add_metadata( $meta_type, $object_id, array $meta_fields ) { + global $wpdb; + + if ( ! $meta_type || ! $meta_fields || ! is_numeric( $object_id ) ) { + return false; + } + + $object_id = absint( $object_id ); + if ( ! $object_id ) { + return false; + } + + $table = _get_meta_table( $meta_type ); + if ( ! $table ) { + return false; + } + + $meta_subtype = get_object_subtype( $meta_type, $object_id ); + $column = sanitize_key( $meta_type . '_id' ); + $data = array(); + $return = []; + + foreach ( $meta_fields as $meta_key => $meta_value ) { + $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); + // @todo reinstate slashed data handling + + /** This filter is documented in wp-includes/meta.php */ + $check = apply_filters( "add_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, false ); + if ( null !== $check ) { + $return[ $meta_key ] = $check; + continue; + } + + // Update the values in the array with sanitized data. + $meta_fields[ $meta_key ] = $meta_value; + + /** This action is documented in wp-includes/meta.php */ + do_action( "add_{$meta_type}_meta", $object_id, $meta_key, $meta_value ); + + $data[] = array( + $object_id, + $meta_key, + maybe_serialize( $meta_value ), + ); + } + + // If there is no data to save, don't attempt to save it. + if ( ! $data ) { + return $return; + } + + $inserted = $wpdb->insert_multiple( + $table, + array( + $column, + 'meta_key', + 'meta_value', + ), + $data, + array( + '%d', + '%s', + '%s', + ) + ); + + if ( ! $inserted ) { + return false; + } + + $first_mid = (int) $wpdb->insert_id; + $all_mids = range( $first_mid, $first_mid + count( $data ) - 1 ); + + $i = 0; + + wp_cache_delete( $object_id, $meta_type . '_meta' ); + + foreach ( $meta_fields as $meta_key => $meta_value ) { + /** This action is documented in wp-includes/meta.php */ + do_action( "added_{$meta_type}_meta", $all_mids[ $i ], $object_id, $meta_key, $meta_value ); + $i++; + } + + return true; +} + /** * Updates metadata for the specified object. If no value already exists for the specified object * ID and metadata key, the metadata will be added. diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index f71a0a061d391..f573cc97a2e23 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2470,6 +2470,24 @@ function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { return add_metadata( 'post', $post_id, $meta_key, $meta_value, $unique ); } +/** + * Adds multiple items of meta data to a post. + * + * This function will always insert all of the provided metadata even if meta data with + * matching keys already exist. If the insert fails, no metadata will be inserted. It's + * not possible for some rows to be inserted and not others. + * + * @since x.y.z + * + * @param int $post_id Post ID. + * @param string $meta_key Metadata name. + * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. + * @return bool True on success, false on failure. + */ +function bulk_add_post_meta( $post_id, array $meta_fields ) { + return bulk_add_metadata( 'post', $post_id, $meta_fields ); +} + /** * Deletes a post meta field for the given post ID. * From 26d8b49f4c1254771ec53daffbbee1df923ebf92 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Fri, 1 Sep 2023 23:43:17 +0100 Subject: [PATCH 03/60] Add PHPBench tests. --- .github/workflows/phpbench-tests.yml | 104 ++++++++++++++++++++++++ .gitignore | 1 + composer.json | 2 + tests/phpbench/InsertMultipleBench.php | 108 +++++++++++++++++++++++++ 4 files changed, 215 insertions(+) create mode 100644 .github/workflows/phpbench-tests.yml create mode 100644 tests/phpbench/InsertMultipleBench.php diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml new file mode 100644 index 0000000000000..57ed81fecc195 --- /dev/null +++ b/.github/workflows/phpbench-tests.yml @@ -0,0 +1,104 @@ +name: PHPBench Tests + +on: + push: + branches: + - trunk + pull_request: + branches: + - trunk + workflow_dispatch: + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + # Runs the PHPBench tests for WordPress. + bench-php: + name: PHP ${{ matrix.php }} + runs-on: ubuntu-latest + if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} + strategy: + fail-fast: false + matrix: + php: + - '7.4' + - '8.2' + + env: + LOCAL_PHP: ${{ matrix.php }}-fpm + + steps: + - name: Configure environment variables + run: | + echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV + echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + + - name: Set up Node.js + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + with: + node-version-file: '.nvmrc' + cache: npm + + ## + # This allows Composer dependencies to be installed using a single step. + # + # Since the tests are currently run within the Docker containers where the PHP version varies, + # the same PHP version needs to be configured for the action runner machine so that the correct + # dependency versions are installed and cached. + ## + - name: Set up PHP + uses: shivammathur/setup-php@d30ad8b1843ace22e6698ab99bbafaa747b6bd0d # v2.24.0 + with: + php-version: '${{ inputs.php }}' + coverage: none + + # 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@83af392bf5f031813d25e6fe4cd626cdba9a2df6 # v2.2.0 + with: + custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") + + - name: Install npm dependencies + run: npm ci + + - name: General debug information + run: | + npm --version + node --version + curl --version + git --version + svn --version + composer --version + locale -a + + - name: Docker debug information + run: | + docker -v + docker-compose -v + + - name: Start Docker environment + run: | + npm run env:start + + - name: Log running Docker containers + run: docker ps -a + + - name: WordPress Docker container debug information + run: | + docker-compose run --rm mysql ${{ env.LOCAL_DB_TYPE }} --version + docker-compose run --rm php php --version + docker-compose run --rm php php -m + docker-compose run --rm php php -i + docker-compose run --rm php locale -a + + - name: Install WordPress + run: npm run env:install + + - name: Run PHPBench tests + run: composer run bench diff --git a/.gitignore b/.gitignore index c0d8a2d79f4bd..b517630618e1e 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ wp-tests-config.php /packagehash.txt /artifacts /setup.log +/.phpbench # Files and folders that get created in wp-content /src/wp-content/blogs.dir diff --git a/composer.json b/composer.json index 46cc81b28acb2..13f86f63422e2 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", "squizlabs/php_codesniffer": "3.6.0", "wp-coding-standards/wpcs": "~2.3.0", + "phpbench/phpbench": "^1.2", "phpcompatibility/phpcompatibility-wp": "~2.1.3", "yoast/phpunit-polyfills": "^1.1.0" }, @@ -25,6 +26,7 @@ } }, "scripts": { + "bench": "docker-compose run php ./vendor/bin/phpbench run tests/phpbench --report=aggregate", "compat": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --standard=phpcompat.xml.dist --report=summary,source", "format": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf --report=summary,source", "lint": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --report=summary,source", diff --git a/tests/phpbench/InsertMultipleBench.php b/tests/phpbench/InsertMultipleBench.php new file mode 100644 index 0000000000000..7a2f490570a41 --- /dev/null +++ b/tests/phpbench/InsertMultipleBench.php @@ -0,0 +1,108 @@ +post_id = wp_insert_post( + [ + 'post_title' => 'Test Post', + 'post_status' => 'publish', + ] + ); + } + + /** + * @ParamProviders("provideMetaData") + * @Revs(10) + * @Iterations(3) + * @Warmup(1) + */ + public function benchAddMeta( array $meta ): void { + foreach ( $meta as $key => $value ) { + add_post_meta( $this->post_id, $key, $value ); + } + } + + /** + * @ParamProviders("provideMetaData") + * @Revs(10) + * @Iterations(3) + * @Warmup(1) + */ + public function benchAddMultipleMeta( array $meta ): void { + bulk_add_post_meta( $this->post_id, $meta ); + } + + public function provideMetaData(): Generator { + $rows = []; + // 3 rows ranging from 10 to 30 bytes. + for ( $i = 1; $i <= 3; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i ); + } + yield '3 small rows' => $rows; + + $rows = []; + // 20 rows ranging from 10 to 200 bytes. + for ( $i = 1; $i <= 20; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i ); + } + yield '20 small rows' => $rows; + + $rows = []; + // 50 rows ranging from 5 to 250 bytes. + for ( $i = 1; $i <= 50; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '12345', $i ); + } + yield '50 small rows' => $rows; + + $rows = []; + // 100 rows ranging from 5 to 500 bytes. + for ( $i = 1; $i <= 100; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '12345', $i ); + } + yield '100 small rows' => $rows; + + $rows = []; + // 10 rows ranging from 100 bytes to 1 KB. + for ( $i = 1; $i <= 10; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 10 ); + } + yield '10 medium rows' => $rows; + + $rows = []; + // 10 rows ranging from 1 KB to 10 KB. + for ( $i = 1; $i <= 10; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 100 ); + } + yield '10 large rows' => $rows; + + $rows = []; + // 3 rows ranging from 30 KB to 90 KB. + for ( $i = 1; $i <= 3; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 3000 ); + } + yield '3 xl rows' => $rows; + + $rows = []; + // 10 rows ranging from 10 KB to 100 KB. + for ( $i = 1; $i <= 10; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 1000 ); + } + yield '10 xl rows' => $rows; + + $rows = []; + // 20 rows ranging from 10 KB to 200 KB. + for ( $i = 1; $i <= 20; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 1000 ); + } + yield '20 xl rows' => $rows; + } +} From 7539a67149fa9a6d8cded86f8eb6ed2454f658f1 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Fri, 1 Sep 2023 23:45:49 +0100 Subject: [PATCH 04/60] Fix indentation level. --- .github/workflows/phpbench-tests.yml | 146 +++++++++++++-------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index 57ed81fecc195..7ddb5fae6db3d 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -29,76 +29,76 @@ jobs: env: LOCAL_PHP: ${{ matrix.php }}-fpm - steps: - - name: Configure environment variables - run: | - echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV - echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV - - - name: Checkout repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - - - name: Set up Node.js - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm - - ## - # This allows Composer dependencies to be installed using a single step. - # - # Since the tests are currently run within the Docker containers where the PHP version varies, - # the same PHP version needs to be configured for the action runner machine so that the correct - # dependency versions are installed and cached. - ## - - name: Set up PHP - uses: shivammathur/setup-php@d30ad8b1843ace22e6698ab99bbafaa747b6bd0d # v2.24.0 - with: - php-version: '${{ inputs.php }}' - coverage: none - - # 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@83af392bf5f031813d25e6fe4cd626cdba9a2df6 # v2.2.0 - with: - custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") - - - name: Install npm dependencies - run: npm ci - - - name: General debug information - run: | - npm --version - node --version - curl --version - git --version - svn --version - composer --version - locale -a - - - name: Docker debug information - run: | - docker -v - docker-compose -v - - - name: Start Docker environment - run: | - npm run env:start - - - name: Log running Docker containers - run: docker ps -a - - - name: WordPress Docker container debug information - run: | - docker-compose run --rm mysql ${{ env.LOCAL_DB_TYPE }} --version - docker-compose run --rm php php --version - docker-compose run --rm php php -m - docker-compose run --rm php php -i - docker-compose run --rm php locale -a - - - name: Install WordPress - run: npm run env:install - - - name: Run PHPBench tests - run: composer run bench + steps: + - name: Configure environment variables + run: | + echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV + echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + + - name: Set up Node.js + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + with: + node-version-file: '.nvmrc' + cache: npm + + ## + # This allows Composer dependencies to be installed using a single step. + # + # Since the tests are currently run within the Docker containers where the PHP version varies, + # the same PHP version needs to be configured for the action runner machine so that the correct + # dependency versions are installed and cached. + ## + - name: Set up PHP + uses: shivammathur/setup-php@d30ad8b1843ace22e6698ab99bbafaa747b6bd0d # v2.24.0 + with: + php-version: '${{ inputs.php }}' + coverage: none + + # 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@83af392bf5f031813d25e6fe4cd626cdba9a2df6 # v2.2.0 + with: + custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") + + - name: Install npm dependencies + run: npm ci + + - name: General debug information + run: | + npm --version + node --version + curl --version + git --version + svn --version + composer --version + locale -a + + - name: Docker debug information + run: | + docker -v + docker-compose -v + + - name: Start Docker environment + run: | + npm run env:start + + - name: Log running Docker containers + run: docker ps -a + + - name: WordPress Docker container debug information + run: | + docker-compose run --rm mysql ${{ env.LOCAL_DB_TYPE }} --version + docker-compose run --rm php php --version + docker-compose run --rm php php -m + docker-compose run --rm php php -i + docker-compose run --rm php locale -a + + - name: Install WordPress + run: npm run env:install + + - name: Run PHPBench tests + run: composer run bench From ccfd9f2f2157429bafb326eed38a072ea3f90420 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sat, 2 Sep 2023 10:47:46 +0100 Subject: [PATCH 05/60] More test fixes. --- .github/workflows/phpbench-tests.yml | 2 +- tests/phpbench/InsertMultipleBench.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index 7ddb5fae6db3d..ad1e87b81bb6b 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -54,7 +54,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@d30ad8b1843ace22e6698ab99bbafaa747b6bd0d # v2.24.0 with: - php-version: '${{ inputs.php }}' + php-version: '${{ matrix.php }}' coverage: none # Since Composer dependencies are installed using `composer update` and no lock file is in version control, diff --git a/tests/phpbench/InsertMultipleBench.php b/tests/phpbench/InsertMultipleBench.php index 7a2f490570a41..87dc62632550e 100644 --- a/tests/phpbench/InsertMultipleBench.php +++ b/tests/phpbench/InsertMultipleBench.php @@ -6,6 +6,7 @@ /** * @BeforeMethods("setUp") + * @AfterMethods("tearDown") */ class InsertMultipleBench { public $post_id; @@ -19,6 +20,10 @@ public function setUp(): void { ); } + public function tearDown(): void { + wp_delete_post( $this->post_id, true ); + } + /** * @ParamProviders("provideMetaData") * @Revs(10) From bde79b0fba673ae0eff87f6c954fbf639de53834 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sat, 2 Sep 2023 10:48:33 +0100 Subject: [PATCH 06/60] Disable these workflows while we're getting set up. --- .github/workflows/end-to-end-tests.yml | 159 ---------------- .github/workflows/javascript-tests.yml | 135 -------------- .github/workflows/performance.yml | 249 ------------------------- 3 files changed, 543 deletions(-) delete mode 100644 .github/workflows/end-to-end-tests.yml delete mode 100644 .github/workflows/javascript-tests.yml delete mode 100644 .github/workflows/performance.yml diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml deleted file mode 100644 index 1e3e442752d58..0000000000000 --- a/.github/workflows/end-to-end-tests.yml +++ /dev/null @@ -1,159 +0,0 @@ -name: End-to-end Tests - -on: - # The end-to-end test suite was introduced in WordPress 5.3. - push: - branches: - - trunk - - '5.[3-9]' - - '[6-9].[0-9]' - tags: - - '[0-9]+.[0-9]' - - '[0-9]+.[0-9].[0-9]+' - - '![34].[0-9].[0-9]+' - - '!5.[0-2].[0-9]+' - pull_request: - branches: - - trunk - - '5.[3-9]' - - '[6-9].[0-9]' - workflow_dispatch: - -# Cancels all previous workflow runs for pull requests that have not completed. -concurrency: - # The concurrency group contains the workflow name and the branch name for pull requests - # or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} - cancel-in-progress: true - -# Disable permissions for all available scopes by default. -# Any needed permissions should be configured at the job level. -permissions: {} - -env: - LOCAL_DIR: build - -jobs: - # Runs the end-to-end test suite. - # - # Performs the following steps: - # - Sets environment variables. - # - Checks out the repository. - # - Sets up Node.js. - # - Logs debug information about the GitHub Action runner. - # - Installs npm dependencies. - # - Builds WordPress to run from the `build` directory. - # - Starts the WordPress Docker container. - # - Logs the running Docker containers. - # - Logs Docker debug information (about both the Docker installation within the runner and the WordPress container). - # - Install WordPress within the Docker container. - # - Run the E2E tests. - # - Ensures version-controlled files are not modified or deleted. - e2e-tests: - name: E2E Tests with SCRIPT_DEBUG ${{ matrix.LOCAL_SCRIPT_DEBUG && 'enabled' || 'disabled' }} - runs-on: ubuntu-latest - permissions: - contents: read - timeout-minutes: 20 - if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} - strategy: - fail-fast: false - matrix: - LOCAL_SCRIPT_DEBUG: [ true, false ] - - steps: - - name: Configure environment variables - run: | - echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV - echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV - - - name: Checkout repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - - - name: Set up Node.js - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm - - - name: Log debug information - run: | - npm --version - node --version - curl --version - git --version - svn --version - locale -a - - - name: Install npm Dependencies - run: npm ci - - - name: Build WordPress - run: npm run build - - - name: Start Docker environment - run: | - npm run env:start - - - name: Log running Docker containers - run: docker ps -a - - - name: Docker debug information - run: | - docker -v - docker-compose -v - docker-compose run --rm mysql mysql --version - docker-compose run --rm php php --version - docker-compose run --rm php php -m - docker-compose run --rm php php -i - docker-compose run --rm php locale -a - - - name: Install WordPress - env: - LOCAL_SCRIPT_DEBUG: ${{ matrix.LOCAL_SCRIPT_DEBUG }} - run: npm run env:install - - - name: Run E2E tests - run: npm run test:e2e - - - name: Archive debug artifacts (screenshots, HTML snapshots) - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - if: always() - with: - name: failures-artifacts - path: artifacts - if-no-files-found: ignore - - - name: Ensure version-controlled files are not modified or deleted - run: git diff --exit-code - - failed-workflow: - name: Failed workflow tasks - runs-on: ubuntu-latest - permissions: - actions: write - needs: [ e2e-tests ] - if: | - always() && - github.repository == 'WordPress/wordpress-develop' && - github.event_name != 'pull_request' && - github.run_attempt < 2 && - ( - needs.e2e-tests.result == 'cancelled' || needs.e2e-tests.result == 'failure' - ) - steps: - - name: Dispatch workflow run - uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 - with: - retries: 2 - retry-exempt-status-codes: 418 - script: | - github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'failed-workflow.yml', - ref: 'trunk', - inputs: { - run_id: '${{ github.run_id }}' - } - }); diff --git a/.github/workflows/javascript-tests.yml b/.github/workflows/javascript-tests.yml deleted file mode 100644 index 2677096b1b7e0..0000000000000 --- a/.github/workflows/javascript-tests.yml +++ /dev/null @@ -1,135 +0,0 @@ -name: JavaScript Tests - -on: - # JavaScript testing was introduced in WordPress 3.8. - push: - branches: - - trunk - - '3.[89]' - - '[4-9].[0-9]' - tags: - - '[0-9]+.[0-9]' - - '[0-9]+.[0-9].[0-9]+' - - '!3.7.[0-9]+' - pull_request: - branches: - - trunk - - '3.[89]' - - '[4-9].[0-9]' - paths: - # Any change to a JavaScript file should run tests. - - '**.js' - # These files configure npm. Changes could affect the outcome. - - 'package*.json' - # This file configures ESLint. Changes could affect the outcome. - - '.eslintignore' - # This file configures JSHint. Changes could affect the outcome. - - '.jshintrc' - # Any change to the QUnit directory should run tests. - - 'tests/qunit/**' - # Changes to workflow files should always verify all workflows are successful. - - '.github/workflows/*.yml' - workflow_dispatch: - -# Cancels all previous workflow runs for pull requests that have not completed. -concurrency: - # The concurrency group contains the workflow name and the branch name for pull requests - # or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} - cancel-in-progress: true - -# Disable permissions for all available scopes by default. -# Any needed permissions should be configured at the job level. -permissions: {} - -jobs: - # Runs the QUnit tests for WordPress. - # - # Performs the following steps: - # - Checks out the repository. - # - Sets up Node.js. - # - Logs debug information about the GitHub Action runner. - # - Installs npm dependencies. - # - Run the WordPress QUnit tests. - # - Ensures version-controlled files are not modified or deleted. - test-js: - name: QUnit Tests - runs-on: ubuntu-latest - permissions: - contents: read - timeout-minutes: 20 - if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} - - steps: - - name: Checkout repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - - - name: Set up Node.js - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm - - - name: Log debug information - run: | - npm --version - node --version - git --version - svn --version - - - name: Install npm Dependencies - run: npm ci - - - name: Run QUnit tests - run: npm run grunt qunit:compiled - - - name: Ensure version-controlled files are not modified or deleted - run: git diff --exit-code - - slack-notifications: - name: Slack Notifications - uses: WordPress/wordpress-develop/.github/workflows/slack-notifications.yml@trunk - permissions: - actions: read - contents: read - needs: [ test-js ] - if: ${{ github.repository == 'WordPress/wordpress-develop' && github.event_name != 'pull_request' && always() }} - with: - calling_status: ${{ needs.test-js.result == 'success' && 'success' || needs.test-js.result == 'cancelled' && 'cancelled' || 'failure' }} - secrets: - SLACK_GHA_SUCCESS_WEBHOOK: ${{ secrets.SLACK_GHA_SUCCESS_WEBHOOK }} - SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} - SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} - SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} - - failed-workflow: - name: Failed workflow tasks - runs-on: ubuntu-latest - permissions: - actions: write - needs: [ slack-notifications ] - if: | - always() && - github.repository == 'WordPress/wordpress-develop' && - github.event_name != 'pull_request' && - github.run_attempt < 2 && - ( - needs.test-js.result == 'cancelled' || needs.test-js.result == 'failure' - ) - - steps: - - name: Dispatch workflow run - uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 - with: - retries: 2 - retry-exempt-status-codes: 418 - script: | - github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'failed-workflow.yml', - ref: 'trunk', - inputs: { - run_id: '${{ github.run_id }}' - } - }); diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml deleted file mode 100644 index 53b9efb6ba462..0000000000000 --- a/.github/workflows/performance.yml +++ /dev/null @@ -1,249 +0,0 @@ -name: Performance Tests - -on: - push: - branches: - - trunk - - '6.[2-9]' - - '[7-9].[0-9]' - tags: - - '[0-9]+.[0-9]' - - '[0-9]+.[0-9].[0-9]+' - - '![45].[0-9].[0-9]+' - - '!6.[01].[0-9]+' - pull_request: - branches: - - trunk - - '6.[2-9]' - - '[7-9].[0-9]' - workflow_dispatch: - -# Cancels all previous workflow runs for pull requests that have not completed. -concurrency: - # The concurrency group contains the workflow name and the branch name for pull requests - # or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} - cancel-in-progress: true - -# Disable permissions for all available scopes by default. -# Any needed permissions should be configured at the job level. -permissions: {} - -env: - # Performance testing should be performed in an environment reflecting a standard production environment. - WP_DEBUG: false - SCRIPT_DEBUG: false - SAVEQUERIES : false - WP_DEVELOPMENT_MODE: '' - - # This workflow takes two sets of measurements — one for the current commit, - # and another against a consistent version that is used as a baseline measurement. - # This is done to isolate variance in measurements caused by the GitHub runners - # from differences caused by code changes between commits. The BASE_TAG value here - # represents the version being used for baseline measurements. It should only be - # changed if we want to normalize results against a different baseline. - BASE_TAG: '6.1.1' - LOCAL_DIR: build - -jobs: - # Runs the performance test suite. - # - # Performs the following steps: - # - Configure environment variables. - # - Checkout repository. - # - Set up Node.js. - # - Log debug information. - # - Install npm dependencies. - # - Build WordPress. - # - Start Docker environment. - # - Log running Docker containers. - # - Docker debug information. - # - Install WordPress. - # - Install WordPress Importer plugin. - # - Import mock data. - # - Update permalink structure. - # - Install MU plugin. - # - Run performance tests (current commit). - # - Print performance tests results. - # - Set the environment to the baseline version. - # - Run baseline performance tests. - # - Print base line performance tests results. - # - Set the base sha. - # - Set commit details. - # - Publish performance results. - # - Ensure version-controlled files are not modified or deleted. - # - Dispatch workflow run. - performance: - name: Run performance tests - runs-on: ubuntu-latest - permissions: - contents: read - if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} - - steps: - - name: Configure environment variables - run: | - echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV - echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV - - - name: Checkout repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - - - name: Set up Node.js - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm - - - name: Log debug information - run: | - npm --version - node --version - curl --version - git --version - svn --version - locale -a - - - name: Install npm dependencies - run: npm ci - - - name: Build WordPress - run: npm run build - - - name: Start Docker environment - run: | - npm run env:start - - - name: Log running Docker containers - run: docker ps -a - - - name: Docker debug information - run: | - docker -v - docker-compose -v - docker-compose run --rm mysql mysql --version - docker-compose run --rm php php --version - docker-compose run --rm php php -m - docker-compose run --rm php php -i - docker-compose run --rm php locale -a - - - name: Install WordPress - run: npm run env:install - - - name: Install WordPress Importer plugin - run: npm run env:cli -- plugin install wordpress-importer --activate --path=/var/www/${{ env.LOCAL_DIR }} - - - name: Import mock data - run: | - curl -O https://raw.githubusercontent.com/WordPress/theme-test-data/b9752e0533a5acbb876951a8cbb5bcc69a56474c/themeunittestdata.wordpress.xml - npm run env:cli -- import themeunittestdata.wordpress.xml --authors=create --path=/var/www/${{ env.LOCAL_DIR }} - rm themeunittestdata.wordpress.xml - - - name: Update permalink structure - run: | - npm run env:cli -- rewrite structure '/%year%/%monthnum%/%postname%/' --path=/var/www/${{ env.LOCAL_DIR }} - - - name: Install MU plugin - run: | - mkdir ./${{ env.LOCAL_DIR }}/wp-content/mu-plugins - cp ./tests/performance/wp-content/mu-plugins/server-timing.php ./${{ env.LOCAL_DIR }}/wp-content/mu-plugins/server-timing.php - - - name: Run performance tests (current commit) - run: npm run test:performance - - - name: Print performance tests results - run: "node ./tests/performance/results.js" - - - name: Set the environment to the baseline version - run: | - npm run env:cli -- core update --version=${{ env.BASE_TAG }} --force --path=/var/www/${{ env.LOCAL_DIR }} - npm run env:cli -- core version --path=/var/www/${{ env.LOCAL_DIR }} - - - name: Run baseline performance tests - run: npm run test:performance -- --prefix=base - - - name: Print base line performance tests results - run: "node ./tests/performance/results.js --prefix=base" - - - name: Set the base sha - # Only needed when publishing results. - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }} - uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 - id: base-sha - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const baseRef = await github.rest.git.getRef({ owner: context.repo.owner, repo: context.repo.repo, ref: 'tags/${{ env.BASE_TAG }}' }); - return baseRef.data.object.sha; - - - name: Set commit details - # Only needed when publishing results. - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }} - uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 - id: commit-timestamp - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const commit_details = await github.rest.git.getCommit({ owner: context.repo.owner, repo: context.repo.repo, commit_sha: context.sha }); - return parseInt((new Date( commit_details.data.author.date ).getTime() / 1000).toFixed(0)) - - - name: Publish performance results - # Only publish results on pushes to trunk. - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }} - env: - BASE_SHA: ${{ steps.base-sha.outputs.result }} - COMMITTED_AT: ${{ steps.commit-timestamp.outputs.result }} - CODEVITALS_PROJECT_TOKEN: ${{ secrets.CODEVITALS_PROJECT_TOKEN }} - HOST_NAME: "www.codevitals.run" - run: node ./tests/performance/log-results.js $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA $BASE_SHA $COMMITTED_AT $HOST_NAME - - - name: Ensure version-controlled files are not modified or deleted - run: git diff --exit-code - - slack-notifications: - name: Slack Notifications - uses: WordPress/wordpress-develop/.github/workflows/slack-notifications.yml@trunk - permissions: - actions: read - contents: read - needs: [ performance ] - if: ${{ github.repository == 'WordPress/wordpress-develop' && github.event_name != 'pull_request' && always() }} - with: - calling_status: ${{ needs.performance.result == 'success' && 'success' || needs.performance.result == 'cancelled' && 'cancelled' || 'failure' }} - secrets: - SLACK_GHA_SUCCESS_WEBHOOK: ${{ secrets.SLACK_GHA_SUCCESS_WEBHOOK }} - SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} - SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} - SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} - - failed-workflow: - name: Failed workflow tasks - runs-on: ubuntu-latest - permissions: - actions: write - needs: [ slack-notifications ] - if: | - always() && - github.repository == 'WordPress/wordpress-develop' && - github.event_name != 'pull_request' && - github.run_attempt < 2 && - ( - needs.performance.result == 'cancelled' || needs.performance.result == 'failure' - ) - - steps: - - name: Dispatch workflow run - uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 - with: - retries: 2 - retry-exempt-status-codes: 418 - script: | - github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'failed-workflow.yml', - ref: 'trunk', - inputs: { - run_id: '${{ github.run_id }}' - } - }); From 63ad9a0dfb764b9dcb16d9e832511247afc4b56f Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sat, 2 Sep 2023 11:37:26 +0100 Subject: [PATCH 07/60] Docs. --- src/wp-includes/class-wpdb.php | 21 ++++++++++++++++----- src/wp-includes/meta.php | 13 ++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/wp-includes/class-wpdb.php b/src/wp-includes/class-wpdb.php index 5a2e42dfabb95..b4e7f785c783b 100644 --- a/src/wp-includes/class-wpdb.php +++ b/src/wp-includes/class-wpdb.php @@ -2477,7 +2477,7 @@ public function remove_placeholder_escape( $query ) { * Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped). * Sending a null value will cause the column to be set to NULL - the corresponding * format is ignored in this case. - * @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`. + * @param string[]|string $format Optional. An array of formats to be mapped to each of the values in `$data`. * If string, that format will be used for all of the values in `$data`. * A format is one of '%d', '%f', '%s' (integer, float, string). * If omitted, all values in `$data` will be treated as strings unless otherwise @@ -2491,6 +2491,9 @@ public function insert( $table, $data, $format = null ) { /** * Inserts multiple rows into the table in one query. * + * If the insert fails, no rows will be inserted. It's not possible for some rows + * to be inserted and not others. + * * Examples: * * $wpdb->insert_multiple( @@ -2542,11 +2545,19 @@ public function insert( $table, $data, $format = null ) { * * @since x.y.z * - * @param string $table Table name. - * @todo + * @param string $table Table name. + * @param string[] $columns Array of column names. + * @param array[] $rows Array of rows of values to insert. Values should be "raw" (should not be SQL escaped). + * Sending a null value will cause the column to be set to NULL - the corresponding + * format is ignored in this case. + * @param string[]|string $format Optional. An array of formats to be mapped to each of the values in each row. + * If string, that format will be used for all of the values in `$data`. + * A format is one of '%d', '%f', '%s' (integer, float, string). + * If omitted, all values in `$data` will be treated as strings unless otherwise + * specified in wpdb::$field_types. Default null. * @return int|false The number of rows inserted, or false on error. */ - public function insert_multiple( $table, array $columns, array $datas, array $format = null ) { + public function insert_multiple( $table, array $columns, array $rows, array $format = null ) { $this->insert_id = 0; $values_sql = array(); @@ -2554,7 +2565,7 @@ public function insert_multiple( $table, array $columns, array $datas, array $fo $table, ); - foreach ( $datas as $data ) { + foreach ( $rows as $data ) { $data = $this->process_fields( $table, $data, $format ); if ( false === $data ) { return false; diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 8dd256888ca4c..632af5350406e 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -161,18 +161,22 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = /** * Adds multiple items of metadata for the specified object. * - * For historial reasons the data should be slashed. + * For historial reasons the metadata values should be slashed. * * This is more performant than calling `add_metadata()` multiple times because it * only queries the database once and only clears the meta cache once. * - * This function will always insert all of the provided metadata even if meta data with - * matching keys already exist. If the insert fails, no metadata will be inserted. It's - * not possible for some rows to be inserted and not others. + * This function will always insert all of the provided metadata even if matching keys + * already exist. This behaviour matches that of add_metadata() when its `$unique` + * parameter is set to false. + * + * If the insert fails, no metadata will be inserted. It's not possible for some rows + * to be inserted and not others. * * @todo: * * [X] change this function so it returns the array of mids instead of true + * [ ] reinstate slashed data handling * [ ] need to account for the return value of "add_{$meta_type}_metadata" for each key * [ ] need tests to cover when the "add_{$meta_type}_metadata" filter returns a value for a key * [ ] confirm that the method of getting the mids via `range()` is reliable @@ -211,7 +215,6 @@ function bulk_add_metadata( $meta_type, $object_id, array $meta_fields ) { foreach ( $meta_fields as $meta_key => $meta_value ) { $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); - // @todo reinstate slashed data handling /** This filter is documented in wp-includes/meta.php */ $check = apply_filters( "add_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, false ); From 25568cf705a8f0092f563695ba29243442fc7f20 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sat, 2 Sep 2023 11:43:35 +0100 Subject: [PATCH 08/60] More tests. --- tests/phpunit/tests/db.php | 34 ---------- tests/phpunit/tests/db/insertMultiple.php | 78 +++++++++++++++++++++++ 2 files changed, 78 insertions(+), 34 deletions(-) create mode 100644 tests/phpunit/tests/db/insertMultiple.php diff --git a/tests/phpunit/tests/db.php b/tests/phpunit/tests/db.php index a436dd68d2e37..21c34cbadb856 100644 --- a/tests/phpunit/tests/db.php +++ b/tests/phpunit/tests/db.php @@ -2469,38 +2469,4 @@ public function data_parse_db_host() { ), ); } - - /** - * @dataProvider data_insert_multiple - * @ticket nnnnn - */ - public function test_insert_multiple( $table, $columns, $datas, $format ) { - global $wpdb; - - $result = $wpdb->insert_multiple( - $table, - $columns, - $datas, - $format - ); - - $this->assertSame( 3, $result ); - } - - public function data_insert_multiple() { - global $wpdb; - - return array( - array( - $wpdb->postmeta, - array( 'post_id', 'meta_key', 'meta_value' ), - array( - array( 1, 'foo', 'bar' ), - array( 2, 'foo', 'bar' ), - array( 3, 'foo', 'bar' ), - ), - array( '%d', '%s', '%s' ), - ), - ); - } } diff --git a/tests/phpunit/tests/db/insertMultiple.php b/tests/phpunit/tests/db/insertMultiple.php new file mode 100644 index 0000000000000..06985dea67ce0 --- /dev/null +++ b/tests/phpunit/tests/db/insertMultiple.php @@ -0,0 +1,78 @@ +wpdb = $GLOBALS['wpdb']; + } + + /** + * @ticket 59269 + */ + public function test_correct_rows_are_inserted() { + $table = $this->wpdb->postmeta; + $columns = array( + 'post_id', + 'meta_key', + 'meta_value', + ); + $datas = array( + array( 1, 'key1', 'value1' ), + array( 2, 'key2', 'value2' ), + array( 3, 'key3', 'value3' ), + ); + $format = array( + '%d', + '%s', + '%s', + ); + + $inserted = $this->wpdb->insert_multiple( + $table, + $columns, + $datas, + $format + ); + + $rows = $this->wpdb->get_results( + " + SELECT post_id, meta_key, meta_value + FROM $table + ORDER BY post_id ASC + ", + ARRAY_A + ); + + $expected_rows = array( + array( + 'post_id' => '1', + 'meta_key' => 'key1', + 'meta_value' => 'value1', + ), + array( + 'post_id' => '2', + 'meta_key' => 'key2', + 'meta_value' => 'value2', + ), + array( + 'post_id' => '3', + 'meta_key' => 'key3', + 'meta_value' => 'value3', + ), + ); + + $this->assertSame( 3, $inserted ); + $this->assertSame( $expected_rows, $rows ); + } +} From 6628c5d2395026d44c66bd02c20960cf79ffc3fc Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sat, 2 Sep 2023 11:56:19 +0100 Subject: [PATCH 09/60] Add all the other bulk meta functions. --- src/wp-includes/comment.php | 16 ++++++++++++++++ src/wp-includes/ms-site.php | 16 ++++++++++++++++ src/wp-includes/taxonomy.php | 21 +++++++++++++++++++++ src/wp-includes/user.php | 16 ++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index b538b2c5c12ef..5be660ead8efa 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -440,6 +440,22 @@ function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false return add_metadata( 'comment', $comment_id, $meta_key, $meta_value, $unique ); } +/** + * Adds multiple items of meta data to a comment. + * + * @todo docs + * + * @since x.y.z + * + * @param int $comment_id Comment ID. + * @param string $meta_key Metadata name. + * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. + * @return int[]|false Array of meta IDs on success, false on failure. + */ +function bulk_add_comment_meta( $comment_id, array $meta_fields ) { + return bulk_add_metadata( 'comment', $comment_id, $meta_fields ); +} + /** * Removes metadata matching criteria from a comment. * diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index eaee99f5685c2..1e9b1d4cbcdcc 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1039,6 +1039,22 @@ function add_site_meta( $site_id, $meta_key, $meta_value, $unique = false ) { return add_metadata( 'blog', $site_id, $meta_key, $meta_value, $unique ); } +/** + * Adds multiple items of meta data to a site. + * + * @todo docs + * + * @since x.y.z + * + * @param int $site_id Site ID. + * @param string $meta_key Metadata name. + * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. + * @return int[]|false Array of meta IDs on success, false on failure. + */ +function bulk_add_site_meta( $site_id, array $meta_fields ) { + return bulk_add_metadata( 'site', $site_id, $meta_fields ); +} + /** * Removes metadata matching criteria from a site. * diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 52a5705331798..19f770c1c31ec 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1351,6 +1351,27 @@ function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) { return add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique ); } +/** + * Adds multiple items of meta data to a term. + * + * @todo docs + * + * @since x.y.z + * + * @param int $term_id Term ID. + * @param string $meta_key Metadata name. + * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. + * @return int[]|false|WP_Error Array of meta IDs on success, false on failure, + * WP_Error when term_id is ambiguous between taxonomies. + */ +function bulk_add_term_meta( $term_id, array $meta_fields ) { + if ( wp_term_is_shared( $term_id ) ) { + return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.' ), $term_id ); + } + + return bulk_add_metadata( 'term', $term_id, $meta_fields ); +} + /** * Removes metadata matching criteria from a term. * diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 5f8fe3807154e..566d83bb08d01 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -1128,6 +1128,22 @@ function add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) { return add_metadata( 'user', $user_id, $meta_key, $meta_value, $unique ); } +/** + * Adds multiple items of meta data to a user. + * + * @todo docs + * + * @since x.y.z + * + * @param int $user_id User ID. + * @param string $meta_key Metadata name. + * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. + * @return int[]|false Array of meta IDs on success, false on failure. + */ +function bulk_add_user_meta( $user_id, array $meta_fields ) { + return bulk_add_metadata( 'user', $user_id, $meta_fields ); +} + /** * Removes metadata matching criteria from a user. * From ae4d0e24700d60db68238a34f57ff476410695e5 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sat, 2 Sep 2023 11:56:38 +0100 Subject: [PATCH 10/60] Some implementations. Not fully tested yet. --- src/wp-includes/comment.php | 4 +--- src/wp-includes/post.php | 8 ++++++-- src/wp-includes/user.php | 4 +--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 5be660ead8efa..d003f352e4bec 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2070,9 +2070,7 @@ function wp_insert_comment( $commentdata ) { // If metadata is provided, store it. if ( isset( $commentdata['comment_meta'] ) && is_array( $commentdata['comment_meta'] ) ) { - foreach ( $commentdata['comment_meta'] as $meta_key => $meta_value ) { - add_comment_meta( $comment->comment_ID, $meta_key, $meta_value, true ); - } + bulk_add_comment_meta( $comment->comment_ID, $commentdata['comment_meta'] ); } /** diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index f573cc97a2e23..30fc2b05b9636 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -4592,8 +4592,12 @@ function wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true ) } if ( ! empty( $postarr['meta_input'] ) ) { - foreach ( $postarr['meta_input'] as $field => $value ) { - update_post_meta( $post_id, $field, $value ); + if ( $update ) { + foreach ( $postarr['meta_input'] as $field => $value ) { + update_post_meta( $post_id, $field, $value ); + } + } else { + bulk_add_post_meta( $post_id, $postarr['meta_input'] ); } } diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 566d83bb08d01..3ad6427c76eda 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -2454,9 +2454,7 @@ function wp_insert_user( $userdata ) { } } else { // Add user meta. - foreach ( $meta as $key => $value ) { - add_user_meta( $user_id, $key, $value ); - } + bulk_add_user_meta( $user_id, $meta ); } foreach ( wp_get_user_contact_methods( $user ) as $key => $value ) { From bd445227e80c3558f58c17359956e6be6a938dc6 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sat, 2 Sep 2023 11:56:55 +0100 Subject: [PATCH 11/60] Docs. --- src/wp-includes/meta.php | 20 +++++++++++--------- src/wp-includes/post.php | 6 ++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 632af5350406e..19b9df0329c45 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -71,11 +71,11 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * * @since 3.1.0 * - * @param null|bool $check Whether to allow adding metadata for the given type. - * @param int $object_id ID of the object metadata is for. - * @param string $meta_key Metadata key. - * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. - * @param bool $unique Whether the specified meta key should be unique for the object. + * @param null|int|false $check Whether to allow adding metadata for the given type. @todo improve description + * @param int $object_id ID of the object metadata is for. + * @param string $meta_key Metadata key. + * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. + * @param bool $unique Whether the specified meta key should be unique for the object. */ $check = apply_filters( "add_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $unique ); if ( null !== $check ) { @@ -161,10 +161,9 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = /** * Adds multiple items of metadata for the specified object. * - * For historial reasons the metadata values should be slashed. - * - * This is more performant than calling `add_metadata()` multiple times because it - * only queries the database once and only clears the meta cache once. + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes + * escaped) on input. This means if the data is coming from user-generated + * content you will need to wp_slash() it before passing it to this function. * * This function will always insert all of the provided metadata even if matching keys * already exist. This behaviour matches that of add_metadata() when its `$unique` @@ -173,6 +172,9 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * If the insert fails, no metadata will be inserted. It's not possible for some rows * to be inserted and not others. * + * This is more performant than calling `add_metadata()` multiple times because it + * only queries the database once and only clears the meta cache once. + * * @todo: * * [X] change this function so it returns the array of mids instead of true diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 30fc2b05b9636..955a414633a17 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2473,16 +2473,14 @@ function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { /** * Adds multiple items of meta data to a post. * - * This function will always insert all of the provided metadata even if meta data with - * matching keys already exist. If the insert fails, no metadata will be inserted. It's - * not possible for some rows to be inserted and not others. + * @todo docs * * @since x.y.z * * @param int $post_id Post ID. * @param string $meta_key Metadata name. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. - * @return bool True on success, false on failure. + * @return int[]|false Array of meta IDs on success, false on failure. */ function bulk_add_post_meta( $post_id, array $meta_fields ) { return bulk_add_metadata( 'post', $post_id, $meta_fields ); From 8b208839d47c487c8ec472cbf02546b7640f31bc Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sat, 2 Sep 2023 11:58:04 +0100 Subject: [PATCH 12/60] Revert "Disable these workflows while we're getting set up." This reverts commit bde79b0fba673ae0eff87f6c954fbf639de53834. --- .github/workflows/end-to-end-tests.yml | 159 ++++++++++++++++ .github/workflows/javascript-tests.yml | 135 ++++++++++++++ .github/workflows/performance.yml | 249 +++++++++++++++++++++++++ 3 files changed, 543 insertions(+) create mode 100644 .github/workflows/end-to-end-tests.yml create mode 100644 .github/workflows/javascript-tests.yml create mode 100644 .github/workflows/performance.yml diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml new file mode 100644 index 0000000000000..1e3e442752d58 --- /dev/null +++ b/.github/workflows/end-to-end-tests.yml @@ -0,0 +1,159 @@ +name: End-to-end Tests + +on: + # The end-to-end test suite was introduced in WordPress 5.3. + push: + branches: + - trunk + - '5.[3-9]' + - '[6-9].[0-9]' + tags: + - '[0-9]+.[0-9]' + - '[0-9]+.[0-9].[0-9]+' + - '![34].[0-9].[0-9]+' + - '!5.[0-2].[0-9]+' + pull_request: + branches: + - trunk + - '5.[3-9]' + - '[6-9].[0-9]' + workflow_dispatch: + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +env: + LOCAL_DIR: build + +jobs: + # Runs the end-to-end test suite. + # + # Performs the following steps: + # - Sets environment variables. + # - Checks out the repository. + # - Sets up Node.js. + # - Logs debug information about the GitHub Action runner. + # - Installs npm dependencies. + # - Builds WordPress to run from the `build` directory. + # - Starts the WordPress Docker container. + # - Logs the running Docker containers. + # - Logs Docker debug information (about both the Docker installation within the runner and the WordPress container). + # - Install WordPress within the Docker container. + # - Run the E2E tests. + # - Ensures version-controlled files are not modified or deleted. + e2e-tests: + name: E2E Tests with SCRIPT_DEBUG ${{ matrix.LOCAL_SCRIPT_DEBUG && 'enabled' || 'disabled' }} + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 20 + if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} + strategy: + fail-fast: false + matrix: + LOCAL_SCRIPT_DEBUG: [ true, false ] + + steps: + - name: Configure environment variables + run: | + echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV + echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + + - name: Set up Node.js + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: Log debug information + run: | + npm --version + node --version + curl --version + git --version + svn --version + locale -a + + - name: Install npm Dependencies + run: npm ci + + - name: Build WordPress + run: npm run build + + - name: Start Docker environment + run: | + npm run env:start + + - name: Log running Docker containers + run: docker ps -a + + - name: Docker debug information + run: | + docker -v + docker-compose -v + docker-compose run --rm mysql mysql --version + docker-compose run --rm php php --version + docker-compose run --rm php php -m + docker-compose run --rm php php -i + docker-compose run --rm php locale -a + + - name: Install WordPress + env: + LOCAL_SCRIPT_DEBUG: ${{ matrix.LOCAL_SCRIPT_DEBUG }} + run: npm run env:install + + - name: Run E2E tests + run: npm run test:e2e + + - name: Archive debug artifacts (screenshots, HTML snapshots) + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + if: always() + with: + name: failures-artifacts + path: artifacts + if-no-files-found: ignore + + - name: Ensure version-controlled files are not modified or deleted + run: git diff --exit-code + + failed-workflow: + name: Failed workflow tasks + runs-on: ubuntu-latest + permissions: + actions: write + needs: [ e2e-tests ] + if: | + always() && + github.repository == 'WordPress/wordpress-develop' && + github.event_name != 'pull_request' && + github.run_attempt < 2 && + ( + needs.e2e-tests.result == 'cancelled' || needs.e2e-tests.result == 'failure' + ) + steps: + - name: Dispatch workflow run + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + with: + retries: 2 + retry-exempt-status-codes: 418 + script: | + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'failed-workflow.yml', + ref: 'trunk', + inputs: { + run_id: '${{ github.run_id }}' + } + }); diff --git a/.github/workflows/javascript-tests.yml b/.github/workflows/javascript-tests.yml new file mode 100644 index 0000000000000..2677096b1b7e0 --- /dev/null +++ b/.github/workflows/javascript-tests.yml @@ -0,0 +1,135 @@ +name: JavaScript Tests + +on: + # JavaScript testing was introduced in WordPress 3.8. + push: + branches: + - trunk + - '3.[89]' + - '[4-9].[0-9]' + tags: + - '[0-9]+.[0-9]' + - '[0-9]+.[0-9].[0-9]+' + - '!3.7.[0-9]+' + pull_request: + branches: + - trunk + - '3.[89]' + - '[4-9].[0-9]' + paths: + # Any change to a JavaScript file should run tests. + - '**.js' + # These files configure npm. Changes could affect the outcome. + - 'package*.json' + # This file configures ESLint. Changes could affect the outcome. + - '.eslintignore' + # This file configures JSHint. Changes could affect the outcome. + - '.jshintrc' + # Any change to the QUnit directory should run tests. + - 'tests/qunit/**' + # Changes to workflow files should always verify all workflows are successful. + - '.github/workflows/*.yml' + workflow_dispatch: + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + # Runs the QUnit tests for WordPress. + # + # Performs the following steps: + # - Checks out the repository. + # - Sets up Node.js. + # - Logs debug information about the GitHub Action runner. + # - Installs npm dependencies. + # - Run the WordPress QUnit tests. + # - Ensures version-controlled files are not modified or deleted. + test-js: + name: QUnit Tests + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 20 + if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} + + steps: + - name: Checkout repository + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + + - name: Set up Node.js + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: Log debug information + run: | + npm --version + node --version + git --version + svn --version + + - name: Install npm Dependencies + run: npm ci + + - name: Run QUnit tests + run: npm run grunt qunit:compiled + + - name: Ensure version-controlled files are not modified or deleted + run: git diff --exit-code + + slack-notifications: + name: Slack Notifications + uses: WordPress/wordpress-develop/.github/workflows/slack-notifications.yml@trunk + permissions: + actions: read + contents: read + needs: [ test-js ] + if: ${{ github.repository == 'WordPress/wordpress-develop' && github.event_name != 'pull_request' && always() }} + with: + calling_status: ${{ needs.test-js.result == 'success' && 'success' || needs.test-js.result == 'cancelled' && 'cancelled' || 'failure' }} + secrets: + SLACK_GHA_SUCCESS_WEBHOOK: ${{ secrets.SLACK_GHA_SUCCESS_WEBHOOK }} + SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} + SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} + SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + + failed-workflow: + name: Failed workflow tasks + runs-on: ubuntu-latest + permissions: + actions: write + needs: [ slack-notifications ] + if: | + always() && + github.repository == 'WordPress/wordpress-develop' && + github.event_name != 'pull_request' && + github.run_attempt < 2 && + ( + needs.test-js.result == 'cancelled' || needs.test-js.result == 'failure' + ) + + steps: + - name: Dispatch workflow run + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + with: + retries: 2 + retry-exempt-status-codes: 418 + script: | + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'failed-workflow.yml', + ref: 'trunk', + inputs: { + run_id: '${{ github.run_id }}' + } + }); diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml new file mode 100644 index 0000000000000..53b9efb6ba462 --- /dev/null +++ b/.github/workflows/performance.yml @@ -0,0 +1,249 @@ +name: Performance Tests + +on: + push: + branches: + - trunk + - '6.[2-9]' + - '[7-9].[0-9]' + tags: + - '[0-9]+.[0-9]' + - '[0-9]+.[0-9].[0-9]+' + - '![45].[0-9].[0-9]+' + - '!6.[01].[0-9]+' + pull_request: + branches: + - trunk + - '6.[2-9]' + - '[7-9].[0-9]' + workflow_dispatch: + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +env: + # Performance testing should be performed in an environment reflecting a standard production environment. + WP_DEBUG: false + SCRIPT_DEBUG: false + SAVEQUERIES : false + WP_DEVELOPMENT_MODE: '' + + # This workflow takes two sets of measurements — one for the current commit, + # and another against a consistent version that is used as a baseline measurement. + # This is done to isolate variance in measurements caused by the GitHub runners + # from differences caused by code changes between commits. The BASE_TAG value here + # represents the version being used for baseline measurements. It should only be + # changed if we want to normalize results against a different baseline. + BASE_TAG: '6.1.1' + LOCAL_DIR: build + +jobs: + # Runs the performance test suite. + # + # Performs the following steps: + # - Configure environment variables. + # - Checkout repository. + # - Set up Node.js. + # - Log debug information. + # - Install npm dependencies. + # - Build WordPress. + # - Start Docker environment. + # - Log running Docker containers. + # - Docker debug information. + # - Install WordPress. + # - Install WordPress Importer plugin. + # - Import mock data. + # - Update permalink structure. + # - Install MU plugin. + # - Run performance tests (current commit). + # - Print performance tests results. + # - Set the environment to the baseline version. + # - Run baseline performance tests. + # - Print base line performance tests results. + # - Set the base sha. + # - Set commit details. + # - Publish performance results. + # - Ensure version-controlled files are not modified or deleted. + # - Dispatch workflow run. + performance: + name: Run performance tests + runs-on: ubuntu-latest + permissions: + contents: read + if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} + + steps: + - name: Configure environment variables + run: | + echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV + echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + + - name: Set up Node.js + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: Log debug information + run: | + npm --version + node --version + curl --version + git --version + svn --version + locale -a + + - name: Install npm dependencies + run: npm ci + + - name: Build WordPress + run: npm run build + + - name: Start Docker environment + run: | + npm run env:start + + - name: Log running Docker containers + run: docker ps -a + + - name: Docker debug information + run: | + docker -v + docker-compose -v + docker-compose run --rm mysql mysql --version + docker-compose run --rm php php --version + docker-compose run --rm php php -m + docker-compose run --rm php php -i + docker-compose run --rm php locale -a + + - name: Install WordPress + run: npm run env:install + + - name: Install WordPress Importer plugin + run: npm run env:cli -- plugin install wordpress-importer --activate --path=/var/www/${{ env.LOCAL_DIR }} + + - name: Import mock data + run: | + curl -O https://raw.githubusercontent.com/WordPress/theme-test-data/b9752e0533a5acbb876951a8cbb5bcc69a56474c/themeunittestdata.wordpress.xml + npm run env:cli -- import themeunittestdata.wordpress.xml --authors=create --path=/var/www/${{ env.LOCAL_DIR }} + rm themeunittestdata.wordpress.xml + + - name: Update permalink structure + run: | + npm run env:cli -- rewrite structure '/%year%/%monthnum%/%postname%/' --path=/var/www/${{ env.LOCAL_DIR }} + + - name: Install MU plugin + run: | + mkdir ./${{ env.LOCAL_DIR }}/wp-content/mu-plugins + cp ./tests/performance/wp-content/mu-plugins/server-timing.php ./${{ env.LOCAL_DIR }}/wp-content/mu-plugins/server-timing.php + + - name: Run performance tests (current commit) + run: npm run test:performance + + - name: Print performance tests results + run: "node ./tests/performance/results.js" + + - name: Set the environment to the baseline version + run: | + npm run env:cli -- core update --version=${{ env.BASE_TAG }} --force --path=/var/www/${{ env.LOCAL_DIR }} + npm run env:cli -- core version --path=/var/www/${{ env.LOCAL_DIR }} + + - name: Run baseline performance tests + run: npm run test:performance -- --prefix=base + + - name: Print base line performance tests results + run: "node ./tests/performance/results.js --prefix=base" + + - name: Set the base sha + # Only needed when publishing results. + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }} + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + id: base-sha + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const baseRef = await github.rest.git.getRef({ owner: context.repo.owner, repo: context.repo.repo, ref: 'tags/${{ env.BASE_TAG }}' }); + return baseRef.data.object.sha; + + - name: Set commit details + # Only needed when publishing results. + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }} + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + id: commit-timestamp + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const commit_details = await github.rest.git.getCommit({ owner: context.repo.owner, repo: context.repo.repo, commit_sha: context.sha }); + return parseInt((new Date( commit_details.data.author.date ).getTime() / 1000).toFixed(0)) + + - name: Publish performance results + # Only publish results on pushes to trunk. + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }} + env: + BASE_SHA: ${{ steps.base-sha.outputs.result }} + COMMITTED_AT: ${{ steps.commit-timestamp.outputs.result }} + CODEVITALS_PROJECT_TOKEN: ${{ secrets.CODEVITALS_PROJECT_TOKEN }} + HOST_NAME: "www.codevitals.run" + run: node ./tests/performance/log-results.js $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA $BASE_SHA $COMMITTED_AT $HOST_NAME + + - name: Ensure version-controlled files are not modified or deleted + run: git diff --exit-code + + slack-notifications: + name: Slack Notifications + uses: WordPress/wordpress-develop/.github/workflows/slack-notifications.yml@trunk + permissions: + actions: read + contents: read + needs: [ performance ] + if: ${{ github.repository == 'WordPress/wordpress-develop' && github.event_name != 'pull_request' && always() }} + with: + calling_status: ${{ needs.performance.result == 'success' && 'success' || needs.performance.result == 'cancelled' && 'cancelled' || 'failure' }} + secrets: + SLACK_GHA_SUCCESS_WEBHOOK: ${{ secrets.SLACK_GHA_SUCCESS_WEBHOOK }} + SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} + SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} + SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + + failed-workflow: + name: Failed workflow tasks + runs-on: ubuntu-latest + permissions: + actions: write + needs: [ slack-notifications ] + if: | + always() && + github.repository == 'WordPress/wordpress-develop' && + github.event_name != 'pull_request' && + github.run_attempt < 2 && + ( + needs.performance.result == 'cancelled' || needs.performance.result == 'failure' + ) + + steps: + - name: Dispatch workflow run + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + with: + retries: 2 + retry-exempt-status-codes: 418 + script: | + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'failed-workflow.yml', + ref: 'trunk', + inputs: { + run_id: '${{ github.run_id }}' + } + }); From 8636e97543acfbad7ed8a63a20bac11c31a27670 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sat, 2 Sep 2023 12:31:08 +0100 Subject: [PATCH 13/60] Add some PHPBench config. --- phpbench.json.dist | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 phpbench.json.dist diff --git a/phpbench.json.dist b/phpbench.json.dist new file mode 100644 index 0000000000000..77a13542f85ac --- /dev/null +++ b/phpbench.json.dist @@ -0,0 +1,7 @@ +{ + "$schema": "./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.env_enabled_providers": [ + "sampler", + "php" + ] +} From fd188a772fae57bdbb73bee9e8f2ca8b5496aa4d Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sat, 2 Sep 2023 13:29:11 +0100 Subject: [PATCH 14/60] Looks like we need a build here. --- .github/workflows/phpbench-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index ad1e87b81bb6b..c51baa36931ea 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -100,5 +100,8 @@ jobs: - name: Install WordPress run: npm run env:install + - name: Build WordPress + run: npm run build:dev + - name: Run PHPBench tests run: composer run bench From 0519ff5506d87232b23b1b99618cfa0aa09da4a2 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Mon, 26 May 2025 18:12:51 +0100 Subject: [PATCH 15/60] Docs. --- src/wp-includes/comment.php | 7 +++---- src/wp-includes/meta.php | 12 ++++++------ src/wp-includes/ms-site.php | 7 +++---- src/wp-includes/post.php | 7 +++---- src/wp-includes/taxonomy.php | 9 ++++----- src/wp-includes/user.php | 7 +++---- 6 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 9c7324bf62482..d185c70753eaf 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -474,10 +474,9 @@ function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false * * @since x.y.z * - * @param int $comment_id Comment ID. - * @param string $meta_key Metadata name. - * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. - * @return int[]|false Array of meta IDs on success, false on failure. + * @param int $comment_id Comment ID. + * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. + * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ function bulk_add_comment_meta( $comment_id, array $meta_fields ) { return bulk_add_metadata( 'comment', $comment_id, $meta_fields ); diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 3955c0720e3f4..d6d1d255c168b 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -193,13 +193,13 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * * @global wpdb $wpdb WordPress database abstraction object. * - * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', - * or any other object type with an associated meta table. - * @param int $object_id ID of the object metadata is for. - * @param array $meta_fields Metadata values keyed by their meta key. - * @return int[]|false Array of meta IDs on success, false on failure. + * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', + * or any other object type with an associated meta table. + * @param int $object_id ID of the object metadata is for. + * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. + * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ -function bulk_add_metadata( $meta_type, $object_id, array $meta_fields ) { +function bulk_add_metadata( string $meta_type, $object_id, array $meta_fields ) { global $wpdb; if ( ! $meta_type || ! $meta_fields || ! is_numeric( $object_id ) ) { diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index c70c1916438b7..c34769cad9943 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1048,10 +1048,9 @@ function add_site_meta( $site_id, $meta_key, $meta_value, $unique = false ) { * * @since x.y.z * - * @param int $site_id Site ID. - * @param string $meta_key Metadata name. - * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. - * @return int[]|false Array of meta IDs on success, false on failure. + * @param int $site_id Site ID. + * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. + * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ function bulk_add_site_meta( $site_id, array $meta_fields ) { return bulk_add_metadata( 'site', $site_id, $meta_fields ); diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 4616a8135c46a..b1330ed59fbf2 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2640,10 +2640,9 @@ function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { * * @since x.y.z * - * @param int $post_id Post ID. - * @param string $meta_key Metadata name. - * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. - * @return int[]|false Array of meta IDs on success, false on failure. + * @param int $post_id Post ID. + * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. + * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ function bulk_add_post_meta( $post_id, array $meta_fields ) { return bulk_add_metadata( 'post', $post_id, $meta_fields ); diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index b1fc796094d20..b40b9d4527665 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1413,11 +1413,10 @@ function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) { * * @since x.y.z * - * @param int $term_id Term ID. - * @param string $meta_key Metadata name. - * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. - * @return int[]|false|WP_Error Array of meta IDs on success, false on failure, - * WP_Error when term_id is ambiguous between taxonomies. + * @param int $term_id Term ID. + * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. + * @return array|false|WP_Error Array of meta IDs keyed by their meta key on success, false on failure, + * WP_Error when term_id is ambiguous between taxonomies. */ function bulk_add_term_meta( $term_id, array $meta_fields ) { if ( wp_term_is_shared( $term_id ) ) { diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 48087db492d0d..33aa20bc11663 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -1228,10 +1228,9 @@ function add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) { * * @since x.y.z * - * @param int $user_id User ID. - * @param string $meta_key Metadata name. - * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. - * @return int[]|false Array of meta IDs on success, false on failure. + * @param int $user_id User ID. + * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. + * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ function bulk_add_user_meta( $user_id, array $meta_fields ) { return bulk_add_metadata( 'user', $user_id, $meta_fields ); From 3412032fbc2a9aa45f6dc037e2f59b3c4845409d Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Mon, 26 May 2025 18:49:31 +0100 Subject: [PATCH 16/60] Tweak this signature. --- src/wp-includes/class-wpdb.php | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/class-wpdb.php b/src/wp-includes/class-wpdb.php index a142fa7f5c2c8..4ccbde81318a1 100644 --- a/src/wp-includes/class-wpdb.php +++ b/src/wp-includes/class-wpdb.php @@ -2560,19 +2560,18 @@ public function insert( $table, $data, $format = null ) { * * @since x.y.z * - * @param string $table Table name. - * @param string[] $columns Array of column names. - * @param array[] $rows Array of rows of values to insert. Values should be "raw" (should not be SQL escaped). - * Sending a null value will cause the column to be set to NULL - the corresponding - * format is ignored in this case. - * @param string[]|string $format Optional. An array of formats to be mapped to each of the values in each row. - * If string, that format will be used for all of the values in `$data`. - * A format is one of '%d', '%f', '%s' (integer, float, string). - * If omitted, all values in `$data` will be treated as strings unless otherwise - * specified in wpdb::$field_types. Default null. + * @param string $table Table name. + * @param list $columns Array of column names. + * @param list> $rows Array of rows of values to insert. Values should be "raw" (should not be SQL escaped). + * Sending a null value will cause the column to be set to NULL - the corresponding + * format is ignored in this case. + * @param string[] $format Optional. An array of formats to be mapped to each of the values in each row. + * A format is one of '%d', '%f', '%s' (integer, float, string). + * If omitted, all values in `$data` will be treated as strings unless otherwise + * specified in wpdb::$field_types. Default empty array. * @return int|false The number of rows inserted, or false on error. */ - public function insert_multiple( $table, array $columns, array $rows, array $format = null ) { + public function insert_multiple( string $table, array $columns, array $rows, array $format = array() ) { $this->insert_id = 0; $values_sql = array(); From 2bdfb62ee7dab06a7d79ac2f6865d95ec19e5b1c Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 12:01:13 +0100 Subject: [PATCH 17/60] Use the built-in field format mapping. --- src/wp-includes/class-wpdb.php | 5 +++-- src/wp-includes/meta.php | 7 +------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/class-wpdb.php b/src/wp-includes/class-wpdb.php index 74abd7f264b4e..562b92ea06581 100644 --- a/src/wp-includes/class-wpdb.php +++ b/src/wp-includes/class-wpdb.php @@ -536,6 +536,7 @@ class wpdb { * * @see wpdb::prepare() * @see wpdb::insert() + * @see wpdb::insert_multiple() * @see wpdb::update() * @see wpdb::delete() * @see wp_set_wpdb_vars() @@ -2579,8 +2580,8 @@ public function insert_multiple( string $table, array $columns, array $rows, arr $table, ); - foreach ( $rows as $data ) { - $data = $this->process_fields( $table, $data, $format ); + foreach ( $rows as $row ) { + $data = $this->process_fields( $table, array_combine( $columns, $row ), $format ); if ( false === $data ) { return false; } diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index d6d1d255c168b..de4d32c7bd87b 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -256,12 +256,7 @@ function bulk_add_metadata( string $meta_type, $object_id, array $meta_fields ) 'meta_key', 'meta_value', ), - $data, - array( - '%d', - '%s', - '%s', - ) + $data ); if ( ! $inserted ) { From ec59fbf06a5644c519f2a755524ac28d5fa79644 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 12:07:16 +0100 Subject: [PATCH 18/60] Workflow tweaks. --- .github/workflows/phpbench-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index c51baa36931ea..f3dac574b8a3b 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -32,8 +32,8 @@ jobs: steps: - name: Configure environment variables run: | - echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV - echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV + echo "PHP_FPM_UID=$(id -u)" >> "$GITHUB_ENV" + echo "PHP_FPM_GID=$(id -g)" >> "$GITHUB_ENV" - name: Checkout repository uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 @@ -91,7 +91,7 @@ jobs: - name: WordPress Docker container debug information run: | - docker-compose run --rm mysql ${{ env.LOCAL_DB_TYPE }} --version + docker-compose run --rm mysql mysql --version docker-compose run --rm php php --version docker-compose run --rm php php -m docker-compose run --rm php php -i From 3b17bd63b7153f6e4c6cff571b8596f6872f6eaf Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 12:08:35 +0100 Subject: [PATCH 19/60] Give me strength. --- src/wp-includes/meta.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index de4d32c7bd87b..4e39c7d3100d7 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -219,7 +219,7 @@ function bulk_add_metadata( string $meta_type, $object_id, array $meta_fields ) $meta_subtype = get_object_subtype( $meta_type, $object_id ); $column = sanitize_key( $meta_type . '_id' ); $data = array(); - $return = []; + $return = array(); foreach ( $meta_fields as $meta_key => $meta_value ) { $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); From 96d3494c84a83a54479ed27b74e739276e8523ab Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 12:10:56 +0100 Subject: [PATCH 20/60] Use Ubuntu 24.04. --- .github/workflows/phpbench-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index f3dac574b8a3b..88fe63e25c00f 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -17,7 +17,7 @@ jobs: # Runs the PHPBench tests for WordPress. bench-php: name: PHP ${{ matrix.php }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} strategy: fail-fast: false From 35613312550bb7e86b6a30eec3f11fa09c5ab444 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 12:19:20 +0100 Subject: [PATCH 21/60] Docs. --- src/wp-includes/comment.php | 22 ++++++++++++++++++++- src/wp-includes/meta.php | 37 ++++++++++++++++++++++++++---------- src/wp-includes/ms-site.php | 22 ++++++++++++++++++++- src/wp-includes/post.php | 22 ++++++++++++++++++++- src/wp-includes/taxonomy.php | 22 ++++++++++++++++++++- src/wp-includes/user.php | 22 ++++++++++++++++++++- 6 files changed, 132 insertions(+), 15 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index d185c70753eaf..70c1c371d8e22 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -446,6 +446,9 @@ function get_comment_count( $post_id = 0 ) { /** * Adds meta data field to a comment. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 2.9.0 * * @link https://developer.wordpress.org/reference/functions/add_comment_meta/ @@ -470,7 +473,18 @@ function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false /** * Adds multiple items of meta data to a comment. * - * @todo docs + * Examples: + * + * bulk_add_comment_meta( + * $comment->comment_ID, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since x.y.z * @@ -489,6 +503,9 @@ function bulk_add_comment_meta( $comment_id, array $meta_fields ) { * value, will keep from removing duplicate metadata with the same key. It also * allows removing all metadata matching key, if needed. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 2.9.0 * * @link https://developer.wordpress.org/reference/functions/delete_comment_meta/ @@ -555,6 +572,9 @@ function wp_lazyload_comment_meta( array $comment_ids ) { * * If the meta field for the comment does not exist, it will be added. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 2.9.0 * * @link https://developer.wordpress.org/reference/functions/update_comment_meta/ diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 4e39c7d3100d7..9c3c070e7809d 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -15,6 +15,9 @@ /** * Adds metadata for the specified object. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. @@ -167,19 +170,27 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = /** * Adds multiple items of metadata for the specified object. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes - * escaped) on input. This means if the data is coming from user-generated - * content you will need to wp_slash() it before passing it to this function. + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * + * This function will always insert all of the provided metadata even if matching keys already exist. This behaviour + * matches that of add_metadata() when its `$unique` parameter is set to false. + * + * If the insert fails, no metadata will be inserted. It's not possible for some rows to be inserted and not others. * - * This function will always insert all of the provided metadata even if matching keys - * already exist. This behaviour matches that of add_metadata() when its `$unique` - * parameter is set to false. + * This is more performant than calling `add_metadata()` multiple times because it only queries the database once and + * only clears the meta cache once. * - * If the insert fails, no metadata will be inserted. It's not possible for some rows - * to be inserted and not others. + * Examples: * - * This is more performant than calling `add_metadata()` multiple times because it - * only queries the database once and only clears the meta cache once. + * bulk_add_metadata( + * 'post', + * $post_id, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); * * @todo: * @@ -283,6 +294,9 @@ function bulk_add_metadata( string $meta_type, $object_id, array $meta_fields ) * Updates metadata for the specified object. If no value already exists for the specified object * ID and metadata key, the metadata will be added. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. @@ -478,6 +492,9 @@ function update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_ /** * Deletes metadata for the specified object. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index c34769cad9943..fefdb78b34b2b 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1022,6 +1022,9 @@ function clean_blog_cache( $blog ) { /** * Adds metadata to a site. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 5.1.0 * * @param int $site_id Site ID. @@ -1044,7 +1047,18 @@ function add_site_meta( $site_id, $meta_key, $meta_value, $unique = false ) { /** * Adds multiple items of meta data to a site. * - * @todo docs + * Examples: + * + * bulk_add_site_meta( + * $site_id, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since x.y.z * @@ -1063,6 +1077,9 @@ function bulk_add_site_meta( $site_id, array $meta_fields ) { * value, will keep from removing duplicate metadata with the same key. It also * allows removing all metadata matching key, if needed. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 5.1.0 * * @param int $site_id Site ID. @@ -1110,6 +1127,9 @@ function get_site_meta( $site_id, $key = '', $single = false ) { * * If the meta field for the site does not exist, it will be added. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 5.1.0 * * @param int $site_id Site ID. diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index b1330ed59fbf2..628ebd718c97b 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2608,6 +2608,9 @@ function get_posts( $args = null ) { * * Post meta data is called "Custom Fields" on the Administration Screen. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 1.5.0 * * @param int $post_id Post ID. @@ -2636,7 +2639,18 @@ function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { /** * Adds multiple items of meta data to a post. * - * @todo docs + * Examples: + * + * bulk_add_post_meta( + * $post->ID, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since x.y.z * @@ -2655,6 +2669,9 @@ function bulk_add_post_meta( $post_id, array $meta_fields ) { * value, will keep from removing duplicate metadata with the same key. It also * allows removing all metadata matching the key, if needed. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 1.5.0 * * @param int $post_id Post ID. @@ -2710,6 +2727,9 @@ function get_post_meta( $post_id, $key = '', $single = false ) { * * Can be used in place of add_post_meta(). * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 1.5.0 * * @param int $post_id Post ID. diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index b40b9d4527665..dbb3a20e35a55 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1382,6 +1382,9 @@ function get_terms( $args = array(), $deprecated = '' ) { /** * Adds metadata to a term. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 4.4.0 * * @param int $term_id Term ID. @@ -1409,7 +1412,18 @@ function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) { /** * Adds multiple items of meta data to a term. * - * @todo docs + * Examples: + * + * bulk_add_term_meta( + * $term->term_id, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since x.y.z * @@ -1429,6 +1443,9 @@ function bulk_add_term_meta( $term_id, array $meta_fields ) { /** * Removes metadata matching criteria from a term. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 4.4.0 * * @param int $term_id Term ID. @@ -1475,6 +1492,9 @@ function get_term_meta( $term_id, $key = '', $single = false ) { * * If the meta field for the term does not exist, it will be added. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 4.4.0 * * @param int $term_id Term ID. diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 33aa20bc11663..8ebf8ba2c13e5 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -1202,6 +1202,9 @@ function is_user_member_of_blog( $user_id = 0, $blog_id = 0 ) { /** * Adds meta data to a user. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 3.0.0 * * @param int $user_id User ID. @@ -1224,7 +1227,18 @@ function add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) { /** * Adds multiple items of meta data to a user. * - * @todo docs + * Examples: + * + * bulk_add_user_meta( + * $user->ID, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since x.y.z * @@ -1243,6 +1257,9 @@ function bulk_add_user_meta( $user_id, array $meta_fields ) { * value, will keep from removing duplicate metadata with the same key. It also * allows removing all metadata matching key, if needed. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 3.0.0 * * @link https://developer.wordpress.org/reference/functions/delete_user_meta/ @@ -1294,6 +1311,9 @@ function get_user_meta( $user_id, $key = '', $single = false ) { * * If the meta field for the user does not exist, it will be added. * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @since 3.0.0 * * @link https://developer.wordpress.org/reference/functions/update_user_meta/ From 28f77cad463b97be70d638e01c295f6d0327dfce Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 15:11:26 +0100 Subject: [PATCH 22/60] Refine the handling of mids and the metadata hooks. --- src/wp-includes/meta.php | 22 ++--- tests/phpunit/tests/meta/bulkAddMetadata.php | 86 ++++++++++++++++++++ 2 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 tests/phpunit/tests/meta/bulkAddMetadata.php diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 9c3c070e7809d..a6802b873ba26 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -196,9 +196,9 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * * [X] change this function so it returns the array of mids instead of true * [ ] reinstate slashed data handling - * [ ] need to account for the return value of "add_{$meta_type}_metadata" for each key - * [ ] need tests to cover when the "add_{$meta_type}_metadata" filter returns a value for a key - * [ ] confirm that the method of getting the mids via `range()` is reliable + * [X] need to account for the return value of "add_{$meta_type}_metadata" for each key + * [X] need tests to cover when the "add_{$meta_type}_metadata" filter returns a value for a key + * [X] confirm that the method of getting the mids via `range()` is reliable * * @since x.y.z * @@ -274,20 +274,20 @@ function bulk_add_metadata( string $meta_type, $object_id, array $meta_fields ) return false; } - $first_mid = (int) $wpdb->insert_id; - $all_mids = range( $first_mid, $first_mid + count( $data ) - 1 ); - - $i = 0; + $first_mid = (int) $wpdb->insert_id; + $inserted_mids = range( $first_mid, $first_mid + $inserted - 1 ); + $keyed_mids = array_combine( array_column( $data, 1 ), $inserted_mids ); + $all_mids = array_merge( $return, $keyed_mids ); wp_cache_delete( $object_id, $meta_type . '_meta' ); - foreach ( $meta_fields as $meta_key => $meta_value ) { + foreach ( $data as $datum ) { + list( $object_id, $meta_key, $meta_value ) = $datum; /** This action is documented in wp-includes/meta.php */ - do_action( "added_{$meta_type}_meta", $all_mids[ $i ], $object_id, $meta_key, $meta_value ); - $i++; + do_action( "added_{$meta_type}_meta", $all_mids[ $meta_key ], $object_id, $meta_key, $meta_value ); } - return true; + return $all_mids; } /** diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php new file mode 100644 index 0000000000000..21fc580157c4a --- /dev/null +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -0,0 +1,86 @@ +post->create(); + $meta = array( + 'key1' => '1', + 'key2' => '2', + 'key3' => '3', + ); + $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); + $filters = did_filter( 'add_post_metadata' ); + $actions = did_action( 'added_post_meta' ); + + $result = bulk_add_metadata( 'post', $post_id, $meta ); + + $actual_vals = array( + 'key1' => get_post_meta( $post_id, 'key1', true ), + 'key2' => get_post_meta( $post_id, 'key2', true ), + 'key3' => get_post_meta( $post_id, 'key3', true ), + ); + $expected_vals = $meta; + $expected_mids = array( + 'key1' => ( $latest_mid + 1 ), + 'key2' => ( $latest_mid + 2 ), + 'key3' => ( $latest_mid + 3 ), + ); + + $this->assertSame( $expected_vals, $actual_vals ); + $this->assertSame( $expected_mids, $result ); + $this->assertSame( $filters + 3, did_filter( 'add_post_metadata' ) ); + $this->assertSame( $actions + 3, did_action( 'added_post_meta' ) ); + } + + public function test_correct_mids_should_be_returned_when_filter_is_in_place() { + global $wpdb; + + add_filter( 'add_post_metadata', function( $check, $object_id, $meta_key ) { + if ( 'key2' === $meta_key ) { + return 123456; + } + + return $check; + }, 10, 3 ); + + $post_id = self::factory()->post->create(); + $meta = array( + 'key1' => '1', + 'key2' => '2', + 'key3' => '3', + ); + $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); + $filters = did_filter( 'add_post_metadata' ); + $actions = did_action( 'added_post_meta' ); + + $result = bulk_add_metadata( 'post', $post_id, $meta ); + + $expected_vals = array( + 'key1' => '1', + 'key2' => '', + 'key3' => '3', + ); + $actual_vals = array( + 'key1' => get_post_meta( $post_id, 'key1', true ), + 'key2' => get_post_meta( $post_id, 'key2', true ), + 'key3' => get_post_meta( $post_id, 'key3', true ), + ); + $expected_mids = array( + 'key2' => 123456, + 'key1' => ( $latest_mid + 1 ), + 'key3' => ( $latest_mid + 2 ), + ); + + $this->assertSame( $expected_vals, $actual_vals ); + $this->assertSame( $expected_mids, $result ); + $this->assertSame( $filters + 3, did_filter( 'add_post_metadata' ) ); + $this->assertSame( $actions + 2, did_action( 'added_post_meta' ) ); + } + +} From fcbae4e97e65b9b52caead79a05e1a7506b77cf2 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 15:32:07 +0100 Subject: [PATCH 23/60] More test updates. --- tests/phpunit/tests/meta/bulkAddMetadata.php | 35 +++++++++++--------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index 21fc580157c4a..2042e72ba06fa 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -15,8 +15,9 @@ public function test_all_meta_fields_should_be_added() { 'key3' => '3', ); $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); - $filters = did_filter( 'add_post_metadata' ); - $actions = did_action( 'added_post_meta' ); + $add_post_metadata_calls = did_filter( 'add_post_metadata' ); + $add_post_meta_calls = did_action( 'add_post_meta' ); + $added_post_meta_calls = did_action( 'added_post_meta' ); $result = bulk_add_metadata( 'post', $post_id, $meta ); @@ -34,20 +35,22 @@ public function test_all_meta_fields_should_be_added() { $this->assertSame( $expected_vals, $actual_vals ); $this->assertSame( $expected_mids, $result ); - $this->assertSame( $filters + 3, did_filter( 'add_post_metadata' ) ); - $this->assertSame( $actions + 3, did_action( 'added_post_meta' ) ); + $this->assertSame( $add_post_metadata_calls + 3, did_filter( 'add_post_metadata' ) ); + $this->assertSame( $add_post_meta_calls + 3, did_action( 'add_post_meta' ) ); + $this->assertSame( $added_post_meta_calls + 3, did_action( 'added_post_meta' ) ); } public function test_correct_mids_should_be_returned_when_filter_is_in_place() { global $wpdb; - add_filter( 'add_post_metadata', function( $check, $object_id, $meta_key ) { - if ( 'key2' === $meta_key ) { - return 123456; - } - - return $check; - }, 10, 3 ); + add_filter( + 'add_post_metadata', + static function ( $check, $object_id, $meta_key ) { + return ( 'key2' === $meta_key ) ? 123456 : $check; + }, + 10, + 3 + ); $post_id = self::factory()->post->create(); $meta = array( @@ -56,8 +59,9 @@ public function test_correct_mids_should_be_returned_when_filter_is_in_place() { 'key3' => '3', ); $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); - $filters = did_filter( 'add_post_metadata' ); - $actions = did_action( 'added_post_meta' ); + $add_post_metadata_calls = did_filter( 'add_post_metadata' ); + $add_post_meta_calls = did_action( 'add_post_meta' ); + $added_post_meta_calls = did_action( 'added_post_meta' ); $result = bulk_add_metadata( 'post', $post_id, $meta ); @@ -79,8 +83,9 @@ public function test_correct_mids_should_be_returned_when_filter_is_in_place() { $this->assertSame( $expected_vals, $actual_vals ); $this->assertSame( $expected_mids, $result ); - $this->assertSame( $filters + 3, did_filter( 'add_post_metadata' ) ); - $this->assertSame( $actions + 2, did_action( 'added_post_meta' ) ); + $this->assertSame( $add_post_metadata_calls + 3, did_filter( 'add_post_metadata' ) ); + $this->assertSame( $add_post_meta_calls + 2, did_action( 'add_post_meta' ) ); + $this->assertSame( $added_post_meta_calls + 2, did_action( 'added_post_meta' ) ); } } From 764c4fed20ca1663bd016d056ae1278365e71f92 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 15:36:12 +0100 Subject: [PATCH 24/60] Docs. --- src/wp-includes/meta.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index a6802b873ba26..be4277c0fc52f 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -80,7 +80,8 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * * @since 3.1.0 * - * @param null|int|false $check Whether to allow adding metadata for the given type. @todo improve description + * @param null|int|false $check Whether to allow adding metadata for the given type. Return false or a meta ID + * to short-circuit the function. Return null to continue with the default behavior. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. @@ -170,9 +171,6 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = /** * Adds multiple items of metadata for the specified object. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. - * * This function will always insert all of the provided metadata even if matching keys already exist. This behaviour * matches that of add_metadata() when its `$unique` parameter is set to false. * @@ -192,6 +190,9 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * ) * ); * + * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. + * * @todo: * * [X] change this function so it returns the array of mids instead of true From 33bbd80876d50ce1129485bf0f5b0ee789fa2175 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 15:48:53 +0100 Subject: [PATCH 25/60] Reinstate slashed data handling. --- src/wp-includes/meta.php | 8 ++-- tests/phpunit/tests/meta/bulkAddMetadata.php | 48 ++++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index be4277c0fc52f..96f410b245a37 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -196,7 +196,7 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * @todo: * * [X] change this function so it returns the array of mids instead of true - * [ ] reinstate slashed data handling + * [X] reinstate slashed data handling * [X] need to account for the return value of "add_{$meta_type}_metadata" for each key * [X] need tests to cover when the "add_{$meta_type}_metadata" filter returns a value for a key * [X] confirm that the method of getting the mids via `range()` is reliable @@ -234,6 +234,9 @@ function bulk_add_metadata( string $meta_type, $object_id, array $meta_fields ) $return = array(); foreach ( $meta_fields as $meta_key => $meta_value ) { + // expected_slashed ($meta_key) + $meta_key = wp_unslash( $meta_key ); + $meta_value = wp_unslash( $meta_value ); $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); /** This filter is documented in wp-includes/meta.php */ @@ -243,9 +246,6 @@ function bulk_add_metadata( string $meta_type, $object_id, array $meta_fields ) continue; } - // Update the values in the array with sanitized data. - $meta_fields[ $meta_key ] = $meta_value; - /** This action is documented in wp-includes/meta.php */ do_action( "add_{$meta_type}_meta", $object_id, $meta_key, $meta_value ); diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index 2042e72ba06fa..11c91bfad54d4 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -5,6 +5,19 @@ */ class Tests_Meta_BulkAddMetadata extends WP_UnitTestCase { + /* + * It is important to test with both even and odd numbered slashes, + * as KSES does a strip-then-add slashes in some of its function calls. + */ + + const SLASH_1 = 'String with 1 slash \\'; + const SLASH_2 = 'String with 2 slashes \\\\'; + const SLASH_3 = 'String with 3 slashes \\\\\\'; + const SLASH_4 = 'String with 4 slashes \\\\\\\\'; + const SLASH_5 = 'String with 5 slashes \\\\\\\\\\'; + const SLASH_6 = 'String with 6 slashes \\\\\\\\\\\\'; + const SLASH_7 = 'String with 7 slashes \\\\\\\\\\\\\\'; + public function test_all_meta_fields_should_be_added() { global $wpdb; @@ -88,4 +101,39 @@ static function ( $check, $object_id, $meta_key ) { $this->assertSame( $added_post_meta_calls + 2, did_action( 'added_post_meta' ) ); } + public function test_slashed_data_should_be_handled_correctly() { + $post_id = self::factory()->post->create(); + $meta = array( + 'key1' => addslashes( self::SLASH_1 ), + 'key2' => addslashes( self::SLASH_2 ), + 'key3' => addslashes( self::SLASH_3 ), + 'key4' => addslashes( self::SLASH_4 ), + 'key5' => addslashes( self::SLASH_5 ), + 'key6' => addslashes( self::SLASH_6 ), + 'key7' => addslashes( self::SLASH_7 ), + ); + $result = bulk_add_metadata( 'post', $post_id, $meta ); + + $actual_vals = array( + 'key1' => get_post_meta( $post_id, 'key1', true ), + 'key2' => get_post_meta( $post_id, 'key2', true ), + 'key3' => get_post_meta( $post_id, 'key3', true ), + 'key4' => get_post_meta( $post_id, 'key4', true ), + 'key5' => get_post_meta( $post_id, 'key5', true ), + 'key6' => get_post_meta( $post_id, 'key6', true ), + 'key7' => get_post_meta( $post_id, 'key7', true ), + ); + $expected_vals = array( + 'key1' => self::SLASH_1, + 'key2' => self::SLASH_2, + 'key3' => self::SLASH_3, + 'key4' => self::SLASH_4, + 'key5' => self::SLASH_5, + 'key6' => self::SLASH_6, + 'key7' => self::SLASH_7, + ); + + $this->assertCount( 7, $result ); + $this->assertSame( $expected_vals, $actual_vals ); + } } From b01ea4a39814043ac06faccefe4bd5cad55b4646 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 15:56:37 +0100 Subject: [PATCH 26/60] Docs. --- src/wp-includes/comment.php | 7 +++---- src/wp-includes/meta.php | 20 ++++---------------- src/wp-includes/ms-site.php | 7 +++---- src/wp-includes/post.php | 7 +++---- src/wp-includes/taxonomy.php | 7 +++---- src/wp-includes/user.php | 7 +++---- 6 files changed, 19 insertions(+), 36 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 70c1c371d8e22..02cbee578752a 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -447,7 +447,6 @@ function get_comment_count( $post_id = 0 ) { * Adds meta data field to a comment. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 2.9.0 * @@ -473,6 +472,9 @@ function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false /** * Adds multiple items of meta data to a comment. * + * This function is more performant than calling `add_comment_meta()` multiple times because it queries the database + * only once and clears the meta cache only once. + * * Examples: * * bulk_add_comment_meta( @@ -484,7 +486,6 @@ function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false * ); * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since x.y.z * @@ -504,7 +505,6 @@ function bulk_add_comment_meta( $comment_id, array $meta_fields ) { * allows removing all metadata matching key, if needed. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 2.9.0 * @@ -573,7 +573,6 @@ function wp_lazyload_comment_meta( array $comment_ids ) { * If the meta field for the comment does not exist, it will be added. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 2.9.0 * diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 96f410b245a37..2c435295f52cd 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -16,7 +16,6 @@ * Adds metadata for the specified object. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 2.9.0 * @@ -171,13 +170,13 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = /** * Adds multiple items of metadata for the specified object. * + * This function is more performant than calling `add_metadata()` multiple times because it queries the database only + * once and clears the meta cache only once. + * * This function will always insert all of the provided metadata even if matching keys already exist. This behaviour * matches that of add_metadata() when its `$unique` parameter is set to false. * - * If the insert fails, no metadata will be inserted. It's not possible for some rows to be inserted and not others. - * - * This is more performant than calling `add_metadata()` multiple times because it only queries the database once and - * only clears the meta cache once. + * If the insert fails, no metadata will be inserted. It's not possible for a subset of the rows to be inserted. * * Examples: * @@ -191,15 +190,6 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * ); * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. - * - * @todo: - * - * [X] change this function so it returns the array of mids instead of true - * [X] reinstate slashed data handling - * [X] need to account for the return value of "add_{$meta_type}_metadata" for each key - * [X] need tests to cover when the "add_{$meta_type}_metadata" filter returns a value for a key - * [X] confirm that the method of getting the mids via `range()` is reliable * * @since x.y.z * @@ -296,7 +286,6 @@ function bulk_add_metadata( string $meta_type, $object_id, array $meta_fields ) * ID and metadata key, the metadata will be added. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 2.9.0 * @@ -494,7 +483,6 @@ function update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_ * Deletes metadata for the specified object. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 2.9.0 * diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index fefdb78b34b2b..2e0d9b4d27a2a 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1023,7 +1023,6 @@ function clean_blog_cache( $blog ) { * Adds metadata to a site. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 5.1.0 * @@ -1047,6 +1046,9 @@ function add_site_meta( $site_id, $meta_key, $meta_value, $unique = false ) { /** * Adds multiple items of meta data to a site. * + * This function is more performant than calling `add_site_meta()` multiple times because it queries the database + * only once and clears the meta cache only once. + * * Examples: * * bulk_add_site_meta( @@ -1058,7 +1060,6 @@ function add_site_meta( $site_id, $meta_key, $meta_value, $unique = false ) { * ); * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since x.y.z * @@ -1078,7 +1079,6 @@ function bulk_add_site_meta( $site_id, array $meta_fields ) { * allows removing all metadata matching key, if needed. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 5.1.0 * @@ -1128,7 +1128,6 @@ function get_site_meta( $site_id, $key = '', $single = false ) { * If the meta field for the site does not exist, it will be added. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 5.1.0 * diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 628ebd718c97b..cce6296e1a1d9 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2609,7 +2609,6 @@ function get_posts( $args = null ) { * Post meta data is called "Custom Fields" on the Administration Screen. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 1.5.0 * @@ -2639,6 +2638,9 @@ function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { /** * Adds multiple items of meta data to a post. * + * This function is more performant than calling `add_post_meta()` multiple times because it queries the database + * only once and clears the meta cache only once. + * * Examples: * * bulk_add_post_meta( @@ -2650,7 +2652,6 @@ function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { * ); * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since x.y.z * @@ -2670,7 +2671,6 @@ function bulk_add_post_meta( $post_id, array $meta_fields ) { * allows removing all metadata matching the key, if needed. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 1.5.0 * @@ -2728,7 +2728,6 @@ function get_post_meta( $post_id, $key = '', $single = false ) { * Can be used in place of add_post_meta(). * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 1.5.0 * diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index dbb3a20e35a55..dd5c1f1ad73e8 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1383,7 +1383,6 @@ function get_terms( $args = array(), $deprecated = '' ) { * Adds metadata to a term. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 4.4.0 * @@ -1412,6 +1411,9 @@ function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) { /** * Adds multiple items of meta data to a term. * + * This function is more performant than calling `add_term_meta()` multiple times because it queries the database + * only once and clears the meta cache only once. + * * Examples: * * bulk_add_term_meta( @@ -1423,7 +1425,6 @@ function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) { * ); * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since x.y.z * @@ -1444,7 +1445,6 @@ function bulk_add_term_meta( $term_id, array $meta_fields ) { * Removes metadata matching criteria from a term. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 4.4.0 * @@ -1493,7 +1493,6 @@ function get_term_meta( $term_id, $key = '', $single = false ) { * If the meta field for the term does not exist, it will be added. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 4.4.0 * diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 8ebf8ba2c13e5..c17654dc8a958 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -1203,7 +1203,6 @@ function is_user_member_of_blog( $user_id = 0, $blog_id = 0 ) { * Adds meta data to a user. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 3.0.0 * @@ -1227,6 +1226,9 @@ function add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) { /** * Adds multiple items of meta data to a user. * + * This function is more performant than calling `add_user_meta()` multiple times because it queries the database + * only once and clears the meta cache only once. + * * Examples: * * bulk_add_user_meta( @@ -1238,7 +1240,6 @@ function add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) { * ); * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since x.y.z * @@ -1258,7 +1259,6 @@ function bulk_add_user_meta( $user_id, array $meta_fields ) { * allows removing all metadata matching key, if needed. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 3.0.0 * @@ -1312,7 +1312,6 @@ function get_user_meta( $user_id, $key = '', $single = false ) { * If the meta field for the user does not exist, it will be added. * * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. - * This means if data is coming from user-generated content you need to wp_slash() it before passing it to this function. * * @since 3.0.0 * From 0403bae71adc8820c04852aac7ac7904a699ecde Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 16:01:28 +0100 Subject: [PATCH 27/60] Formatting. --- tests/phpunit/tests/meta/bulkAddMetadata.php | 25 +++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index 11c91bfad54d4..535d85796bda6 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -22,15 +22,16 @@ public function test_all_meta_fields_should_be_added() { global $wpdb; $post_id = self::factory()->post->create(); - $meta = array( + $meta = array( 'key1' => '1', 'key2' => '2', 'key3' => '3', ); - $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); + + $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); $add_post_metadata_calls = did_filter( 'add_post_metadata' ); - $add_post_meta_calls = did_action( 'add_post_meta' ); - $added_post_meta_calls = did_action( 'added_post_meta' ); + $add_post_meta_calls = did_action( 'add_post_meta' ); + $added_post_meta_calls = did_action( 'added_post_meta' ); $result = bulk_add_metadata( 'post', $post_id, $meta ); @@ -39,6 +40,7 @@ public function test_all_meta_fields_should_be_added() { 'key2' => get_post_meta( $post_id, 'key2', true ), 'key3' => get_post_meta( $post_id, 'key3', true ), ); + $expected_vals = $meta; $expected_mids = array( 'key1' => ( $latest_mid + 1 ), @@ -66,15 +68,16 @@ static function ( $check, $object_id, $meta_key ) { ); $post_id = self::factory()->post->create(); - $meta = array( + $meta = array( 'key1' => '1', 'key2' => '2', 'key3' => '3', ); - $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); + + $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); $add_post_metadata_calls = did_filter( 'add_post_metadata' ); - $add_post_meta_calls = did_action( 'add_post_meta' ); - $added_post_meta_calls = did_action( 'added_post_meta' ); + $add_post_meta_calls = did_action( 'add_post_meta' ); + $added_post_meta_calls = did_action( 'added_post_meta' ); $result = bulk_add_metadata( 'post', $post_id, $meta ); @@ -83,11 +86,13 @@ static function ( $check, $object_id, $meta_key ) { 'key2' => '', 'key3' => '3', ); + $actual_vals = array( 'key1' => get_post_meta( $post_id, 'key1', true ), 'key2' => get_post_meta( $post_id, 'key2', true ), 'key3' => get_post_meta( $post_id, 'key3', true ), ); + $expected_mids = array( 'key2' => 123456, 'key1' => ( $latest_mid + 1 ), @@ -103,7 +108,7 @@ static function ( $check, $object_id, $meta_key ) { public function test_slashed_data_should_be_handled_correctly() { $post_id = self::factory()->post->create(); - $meta = array( + $meta = array( 'key1' => addslashes( self::SLASH_1 ), 'key2' => addslashes( self::SLASH_2 ), 'key3' => addslashes( self::SLASH_3 ), @@ -112,6 +117,7 @@ public function test_slashed_data_should_be_handled_correctly() { 'key6' => addslashes( self::SLASH_6 ), 'key7' => addslashes( self::SLASH_7 ), ); + $result = bulk_add_metadata( 'post', $post_id, $meta ); $actual_vals = array( @@ -123,6 +129,7 @@ public function test_slashed_data_should_be_handled_correctly() { 'key6' => get_post_meta( $post_id, 'key6', true ), 'key7' => get_post_meta( $post_id, 'key7', true ), ); + $expected_vals = array( 'key1' => self::SLASH_1, 'key2' => self::SLASH_2, From c2cfd4a8d22b0614972ee9039f4b10c7de6a1ca4 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 16:02:26 +0100 Subject: [PATCH 28/60] Don't need this. --- .github/workflows/phpbench-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index 88fe63e25c00f..985d80812081c 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -73,7 +73,6 @@ jobs: node --version curl --version git --version - svn --version composer --version locale -a From 60b07f947b4135e85e3a7d139abb89db41607692 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 16:15:18 +0100 Subject: [PATCH 29/60] Update the phpbench workflow. --- .github/workflows/phpbench-tests.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index 985d80812081c..bf1f4acd9dcc3 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -24,7 +24,7 @@ jobs: matrix: php: - '7.4' - - '8.2' + - '8.4' env: LOCAL_PHP: ${{ matrix.php }}-fpm @@ -79,7 +79,7 @@ jobs: - name: Docker debug information run: | docker -v - docker-compose -v + docker compose -v - name: Start Docker environment run: | @@ -90,11 +90,11 @@ jobs: - name: WordPress Docker container debug information run: | - docker-compose run --rm mysql mysql --version - docker-compose run --rm php php --version - docker-compose run --rm php php -m - docker-compose run --rm php php -i - docker-compose run --rm php locale -a + docker compose run --rm mysql mysql --version + docker compose run --rm php php --version + docker compose run --rm php php -m + docker compose run --rm php php -i + docker compose run --rm php locale -a - name: Install WordPress run: npm run env:install From 532267b5ec12a1a6c2311ccf907286c9dbb56b19 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 16:18:22 +0100 Subject: [PATCH 30/60] Coding standards. --- tests/phpunit/tests/db/insertMultiple.php | 28 ++++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/tests/phpunit/tests/db/insertMultiple.php b/tests/phpunit/tests/db/insertMultiple.php index 06985dea67ce0..237610c59cd6d 100644 --- a/tests/phpunit/tests/db/insertMultiple.php +++ b/tests/phpunit/tests/db/insertMultiple.php @@ -22,16 +22,19 @@ public function set_up() { */ public function test_correct_rows_are_inserted() { $table = $this->wpdb->postmeta; + $columns = array( 'post_id', 'meta_key', 'meta_value', ); + $datas = array( array( 1, 'key1', 'value1' ), array( 2, 'key2', 'value2' ), array( 3, 'key3', 'value3' ), ); + $format = array( '%d', '%s', @@ -46,28 +49,31 @@ public function test_correct_rows_are_inserted() { ); $rows = $this->wpdb->get_results( - " - SELECT post_id, meta_key, meta_value - FROM $table - ORDER BY post_id ASC - ", + $this->wpdb->prepare( + " + SELECT post_id, meta_key, meta_value + FROM %i + ORDER BY post_id ASC + ", + $table + ), ARRAY_A ); $expected_rows = array( array( - 'post_id' => '1', - 'meta_key' => 'key1', + 'post_id' => '1', + 'meta_key' => 'key1', 'meta_value' => 'value1', ), array( - 'post_id' => '2', - 'meta_key' => 'key2', + 'post_id' => '2', + 'meta_key' => 'key2', 'meta_value' => 'value2', ), array( - 'post_id' => '3', - 'meta_key' => 'key3', + 'post_id' => '3', + 'meta_key' => 'key3', 'meta_value' => 'value3', ), ); From 77a2dd39c3a69fcd5032ce15083080191864b052 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 16:24:33 +0100 Subject: [PATCH 31/60] Coding standards. --- tests/phpunit/tests/db/insertMultiple.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/phpunit/tests/db/insertMultiple.php b/tests/phpunit/tests/db/insertMultiple.php index 237610c59cd6d..f0cca9160ae5a 100644 --- a/tests/phpunit/tests/db/insertMultiple.php +++ b/tests/phpunit/tests/db/insertMultiple.php @@ -50,11 +50,7 @@ public function test_correct_rows_are_inserted() { $rows = $this->wpdb->get_results( $this->wpdb->prepare( - " - SELECT post_id, meta_key, meta_value - FROM %i - ORDER BY post_id ASC - ", + 'SELECT post_id, meta_key, meta_value FROM %i ORDER BY post_id ASC', $table ), ARRAY_A From e56a4decc6d112a0e0c931d5edd8afeaedd2eb7c Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 16:32:12 +0100 Subject: [PATCH 32/60] More phpbench adjustments. --- .github/workflows/phpbench-tests.yml | 5 ++++- composer.json | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index bf1f4acd9dcc3..9f8dce08f2532 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -96,6 +96,9 @@ jobs: docker compose run --rm php php -i docker compose run --rm php locale -a + - name: Install phpbench + run: docker compose run --rm php composer require --dev phpbench/phpbench:"^1.2" --update-with-dependencies + - name: Install WordPress run: npm run env:install @@ -103,4 +106,4 @@ jobs: run: npm run build:dev - name: Run PHPBench tests - run: composer run bench + run: docker compose run --rm php ./vendor/bin/phpbench run tests/phpbench --report=aggregate diff --git a/composer.json b/composer.json index 0d664ba755a11..dbe4e2ba7add2 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,6 @@ "composer/ca-bundle": "1.5.6", "squizlabs/php_codesniffer": "3.10.3", "wp-coding-standards/wpcs": "~3.1.0", - "phpbench/phpbench": "^1.2", "phpcompatibility/phpcompatibility-wp": "~2.1.3", "yoast/phpunit-polyfills": "^1.1.0" }, @@ -33,7 +32,6 @@ "lock": false }, "scripts": { - "bench": "docker-compose run php ./vendor/bin/phpbench run tests/phpbench --report=aggregate", "compat": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --standard=phpcompat.xml.dist --report=summary,source", "format": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf --report=summary,source", "lint": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --report=summary,source", From bf3f6c89795992f6f372dd103731a1a6f8b88a3e Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 16:39:40 +0100 Subject: [PATCH 33/60] Don't need to run the build here. --- .github/workflows/phpbench-tests.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index 9f8dce08f2532..70eb0e79d2fa1 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -102,8 +102,5 @@ jobs: - name: Install WordPress run: npm run env:install - - name: Build WordPress - run: npm run build:dev - - name: Run PHPBench tests run: docker compose run --rm php ./vendor/bin/phpbench run tests/phpbench --report=aggregate From 9c47a613219ae397f41a2c0ab96ad4fb18f41222 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 16:42:02 +0100 Subject: [PATCH 34/60] Remove an unnecessary wpdb property. --- tests/phpunit/tests/db/insertMultiple.php | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/phpunit/tests/db/insertMultiple.php b/tests/phpunit/tests/db/insertMultiple.php index f0cca9160ae5a..47716a230f222 100644 --- a/tests/phpunit/tests/db/insertMultiple.php +++ b/tests/phpunit/tests/db/insertMultiple.php @@ -6,22 +6,14 @@ * @group wpdb */ class Tests_DB_InsertMultiple extends WP_UnitTestCase { - /** - * @var wpdb - */ - protected $wpdb; - - public function set_up() { - parent::set_up(); - - $this->wpdb = $GLOBALS['wpdb']; - } /** * @ticket 59269 */ public function test_correct_rows_are_inserted() { - $table = $this->wpdb->postmeta; + global $wpdb; + + $table = $wpdb->postmeta; $columns = array( 'post_id', @@ -41,15 +33,15 @@ public function test_correct_rows_are_inserted() { '%s', ); - $inserted = $this->wpdb->insert_multiple( + $inserted = $wpdb->insert_multiple( $table, $columns, $datas, $format ); - $rows = $this->wpdb->get_results( - $this->wpdb->prepare( + $rows = $wpdb->get_results( + $wpdb->prepare( 'SELECT post_id, meta_key, meta_value FROM %i ORDER BY post_id ASC', $table ), From c2ae9aa2f7217a226848cf7175c1702fad7180e6 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 16:48:04 +0100 Subject: [PATCH 35/60] Add coverage annotations. --- tests/phpunit/tests/db/insertMultiple.php | 2 ++ tests/phpunit/tests/meta/bulkAddMetadata.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/phpunit/tests/db/insertMultiple.php b/tests/phpunit/tests/db/insertMultiple.php index 47716a230f222..80e7baa223b7c 100644 --- a/tests/phpunit/tests/db/insertMultiple.php +++ b/tests/phpunit/tests/db/insertMultiple.php @@ -4,6 +4,8 @@ * Test the insertion of multiple rows. * * @group wpdb + * + * @covers wpdb::insert_multiple */ class Tests_DB_InsertMultiple extends WP_UnitTestCase { diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index 535d85796bda6..376f1af7999c5 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -2,6 +2,8 @@ /** * @group meta + * + * @covers bulk_add_metadata */ class Tests_Meta_BulkAddMetadata extends WP_UnitTestCase { From 71768abe03967154b915252861fd875fe71ca179 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 16:51:19 +0100 Subject: [PATCH 36/60] Rename these benchmark tests. --- .../{InsertMultipleBench.php => BulkAddMetaBench.php} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/phpbench/{InsertMultipleBench.php => BulkAddMetaBench.php} (96%) diff --git a/tests/phpbench/InsertMultipleBench.php b/tests/phpbench/BulkAddMetaBench.php similarity index 96% rename from tests/phpbench/InsertMultipleBench.php rename to tests/phpbench/BulkAddMetaBench.php index 87dc62632550e..6f0ebfd2b7cde 100644 --- a/tests/phpbench/InsertMultipleBench.php +++ b/tests/phpbench/BulkAddMetaBench.php @@ -8,7 +8,7 @@ * @BeforeMethods("setUp") * @AfterMethods("tearDown") */ -class InsertMultipleBench { +class BulkAddMetaBench { public $post_id; public function setUp(): void { @@ -42,7 +42,7 @@ public function benchAddMeta( array $meta ): void { * @Iterations(3) * @Warmup(1) */ - public function benchAddMultipleMeta( array $meta ): void { + public function benchBulkAddMeta( array $meta ): void { bulk_add_post_meta( $this->post_id, $meta ); } From fa73e3df2ba3ab05a32c1bb07edb3504e6fbb5dc Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 18:37:04 +0100 Subject: [PATCH 37/60] Let's get an actual PHPBench comparison working. --- .github/workflows/phpbench-tests.yml | 2 +- composer.json | 4 + phpbench.json.dist | 17 +++++ tests/phpbench/AddMetaBenchBase.php | 91 +++++++++++++++++++++++ tests/phpbench/BulkAddMetaBench.php | 103 +------------------------- tests/phpbench/SingleAddMetaBench.php | 16 ++++ 6 files changed, 131 insertions(+), 102 deletions(-) create mode 100644 tests/phpbench/AddMetaBenchBase.php create mode 100644 tests/phpbench/SingleAddMetaBench.php diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index 70eb0e79d2fa1..df1b5c37fdf11 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -103,4 +103,4 @@ jobs: run: npm run env:install - name: Run PHPBench tests - run: docker compose run --rm php ./vendor/bin/phpbench run tests/phpbench --report=aggregate + run: composer run bench diff --git a/composer.json b/composer.json index dbe4e2ba7add2..54c5612c68ca5 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,10 @@ "lock": false }, "scripts": { + "bench": [ + "docker compose run --rm php ./vendor/bin/phpbench run tests/phpbench/SingleAddMetaBench.php --tag=single --report=single-add-meta", + "docker compose run --rm php ./vendor/bin/phpbench run tests/phpbench/BulkAddMetaBench.php --ref=single --report=bulk-add-meta" + ], "compat": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --standard=phpcompat.xml.dist --report=summary,source", "format": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf --report=summary,source", "lint": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --report=summary,source", diff --git a/phpbench.json.dist b/phpbench.json.dist index 77a13542f85ac..c35946cf87005 100644 --- a/phpbench.json.dist +++ b/phpbench.json.dist @@ -1,5 +1,22 @@ { "$schema": "./vendor/phpbench/phpbench/phpbench.schema.json", + "report.generators": { + "single-add-meta": { + "title": "Adding post meta", + "description": "Multiple calls to add_post_meta().", + "extends": "aggregate", + "cols": [ "set", "mem_peak", "mode" ] + }, + "bulk-add-meta": { + "title": "Bulk adding post meta", + "description": "Comparing multiple calls to add_post_meta() vs a single call to bulk_add_post_meta().", + "extends": "single-add-meta", + "cols": [ "set", "mem_peak", "mode" ] + } + }, + "runner.revs": 10, + "runner.iterations": 3, + "runner.warmup": 1, "runner.env_enabled_providers": [ "sampler", "php" diff --git a/tests/phpbench/AddMetaBenchBase.php b/tests/phpbench/AddMetaBenchBase.php new file mode 100644 index 0000000000000..8f790e7c42ffc --- /dev/null +++ b/tests/phpbench/AddMetaBenchBase.php @@ -0,0 +1,91 @@ +post_id = wp_insert_post( + [ + 'post_title' => 'Test Post', + 'post_status' => 'publish', + ] + ); + } + + public function tearDown(): void { + wp_delete_post( $this->post_id, true ); + } + + public function provideMetaData(): Generator { + $rows = []; + // 3 rows ranging from 10 to 30 bytes. + for ( $i = 1; $i <= 3; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i ); + } + yield '3 small rows' => $rows; + + $rows = []; + // 20 rows ranging from 10 to 200 bytes. + for ( $i = 1; $i <= 20; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i ); + } + yield '20 small rows' => $rows; + + $rows = []; + // 50 rows ranging from 5 to 250 bytes. + for ( $i = 1; $i <= 50; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '12345', $i ); + } + yield '50 small rows' => $rows; + + $rows = []; + // 100 rows ranging from 5 to 500 bytes. + for ( $i = 1; $i <= 100; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '12345', $i ); + } + yield '100 small rows' => $rows; + + $rows = []; + // 10 rows ranging from 100 bytes to 1 KB. + for ( $i = 1; $i <= 10; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 10 ); + } + yield '10 medium rows' => $rows; + + $rows = []; + // 10 rows ranging from 1 KB to 10 KB. + for ( $i = 1; $i <= 10; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 100 ); + } + yield '10 large rows' => $rows; + + $rows = []; + // 3 rows ranging from 30 KB to 90 KB. + for ( $i = 1; $i <= 3; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 3000 ); + } + yield '3 xl rows' => $rows; + + $rows = []; + // 10 rows ranging from 10 KB to 100 KB. + for ( $i = 1; $i <= 10; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 1000 ); + } + yield '10 xl rows' => $rows; + + $rows = []; + // 20 rows ranging from 10 KB to 200 KB. + for ( $i = 1; $i <= 20; $i++ ) { + $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 1000 ); + } + yield '20 xl rows' => $rows; + } +} diff --git a/tests/phpbench/BulkAddMetaBench.php b/tests/phpbench/BulkAddMetaBench.php index 6f0ebfd2b7cde..7c13a9fb3906e 100644 --- a/tests/phpbench/BulkAddMetaBench.php +++ b/tests/phpbench/BulkAddMetaBench.php @@ -2,112 +2,13 @@ // phpcs:ignoreFile declare( strict_types=1 ); -require_once dirname( __DIR__, 2 ) . '/src/wp-load.php'; - -/** - * @BeforeMethods("setUp") - * @AfterMethods("tearDown") - */ -class BulkAddMetaBench { - public $post_id; - - public function setUp(): void { - $this->post_id = wp_insert_post( - [ - 'post_title' => 'Test Post', - 'post_status' => 'publish', - ] - ); - } - - public function tearDown(): void { - wp_delete_post( $this->post_id, true ); - } +require_once __DIR__ . '/AddMetaBenchBase.php'; +class AddMetaBench extends AddMetaBenchBase { /** * @ParamProviders("provideMetaData") - * @Revs(10) - * @Iterations(3) - * @Warmup(1) */ public function benchAddMeta( array $meta ): void { - foreach ( $meta as $key => $value ) { - add_post_meta( $this->post_id, $key, $value ); - } - } - - /** - * @ParamProviders("provideMetaData") - * @Revs(10) - * @Iterations(3) - * @Warmup(1) - */ - public function benchBulkAddMeta( array $meta ): void { bulk_add_post_meta( $this->post_id, $meta ); } - - public function provideMetaData(): Generator { - $rows = []; - // 3 rows ranging from 10 to 30 bytes. - for ( $i = 1; $i <= 3; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i ); - } - yield '3 small rows' => $rows; - - $rows = []; - // 20 rows ranging from 10 to 200 bytes. - for ( $i = 1; $i <= 20; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i ); - } - yield '20 small rows' => $rows; - - $rows = []; - // 50 rows ranging from 5 to 250 bytes. - for ( $i = 1; $i <= 50; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '12345', $i ); - } - yield '50 small rows' => $rows; - - $rows = []; - // 100 rows ranging from 5 to 500 bytes. - for ( $i = 1; $i <= 100; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '12345', $i ); - } - yield '100 small rows' => $rows; - - $rows = []; - // 10 rows ranging from 100 bytes to 1 KB. - for ( $i = 1; $i <= 10; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 10 ); - } - yield '10 medium rows' => $rows; - - $rows = []; - // 10 rows ranging from 1 KB to 10 KB. - for ( $i = 1; $i <= 10; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 100 ); - } - yield '10 large rows' => $rows; - - $rows = []; - // 3 rows ranging from 30 KB to 90 KB. - for ( $i = 1; $i <= 3; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 3000 ); - } - yield '3 xl rows' => $rows; - - $rows = []; - // 10 rows ranging from 10 KB to 100 KB. - for ( $i = 1; $i <= 10; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 1000 ); - } - yield '10 xl rows' => $rows; - - $rows = []; - // 20 rows ranging from 10 KB to 200 KB. - for ( $i = 1; $i <= 20; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 1000 ); - } - yield '20 xl rows' => $rows; - } } diff --git a/tests/phpbench/SingleAddMetaBench.php b/tests/phpbench/SingleAddMetaBench.php new file mode 100644 index 0000000000000..d4a9ef65acc14 --- /dev/null +++ b/tests/phpbench/SingleAddMetaBench.php @@ -0,0 +1,16 @@ + $value ) { + add_post_meta( $this->post_id, $key, $value ); + } + } +} From 26fea608897b7b80c6668781baad1845c00991fe Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 28 May 2025 18:59:19 +0100 Subject: [PATCH 38/60] Tidying up. --- tests/phpbench/AddMetaBenchBase.php | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/tests/phpbench/AddMetaBenchBase.php b/tests/phpbench/AddMetaBenchBase.php index 8f790e7c42ffc..ee442dd6d5d4b 100644 --- a/tests/phpbench/AddMetaBenchBase.php +++ b/tests/phpbench/AddMetaBenchBase.php @@ -26,66 +26,57 @@ public function tearDown(): void { public function provideMetaData(): Generator { $rows = []; - // 3 rows ranging from 10 to 30 bytes. for ( $i = 1; $i <= 3; $i++ ) { $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i ); } - yield '3 small rows' => $rows; + yield '3 metas, 10-30 bytes' => $rows; $rows = []; - // 20 rows ranging from 10 to 200 bytes. for ( $i = 1; $i <= 20; $i++ ) { $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i ); } - yield '20 small rows' => $rows; + yield '20 metas, 10-200 bytes' => $rows; $rows = []; - // 50 rows ranging from 5 to 250 bytes. for ( $i = 1; $i <= 50; $i++ ) { $rows[ "key_{$i}" ] = str_repeat( '12345', $i ); } - yield '50 small rows' => $rows; + yield '50 metas, 5-250 bytes' => $rows; $rows = []; - // 100 rows ranging from 5 to 500 bytes. for ( $i = 1; $i <= 100; $i++ ) { $rows[ "key_{$i}" ] = str_repeat( '12345', $i ); } - yield '100 small rows' => $rows; + yield '100 metas, 5-500 bytes' => $rows; $rows = []; - // 10 rows ranging from 100 bytes to 1 KB. for ( $i = 1; $i <= 10; $i++ ) { $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 10 ); } - yield '10 medium rows' => $rows; + yield '10 metas, 0.1-1 Kb' => $rows; $rows = []; - // 10 rows ranging from 1 KB to 10 KB. for ( $i = 1; $i <= 10; $i++ ) { $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 100 ); } - yield '10 large rows' => $rows; + yield '10 metas, 1-10 Kb' => $rows; $rows = []; - // 3 rows ranging from 30 KB to 90 KB. for ( $i = 1; $i <= 3; $i++ ) { $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 3000 ); } - yield '3 xl rows' => $rows; + yield '3 metas, 30-90 Kb' => $rows; $rows = []; - // 10 rows ranging from 10 KB to 100 KB. for ( $i = 1; $i <= 10; $i++ ) { $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 1000 ); } - yield '10 xl rows' => $rows; + yield '10 metas, 10-100 Kb' => $rows; $rows = []; - // 20 rows ranging from 10 KB to 200 KB. for ( $i = 1; $i <= 20; $i++ ) { $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 1000 ); } - yield '20 xl rows' => $rows; + yield '20 metas, 10-200 Kb' => $rows; } } From f4fc1147cd3cb413cc791af6b60f0e57c2d949dc Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 29 May 2025 10:50:06 +0100 Subject: [PATCH 39/60] Apply suggestions from code review Co-authored-by: Weston Ruter --- src/wp-includes/ms-site.php | 6 +++--- src/wp-includes/post.php | 4 ++-- src/wp-includes/taxonomy.php | 8 ++++---- src/wp-includes/user.php | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index 2e0d9b4d27a2a..06ef9dbe091c9 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1059,7 +1059,7 @@ function add_site_meta( $site_id, $meta_key, $meta_value, $unique = false ) { * ) * ); * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since x.y.z * @@ -1078,7 +1078,7 @@ function bulk_add_site_meta( $site_id, array $meta_fields ) { * value, will keep from removing duplicate metadata with the same key. It also * allows removing all metadata matching key, if needed. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 5.1.0 * @@ -1127,7 +1127,7 @@ function get_site_meta( $site_id, $key = '', $single = false ) { * * If the meta field for the site does not exist, it will be added. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 5.1.0 * diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index cce6296e1a1d9..15f7ebcf795a0 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2608,7 +2608,7 @@ function get_posts( $args = null ) { * * Post meta data is called "Custom Fields" on the Administration Screen. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 1.5.0 * @@ -2651,7 +2651,7 @@ function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { * ) * ); * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since x.y.z * diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index dd5c1f1ad73e8..c91eed66fb7e8 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1382,7 +1382,7 @@ function get_terms( $args = array(), $deprecated = '' ) { /** * Adds metadata to a term. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 4.4.0 * @@ -1424,7 +1424,7 @@ function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) { * ) * ); * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since x.y.z * @@ -1444,7 +1444,7 @@ function bulk_add_term_meta( $term_id, array $meta_fields ) { /** * Removes metadata matching criteria from a term. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 4.4.0 * @@ -1492,7 +1492,7 @@ function get_term_meta( $term_id, $key = '', $single = false ) { * * If the meta field for the term does not exist, it will be added. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 4.4.0 * diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index c17654dc8a958..5e39b604dff21 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -1202,7 +1202,7 @@ function is_user_member_of_blog( $user_id = 0, $blog_id = 0 ) { /** * Adds meta data to a user. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 3.0.0 * @@ -1239,7 +1239,7 @@ function add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) { * ) * ); * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since x.y.z * @@ -1258,7 +1258,7 @@ function bulk_add_user_meta( $user_id, array $meta_fields ) { * value, will keep from removing duplicate metadata with the same key. It also * allows removing all metadata matching key, if needed. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 3.0.0 * @@ -1311,7 +1311,7 @@ function get_user_meta( $user_id, $key = '', $single = false ) { * * If the meta field for the user does not exist, it will be added. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 3.0.0 * From 41d8473d05c293c7fc1bcc2bd95e179bc82b805e Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 29 May 2025 10:50:49 +0100 Subject: [PATCH 40/60] More typos. --- src/wp-includes/comment.php | 8 ++++---- src/wp-includes/meta.php | 8 ++++---- src/wp-includes/ms-site.php | 2 +- src/wp-includes/post.php | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 02cbee578752a..57e5a9beb87cd 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -446,7 +446,7 @@ function get_comment_count( $post_id = 0 ) { /** * Adds meta data field to a comment. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 2.9.0 * @@ -485,7 +485,7 @@ function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false * ) * ); * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since x.y.z * @@ -504,7 +504,7 @@ function bulk_add_comment_meta( $comment_id, array $meta_fields ) { * value, will keep from removing duplicate metadata with the same key. It also * allows removing all metadata matching key, if needed. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 2.9.0 * @@ -572,7 +572,7 @@ function wp_lazyload_comment_meta( array $comment_ids ) { * * If the meta field for the comment does not exist, it will be added. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 2.9.0 * diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 2c435295f52cd..4ae1d7442262d 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -15,7 +15,7 @@ /** * Adds metadata for the specified object. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 2.9.0 * @@ -189,7 +189,7 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * ) * ); * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since x.y.z * @@ -285,7 +285,7 @@ function bulk_add_metadata( string $meta_type, $object_id, array $meta_fields ) * Updates metadata for the specified object. If no value already exists for the specified object * ID and metadata key, the metadata will be added. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 2.9.0 * @@ -482,7 +482,7 @@ function update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_ /** * Deletes metadata for the specified object. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 2.9.0 * diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index 06ef9dbe091c9..9db2144190054 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1022,7 +1022,7 @@ function clean_blog_cache( $blog ) { /** * Adds metadata to a site. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 5.1.0 * diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 15f7ebcf795a0..2501ebbab006d 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2670,7 +2670,7 @@ function bulk_add_post_meta( $post_id, array $meta_fields ) { * value, will keep from removing duplicate metadata with the same key. It also * allows removing all metadata matching the key, if needed. * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 1.5.0 * @@ -2727,7 +2727,7 @@ function get_post_meta( $post_id, $key = '', $single = false ) { * * Can be used in place of add_post_meta(). * - * For historial reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. * * @since 1.5.0 * From e09e8b0d9da12e8d85c333db1f12f0867bd0a8e6 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 29 May 2025 11:45:04 +0100 Subject: [PATCH 41/60] Apply suggestions from code review Co-authored-by: Weston Ruter --- src/wp-includes/ms-site.php | 2 +- src/wp-includes/post.php | 2 +- src/wp-includes/taxonomy.php | 2 +- src/wp-includes/user.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index 9db2144190054..cf5e53c65e254 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1067,7 +1067,7 @@ function add_site_meta( $site_id, $meta_key, $meta_value, $unique = false ) { * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ -function bulk_add_site_meta( $site_id, array $meta_fields ) { +function bulk_add_site_meta( int $site_id, array $meta_fields ) { return bulk_add_metadata( 'site', $site_id, $meta_fields ); } diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 2501ebbab006d..ed6cd9ff5768b 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2659,7 +2659,7 @@ function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ -function bulk_add_post_meta( $post_id, array $meta_fields ) { +function bulk_add_post_meta( int $post_id, array $meta_fields ) { return bulk_add_metadata( 'post', $post_id, $meta_fields ); } diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index c91eed66fb7e8..5e2bea666ad85 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1433,7 +1433,7 @@ function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) { * @return array|false|WP_Error Array of meta IDs keyed by their meta key on success, false on failure, * WP_Error when term_id is ambiguous between taxonomies. */ -function bulk_add_term_meta( $term_id, array $meta_fields ) { +function bulk_add_term_meta( int $term_id, array $meta_fields ) { if ( wp_term_is_shared( $term_id ) ) { return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.' ), $term_id ); } diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 5e39b604dff21..ad33f9cea7d30 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -1247,7 +1247,7 @@ function add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) { * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ -function bulk_add_user_meta( $user_id, array $meta_fields ) { +function bulk_add_user_meta( int $user_id, array $meta_fields ) { return bulk_add_metadata( 'user', $user_id, $meta_fields ); } From db01cb11e476521dd5a45a761ccfe7e8b75a13c4 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 29 May 2025 11:47:17 +0100 Subject: [PATCH 42/60] More integer casting. --- src/wp-includes/comment.php | 2 +- src/wp-includes/meta.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 57e5a9beb87cd..c581312e1f5f3 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -493,7 +493,7 @@ function add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ -function bulk_add_comment_meta( $comment_id, array $meta_fields ) { +function bulk_add_comment_meta( int $comment_id, array $meta_fields ) { return bulk_add_metadata( 'comment', $comment_id, $meta_fields ); } diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 4ae1d7442262d..91f22ca211ee2 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -201,10 +201,10 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ -function bulk_add_metadata( string $meta_type, $object_id, array $meta_fields ) { +function bulk_add_metadata( string $meta_type, int $object_id, array $meta_fields ) { global $wpdb; - if ( ! $meta_type || ! $meta_fields || ! is_numeric( $object_id ) ) { + if ( ! $meta_type || ! $meta_fields ) { return false; } From b8429c76ea9e25d58466481dab347aeb3094f03e Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 29 May 2025 11:52:31 +0100 Subject: [PATCH 43/60] Make this more readable. --- src/wp-includes/meta.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 91f22ca211ee2..9a63401927384 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -222,6 +222,7 @@ function bulk_add_metadata( string $meta_type, int $object_id, array $meta_field $column = sanitize_key( $meta_type . '_id' ); $data = array(); $return = array(); + $added_keys = array(); foreach ( $meta_fields as $meta_key => $meta_value ) { // expected_slashed ($meta_key) @@ -239,6 +240,8 @@ function bulk_add_metadata( string $meta_type, int $object_id, array $meta_field /** This action is documented in wp-includes/meta.php */ do_action( "add_{$meta_type}_meta", $object_id, $meta_key, $meta_value ); + $added_keys[] = $meta_key; + $data[] = array( $object_id, $meta_key, @@ -267,7 +270,7 @@ function bulk_add_metadata( string $meta_type, int $object_id, array $meta_field $first_mid = (int) $wpdb->insert_id; $inserted_mids = range( $first_mid, $first_mid + $inserted - 1 ); - $keyed_mids = array_combine( array_column( $data, 1 ), $inserted_mids ); + $keyed_mids = array_combine( $added_keys, $inserted_mids ); $all_mids = array_merge( $return, $keyed_mids ); wp_cache_delete( $object_id, $meta_type . '_meta' ); From 14f618727ba35f852239f991b45c6f1813a6f476 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 10 Sep 2025 15:56:39 +0100 Subject: [PATCH 44/60] Docs. --- tests/phpunit/tests/meta/bulkAddMetadata.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index 376f1af7999c5..7b155a2ec19ad 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -20,6 +20,9 @@ class Tests_Meta_BulkAddMetadata extends WP_UnitTestCase { const SLASH_6 = 'String with 6 slashes \\\\\\\\\\\\'; const SLASH_7 = 'String with 7 slashes \\\\\\\\\\\\\\'; + /** + * @ticket 59269 + */ public function test_all_meta_fields_should_be_added() { global $wpdb; @@ -57,6 +60,9 @@ public function test_all_meta_fields_should_be_added() { $this->assertSame( $added_post_meta_calls + 3, did_action( 'added_post_meta' ) ); } + /** + * @ticket 59269 + */ public function test_correct_mids_should_be_returned_when_filter_is_in_place() { global $wpdb; @@ -108,6 +114,9 @@ static function ( $check, $object_id, $meta_key ) { $this->assertSame( $added_post_meta_calls + 2, did_action( 'added_post_meta' ) ); } + /** + * @ticket 59269 + */ public function test_slashed_data_should_be_handled_correctly() { $post_id = self::factory()->post->create(); $meta = array( From 03697e05a284cbad1415954ecf952e1d0603062c Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 10 Sep 2025 15:58:31 +0100 Subject: [PATCH 45/60] Use MockAction for filter call count assertions. --- tests/phpunit/tests/meta/bulkAddMetadata.php | 38 ++++++++++++-------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index 7b155a2ec19ad..9badf9adf7f64 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -33,10 +33,15 @@ public function test_all_meta_fields_should_be_added() { 'key3' => '3', ); - $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); - $add_post_metadata_calls = did_filter( 'add_post_metadata' ); - $add_post_meta_calls = did_action( 'add_post_meta' ); - $added_post_meta_calls = did_action( 'added_post_meta' ); + $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); + + $action1 = new MockAction(); + $action2 = new MockAction(); + $action3 = new MockAction(); + + add_filter( 'add_post_metadata', array( $action1, 'filter' ), 10, 5 ); + add_action( 'add_post_meta', array( $action2, 'action' ) ); + add_action( 'added_post_meta', array( $action3, 'action' ) ); $result = bulk_add_metadata( 'post', $post_id, $meta ); @@ -55,9 +60,9 @@ public function test_all_meta_fields_should_be_added() { $this->assertSame( $expected_vals, $actual_vals ); $this->assertSame( $expected_mids, $result ); - $this->assertSame( $add_post_metadata_calls + 3, did_filter( 'add_post_metadata' ) ); - $this->assertSame( $add_post_meta_calls + 3, did_action( 'add_post_meta' ) ); - $this->assertSame( $added_post_meta_calls + 3, did_action( 'added_post_meta' ) ); + $this->assertSame( 3, $action1->get_call_count() ); + $this->assertSame( 3, $action2->get_call_count() ); + $this->assertSame( 3, $action3->get_call_count() ); } /** @@ -82,10 +87,15 @@ static function ( $check, $object_id, $meta_key ) { 'key3' => '3', ); - $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); - $add_post_metadata_calls = did_filter( 'add_post_metadata' ); - $add_post_meta_calls = did_action( 'add_post_meta' ); - $added_post_meta_calls = did_action( 'added_post_meta' ); + $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); + + $action1 = new MockAction(); + $action2 = new MockAction(); + $action3 = new MockAction(); + + add_filter( 'add_post_metadata', array( $action1, 'filter' ), 10, 5 ); + add_action( 'add_post_meta', array( $action2, 'action' ) ); + add_action( 'added_post_meta', array( $action3, 'action' ) ); $result = bulk_add_metadata( 'post', $post_id, $meta ); @@ -109,9 +119,9 @@ static function ( $check, $object_id, $meta_key ) { $this->assertSame( $expected_vals, $actual_vals ); $this->assertSame( $expected_mids, $result ); - $this->assertSame( $add_post_metadata_calls + 3, did_filter( 'add_post_metadata' ) ); - $this->assertSame( $add_post_meta_calls + 2, did_action( 'add_post_meta' ) ); - $this->assertSame( $added_post_meta_calls + 2, did_action( 'added_post_meta' ) ); + $this->assertSame( 3, $action1->get_call_count() ); + $this->assertSame( 2, $action2->get_call_count() ); + $this->assertSame( 2, $action3->get_call_count() ); } /** From 7299c45685942bfdafaeb2bc9b597a09b1af69ad Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 10 Sep 2025 16:15:29 +0100 Subject: [PATCH 46/60] Add an assertion for the query count. --- tests/phpunit/tests/db/insertMultiple.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/phpunit/tests/db/insertMultiple.php b/tests/phpunit/tests/db/insertMultiple.php index 80e7baa223b7c..1a428f683b612 100644 --- a/tests/phpunit/tests/db/insertMultiple.php +++ b/tests/phpunit/tests/db/insertMultiple.php @@ -35,6 +35,8 @@ public function test_correct_rows_are_inserted() { '%s', ); + $query_count_before = $wpdb->num_queries; + $inserted = $wpdb->insert_multiple( $table, $columns, @@ -42,6 +44,8 @@ public function test_correct_rows_are_inserted() { $format ); + $query_count_after_insert = $wpdb->num_queries; + $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT post_id, meta_key, meta_value FROM %i ORDER BY post_id ASC', @@ -68,7 +72,15 @@ public function test_correct_rows_are_inserted() { ), ); + $queries_used = $query_count_after_insert - $query_count_before; + $this->assertSame( 3, $inserted ); $this->assertSame( $expected_rows, $rows ); + + // insert_multiple should use at most 2 queries: + // 1 for charset info (if not cached) + 1 for the bulk insert. + // When run in isolation, it uses 2 queries. When run as part of the full wpdb test suite, + // charset info is cached from previous tests, so it only uses 1 query. + $this->assertLessThanOrEqual( 2, $queries_used, "Expected insert_multiple to use at most 2 queries for bulk insert" ); } } From 3f3e1c4942d476aae2cf487aef5d71e6f95393df Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 10 Sep 2025 16:20:52 +0100 Subject: [PATCH 47/60] Coding standards. --- tests/phpunit/tests/db/insertMultiple.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/db/insertMultiple.php b/tests/phpunit/tests/db/insertMultiple.php index 1a428f683b612..e426777b61e8f 100644 --- a/tests/phpunit/tests/db/insertMultiple.php +++ b/tests/phpunit/tests/db/insertMultiple.php @@ -76,11 +76,11 @@ public function test_correct_rows_are_inserted() { $this->assertSame( 3, $inserted ); $this->assertSame( $expected_rows, $rows ); - - // insert_multiple should use at most 2 queries: + + // insert_multiple should use at most 2 queries: // 1 for charset info (if not cached) + 1 for the bulk insert. // When run in isolation, it uses 2 queries. When run as part of the full wpdb test suite, // charset info is cached from previous tests, so it only uses 1 query. - $this->assertLessThanOrEqual( 2, $queries_used, "Expected insert_multiple to use at most 2 queries for bulk insert" ); + $this->assertLessThanOrEqual( 2, $queries_used, 'Expected insert_multiple to use at most 2 queries for bulk insert' ); } } From a142584e29132bc8fbf3d068b2f483ebee78807b Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 10 Sep 2025 16:34:17 +0100 Subject: [PATCH 48/60] Save ourselves some trouble. This is a temporary workflow. --- .github/workflows/phpbench-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index df1b5c37fdf11..479283b08d450 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -24,7 +24,7 @@ jobs: matrix: php: - '7.4' - - '8.4' + - '8.2' env: LOCAL_PHP: ${{ matrix.php }}-fpm From 7cfb673941078e2243eb981a544982876babf1e1 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 11 Sep 2025 11:34:11 +0100 Subject: [PATCH 49/60] Fix the images for 8.3 and 8.4. --- .github/workflows/phpbench-tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index 479283b08d450..403cca115f539 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -24,7 +24,7 @@ jobs: matrix: php: - '7.4' - - '8.2' + - '8.4' env: LOCAL_PHP: ${{ matrix.php }}-fpm @@ -38,6 +38,12 @@ jobs: - name: Checkout repository uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - name: Create a Docker override file + if: ${{ contains( fromJSON('["8.3", "8.4"]'), inputs.php ) }} + env: + PHP_VERSION: ${{ inputs.php }} + run: cp "tools/local-env/php-${{ env.PHP_VERSION }}-docker-compose.override.yml" docker-compose.override.yml + - name: Set up Node.js uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: From f84a39c4a63b1bfca1ad813e3ab7d294e855af13 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 11 Sep 2025 11:49:06 +0100 Subject: [PATCH 50/60] Correct this property. --- .github/workflows/phpbench-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml index 403cca115f539..f8e1df0098b65 100644 --- a/.github/workflows/phpbench-tests.yml +++ b/.github/workflows/phpbench-tests.yml @@ -39,9 +39,9 @@ jobs: uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Create a Docker override file - if: ${{ contains( fromJSON('["8.3", "8.4"]'), inputs.php ) }} + if: ${{ contains( fromJSON('["8.3", "8.4"]'), matrix.php ) }} env: - PHP_VERSION: ${{ inputs.php }} + PHP_VERSION: ${{ matrix.php }} run: cp "tools/local-env/php-${{ env.PHP_VERSION }}-docker-compose.override.yml" docker-compose.override.yml - name: Set up Node.js From 54f7d51d6a56dd9fa070df96f9b9353075fc8f97 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sun, 19 Oct 2025 17:32:30 +0100 Subject: [PATCH 51/60] Document site meta. But it's blog meta. For a site. --- src/wp-includes/meta.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index ab0365a8694b2..942b177111c61 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -199,7 +199,7 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', - * or any other object type with an associated meta table. + * 'blog', or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param array $meta_fields Metadata values keyed by their meta key. Values must be serializable if non-scalar. * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. From 9a11cc5357b3deee66ce0d3ca010035768797db5 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sun, 19 Oct 2025 18:04:42 +0100 Subject: [PATCH 52/60] Adjust the tests to cover all meta types. --- tests/phpunit/tests/meta/bulkAddMetadata.php | 169 +++++++++++++------ 1 file changed, 115 insertions(+), 54 deletions(-) diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index 9badf9adf7f64..5ba78f6bcc18f 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -22,57 +22,66 @@ class Tests_Meta_BulkAddMetadata extends WP_UnitTestCase { /** * @ticket 59269 + * @dataProvider data_meta_types */ - public function test_all_meta_fields_should_be_added() { + public function test_all_meta_fields_should_be_added( string $meta_type ) { global $wpdb; - $post_id = self::factory()->post->create(); - $meta = array( + // Create the object. + $object_id = self::factory()->{$meta_type}->create(); + + // Prepare the meta values to add in bulk. + $meta = array( 'key1' => '1', 'key2' => '2', 'key3' => '3', ); - $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); + // Track the mid before adding new meta. + $next_mid = self::get_autoincrement( $meta_type ); + // Set up mock actions and filters to track calls. $action1 = new MockAction(); $action2 = new MockAction(); $action3 = new MockAction(); + add_filter( "add_{$meta_type}_metadata", array( $action1, 'filter' ), 10, 5 ); + add_action( "add_{$meta_type}_meta", array( $action2, 'action' ) ); + add_action( "added_{$meta_type}_meta", array( $action3, 'action' ) ); - add_filter( 'add_post_metadata', array( $action1, 'filter' ), 10, 5 ); - add_action( 'add_post_meta', array( $action2, 'action' ) ); - add_action( 'added_post_meta', array( $action3, 'action' ) ); - - $result = bulk_add_metadata( 'post', $post_id, $meta ); + // Bulk add the meta. + $result = bulk_add_metadata( $meta_type, $object_id, $meta ); + // Read back the actual meta values. $actual_vals = array( - 'key1' => get_post_meta( $post_id, 'key1', true ), - 'key2' => get_post_meta( $post_id, 'key2', true ), - 'key3' => get_post_meta( $post_id, 'key3', true ), + 'key1' => get_metadata( $meta_type, $object_id, 'key1', true ), + 'key2' => get_metadata( $meta_type, $object_id, 'key2', true ), + 'key3' => get_metadata( $meta_type, $object_id, 'key3', true ), ); - $expected_vals = $meta; + // Prepare expected meta IDs. $expected_mids = array( - 'key1' => ( $latest_mid + 1 ), - 'key2' => ( $latest_mid + 2 ), - 'key3' => ( $latest_mid + 3 ), + 'key1' => ( $next_mid ), + 'key2' => ( $next_mid + 1 ), + 'key3' => ( $next_mid + 2 ), ); - $this->assertSame( $expected_vals, $actual_vals ); - $this->assertSame( $expected_mids, $result ); - $this->assertSame( 3, $action1->get_call_count() ); - $this->assertSame( 3, $action2->get_call_count() ); - $this->assertSame( 3, $action3->get_call_count() ); + $this->assertSame( $meta, $actual_vals, 'Actual meta values should match expected values.' ); + $this->assertSame( $expected_mids, $result, 'Actual meta IDs should match expected meta IDs.' ); + $this->assertSame( 3, $action1->get_call_count(), "'add_{$meta_type}_metadata' filter should be called the correct number of times." ); + $this->assertSame( 3, $action2->get_call_count(), "'add_{$meta_type}_meta' action should be called the correct number of times." ); + $this->assertSame( 3, $action3->get_call_count(), "'added_{$meta_type}_meta' action should be called the correct number of times." ); } /** * @ticket 59269 + * @dataProvider data_meta_types */ - public function test_correct_mids_should_be_returned_when_filter_is_in_place() { + public function test_correct_mids_should_be_returned_when_filter_is_in_place( string $meta_type ) { global $wpdb; + // Set up a filter to modify the mid for 'key2'. add_filter( - 'add_post_metadata', + "add_{$meta_type}_metadata", static function ( $check, $object_id, $meta_key ) { return ( 'key2' === $meta_key ) ? 123456 : $check; }, @@ -80,56 +89,68 @@ static function ( $check, $object_id, $meta_key ) { 3 ); - $post_id = self::factory()->post->create(); - $meta = array( + // Create the object. + $object_id = self::factory()->{$meta_type}->create(); + + // Prepare the meta values to add in bulk. + $meta = array( 'key1' => '1', 'key2' => '2', 'key3' => '3', ); - $latest_mid = (int) $wpdb->get_var( "SELECT MAX( meta_id ) FROM {$wpdb->postmeta}" ); + // Track the mid before adding new meta. + $next_mid = self::get_autoincrement( $meta_type ); + // Set up mock actions and filters to track calls. $action1 = new MockAction(); $action2 = new MockAction(); $action3 = new MockAction(); + add_filter( "add_{$meta_type}_metadata", array( $action1, 'filter' ), 10, 5 ); + add_action( "add_{$meta_type}_meta", array( $action2, 'action' ) ); + add_action( "added_{$meta_type}_meta", array( $action3, 'action' ) ); - add_filter( 'add_post_metadata', array( $action1, 'filter' ), 10, 5 ); - add_action( 'add_post_meta', array( $action2, 'action' ) ); - add_action( 'added_post_meta', array( $action3, 'action' ) ); - - $result = bulk_add_metadata( 'post', $post_id, $meta ); + // Bulk add the meta. + $result = bulk_add_metadata( $meta_type, $object_id, $meta ); + // Prepare expected meta values. $expected_vals = array( 'key1' => '1', 'key2' => '', 'key3' => '3', ); + // Read back the actual meta values. $actual_vals = array( - 'key1' => get_post_meta( $post_id, 'key1', true ), - 'key2' => get_post_meta( $post_id, 'key2', true ), - 'key3' => get_post_meta( $post_id, 'key3', true ), + 'key1' => get_metadata( $meta_type, $object_id, 'key1', true ), + 'key2' => get_metadata( $meta_type, $object_id, 'key2', true ), + 'key3' => get_metadata( $meta_type, $object_id, 'key3', true ), ); + // Prepare expected meta IDs. $expected_mids = array( 'key2' => 123456, - 'key1' => ( $latest_mid + 1 ), - 'key3' => ( $latest_mid + 2 ), + 'key1' => ( $next_mid ), + 'key3' => ( $next_mid + 1 ), ); - $this->assertSame( $expected_vals, $actual_vals ); - $this->assertSame( $expected_mids, $result ); - $this->assertSame( 3, $action1->get_call_count() ); - $this->assertSame( 2, $action2->get_call_count() ); - $this->assertSame( 2, $action3->get_call_count() ); + $this->assertSame( $expected_vals, $actual_vals, 'Actual meta values should match expected values.' ); + $this->assertSame( $expected_mids, $result , 'Actual meta IDs should match expected meta IDs.' ); + $this->assertSame( 3, $action1->get_call_count(), "'add_{$meta_type}_metadata' filter should be called the correct number of times." ); + $this->assertSame( 2, $action2->get_call_count(), "'add_{$meta_type}_meta' action should be called the correct number of times." ); + $this->assertSame( 2, $action3->get_call_count(), "'added_{$meta_type}_meta' action should be called the correct number of times." ); } /** * @ticket 59269 + * @dataProvider data_meta_types */ - public function test_slashed_data_should_be_handled_correctly() { - $post_id = self::factory()->post->create(); - $meta = array( + public function test_slashed_data_should_be_handled_correctly( string $meta_type ) { + // Create the object. + $object_id = self::factory()->{$meta_type}->create(); + + // Prepare the meta values to add in bulk. + $meta = array( 'key1' => addslashes( self::SLASH_1 ), 'key2' => addslashes( self::SLASH_2 ), 'key3' => addslashes( self::SLASH_3 ), @@ -139,18 +160,21 @@ public function test_slashed_data_should_be_handled_correctly() { 'key7' => addslashes( self::SLASH_7 ), ); - $result = bulk_add_metadata( 'post', $post_id, $meta ); + // Bulk add the meta. + $result = bulk_add_metadata( $meta_type, $object_id, $meta ); + // Read back the actual meta values. $actual_vals = array( - 'key1' => get_post_meta( $post_id, 'key1', true ), - 'key2' => get_post_meta( $post_id, 'key2', true ), - 'key3' => get_post_meta( $post_id, 'key3', true ), - 'key4' => get_post_meta( $post_id, 'key4', true ), - 'key5' => get_post_meta( $post_id, 'key5', true ), - 'key6' => get_post_meta( $post_id, 'key6', true ), - 'key7' => get_post_meta( $post_id, 'key7', true ), + 'key1' => get_metadata( $meta_type, $object_id, 'key1', true ), + 'key2' => get_metadata( $meta_type, $object_id, 'key2', true ), + 'key3' => get_metadata( $meta_type, $object_id, 'key3', true ), + 'key4' => get_metadata( $meta_type, $object_id, 'key4', true ), + 'key5' => get_metadata( $meta_type, $object_id, 'key5', true ), + 'key6' => get_metadata( $meta_type, $object_id, 'key6', true ), + 'key7' => get_metadata( $meta_type, $object_id, 'key7', true ), ); + // Prepare expected meta values. $expected_vals = array( 'key1' => self::SLASH_1, 'key2' => self::SLASH_2, @@ -161,7 +185,44 @@ public function test_slashed_data_should_be_handled_correctly() { 'key7' => self::SLASH_7, ); - $this->assertCount( 7, $result ); - $this->assertSame( $expected_vals, $actual_vals ); + $this->assertCount( 7, $result, 'The correct number of meta IDs should be returned.' ); + $this->assertSame( $expected_vals, $actual_vals, 'Actual meta values should match expected values.' ); + } + + /** + * @return array> + */ + protected function data_meta_types(): array { + $types = [ + 'post' => [ 'post' ], + 'user' => [ 'user' ], + 'comment' => [ 'comment' ], + 'term' => [ 'term' ], + ]; + + if ( is_multisite() ) { + $types['blog'] = [ 'blog' ]; + } + + return $types; + } + + private static function get_autoincrement( string $meta_type ): int { + global $wpdb; + + $table = "{$meta_type}meta"; + $sql = " + SELECT AUTO_INCREMENT + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = %s + "; + + return (int) $wpdb->get_var( + $wpdb->prepare( + $sql, + $wpdb->$table + ) + ); } } From 30dca5b4aa55913627d3c159edd58b8516aacefa Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sun, 19 Oct 2025 18:07:21 +0100 Subject: [PATCH 53/60] Remove PHPBench infrastructure. --- .github/workflows/phpbench-tests.yml | 112 -------------------------- composer.json | 4 - phpbench.json.dist | 24 ------ tests/phpbench/AddMetaBenchBase.php | 82 ------------------- tests/phpbench/BulkAddMetaBench.php | 14 ---- tests/phpbench/SingleAddMetaBench.php | 16 ---- 6 files changed, 252 deletions(-) delete mode 100644 .github/workflows/phpbench-tests.yml delete mode 100644 phpbench.json.dist delete mode 100644 tests/phpbench/AddMetaBenchBase.php delete mode 100644 tests/phpbench/BulkAddMetaBench.php delete mode 100644 tests/phpbench/SingleAddMetaBench.php diff --git a/.github/workflows/phpbench-tests.yml b/.github/workflows/phpbench-tests.yml deleted file mode 100644 index f8e1df0098b65..0000000000000 --- a/.github/workflows/phpbench-tests.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: PHPBench Tests - -on: - push: - branches: - - trunk - pull_request: - branches: - - trunk - workflow_dispatch: - -# Disable permissions for all available scopes by default. -# Any needed permissions should be configured at the job level. -permissions: {} - -jobs: - # Runs the PHPBench tests for WordPress. - bench-php: - name: PHP ${{ matrix.php }} - runs-on: ubuntu-24.04 - if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} - strategy: - fail-fast: false - matrix: - php: - - '7.4' - - '8.4' - - env: - LOCAL_PHP: ${{ matrix.php }}-fpm - - steps: - - name: Configure environment variables - run: | - echo "PHP_FPM_UID=$(id -u)" >> "$GITHUB_ENV" - echo "PHP_FPM_GID=$(id -g)" >> "$GITHUB_ENV" - - - name: Checkout repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - - - name: Create a Docker override file - if: ${{ contains( fromJSON('["8.3", "8.4"]'), matrix.php ) }} - env: - PHP_VERSION: ${{ matrix.php }} - run: cp "tools/local-env/php-${{ env.PHP_VERSION }}-docker-compose.override.yml" docker-compose.override.yml - - - name: Set up Node.js - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version-file: '.nvmrc' - cache: npm - - ## - # This allows Composer dependencies to be installed using a single step. - # - # Since the tests are currently run within the Docker containers where the PHP version varies, - # the same PHP version needs to be configured for the action runner machine so that the correct - # dependency versions are installed and cached. - ## - - name: Set up PHP - uses: shivammathur/setup-php@d30ad8b1843ace22e6698ab99bbafaa747b6bd0d # v2.24.0 - with: - php-version: '${{ matrix.php }}' - coverage: none - - # 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@83af392bf5f031813d25e6fe4cd626cdba9a2df6 # v2.2.0 - with: - custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") - - - name: Install npm dependencies - run: npm ci - - - name: General debug information - run: | - npm --version - node --version - curl --version - git --version - composer --version - locale -a - - - name: Docker debug information - run: | - docker -v - docker compose -v - - - name: Start Docker environment - run: | - npm run env:start - - - name: Log running Docker containers - run: docker ps -a - - - name: WordPress Docker container debug information - run: | - docker compose run --rm mysql mysql --version - docker compose run --rm php php --version - docker compose run --rm php php -m - docker compose run --rm php php -i - docker compose run --rm php locale -a - - - name: Install phpbench - run: docker compose run --rm php composer require --dev phpbench/phpbench:"^1.2" --update-with-dependencies - - - name: Install WordPress - run: npm run env:install - - - name: Run PHPBench tests - run: composer run bench diff --git a/composer.json b/composer.json index 4f189559ffce2..4cd14415597c7 100644 --- a/composer.json +++ b/composer.json @@ -32,10 +32,6 @@ "lock": false }, "scripts": { - "bench": [ - "docker compose run --rm php ./vendor/bin/phpbench run tests/phpbench/SingleAddMetaBench.php --tag=single --report=single-add-meta", - "docker compose run --rm php ./vendor/bin/phpbench run tests/phpbench/BulkAddMetaBench.php --ref=single --report=bulk-add-meta" - ], "compat": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --standard=phpcompat.xml.dist --report=summary,source", "format": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf --report=summary,source", "lint": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --report=summary,source", diff --git a/phpbench.json.dist b/phpbench.json.dist deleted file mode 100644 index c35946cf87005..0000000000000 --- a/phpbench.json.dist +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "./vendor/phpbench/phpbench/phpbench.schema.json", - "report.generators": { - "single-add-meta": { - "title": "Adding post meta", - "description": "Multiple calls to add_post_meta().", - "extends": "aggregate", - "cols": [ "set", "mem_peak", "mode" ] - }, - "bulk-add-meta": { - "title": "Bulk adding post meta", - "description": "Comparing multiple calls to add_post_meta() vs a single call to bulk_add_post_meta().", - "extends": "single-add-meta", - "cols": [ "set", "mem_peak", "mode" ] - } - }, - "runner.revs": 10, - "runner.iterations": 3, - "runner.warmup": 1, - "runner.env_enabled_providers": [ - "sampler", - "php" - ] -} diff --git a/tests/phpbench/AddMetaBenchBase.php b/tests/phpbench/AddMetaBenchBase.php deleted file mode 100644 index ee442dd6d5d4b..0000000000000 --- a/tests/phpbench/AddMetaBenchBase.php +++ /dev/null @@ -1,82 +0,0 @@ -post_id = wp_insert_post( - [ - 'post_title' => 'Test Post', - 'post_status' => 'publish', - ] - ); - } - - public function tearDown(): void { - wp_delete_post( $this->post_id, true ); - } - - public function provideMetaData(): Generator { - $rows = []; - for ( $i = 1; $i <= 3; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i ); - } - yield '3 metas, 10-30 bytes' => $rows; - - $rows = []; - for ( $i = 1; $i <= 20; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i ); - } - yield '20 metas, 10-200 bytes' => $rows; - - $rows = []; - for ( $i = 1; $i <= 50; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '12345', $i ); - } - yield '50 metas, 5-250 bytes' => $rows; - - $rows = []; - for ( $i = 1; $i <= 100; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '12345', $i ); - } - yield '100 metas, 5-500 bytes' => $rows; - - $rows = []; - for ( $i = 1; $i <= 10; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 10 ); - } - yield '10 metas, 0.1-1 Kb' => $rows; - - $rows = []; - for ( $i = 1; $i <= 10; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 100 ); - } - yield '10 metas, 1-10 Kb' => $rows; - - $rows = []; - for ( $i = 1; $i <= 3; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 3000 ); - } - yield '3 metas, 30-90 Kb' => $rows; - - $rows = []; - for ( $i = 1; $i <= 10; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 1000 ); - } - yield '10 metas, 10-100 Kb' => $rows; - - $rows = []; - for ( $i = 1; $i <= 20; $i++ ) { - $rows[ "key_{$i}" ] = str_repeat( '1234567890', $i * 1000 ); - } - yield '20 metas, 10-200 Kb' => $rows; - } -} diff --git a/tests/phpbench/BulkAddMetaBench.php b/tests/phpbench/BulkAddMetaBench.php deleted file mode 100644 index 7c13a9fb3906e..0000000000000 --- a/tests/phpbench/BulkAddMetaBench.php +++ /dev/null @@ -1,14 +0,0 @@ -post_id, $meta ); - } -} diff --git a/tests/phpbench/SingleAddMetaBench.php b/tests/phpbench/SingleAddMetaBench.php deleted file mode 100644 index d4a9ef65acc14..0000000000000 --- a/tests/phpbench/SingleAddMetaBench.php +++ /dev/null @@ -1,16 +0,0 @@ - $value ) { - add_post_meta( $this->post_id, $key, $value ); - } - } -} From 3a037e2a2b7d0d5ef976264bbce9c3f7022d616e Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sun, 19 Oct 2025 18:08:38 +0100 Subject: [PATCH 54/60] Docs. --- src/wp-includes/class-wpdb.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/class-wpdb.php b/src/wp-includes/class-wpdb.php index 6f5833ca3bc1d..a9a383b82ee07 100644 --- a/src/wp-includes/class-wpdb.php +++ b/src/wp-includes/class-wpdb.php @@ -2567,15 +2567,15 @@ public function insert( $table, $data, $format = null ) { * * @since x.y.z * - * @param string $table Table name. - * @param list $columns Array of column names. - * @param list> $rows Array of rows of values to insert. Values should be "raw" (should not be SQL escaped). - * Sending a null value will cause the column to be set to NULL - the corresponding - * format is ignored in this case. - * @param string[] $format Optional. An array of formats to be mapped to each of the values in each row. - * A format is one of '%d', '%f', '%s' (integer, float, string). - * If omitted, all values in `$data` will be treated as strings unless otherwise - * specified in wpdb::$field_types. Default empty array. + * @param string $table Table name. + * @param string[] $columns Array of column names. + * @param array $rows Array of rows of values to insert. Values should be "raw" (should not be SQL escaped). + * Sending a null value will cause the column to be set to NULL - the corresponding + * format is ignored in this case. + * @param string[] $format Optional. An array of formats to be mapped to each of the values in each row. + * A format is one of '%d', '%f', '%s' (integer, float, string). + * If omitted, all values in `$data` will be treated as strings unless otherwise + * specified in wpdb::$field_types. Default empty array. * @return int|false The number of rows inserted, or false on error. */ public function insert_multiple( string $table, array $columns, array $rows, array $format = array() ) { From 01428b9516a77e2a84678223fd355ab91581842a Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sun, 19 Oct 2025 18:09:59 +0100 Subject: [PATCH 55/60] One more. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9cb990660bfcd..58af6fe2fc10d 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,6 @@ wp-tests-config.php /artifacts /setup.log /coverage -/.phpbench # Files and folders that get created in wp-content /src/wp-content/blogs.dir From caf0bcc3cc6d32a24b6e07b76778312a195a1110 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sun, 19 Oct 2025 19:01:28 +0100 Subject: [PATCH 56/60] Coding standards. --- tests/phpunit/tests/meta/bulkAddMetadata.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index 5ba78f6bcc18f..1d0c7b3999c26 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -135,7 +135,7 @@ static function ( $check, $object_id, $meta_key ) { ); $this->assertSame( $expected_vals, $actual_vals, 'Actual meta values should match expected values.' ); - $this->assertSame( $expected_mids, $result , 'Actual meta IDs should match expected meta IDs.' ); + $this->assertSame( $expected_mids, $result, 'Actual meta IDs should match expected meta IDs.' ); $this->assertSame( 3, $action1->get_call_count(), "'add_{$meta_type}_metadata' filter should be called the correct number of times." ); $this->assertSame( 2, $action2->get_call_count(), "'add_{$meta_type}_meta' action should be called the correct number of times." ); $this->assertSame( 2, $action3->get_call_count(), "'added_{$meta_type}_meta' action should be called the correct number of times." ); @@ -193,15 +193,15 @@ public function test_slashed_data_should_be_handled_correctly( string $meta_type * @return array> */ protected function data_meta_types(): array { - $types = [ - 'post' => [ 'post' ], - 'user' => [ 'user' ], - 'comment' => [ 'comment' ], - 'term' => [ 'term' ], - ]; + $types = array( + 'post' => array( 'post' ), + 'user' => array( 'user' ), + 'comment' => array( 'comment' ), + 'term' => array( 'term' ), + ); if ( is_multisite() ) { - $types['blog'] = [ 'blog' ]; + $types['blog'] = array( 'blog' ); } return $types; @@ -211,12 +211,12 @@ private static function get_autoincrement( string $meta_type ): int { global $wpdb; $table = "{$meta_type}meta"; - $sql = " + $sql = ' SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s - "; + '; return (int) $wpdb->get_var( $wpdb->prepare( From 1f197a21a02642e30ce8dba151eb0815f766a1af Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sun, 19 Oct 2025 19:02:44 +0100 Subject: [PATCH 57/60] Yoink. --- tests/phpunit/tests/meta/bulkAddMetadata.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index 1d0c7b3999c26..f97126753c4da 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -192,7 +192,7 @@ public function test_slashed_data_should_be_handled_correctly( string $meta_type /** * @return array> */ - protected function data_meta_types(): array { + public function data_meta_types(): array { $types = array( 'post' => array( 'post' ), 'user' => array( 'user' ), From 0b37c7064b385a382bcee4ff34658b104b064dc2 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sun, 19 Oct 2025 19:07:32 +0100 Subject: [PATCH 58/60] Why doesn't this get detected with `composer lint`? --- tests/phpunit/tests/meta/bulkAddMetadata.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index f97126753c4da..2de5e21ffa224 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -211,16 +211,15 @@ private static function get_autoincrement( string $meta_type ): int { global $wpdb; $table = "{$meta_type}meta"; - $sql = ' - SELECT AUTO_INCREMENT - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = %s - '; return (int) $wpdb->get_var( $wpdb->prepare( - $sql, + ' + SELECT AUTO_INCREMENT + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = %s + ', $wpdb->$table ) ); From 280454f0152f410f718ebea84ed971ba6d427d5e Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Fri, 19 Dec 2025 00:00:27 +0000 Subject: [PATCH 59/60] Update some fixture names. --- tests/phpunit/tests/meta/bulkAddMetadata.php | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php index 2de5e21ffa224..0fe5cecedaf19 100644 --- a/tests/phpunit/tests/meta/bulkAddMetadata.php +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -41,12 +41,12 @@ public function test_all_meta_fields_should_be_added( string $meta_type ) { $next_mid = self::get_autoincrement( $meta_type ); // Set up mock actions and filters to track calls. - $action1 = new MockAction(); - $action2 = new MockAction(); - $action3 = new MockAction(); - add_filter( "add_{$meta_type}_metadata", array( $action1, 'filter' ), 10, 5 ); - add_action( "add_{$meta_type}_meta", array( $action2, 'action' ) ); - add_action( "added_{$meta_type}_meta", array( $action3, 'action' ) ); + $add_metadata_filter = new MockAction(); + $add_meta_action = new MockAction(); + $added_meta_action = new MockAction(); + add_filter( "add_{$meta_type}_metadata", array( $add_metadata_filter, 'filter' ), 10, 5 ); + add_action( "add_{$meta_type}_meta", array( $add_meta_action, 'action' ) ); + add_action( "added_{$meta_type}_meta", array( $added_meta_action, 'action' ) ); // Bulk add the meta. $result = bulk_add_metadata( $meta_type, $object_id, $meta ); @@ -67,9 +67,9 @@ public function test_all_meta_fields_should_be_added( string $meta_type ) { $this->assertSame( $meta, $actual_vals, 'Actual meta values should match expected values.' ); $this->assertSame( $expected_mids, $result, 'Actual meta IDs should match expected meta IDs.' ); - $this->assertSame( 3, $action1->get_call_count(), "'add_{$meta_type}_metadata' filter should be called the correct number of times." ); - $this->assertSame( 3, $action2->get_call_count(), "'add_{$meta_type}_meta' action should be called the correct number of times." ); - $this->assertSame( 3, $action3->get_call_count(), "'added_{$meta_type}_meta' action should be called the correct number of times." ); + $this->assertSame( 3, $add_metadata_filter->get_call_count(), "'add_{$meta_type}_metadata' filter should be called the correct number of times." ); + $this->assertSame( 3, $add_meta_action->get_call_count(), "'add_{$meta_type}_meta' action should be called the correct number of times." ); + $this->assertSame( 3, $added_meta_action->get_call_count(), "'added_{$meta_type}_meta' action should be called the correct number of times." ); } /** @@ -103,12 +103,12 @@ static function ( $check, $object_id, $meta_key ) { $next_mid = self::get_autoincrement( $meta_type ); // Set up mock actions and filters to track calls. - $action1 = new MockAction(); - $action2 = new MockAction(); - $action3 = new MockAction(); - add_filter( "add_{$meta_type}_metadata", array( $action1, 'filter' ), 10, 5 ); - add_action( "add_{$meta_type}_meta", array( $action2, 'action' ) ); - add_action( "added_{$meta_type}_meta", array( $action3, 'action' ) ); + $add_metadata_filter = new MockAction(); + $add_meta_action = new MockAction(); + $added_meta_action = new MockAction(); + add_filter( "add_{$meta_type}_metadata", array( $add_metadata_filter, 'filter' ), 10, 5 ); + add_action( "add_{$meta_type}_meta", array( $add_meta_action, 'action' ) ); + add_action( "added_{$meta_type}_meta", array( $added_meta_action, 'action' ) ); // Bulk add the meta. $result = bulk_add_metadata( $meta_type, $object_id, $meta ); @@ -136,9 +136,9 @@ static function ( $check, $object_id, $meta_key ) { $this->assertSame( $expected_vals, $actual_vals, 'Actual meta values should match expected values.' ); $this->assertSame( $expected_mids, $result, 'Actual meta IDs should match expected meta IDs.' ); - $this->assertSame( 3, $action1->get_call_count(), "'add_{$meta_type}_metadata' filter should be called the correct number of times." ); - $this->assertSame( 2, $action2->get_call_count(), "'add_{$meta_type}_meta' action should be called the correct number of times." ); - $this->assertSame( 2, $action3->get_call_count(), "'added_{$meta_type}_meta' action should be called the correct number of times." ); + $this->assertSame( 3, $add_metadata_filter->get_call_count(), "'add_{$meta_type}_metadata' filter should be called the correct number of times." ); + $this->assertSame( 2, $add_meta_action->get_call_count(), "'add_{$meta_type}_meta' action should be called the correct number of times." ); + $this->assertSame( 2, $added_meta_action->get_call_count(), "'added_{$meta_type}_meta' action should be called the correct number of times." ); } /** From 369d2e700ccb31bcfed1c8f02452cc8754ef5374 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 21 Jan 2026 12:06:42 +0100 Subject: [PATCH 60/60] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/wp-includes/class-wpdb.php | 2 +- src/wp-includes/meta.php | 6 +++--- src/wp-includes/ms-site.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wpdb.php b/src/wp-includes/class-wpdb.php index 058e46be7ccaf..b1731ac8fe9a7 100644 --- a/src/wp-includes/class-wpdb.php +++ b/src/wp-includes/class-wpdb.php @@ -2568,7 +2568,7 @@ public function insert( $table, $data, $format = null ) { * format is ignored in this case. * @param string[] $format Optional. An array of formats to be mapped to each of the values in each row. * A format is one of '%d', '%f', '%s' (integer, float, string). - * If omitted, all values in `$data` will be treated as strings unless otherwise + * If omitted, all values in `$rows` will be treated as strings unless otherwise * specified in wpdb::$field_types. Default empty array. * @return int|false The number of rows inserted, or false on error. */ diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 942b177111c61..faa0ba85d0b16 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -228,7 +228,7 @@ function bulk_add_metadata( string $meta_type, int $object_id, array $meta_field $added_keys = array(); foreach ( $meta_fields as $meta_key => $meta_value ) { - // expected_slashed ($meta_key) + // expected_slashed ($meta_key, $meta_value) $meta_key = wp_unslash( $meta_key ); $meta_value = wp_unslash( $meta_value ); $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); @@ -279,9 +279,9 @@ function bulk_add_metadata( string $meta_type, int $object_id, array $meta_field wp_cache_delete( $object_id, $meta_type . '_meta' ); foreach ( $data as $datum ) { - list( $object_id, $meta_key, $meta_value ) = $datum; + list( $row_object_id, $meta_key, $meta_value ) = $datum; /** This action is documented in wp-includes/meta.php */ - do_action( "added_{$meta_type}_meta", $all_mids[ $meta_key ], $object_id, $meta_key, $meta_value ); + do_action( "added_{$meta_type}_meta", $all_mids[ $meta_key ], $row_object_id, $meta_key, $meta_value ); } return $all_mids; diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index 10e276a0e64bc..386a44c9015a4 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1068,7 +1068,7 @@ function add_site_meta( $site_id, $meta_key, $meta_value, $unique = false ) { * @return array|false Array of meta IDs keyed by their meta key on success, false on failure. */ function bulk_add_site_meta( int $site_id, array $meta_fields ) { - return bulk_add_metadata( 'site', $site_id, $meta_fields ); + return bulk_add_metadata( 'blog', $site_id, $meta_fields ); } /**