diff --git a/src/wp-includes/class-wpdb.php b/src/wp-includes/class-wpdb.php index 23c865b87d817..b1731ac8fe9a7 100644 --- a/src/wp-includes/class-wpdb.php +++ b/src/wp-includes/class-wpdb.php @@ -535,6 +535,7 @@ class wpdb { * * @see wpdb::prepare() * @see wpdb::insert() + * @see wpdb::insert_multiple() * @see wpdb::update() * @see wpdb::delete() * @see wp_set_wpdb_vars() @@ -2492,7 +2493,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 @@ -2503,6 +2504,110 @@ 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. + * + * 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( + * '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. + * @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 `$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. + */ + public function insert_multiple( string $table, array $columns, array $rows, array $format = array() ) { + $this->insert_id = 0; + + $values_sql = array(); + $values = array( + $table, + ); + + foreach ( $rows as $row ) { + $data = $this->process_fields( $table, array_combine( $columns, $row ), $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/src/wp-includes/comment.php b/src/wp-includes/comment.php index 03fed10fa23f6..42f94e2288449 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -472,6 +472,34 @@ 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. + * + * 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( + * $comment->comment_ID, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * + * @since x.y.z + * + * @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( int $comment_id, array $meta_fields ) { + return bulk_add_metadata( 'comment', $comment_id, $meta_fields ); +} + /** * Removes metadata matching criteria from a comment. * @@ -2160,9 +2188,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/meta.php b/src/wp-includes/meta.php index d60bf5e87527b..faa0ba85d0b16 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -170,6 +170,123 @@ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = return $mid; } +/** + * 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 a subset of the rows to be inserted. + * + * Examples: + * + * bulk_add_metadata( + * 'post', + * $post_id, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * + * @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', + * '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. + */ +function bulk_add_metadata( string $meta_type, int $object_id, array $meta_fields ) { + global $wpdb; + + if ( ! $meta_type || ! $meta_fields ) { + 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 = array(); + $added_keys = array(); + + foreach ( $meta_fields as $meta_key => $meta_value ) { + // 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 ); + + /** 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; + } + + /** 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, + 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 + ); + + if ( ! $inserted ) { + return false; + } + + $first_mid = (int) $wpdb->insert_id; + $inserted_mids = range( $first_mid, $first_mid + $inserted - 1 ); + $keyed_mids = array_combine( $added_keys, $inserted_mids ); + $all_mids = array_merge( $return, $keyed_mids ); + + wp_cache_delete( $object_id, $meta_type . '_meta' ); + + foreach ( $data as $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 ], $row_object_id, $meta_key, $meta_value ); + } + + return $all_mids; +} + /** * 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/ms-site.php b/src/wp-includes/ms-site.php index 0058d1a48200d..386a44c9015a4 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1043,6 +1043,34 @@ 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. + * + * 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( + * $site_id, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * + * @since x.y.z + * + * @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( int $site_id, array $meta_fields ) { + return bulk_add_metadata( 'blog', $site_id, $meta_fields ); +} + /** * Removes metadata matching criteria from a site. * diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index a88d1afd57332..0381afe5773c8 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2658,6 +2658,34 @@ 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 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( + * $post->ID, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * + * @since x.y.z + * + * @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( int $post_id, array $meta_fields ) { + return bulk_add_metadata( 'post', $post_id, $meta_fields ); +} + /** * Deletes a post meta field for the given post ID. * @@ -5018,8 +5046,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/taxonomy.php b/src/wp-includes/taxonomy.php index 003b414f4f13b..35cb3117e2e85 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1408,6 +1408,39 @@ 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. + * + * 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( + * $term->term_id, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * + * @since x.y.z + * + * @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( 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 ); + } + + 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 9871f3b18c088..3448831a9e021 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -1230,6 +1230,34 @@ 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. + * + * 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( + * $user->ID, + * array( + * 'meta_key_1' => 'value_1', + * 'meta_key_2' => 'value_2', + * ) + * ); + * + * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input. + * + * @since x.y.z + * + * @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( int $user_id, array $meta_fields ) { + return bulk_add_metadata( 'user', $user_id, $meta_fields ); +} + /** * Removes metadata matching criteria from a user. * @@ -2588,9 +2616,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 ) { diff --git a/tests/phpunit/tests/db/insertMultiple.php b/tests/phpunit/tests/db/insertMultiple.php new file mode 100644 index 0000000000000..e426777b61e8f --- /dev/null +++ b/tests/phpunit/tests/db/insertMultiple.php @@ -0,0 +1,86 @@ +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', + ); + + $query_count_before = $wpdb->num_queries; + + $inserted = $wpdb->insert_multiple( + $table, + $columns, + $datas, + $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', + $table + ), + 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', + ), + ); + + $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' ); + } +} diff --git a/tests/phpunit/tests/meta/bulkAddMetadata.php b/tests/phpunit/tests/meta/bulkAddMetadata.php new file mode 100644 index 0000000000000..0fe5cecedaf19 --- /dev/null +++ b/tests/phpunit/tests/meta/bulkAddMetadata.php @@ -0,0 +1,227 @@ +{$meta_type}->create(); + + // Prepare the meta values to add in bulk. + $meta = array( + 'key1' => '1', + 'key2' => '2', + 'key3' => '3', + ); + + // Track the mid before adding new meta. + $next_mid = self::get_autoincrement( $meta_type ); + + // Set up mock actions and filters to track calls. + $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 ); + + // Read back the actual meta values. + $actual_vals = array( + '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( + 'key1' => ( $next_mid ), + 'key2' => ( $next_mid + 1 ), + 'key3' => ( $next_mid + 2 ), + ); + + $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, $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." ); + } + + /** + * @ticket 59269 + * @dataProvider data_meta_types + */ + 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_{$meta_type}_metadata", + static function ( $check, $object_id, $meta_key ) { + return ( 'key2' === $meta_key ) ? 123456 : $check; + }, + 10, + 3 + ); + + // 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', + ); + + // Track the mid before adding new meta. + $next_mid = self::get_autoincrement( $meta_type ); + + // Set up mock actions and filters to track calls. + $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 ); + + // Prepare expected meta values. + $expected_vals = array( + 'key1' => '1', + 'key2' => '', + 'key3' => '3', + ); + + // Read back the actual meta values. + $actual_vals = array( + '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' => ( $next_mid ), + 'key3' => ( $next_mid + 1 ), + ); + + $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, $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." ); + } + + /** + * @ticket 59269 + * @dataProvider data_meta_types + */ + 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 ), + 'key4' => addslashes( self::SLASH_4 ), + 'key5' => addslashes( self::SLASH_5 ), + 'key6' => addslashes( self::SLASH_6 ), + 'key7' => addslashes( self::SLASH_7 ), + ); + + // Bulk add the meta. + $result = bulk_add_metadata( $meta_type, $object_id, $meta ); + + // Read back the actual meta values. + $actual_vals = array( + '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, + 'key3' => self::SLASH_3, + 'key4' => self::SLASH_4, + 'key5' => self::SLASH_5, + 'key6' => self::SLASH_6, + 'key7' => self::SLASH_7, + ); + + $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> + */ + public function data_meta_types(): array { + $types = array( + 'post' => array( 'post' ), + 'user' => array( 'user' ), + 'comment' => array( 'comment' ), + 'term' => array( 'term' ), + ); + + if ( is_multisite() ) { + $types['blog'] = array( 'blog' ); + } + + return $types; + } + + private static function get_autoincrement( string $meta_type ): int { + global $wpdb; + + $table = "{$meta_type}meta"; + + return (int) $wpdb->get_var( + $wpdb->prepare( + ' + SELECT AUTO_INCREMENT + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = %s + ', + $wpdb->$table + ) + ); + } +}