From 507243a3e2db319ba93b6edc8c9e7cdc61b2b58e Mon Sep 17 00:00:00 2001 From: "Dominik Schilling (ocean90)" Date: Mon, 3 Nov 2014 21:34:44 +0000 Subject: [PATCH] Customizer: Add stable sorting for panels, sections and controls in JS. Improve sorting in PHP. props westonruter. fixes #30225. git-svn-id: https://develop.svn.wordpress.org/trunk@30214 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/js/customize-controls.js | 27 ++++++++++---- .../class-wp-customize-control.php | 36 +++++++++++++++---- .../class-wp-customize-manager.php | 27 ++++++-------- src/wp-includes/class-wp-customize-panel.php | 23 ++++++++++++ .../class-wp-customize-section.php | 23 ++++++++++++ 5 files changed, 107 insertions(+), 29 deletions(-) diff --git a/src/wp-admin/js/customize-controls.js b/src/wp-admin/js/customize-controls.js index 0ea60ceee2..66d63bd13c 100644 --- a/src/wp-admin/js/customize-controls.js +++ b/src/wp-admin/js/customize-controls.js @@ -1,6 +1,6 @@ /* globals _wpCustomizeHeader, _wpMediaViewsL10n */ (function( exports, $ ){ - var bubbleChildValueChanges, Container, focus, isKeydownButNotEnterEvent, areElementListsEqual, api = wp.customize; + var bubbleChildValueChanges, Container, focus, isKeydownButNotEnterEvent, areElementListsEqual, prioritySort, api = wp.customize; // @todo Move private helper functions to wp.customize.utils so they can be unit tested @@ -77,6 +77,23 @@ } }; + /** + * Stable sort for Panels, Sections, and Controls. + * + * If a.priority() === b.priority(), then sort by their respective params.instanceNumber. + * + * @param {(wp.customize.Panel|wp.customize.Section|wp.customize.Control)} a + * @param {(wp.customize.Panel|wp.customize.Section|wp.customize.Control)} b + * @returns {Number} + */ + prioritySort = function ( a, b ) { + if ( a.priority() === b.priority() && typeof a.params.instanceNumber === 'number' && typeof b.params.instanceNumber === 'number' ) { + return a.params.instanceNumber - b.params.instanceNumber; + } else { + return a.priority() - b.priority(); + } + }; + /** * Return whether the supplied Event object is for a keydown event but not the Enter key. * @@ -176,9 +193,7 @@ children.push( child ); } } ); - children.sort( function ( a, b ) { - return a.priority() - b.priority(); - } ); + children.sort( prioritySort ); return children; }, @@ -1952,9 +1967,7 @@ } ); // Sort the root panels and sections - rootNodes.sort( function ( a, b ) { - return a.priority() - b.priority(); - } ); + rootNodes.sort( prioritySort ); rootContainers = _.pluck( rootNodes, 'container' ); appendContainer = $( '#customize-theme-controls' ).children( 'ul' ); // @todo This should be defined elsewhere, and to be configurable if ( ! areElementListsEqual( rootContainers, appendContainer.children() ) ) { diff --git a/src/wp-includes/class-wp-customize-control.php b/src/wp-includes/class-wp-customize-control.php index 2130f0c0de..7fe3c0f3c5 100644 --- a/src/wp-includes/class-wp-customize-control.php +++ b/src/wp-includes/class-wp-customize-control.php @@ -7,6 +7,27 @@ * @since 3.4.0 */ class WP_Customize_Control { + + /** + * Incremented with each new class instantiation, then stored in $instance_number. + * + * Used when sorting two instances whose priorities are equal. + * + * @since 4.1.0 + * @access protected + * @var int + */ + protected static $instance_count = 0; + + /** + * Order in which this instance was created in relation to other instances. + * + * @since 4.1.0 + * @access public + * @var int + */ + public $instance_number; + /** * @access public * @var WP_Customize_Manager @@ -127,6 +148,8 @@ class WP_Customize_Control { if ( empty( $this->active_callback ) ) { $this->active_callback = array( $this, 'active_callback' ); } + self::$instance_count += 1; + $this->instance_number = self::$instance_count; // Process settings. if ( empty( $this->settings ) ) { @@ -218,13 +241,14 @@ class WP_Customize_Control { $this->json['settings'][ $key ] = $setting->id; } - $this->json['type'] = $this->type; - $this->json['priority'] = $this->priority; - $this->json['active'] = $this->active(); - $this->json['section'] = $this->section; - $this->json['content'] = $this->get_content(); - $this->json['label'] = $this->label; + $this->json['type'] = $this->type; + $this->json['priority'] = $this->priority; + $this->json['active'] = $this->active(); + $this->json['section'] = $this->section; + $this->json['content'] = $this->get_content(); + $this->json['label'] = $this->label; $this->json['description'] = $this->description; + $this->json['instanceNumber'] = $this->instance_number; } /** diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index 371ea90fdc..987edc6e85 100644 --- a/src/wp-includes/class-wp-customize-manager.php +++ b/src/wp-includes/class-wp-customize-manager.php @@ -856,28 +856,27 @@ final class WP_Customize_Manager { * @since 4.1.0 */ public function render_control_templates() { - foreach( $this->registered_control_types as $control_type ) { + foreach ( $this->registered_control_types as $control_type ) { $control = new $control_type( $this, 'temp', array() ); $control->print_template(); } } - /** - * Helper function to compare two objects by priority. + /** + * Helper function to compare two objects by priority, ensuring sort stability via instance_number. * * @since 3.4.0 * - * @param object $a Object A. - * @param object $b Object B. + * @param {WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control} $a Object A. + * @param {WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control} $b Object B. * @return int */ protected final function _cmp_priority( $a, $b ) { - $ap = $a->priority; - $bp = $b->priority; - - if ( $ap == $bp ) - return 0; - return ( $ap > $bp ) ? 1 : -1; + if ( $a->priority === $b->priority ) { + return $a->instance_number - $a->instance_number; + } else { + return $a->priority - $b->priority; + } } /** @@ -891,8 +890,8 @@ final class WP_Customize_Manager { */ public function prepare_controls() { - $this->controls = array_reverse( $this->controls ); $controls = array(); + uasort( $this->controls, array( $this, '_cmp_priority' ) ); foreach ( $this->controls as $id => $control ) { if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) { @@ -905,8 +904,6 @@ final class WP_Customize_Manager { $this->controls = $controls; // Prepare sections. - // Reversing makes uasort sort by time added when conflicts occur. - $this->sections = array_reverse( $this->sections ); uasort( $this->sections, array( $this, '_cmp_priority' ) ); $sections = array(); @@ -930,8 +927,6 @@ final class WP_Customize_Manager { $this->sections = $sections; // Prepare panels. - // Reversing makes uasort sort by time added when conflicts occur. - $this->panels = array_reverse( $this->panels ); uasort( $this->panels, array( $this, '_cmp_priority' ) ); $panels = array(); diff --git a/src/wp-includes/class-wp-customize-panel.php b/src/wp-includes/class-wp-customize-panel.php index 201c4b914f..7a5b36453a 100644 --- a/src/wp-includes/class-wp-customize-panel.php +++ b/src/wp-includes/class-wp-customize-panel.php @@ -10,6 +10,26 @@ */ class WP_Customize_Panel { + /** + * Incremented with each new class instantiation, then stored in $instance_number. + * + * Used when sorting two instances whose priorities are equal. + * + * @since 4.1.0 + * @access protected + * @var int + */ + protected static $instance_count = 0; + + /** + * Order in which this instance was created in relation to other instances. + * + * @since 4.1.0 + * @access public + * @var int + */ + public $instance_number; + /** * WP_Customize_Manager instance. * @@ -128,6 +148,8 @@ class WP_Customize_Panel { if ( empty( $this->active_callback ) ) { $this->active_callback = array( $this, 'active_callback' ); } + self::$instance_count += 1; + $this->instance_number = self::$instance_count; $this->sections = array(); // Users cannot customize the $sections array. @@ -185,6 +207,7 @@ class WP_Customize_Panel { $array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'type' ) ); $array['content'] = $this->get_content(); $array['active'] = $this->active(); + $array['instanceNumber'] = $this->instance_number; return $array; } diff --git a/src/wp-includes/class-wp-customize-section.php b/src/wp-includes/class-wp-customize-section.php index 3553285cc4..86c565d56c 100644 --- a/src/wp-includes/class-wp-customize-section.php +++ b/src/wp-includes/class-wp-customize-section.php @@ -10,6 +10,26 @@ */ class WP_Customize_Section { + /** + * Incremented with each new class instantiation, then stored in $instance_number. + * + * Used when sorting two instances whose priorities are equal. + * + * @since 4.1.0 + * @access protected + * @var int + */ + protected static $instance_count = 0; + + /** + * Order in which this instance was created in relation to other instances. + * + * @since 4.1.0 + * @access public + * @var int + */ + public $instance_number; + /** * WP_Customize_Manager instance. * @@ -137,6 +157,8 @@ class WP_Customize_Section { if ( empty( $this->active_callback ) ) { $this->active_callback = array( $this, 'active_callback' ); } + self::$instance_count += 1; + $this->instance_number = self::$instance_count; $this->controls = array(); // Users cannot customize the $controls array. @@ -194,6 +216,7 @@ class WP_Customize_Section { $array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'panel', 'type' ) ); $array['content'] = $this->get_content(); $array['active'] = $this->active(); + $array['instanceNumber'] = $this->instance_number; return $array; }