From 4ec5d65dcc2e480506c22dc77e56af8a6334a33e Mon Sep 17 00:00:00 2001 From: Boone Gorges Date: Tue, 21 Nov 2017 03:14:52 +0000 Subject: [PATCH] Introduce `meta_box_sanitize_cb` taxonomy argument. The `meta_box_cb` argument was introduced in [25572] to allow plugin authors to provide a custom callback for rendering their taxonomy's meta box on the post edit screen. However, the routine used to handle the saving of these custom taxonomy meta boxes was not customizable, but was instead based simply on whether the taxonomy was hierarchicaly. See [13535]. The new `meta_box_sanitize_cb` argument defaults to the "tag" routine for non-hierarchical taxonomies and the "category" routine for hierarchical ones, thereby maintaining the current default behavior. Developers can override this when the data passed from their `meta_box_cb` differs. Props boonebgorges, ZaneMatthew, stephenharris. Fixes #36514. git-svn-id: https://develop.svn.wordpress.org/trunk@42211 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/includes/post.php | 101 ++++++++++++++++---------- src/wp-includes/class-wp-taxonomy.php | 24 ++++++ src/wp-includes/taxonomy.php | 4 + tests/phpunit/tests/taxonomy.php | 38 ++++++++++ 4 files changed, 128 insertions(+), 39 deletions(-) diff --git a/src/wp-admin/includes/post.php b/src/wp-admin/includes/post.php index 908b80c333..adb0dd9cd6 100644 --- a/src/wp-admin/includes/post.php +++ b/src/wp-admin/includes/post.php @@ -327,46 +327,11 @@ function edit_post( $post_data = null ) { // Convert taxonomy input to term IDs, to avoid ambiguity. if ( isset( $post_data['tax_input'] ) ) { foreach ( (array) $post_data['tax_input'] as $taxonomy => $terms ) { - // Hierarchical taxonomy data is already sent as term IDs, so no conversion is necessary. - if ( is_taxonomy_hierarchical( $taxonomy ) ) { - continue; + $tax_object = get_taxonomy( $taxonomy ); + + if ( $tax_object && isset( $tax_object->meta_box_sanitize_cb ) ) { + $post_data['tax_input'][ $taxonomy ] = call_user_func_array( $tax_object->meta_box_sanitize_cb, array( $taxonomy, $terms ) ); } - - /* - * Assume that a 'tax_input' string is a comma-separated list of term names. - * Some languages may use a character other than a comma as a delimiter, so we standardize on - * commas before parsing the list. - */ - if ( ! is_array( $terms ) ) { - $comma = _x( ',', 'tag delimiter' ); - if ( ',' !== $comma ) { - $terms = str_replace( $comma, ',', $terms ); - } - $terms = explode( ',', trim( $terms, " \n\t\r\0\x0B," ) ); - } - - $clean_terms = array(); - foreach ( $terms as $term ) { - // Empty terms are invalid input. - if ( empty( $term ) ) { - continue; - } - - $_term = get_terms( $taxonomy, array( - 'name' => $term, - 'fields' => 'ids', - 'hide_empty' => false, - ) ); - - if ( ! empty( $_term ) ) { - $clean_terms[] = intval( $_term[0] ); - } else { - // No existing term was found, so pass the string. A new term will be created. - $clean_terms[] = $term; - } - } - - $post_data['tax_input'][ $taxonomy ] = $clean_terms; } } @@ -1870,3 +1835,61 @@ function redirect_post($post_id = '') { wp_redirect( apply_filters( 'redirect_post_location', $location, $post_id ) ); exit; } + +/** + * Sanitizes POST values from a checkbox taxonomy metabox. + * + * @since 5.0.0 + * + * @param mixed $terms Raw term data from the 'tax_input' field. + * @return array + */ +function taxonomy_meta_box_sanitize_cb_checkboxes( $taxonmy, $terms ) { + return array_map( 'intval', $terms ); +} + +/** + * Sanitizes POST values from an input taxonomy metabox. + * + * @since 5.0.0 + * + * @param mixed $terms Raw term data from the 'tax_input' field. + * @return array + */ +function taxonomy_meta_box_sanitize_cb_input( $taxonomy, $terms ) { + /* + * Assume that a 'tax_input' string is a comma-separated list of term names. + * Some languages may use a character other than a comma as a delimiter, so we standardize on + * commas before parsing the list. + */ + if ( ! is_array( $terms ) ) { + $comma = _x( ',', 'tag delimiter' ); + if ( ',' !== $comma ) { + $terms = str_replace( $comma, ',', $terms ); + } + $terms = explode( ',', trim( $terms, " \n\t\r\0\x0B," ) ); + } + + $clean_terms = array(); + foreach ( $terms as $term ) { + // Empty terms are invalid input. + if ( empty( $term ) ) { + continue; + } + + $_term = get_terms( $taxonomy, array( + 'name' => $term, + 'fields' => 'ids', + 'hide_empty' => false, + ) ); + + if ( ! empty( $_term ) ) { + $clean_terms[] = intval( $_term[0] ); + } else { + // No existing term was found, so pass the string. A new term will be created. + $clean_terms[] = $term; + } + } + + return $clean_terms; +} diff --git a/src/wp-includes/class-wp-taxonomy.php b/src/wp-includes/class-wp-taxonomy.php index e749eef2d1..3a92ba859e 100644 --- a/src/wp-includes/class-wp-taxonomy.php +++ b/src/wp-includes/class-wp-taxonomy.php @@ -127,6 +127,15 @@ final class WP_Taxonomy { */ public $meta_box_cb = null; + /** + * The callback function for sanitizing taxonomy data saved from a meta box. + * + * @since 5.0.0 + * @access public + * @var callable + */ + public $meta_box_sanitize_cb = null; + /** * An array of object types this taxonomy is registered for. * @@ -257,6 +266,7 @@ final class WP_Taxonomy { 'show_in_quick_edit' => null, 'show_admin_column' => false, 'meta_box_cb' => null, + 'meta_box_sanitize_cb' => null, 'capabilities' => array(), 'rewrite' => true, 'query_var' => $this->name, @@ -345,6 +355,20 @@ final class WP_Taxonomy { $args['name'] = $this->name; + // Default meta box sanitization callback depends on the value of 'meta_box_cb'. + if ( null === $args['meta_box_sanitize_cb'] ) { + switch ( $args['meta_box_cb'] ) { + case 'post_categories_meta_box' : + $args['meta_box_sanitize_cb'] = 'taxonomy_meta_box_sanitize_cb_checkboxes'; + break; + + case 'post_tags_meta_box' : + default : + $args['meta_box_sanitize_cb'] = 'taxonomy_meta_box_sanitize_cb_input'; + break; + } + } + foreach ( $args as $property_name => $property_value ) { $this->$property_name = $property_value; } diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 92a4ee73d2..bdbeb3dc9d 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -297,6 +297,7 @@ function is_taxonomy_hierarchical($taxonomy) { * @since 4.5.0 Introduced `publicly_queryable` argument. * @since 4.7.0 Introduced `show_in_rest`, 'rest_base' and 'rest_controller_class' * arguments to register the Taxonomy in REST API. + * @since 5.0.0 Introduced `meta_box_sanitize_cb` argument. * * @global array $wp_taxonomies Registered taxonomies. * @@ -339,6 +340,9 @@ function is_taxonomy_hierarchical($taxonomy) { * post_categories_meta_box() is used for hierarchical taxonomies, and * post_tags_meta_box() is used for non-hierarchical. If false, no meta * box is shown. + * @type callable $meta_box_sanitize_cb Callback function for sanitizing taxonomy data saved from a meta + * box. If no callback is defined, an appropriate one is determined + * based on the value of `$meta_box_cb`. * @type array $capabilities { * Array of capabilities for this taxonomy. * diff --git a/tests/phpunit/tests/taxonomy.php b/tests/phpunit/tests/taxonomy.php index b5fd536100..7d2a9aa5e1 100644 --- a/tests/phpunit/tests/taxonomy.php +++ b/tests/phpunit/tests/taxonomy.php @@ -791,4 +791,42 @@ class Tests_Taxonomy extends WP_UnitTestCase { $this->assertSame( 'foo', $taxonomy->name ); } + + /** + * @ticket 36514 + */ + public function test_edit_post_hierarchical_taxonomy() { + + $taxonomy_name = 'foo'; + $term_name = 'bar'; + + register_taxonomy( $taxonomy_name, array( 'post' ), array( + 'hierarchical' => false, + 'meta_box_cb' => 'post_categories_meta_box', + ) ); + $post = self::factory()->post->create_and_get( array( + 'post_type' => 'post', + ) ); + + $term_id = self::factory()->term->create_object( array( + 'name' => $term_name, + 'taxonomy' => $taxonomy_name, + ) ); + + wp_set_current_user( self::factory()->user->create( array( 'role' => 'editor' ) ) ); + $updated_post_id = edit_post( array( + 'post_ID' => $post->ID, + 'post_type' => 'post', + 'tax_input' => array( + $taxonomy_name => array( + (string) $term_id // Cast term_id as string to match whats sent in WP Admin. + ), + ), + ) ); + + $terms_obj = get_the_terms( $updated_post_id, $taxonomy_name ); + $problematic_term = current( wp_list_pluck( $terms_obj, 'name' ) ); + + $this->assertEquals( $problematic_term, $term_name ); + } }