From a111eafd41db415e71784e6d596d2f7bf9294b99 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 15 Jan 2016 12:19:15 +0000 Subject: [PATCH] Post Types: Introduce `unregister_post_type()`. This new function can be used to completely unregister non built-in post types. Fixes #14761. git-svn-id: https://develop.svn.wordpress.org/trunk@36316 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/capabilities.php | 4 +- src/wp-includes/post.php | 96 ++++++++++++- tests/phpunit/includes/utils.php | 9 +- tests/phpunit/tests/post/types.php | 220 +++++++++++++++++++++++++++++ 4 files changed, 313 insertions(+), 16 deletions(-) diff --git a/src/wp-includes/capabilities.php b/src/wp-includes/capabilities.php index 7c570c4a1d..d8f52fe20c 100644 --- a/src/wp-includes/capabilities.php +++ b/src/wp-includes/capabilities.php @@ -16,6 +16,8 @@ * * @since 2.0.0 * + * @global array $post_type_meta_caps Used to get post type meta capabilities. + * * @param string $cap Capability name. * @param int $user_id User ID. * @param int $object_id Optional. ID of the specific object to check against if `$cap` is a "meta" cap. @@ -377,7 +379,7 @@ function map_meta_cap( $cap, $user_id ) { break; default: // Handle meta capabilities for custom post types. - $post_type_meta_caps = _post_type_meta_capabilities(); + global $post_type_meta_caps; if ( isset( $post_type_meta_caps[ $cap ] ) ) { $args = array_merge( array( $post_type_meta_caps[ $cap ], $user_id ), $args ); return call_user_func_array( 'map_meta_cap', $args ); diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 690e597c1a..bb7e37a9a2 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -1186,6 +1186,88 @@ function register_post_type( $post_type, $args = array() ) { return $args; } +/** + * Unregister a post type. + * + * Can not be used to unregister built-in post types. + * + * @since 4.5.0 + * + * @global WP_Rewrite $wp_rewrite WordPress rewrite component. + * @global WP $wp Current WordPress environment instance. + * @global array $_wp_post_type_features Used to remove post type features. + * @global array $post_type_meta_caps Used to remove meta capabilities. + * @global array $wp_post_types List of post types. + * + * @param string $post_type Post type key. + * @return bool|WP_Error True on success, WP_Error on failure. + */ +function unregister_post_type( $post_type ) { + if ( ! post_type_exists( $post_type ) ) { + return new WP_Error( 'invalid_post_type', __( 'Invalid post type' ) ); + } + + $post_type_args = get_post_type_object( $post_type ); + + // Do not allow unregistering internal post types. + if ( $post_type_args->_builtin ) { + return new WP_Error( 'invalid_post_type', __( 'Unregistering a built-in post type is not allowed' ) ); + } + + global $wp, $wp_rewrite, $_wp_post_type_features, $post_type_meta_caps, $wp_post_types; + + // Remove query var. + if ( false !== $post_type_args->query_var ) { + $wp->remove_query_var( $post_type_args->query_var ); + } + + // Remove any rewrite rules, permastructs, and rules. + if ( false !== $post_type_args->rewrite ) { + remove_rewrite_tag( "%$post_type%" ); + remove_permastruct( $post_type ); + foreach ( $wp_rewrite->extra_rules_top as $regex => $query ) { + if ( false !== strpos( $query, "index.php?post_type=$post_type" ) ) { + unset( $wp_rewrite->extra_rules_top[ $regex ] ); + } + } + } + + // Remove registered custom meta capabilities. + foreach ( $post_type_args->cap as $cap ) { + unset( $post_type_meta_caps[ $cap ] ); + } + + // Remove all post type support. + unset( $_wp_post_type_features[ $post_type ] ); + + // Unregister the post type meta box if a custom callback was specified. + if ( $post_type_args->register_meta_box_cb ) { + remove_action( 'add_meta_boxes_' . $post_type, $post_type_args->register_meta_box_cb ); + } + + // Remove the post type from all taxonomies. + foreach ( get_object_taxonomies( $post_type ) as $taxonomy ) { + unregister_taxonomy_for_object_type( $taxonomy, $post_type ); + } + + // Remove the future post hook action. + remove_action( 'future_' . $post_type, '_future_post_hook', 5 ); + + // Remove the post type. + unset( $wp_post_types[ $post_type ] ); + + /** + * Fires after a post type was unregistered. + * + * @since 4.5.0 + * + * @param string $post_type Post type key. + */ + do_action( 'unregistered_post_type', $post_type ); + + return true; +} + /** * Build an object with all post type capabilities out of a post type object * @@ -1293,17 +1375,17 @@ function get_post_type_capabilities( $args ) { * @since 3.1.0 * @access private * - * @staticvar array $meta_caps + * @global array $post_type_meta_caps Used to store meta capabilities. * - * @param array|void $capabilities Post type meta capabilities. + * @param array $capabilities Post type meta capabilities. */ function _post_type_meta_capabilities( $capabilities = null ) { - static $meta_caps = array(); - if ( null === $capabilities ) - return $meta_caps; + global $post_type_meta_caps; + foreach ( $capabilities as $core => $custom ) { - if ( in_array( $core, array( 'read_post', 'delete_post', 'edit_post' ) ) ) - $meta_caps[ $custom ] = $core; + if ( in_array( $core, array( 'read_post', 'delete_post', 'edit_post' ) ) ) { + $post_type_meta_caps[ $custom ] = $core; + } } } diff --git a/tests/phpunit/includes/utils.php b/tests/phpunit/includes/utils.php index bbffa4ec98..17b5630b7d 100644 --- a/tests/phpunit/includes/utils.php +++ b/tests/phpunit/includes/utils.php @@ -335,14 +335,7 @@ if ( !function_exists( 'str_getcsv' ) ) { * Removes the post type and its taxonomy associations. */ function _unregister_post_type( $cpt_name ) { - unset( $GLOBALS['wp_post_types'][ $cpt_name ] ); - unset( $GLOBALS['_wp_post_type_features'][ $cpt_name ] ); - - foreach ( $GLOBALS['wp_taxonomies'] as $taxonomy ) { - if ( false !== $key = array_search( $cpt_name, $taxonomy->object_type ) ) { - unset( $taxonomy->object_type[$key] ); - } - } + unregister_post_type( $cpt_name ); } function _unregister_taxonomy( $taxonomy_name ) { diff --git a/tests/phpunit/tests/post/types.php b/tests/phpunit/tests/post/types.php index 02d4590da8..9d19a84213 100644 --- a/tests/phpunit/tests/post/types.php +++ b/tests/phpunit/tests/post/types.php @@ -159,4 +159,224 @@ class Tests_Post_Types extends WP_UnitTestCase { _unregister_post_type( 'foo' ); } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type() { + register_post_type( 'foo' ); + $this->assertTrue( unregister_post_type( 'foo' ) ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_unknown_post_type() { + $this->assertWPError( unregister_post_type( 'foo' ) ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_twice() { + register_post_type( 'foo' ); + $this->assertTrue( unregister_post_type( 'foo' ) ); + $this->assertWPError( unregister_post_type( 'foo' ) ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_disallow_builtin_post_type() { + $this->assertWPError( unregister_post_type( 'post' ) ); + $this->assertWPError( unregister_post_type( 'page' ) ); + $this->assertWPError( unregister_post_type( 'attachment' ) ); + $this->assertWPError( unregister_post_type( 'revision' ) ); + $this->assertWPError( unregister_post_type( 'nav_menu_item' ) ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_removes_query_vars() { + global $wp; + + register_post_type( 'foo', array( + 'public' => true, + 'query_var' => 'bar', + ) ); + + $this->assertInternalType( 'int', array_search( 'bar', $wp->public_query_vars ) ); + $this->assertTrue( unregister_post_type( 'foo' ) ); + $this->assertFalse( array_search( 'bar', $wp->public_query_vars ) ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_removes_rewrite_tags() { + $this->set_permalink_structure( '/%postname%' ); + + global $wp_rewrite; + + register_post_type( 'foo', array( + 'public' => true, + 'query_var' => 'bar', + ) ); + + $count_before = count( $wp_rewrite->rewritereplace ); + + $this->assertContains( '%foo%', $wp_rewrite->rewritecode ); + $this->assertContains( 'bar=', $wp_rewrite->queryreplace ); + $this->assertTrue( unregister_post_type( 'foo' ) ); + $this->assertNotContains( '%foo%', $wp_rewrite->rewritecode ); + $this->assertNotContains( 'bar=', $wp_rewrite->queryreplace ); + $this->assertSame( -- $count_before, count( $wp_rewrite->rewritereplace ) ); // Array was reduced by one value. + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_removes_rewrite_rules() { + $this->set_permalink_structure( '/%postname%' ); + + global $wp_rewrite; + + register_post_type( 'foo', array( + 'public' => true, + 'has_archive' => true, + ) ); + + $this->assertContains( 'index.php?post_type=foo', $wp_rewrite->extra_rules_top ); + $this->assertTrue( unregister_post_type( 'foo' ) ); + $this->assertNotContains( 'index.php?post_type=foo', $wp_rewrite->extra_rules_top ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_removes_custom_meta_capabilities() { + global $post_type_meta_caps; + + register_post_type( 'foo', array( + 'public' => true, + 'capability_type' => 'bar', + 'map_meta_cap' => true, + ) ); + + $this->assertSame( 'read_post', $post_type_meta_caps['read_bar'] ); + $this->assertSame( 'delete_post', $post_type_meta_caps['delete_bar'] ); + $this->assertSame( 'edit_post', $post_type_meta_caps['edit_bar'] ); + + $this->assertTrue( unregister_post_type( 'foo' ) ); + + $this->assertFalse( isset( $post_type_meta_caps['read_bar'] ) ); + $this->assertFalse( isset( $post_type_meta_caps['delete_bar'] ) ); + $this->assertFalse( isset( $post_type_meta_caps['edit_bar'] ) ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_removes_post_type_supports() { + global $_wp_post_type_features; + + register_post_type( 'foo', array( + 'public' => true, + 'supports' => array( 'editor', 'author', 'title' ), + ) ); + + $this->assertEqualSetsWithIndex( + array( + 'editor' => true, + 'author' => true, + 'title' => true, + ), + $_wp_post_type_features['foo'] + ); + $this->assertTrue( unregister_post_type( 'foo' ) ); + $this->assertFalse( isset( $_wp_post_type_features['foo'] ) ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_removes_post_type_from_taxonomies() { + global $wp_taxonomies; + + register_post_type( 'foo', array( + 'public' => true, + 'taxonomies' => array( 'category', 'post_tag' ), + ) ); + + $this->assertInternalType( 'int', array_search( 'foo', $wp_taxonomies['category']->object_type, true ) ); + $this->assertInternalType( 'int', array_search( 'foo', $wp_taxonomies['post_tag']->object_type, true ) ); + $this->assertTrue( unregister_post_type( 'foo' ) ); + $this->assertFalse( array_search( 'foo', $wp_taxonomies['category']->object_type, true ) ); + $this->assertFalse( array_search( 'foo', $wp_taxonomies['post_tag']->object_type, true ) ); + $this->assertEmpty( get_object_taxonomies( 'foo' ) ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_removes_the_future_post_hooks() { + global $wp_filter; + + register_post_type( 'foo', array( + 'public' => true, + ) ); + + $this->assertSame( 1, count( $wp_filter['future_foo'] ) ); + $this->assertTrue( unregister_post_type( 'foo' ) ); + $this->assertSame( array(), $wp_filter['future_foo'] ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_removes_meta_box_callback() { + global $wp_filter; + + register_post_type( 'foo', array( + 'public' => true, + 'register_meta_box_cb' => '__return_empty_string', + ) ); + + $this->assertSame( 1, count( $wp_filter['add_meta_boxes_foo'] ) ); + $this->assertTrue( unregister_post_type( 'foo' ) ); + $this->assertSame( array(), $wp_filter['add_meta_boxes_foo'] ); + } + + /** + * @ticket 14761 + */ + public function test_unregister_post_type_removes_post_type_from_global() { + global $wp_post_types; + + register_post_type( 'foo', array( + 'public' => true, + ) ); + + $this->assertInternalType( 'object', $wp_post_types['foo'] ); + $this->assertInternalType( 'object', get_post_type_object( 'foo' ) ); + + $this->assertTrue( unregister_post_type( 'foo' ) ); + + $this->assertFalse( isset( $wp_post_types['foo'] ) ); + $this->assertNull( get_post_type_object( 'foo' ) ); + } + + /** + * @ticket 14761 + */ + public function test_post_type_does_not_exist_after_unregister_post_type() { + register_post_type( 'foo', array( + 'public' => true, + ) ); + + $this->assertTrue( unregister_post_type( 'foo' ) ); + + $this->assertFalse( post_type_exists( 'foo' ) ); + } }