Make WordPress Core


Ignore:
Timestamp:
06/20/2016 07:49:49 PM (8 years ago)
Author:
swissspidy
Message:

Menus: Support nested array variables in POST data when saving menus.

[36510] allowed larger menus to be created in the Edit Menu screen by JSON-encoding the entire form into a single input field. However, it did not correctly handle nested arrays.

This introduces a new _wp_expand_nav_menu_post_data() helper function to handle this POST data which uses array_replace_recursive() internally. Since the latter is only available on PHP 5.3+, we add a compatibility function to ensure PHP 5.2 support.

Merge of [37748] and [37750] to the 4.5 branch.

Props ericlewis, neverything, swissspidy.
Fixes #36590. See #14134.

Location:
branches/4.5
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • branches/4.5

  • branches/4.5/src/wp-admin/nav-menus.php

    r36852 r37754  
    5050$action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'edit';
    5151
     52/**
     53 * If a JSON blob of navigation menu data is in POST data, expand it and inject
     54 * it into `$_POST` to avoid PHP `max_input_vars` limitations. See #14134.
     55 *
     56 * @ignore
     57 * @since 4.5.3
     58 * @access private
     59 */
     60function _wp_expand_nav_menu_post_data() {
     61    if ( ! isset( $_POST['nav-menu-data'] ) ) {
     62        return;
     63    }
     64
     65    $data = json_decode( stripslashes( $_POST['nav-menu-data'] ) );
     66
     67    if ( ! is_null( $data ) && $data ) {
     68        foreach ( $data as $post_input_data ) {
     69            // For input names that are arrays (e.g. `menu-item-db-id[3][4][5]`),
     70            // derive the array path keys via regex and set the value in $_POST.
     71            preg_match( '#([^\[]*)(\[(.+)\])?#', $post_input_data->name, $matches );
     72
     73            $array_bits = array( $matches[1] );
     74
     75            if ( isset( $matches[3] ) ) {
     76                $array_bits = array_merge( $array_bits, explode( '][', $matches[3] ) );
     77            }
     78
     79            $new_post_data = array();
     80
     81            // Build the new array value from leaf to trunk.
     82            for ( $i = count( $array_bits ) - 1; $i >= 0; $i -- ) {
     83                if ( $i == count( $array_bits ) - 1 ) {
     84                    $new_post_data[ $array_bits[ $i ] ] = wp_slash( $post_input_data->value );
     85                } else {
     86                    $new_post_data = array( $array_bits[ $i ] => $new_post_data );
     87                }
     88            }
     89
     90            $_POST = array_replace_recursive( $_POST, $new_post_data );
     91        }
     92    }
     93}
     94
     95if ( ! function_exists( 'array_replace_recursive' ) ) :
     96    /**
     97     * PHP-agnostic version of {@link array_replace_recursive()}.
     98     *
     99     * The array_replace_recursive() function is a PHP 5.3 function. WordPress
     100     * currently supports down to PHP 5.2, so this method is a workaround
     101     * for PHP 5.2.
     102     *
     103     * Note: array_replace_recursive() supports infinite arguments, but for our use-
     104     * case, we only need to support two arguments.
     105     *
     106     * Subject to removal once WordPress makes PHP 5.3.0 the minimum requirement.
     107     *
     108     * @since 4.5.3
     109     *
     110     * @see http://php.net/manual/en/function.array-replace-recursive.php#109390
     111     *
     112     * @param  array $base         Array with keys needing to be replaced.
     113     * @param  array $replacements Array with the replaced keys.
     114     *
     115     * @return array
     116     */
     117    function array_replace_recursive( $base = array(), $replacements = array() ) {
     118        foreach ( array_slice( func_get_args(), 1 ) as $replacements ) {
     119            $bref_stack = array( &$base );
     120            $head_stack = array( $replacements );
     121
     122            do {
     123                end( $bref_stack );
     124
     125                $bref = &$bref_stack[ key( $bref_stack ) ];
     126                $head = array_pop( $head_stack );
     127
     128                unset( $bref_stack[ key( $bref_stack ) ] );
     129
     130                foreach ( array_keys( $head ) as $key ) {
     131                    if ( isset( $key, $bref ) &&
     132                         isset( $bref[ $key ] ) && is_array( $bref[ $key ] ) &&
     133                         isset( $head[ $key ] ) && is_array( $head[ $key ] )
     134                    ) {
     135                        $bref_stack[] = &$bref[ $key ];
     136                        $head_stack[] = $head[ $key ];
     137                    } else {
     138                        $bref[ $key ] = $head[ $key ];
     139                    }
     140                }
     141            } while ( count( $head_stack ) );
     142        }
     143
     144        return $base;
     145    }
     146endif;
     147
    52148/*
    53149 * If a JSON blob of navigation menu data is found, expand it and inject it
    54150 * into `$_POST` to avoid PHP `max_input_vars` limitations. See #14134.
    55151 */
    56 if ( isset( $_POST['nav-menu-data'] ) ) {
    57     $data = json_decode( stripslashes( $_POST['nav-menu-data'] ) );
    58     if ( ! is_null( $data ) && $data ) {
    59         foreach ( $data as $post_input_data ) {
    60             // For input names that are arrays (e.g. `menu-item-db-id[3]`), derive the array path keys via regex.
    61             if ( preg_match( '#(.*)\[(\w+)\]#', $post_input_data->name, $matches ) ) {
    62                 if ( empty( $_POST[ $matches[1] ] ) ) {
    63                     $_POST[ $matches[1] ] = array();
    64                 }
    65                 // Cast input elements with a numeric array index to integers.
    66                 if ( is_numeric( $matches[2] ) ) {
    67                     $matches[2] = (int) $matches[2];
    68                 }
    69                 $_POST[ $matches[1] ][ $matches[2] ] = wp_slash( $post_input_data->value );
    70             } else {
    71                 $_POST[ $post_input_data->name ] = wp_slash( $post_input_data->value );
    72             }
    73         }
    74     }
    75 }
     152_wp_expand_nav_menu_post_data();
     153
    76154switch ( $action ) {
    77155    case 'add-menu-item':
Note: See TracChangeset for help on using the changeset viewer.