WordPress.org

Make WordPress Core

Ticket #22895: 22895.3.test.diff

File 22895.3.test.diff, 9.1 KB (added by joshlevinson, 6 years ago)

Unit test

  • tests/phpunit/tests/admin/CPTAdminPageCapabilities.php

     
     1<?php
     2
     3/**
     4 * @group plugins
     5 * @group admin
     6 */
     7class Tests_Admin_CPT_Admin_Page_Capabilities extends WP_UnitTestCase {
     8
     9        /**
     10         * @ticket 22895
     11         */
     12        function test_user_can_access_admin_page_without_create_posts_capability() {
     13
     14                $cpt_slug = 'test-cpt';
     15
     16                $original_user = get_current_user_id();
     17
     18                global $pagenow;
     19
     20                //register post type (before menu/typenow stuff!)
     21                $this->_register_test_post_type( $cpt_slug );
     22
     23                //reset globals
     24                $_GET = $_POST = $_REQUEST = array();
     25
     26                //mock the CPT query
     27                $_GET['post_type'] = $_POST['post_type'] = $_REQUEST['post_type'] = $cpt_slug;
     28
     29                //and mock the base page
     30                $GLOBALS['hook_suffix'] = $pagenow = 'edit.php';
     31
     32                //this will set $typenow to $_REQUEST['post_type']
     33                //as long as the query value is a valid post type
     34                set_current_screen();
     35
     36                //subscriber is lowest level, and should work as a good base for adding caps
     37                wp_set_current_user( $this->factory->user->create( array( 'role' => 'subscriber' ) ) );
     38
     39                //mock menus/privs
     40                $this->_mock_post_menu();
     41                $this->_mock_cpt_menu( $cpt_slug );
     42                $this->_mock_menu_privs();
     43
     44                //grant only the plural edit cap
     45                //important part is *no* create cap
     46                $caps = array(
     47                        "edit_{$cpt_slug}s" => true,
     48                );
     49
     50                $role = get_role( 'subscriber' );
     51
     52                if ( $role ) {
     53                        foreach ( $caps as $cap => $grant ) {
     54                                $role->add_cap( $cap, $grant );
     55                        }
     56                        $this->assertTrue( user_can_access_admin_page() );
     57                }
     58
     59                wp_set_current_user( $original_user );
     60
     61        }
     62
     63        /**
     64         * Register a custom post type for testing
     65         *
     66         * @param string $cpt_slug The slug with which to register the post type
     67         * @param array $args Args to override this func's default post type registration args
     68         */
     69        private function _register_test_post_type( $cpt_slug, $args = array() ) {
     70
     71                $args = wp_parse_args( $args, array(
     72                        'label'        => 'Test CPT',
     73                        'show_in_menu' => true,
     74                        'public'       => true,
     75                        'capabilities' => array(
     76                                'edit_post'              => "edit_{$cpt_slug}",
     77                                'edit_posts'             => "edit_{$cpt_slug}s",
     78                                'edit_others_posts'      => "edit_others_{$cpt_slug}s",
     79                                'publish_posts'          => "publish_{$cpt_slug}s",
     80                                'read_private_posts'     => "read_private_{$cpt_slug}s",
     81                                'delete_posts'           => "delete_{$cpt_slug}s",
     82                                'delete_private_posts'   => "delete_private_{$cpt_slug}s",
     83                                'delete_published_posts' => "delete_published_{$cpt_slug}s",
     84                                'delete_others_posts'    => "delete_others_{$cpt_slug}s",
     85                                'edit_private_posts'     => "edit_private_{$cpt_slug}s",
     86                                'edit_published_posts'   => "edit_published_{$cpt_slug}s",
     87                                'create_posts'           => "edit_others_{$cpt_slug}s",
     88                        ),
     89                        'map_meta_cap' => false,
     90                ) );
     91
     92                register_post_type( $cpt_slug, $args );
     93
     94        }
     95
     96        /**
     97         * Mock the addition of the Post menu/submenus
     98         *
     99         * This is necessary in order to have 'edit.php'
     100         * present in the $_wp_menu_no_privs array,
     101         * which is what causes this scenario to occur.
     102         * Pulled from wp-admin/menu.php
     103         *
     104         * Differences:
     105         * Excluded addition of taxonomies as submenus
     106         */
     107        private function _mock_post_menu() {
     108                global $menu, $submenu;
     109                $menu[5]                 = array(
     110                        __( 'Posts' ),
     111                        'edit_posts',
     112                        'edit.php',
     113                        '',
     114                        'open-if-no-js menu-top menu-icon-post',
     115                        'menu-posts',
     116                        'dashicons-admin-post'
     117                );
     118                $submenu['edit.php'][5]  = array( __( 'All Posts' ), 'edit_posts', 'edit.php' );
     119                $submenu['edit.php'][10] = array(
     120                        _x( 'Add New', 'post' ),
     121                        get_post_type_object( 'post' )->cap->create_posts,
     122                        'post-new.php'
     123                );
     124        }
     125
     126        /**
     127         * Mock the addition of a menu/submenu for a CPT
     128         *
     129         * Pulled from wp-admin/menu.php
     130         * Differences: Outside of the get_post_types results loop
     131         * Not adding taxonomies as submenus (unneeded for this test)
     132         */
     133        private function _mock_cpt_menu( $cpt_slug ) {
     134                global $menu, $submenu;
     135
     136                //dupe so the diff between this and original file is minimal
     137                $ptype = $cpt_slug;
     138
     139                $_wp_last_object_menu = 25;
     140
     141                $ptype_obj = get_post_type_object( $ptype );
     142                // Check if it should be a submenu.
     143                if ( $ptype_obj->show_in_menu !== true ) {
     144                        return;
     145                }
     146                $ptype_menu_position = is_int( $ptype_obj->menu_position ) ? $ptype_obj->menu_position : ++ $_wp_last_object_menu; // If we're to use $_wp_last_object_menu, increment it first.
     147                $ptype_for_id        = sanitize_html_class( $ptype );
     148
     149                if ( is_string( $ptype_obj->menu_icon ) ) {
     150                        // Special handling for data:image/svg+xml and Dashicons.
     151                        if ( 0 === strpos( $ptype_obj->menu_icon, 'data:image/svg+xml;base64,' ) || 0 === strpos( $ptype_obj->menu_icon, 'dashicons-' ) ) {
     152                                $menu_icon = $ptype_obj->menu_icon;
     153                        } else {
     154                                $menu_icon = esc_url( $ptype_obj->menu_icon );
     155                        }
     156                        $ptype_class = $ptype_for_id;
     157                } else {
     158                        $menu_icon   = 'dashicons-admin-post';
     159                        $ptype_class = 'post';
     160                }
     161
     162                /*
     163                 * If $ptype_menu_position is already populated or will be populated
     164                 * by a hard-coded value below, increment the position.
     165                 */
     166                $core_menu_positions = array( 59, 60, 65, 70, 75, 80, 85, 99 );
     167                while ( isset( $menu[ $ptype_menu_position ] ) || in_array( $ptype_menu_position, $core_menu_positions ) ) {
     168                        $ptype_menu_position ++;
     169                }
     170
     171                $menu[ $ptype_menu_position ]             = array(
     172                        esc_attr( $ptype_obj->labels->menu_name ),
     173                        $ptype_obj->cap->edit_posts,
     174                        "edit.php?post_type=$ptype",
     175                        '',
     176                        'menu-top menu-icon-' . $ptype_class,
     177                        'menu-posts-' . $ptype_for_id,
     178                        $menu_icon
     179                );
     180                $submenu["edit.php?post_type=$ptype"][5]  = array(
     181                        $ptype_obj->labels->all_items,
     182                        $ptype_obj->cap->edit_posts,
     183                        "edit.php?post_type=$ptype"
     184                );
     185                $submenu["edit.php?post_type=$ptype"][10] = array(
     186                        $ptype_obj->labels->add_new,
     187                        $ptype_obj->cap->create_posts,
     188                        "post-new.php?post_type=$ptype"
     189                );
     190        }
     191
     192        /**
     193         * Mock the building of priveleges for menus/submenus
     194         *
     195         * Pulled from wp-admin/includes/menu.php
     196         */
     197        private function _mock_menu_privs() {
     198                global $menu, $submenu, $_wp_menu_nopriv, $_wp_submenu_nopriv;
     199
     200                $_wp_submenu_nopriv = array();
     201                $_wp_menu_nopriv    = array();
     202
     203                // Loop over submenus and remove pages for which the user does not have privs.
     204                foreach ( $submenu as $parent => $sub ) {
     205                        foreach ( $sub as $index => $data ) {
     206                                if ( ! current_user_can( $data[1] ) ) {
     207                                        unset( $submenu[ $parent ][ $index ] );
     208                                        $_wp_submenu_nopriv[ $parent ][ $data[2] ] = true;
     209                                }
     210                        }
     211                        unset( $index, $data );
     212
     213                        if ( empty( $submenu[ $parent ] ) ) {
     214                                unset( $submenu[ $parent ] );
     215                        }
     216                }
     217                unset( $sub, $parent );
     218
     219                /*
     220                 * Loop over the top-level menu.
     221                 * Menus for which the original parent is not accessible due to lack of privileges
     222                 * will have the next submenu in line be assigned as the new menu parent.
     223                 */
     224                foreach ( $menu as $id => $data ) {
     225                        if ( empty( $submenu[ $data[2] ] ) ) {
     226                                continue;
     227                        }
     228                        $subs       = $submenu[ $data[2] ];
     229                        $first_sub  = reset( $subs );
     230                        $old_parent = $data[2];
     231                        $new_parent = $first_sub[2];
     232                        /*
     233                         * If the first submenu is not the same as the assigned parent,
     234                         * make the first submenu the new parent.
     235                         */
     236                        if ( $new_parent != $old_parent ) {
     237                                $_wp_real_parent_file[ $old_parent ] = $new_parent;
     238                                $menu[ $id ][2]                      = $new_parent;
     239
     240                                foreach ( $submenu[ $old_parent ] as $index => $data ) {
     241                                        $submenu[ $new_parent ][ $index ] = $submenu[ $old_parent ][ $index ];
     242                                        unset( $submenu[ $old_parent ][ $index ] );
     243                                }
     244                                unset( $submenu[ $old_parent ], $index );
     245
     246                                if ( isset( $_wp_submenu_nopriv[ $old_parent ] ) ) {
     247                                        $_wp_submenu_nopriv[ $new_parent ] = $_wp_submenu_nopriv[ $old_parent ];
     248                                }
     249                        }
     250                }
     251                unset( $id, $data, $subs, $first_sub, $old_parent, $new_parent );
     252
     253                /*
     254                 * Remove menus that have no accessible submenus and require privileges
     255                 * that the user does not have. Run re-parent loop again.
     256                 */
     257                foreach ( $menu as $id => $data ) {
     258                        if ( ! current_user_can( $data[1] ) ) {
     259                                $_wp_menu_nopriv[ $data[2] ] = true;
     260                        }
     261
     262                        /*
     263                         * If there is only one submenu and it is has same destination as the parent,
     264                         * remove the submenu.
     265                         */
     266                        if ( ! empty( $submenu[ $data[2] ] ) && 1 == count( $submenu[ $data[2] ] ) ) {
     267                                $subs      = $submenu[ $data[2] ];
     268                                $first_sub = reset( $subs );
     269                                if ( $data[2] == $first_sub[2] ) {
     270                                        unset( $submenu[ $data[2] ] );
     271                                }
     272                        }
     273
     274                        // If submenu is empty...
     275                        if ( empty( $submenu[ $data[2] ] ) ) {
     276                                // And user doesn't have privs, remove menu.
     277                                if ( isset( $_wp_menu_nopriv[ $data[2] ] ) ) {
     278                                        unset( $menu[ $id ] );
     279                                }
     280                        }
     281                }
     282                unset( $id, $data, $subs, $first_sub );
     283        }
     284}
     285 No newline at end of file