Make WordPress Core

Changeset 53104


Ignore:
Timestamp:
04/08/2022 06:15:02 AM (3 years ago)
Author:
peterwilsoncc
Message:

Administration: Allow floats for menu positions.

Permit plugin authors to pass the menu position as a float in add_menu_page() and add_submenu_page(). This allows for a common practice within major plugins to avoid menu collisions by passing a float.

Follow up to [52569].

Props justinbusa, dd32, welcher, SergeyBiryukov, kirtan95, audrasjb, Cybr, chaion07, costdev, peterwilsoncc.
See #40927.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/plugin.php

    r53061 r53104  
    12851285 * @global array $_parent_pages
    12861286 *
    1287  * @param string   $page_title The text to be displayed in the title tags of the page when the menu is selected.
    1288  * @param string   $menu_title The text to be used for the menu.
    1289  * @param string   $capability The capability required for this menu to be displayed to the user.
    1290  * @param string   $menu_slug  The slug name to refer to this menu by. Should be unique for this menu page and only
    1291  *                             include lowercase alphanumeric, dashes, and underscores characters to be compatible
    1292  *                             with sanitize_key().
    1293  * @param callable $function   Optional. The function to be called to output the content for this page.
    1294  * @param string   $icon_url   Optional. The URL to the icon to be used for this menu.
    1295  *                             * Pass a base64-encoded SVG using a data URI, which will be colored to match
    1296  *                               the color scheme. This should begin with 'data:image/svg+xml;base64,'.
    1297  *                             * Pass the name of a Dashicons helper class to use a font icon,
    1298  *                               e.g. 'dashicons-chart-pie'.
    1299  *                             * Pass 'none' to leave div.wp-menu-image empty so an icon can be added via CSS.
    1300  * @param int      $position   Optional. The position in the menu order this item should appear.
     1287 * @param string    $page_title The text to be displayed in the title tags of the page when the menu is selected.
     1288 * @param string    $menu_title The text to be used for the menu.
     1289 * @param string    $capability The capability required for this menu to be displayed to the user.
     1290 * @param string    $menu_slug  The slug name to refer to this menu by. Should be unique for this menu page and only
     1291 *                              include lowercase alphanumeric, dashes, and underscores characters to be compatible
     1292 *                              with sanitize_key().
     1293 * @param callable  $function   Optional. The function to be called to output the content for this page.
     1294 * @param string    $icon_url   Optional. The URL to the icon to be used for this menu.
     1295 *                              * Pass a base64-encoded SVG using a data URI, which will be colored to match
     1296 *                                the color scheme. This should begin with 'data:image/svg+xml;base64,'.
     1297 *                              * Pass the name of a Dashicons helper class to use a font icon,
     1298 *                                e.g. 'dashicons-chart-pie'.
     1299 *                              * Pass 'none' to leave div.wp-menu-image empty so an icon can be added via CSS.
     1300 * @param int|float $position   Optional. The position in the menu order this item should appear.
    13011301 * @return string The resulting page's hook_suffix.
    13021302 */
     
    13241324    $new_menu = array( $menu_title, $capability, $menu_slug, $page_title, 'menu-top ' . $icon_class . $hookname, $hookname, $icon_url );
    13251325
    1326     if ( null === $position ) {
     1326    if ( null === $position || ! is_numeric( $position ) ) {
    13271327        $menu[] = $new_menu;
    1328     } elseif ( isset( $menu[ "$position" ] ) ) {
    1329         $position            = $position + substr( base_convert( md5( $menu_slug . $menu_title ), 16, 10 ), -5 ) * 0.00001;
    1330         $menu[ "$position" ] = $new_menu;
     1328    } elseif ( isset( $menu[ (string) $position ] ) ) {
     1329        $collision_avoider = base_convert( substr( md5( $menu_slug . $menu_title ), -4 ), 16, 10 ) * 0.00001;
     1330        $position          = (string) ( $position + $collision_avoider );
     1331        $menu[ $position ] = $new_menu;
    13311332    } else {
    1332         if ( ! is_int( $position ) ) {
    1333             _doing_it_wrong(
    1334                 __FUNCTION__,
    1335                 sprintf(
    1336                     /* translators: %s: add_menu_page() */
    1337                     __( 'The seventh parameter passed to %s should be an integer representing menu position.' ),
    1338                     '<code>add_menu_page()</code>'
    1339                 ),
    1340                 '6.0.0'
    1341             );
    1342             // If the position is not a string (i.e. float), convert it to string.
    1343             if ( ! is_string( $position ) ) {
    1344                 $position = (string) $position;
    1345             }
    1346         }
     1333        /*
     1334         * Cast menu position to a string.
     1335         *
     1336         * This allows for floats to be passed as the position. PHP will normally cast a float to an
     1337         * integer value, this ensures the float retains its mantissa (positive fractional part).
     1338         *
     1339         * A string containing an integer value, eg "10", is treated as a numeric index.
     1340         */
     1341        $position          = (string) $position;
    13471342        $menu[ $position ] = $new_menu;
    13481343    }
     
    13751370 * @global array $_parent_pages
    13761371 *
    1377  * @param string   $parent_slug The slug name for the parent menu (or the file name of a standard
    1378  *                              WordPress admin page).
    1379  * @param string   $page_title  The text to be displayed in the title tags of the page when the menu
    1380  *                              is selected.
    1381  * @param string   $menu_title  The text to be used for the menu.
    1382  * @param string   $capability  The capability required for this menu to be displayed to the user.
    1383  * @param string   $menu_slug   The slug name to refer to this menu by. Should be unique for this menu
    1384  *                              and only include lowercase alphanumeric, dashes, and underscores characters
    1385  *                              to be compatible with sanitize_key().
    1386  * @param callable $function    Optional. The function to be called to output the content for this page.
    1387  * @param int      $position    Optional. The position in the menu order this item should appear.
     1372 * @param string         $parent_slug The slug name for the parent menu (or the file name of a standard
     1373 *                                    WordPress admin page).
     1374 * @param string         $page_title  The text to be displayed in the title tags of the page when the menu
     1375 *                                    is selected.
     1376 * @param string         $menu_title  The text to be used for the menu.
     1377 * @param string         $capability  The capability required for this menu to be displayed to the user.
     1378 * @param string         $menu_slug   The slug name to refer to this menu by. Should be unique for this menu
     1379 *                                    and only include lowercase alphanumeric, dashes, and underscores characters
     1380 *                                    to be compatible with sanitize_key().
     1381 * @param callable       $function    Optional. The function to be called to output the content for this page.
     1382 * @param int|float      $position    Optional. The position in the menu order this item should appear.
    13881383 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
    13891384 */
     
    14191414
    14201415    $new_sub_menu = array( $menu_title, $capability, $menu_slug, $page_title );
    1421     if ( ! is_int( $position ) ) {
    1422         if ( null !== $position ) {
    1423             _doing_it_wrong(
    1424                 __FUNCTION__,
    1425                 sprintf(
    1426                     /* translators: %s: add_submenu_page() */
    1427                     __( 'The seventh parameter passed to %s should be an integer representing menu position.' ),
    1428                     '<code>add_submenu_page()</code>'
    1429                 ),
    1430                 '5.3.0'
    1431             );
    1432         }
    1433 
     1416
     1417    if ( null !== $position && ! is_numeric( $position ) ) {
     1418        _doing_it_wrong(
     1419            __FUNCTION__,
     1420            sprintf(
     1421                /* translators: %s: add_submenu_page() */
     1422                __( 'The seventh parameter passed to %s should be an integer representing menu position.' ),
     1423                '<code>add_submenu_page()</code>'
     1424            ),
     1425            '5.3.0'
     1426        );
     1427        $position = null;
     1428    }
     1429
     1430    if (
     1431        null === $position ||
     1432        ( ! isset( $submenu[ $parent_slug ] ) || $position >= count( $submenu[ $parent_slug ] ) )
     1433    ) {
    14341434        $submenu[ $parent_slug ][] = $new_sub_menu;
    14351435    } else {
    1436         // Append the submenu if the parent item is not present in the submenu,
    1437         // or if position is equal or higher than the number of items in the array.
    1438         if ( ! isset( $submenu[ $parent_slug ] ) || $position >= count( $submenu[ $parent_slug ] ) ) {
    1439             $submenu[ $parent_slug ][] = $new_sub_menu;
     1436        // Test for a negative position.
     1437        $position = max( $position, 0 );
     1438        if ( 0 === $position ) {
     1439            // For negative or `0` positions, prepend the submenu.
     1440            array_unshift( $submenu[ $parent_slug ], $new_sub_menu );
    14401441        } else {
    1441             // Test for a negative position.
    1442             $position = max( $position, 0 );
    1443             if ( 0 === $position ) {
    1444                 // For negative or `0` positions, prepend the submenu.
    1445                 array_unshift( $submenu[ $parent_slug ], $new_sub_menu );
    1446             } else {
    1447                 // Grab all of the items before the insertion point.
    1448                 $before_items = array_slice( $submenu[ $parent_slug ], 0, $position, true );
    1449                 // Grab all of the items after the insertion point.
    1450                 $after_items = array_slice( $submenu[ $parent_slug ], $position, null, true );
    1451                 // Add the new item.
    1452                 $before_items[] = $new_sub_menu;
    1453                 // Merge the items.
    1454                 $submenu[ $parent_slug ] = array_merge( $before_items, $after_items );
    1455             }
    1456         }
    1457     }
     1442            // Grab all of the items before the insertion point.
     1443            $before_items = array_slice( $submenu[ $parent_slug ], 0, $position, true );
     1444            // Grab all of the items after the insertion point.
     1445            $after_items = array_slice( $submenu[ $parent_slug ], $position, null, true );
     1446            // Add the new item.
     1447            $before_items[] = $new_sub_menu;
     1448            // Merge the items.
     1449            $submenu[ $parent_slug ] = array_merge( $before_items, $after_items );
     1450        }
     1451    }
     1452
    14581453    // Sort the parent array.
    14591454    ksort( $submenu[ $parent_slug ] );
  • trunk/tests/phpunit/tests/admin/includesPlugin.php

    r52570 r53104  
    257257            array( -1, 0 ),                    // Negative numbers are treated the same as passing 0.
    258258            array( -7, 0 ),                    // Negative numbers are treated the same as passing 0.
     259            array( '-7', 0 ),                  // Negative numbers are treated the same as passing 0.
    259260            array( 1, 1 ),                     // Insert as the second item.
     261            array( '1', 1 ),                   // Insert as the second item.
     262            array( 1.5, 1 ),                   // Insert as the second item.
     263            array( '1.5', 1 ),                 // Insert as the second item.
    260264            array( 3, 3 ),                     // Insert as the 4th item.
     265            array( '3', 3 ),                   // Insert as the 4th item.
     266            array( '3e0', 3 ),                 // Insert as the 4th item.
     267            array( 3.5, 3 ),                   // Insert as the 4th item.
     268            array( '3.5', 3 ),                 // Insert as the 4th item.
    261269            array( $menu_count, $menu_count ), // Numbers equal to the number of items are added at the end.
    262270            array( 123456, $menu_count ),      // Numbers higher than the number of items are added at the end.
     
    315323        // Setup a menu with some items.
    316324        add_menu_page( 'Main Menu', 'Main Menu', 'manage_options', 'main_slug', 'main_page_callback' );
    317         add_submenu_page( 'main_slug', 'SubMenu 1', 'SubMenu 1', 'manage_options', 'submenu_page_1', 'submenu_callback_1', '2' );
     325        add_submenu_page( 'main_slug', 'SubMenu 1', 'SubMenu 1', 'manage_options', 'submenu_page_1', 'submenu_callback_1', 'First' );
    318326
    319327        // Clean up the temporary user.
     
    330338     * @ticket 54798
    331339     */
    332     public function test_passing_string_as_position_fires_doing_it_wrong_menu() {
    333         $this->setExpectedIncorrectUsage( 'add_menu_page' );
     340    public function test_passing_float_as_position_does_not_override_int() {
    334341        global $submenu, $menu;
    335342
     
    343350
    344351        // Setup a menu with some items.
    345         add_menu_page( 'Main Menu', 'Main Menu', 'manage_options', 'main_slug', 'main_page_callback', 'icon_url', '1' );
    346         add_menu_page( 'Main Menu 1', 'Main Menu 1', 'manage_options', 'main1_slug', 'main1_page_callback', 'icon_url1', 1.5 );
     352        add_menu_page( 'Main Menu 1', 'Main Menu 1', 'manage_options', 'main_slug_1', 'main_page_callback_1', 'icon_url_1', 1 );
     353        add_menu_page( 'Main Menu 2', 'Main Menu 2', 'manage_options', 'main_slug_2', 'main_page_callback_2', 'icon_url_2', 2 );
     354        add_menu_page( 'Main Menu 1.5', 'Main Menu 1.5', 'manage_options', 'main_slug_15', 'main_page_callback_15', 'icon_url_15', 1.5 );
    347355
    348356        // Clean up the temporary user.
     
    350358        wp_delete_user( $admin_user );
    351359
    352         // Verify the menu was inserted.
    353         $this->assertSame( 'main_slug', $menu[1][2] );
     360        // Verify the menus were inserted.
     361        $this->assertSame( 'main_slug_1', $menu[1][2] );
     362        $this->assertSame( 'main_slug_2', $menu[2][2] );
    354363        // Verify the menu was inserted correctly on passing float as position.
    355         $this->assertSame( 'main1_slug', $menu['1.5'][2] );
     364        $this->assertSame( 'main_slug_15', $menu['1.5'][2] );
    356365    }
    357366
Note: See TracChangeset for help on using the changeset viewer.