<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[38928] trunk: General: Introduce a `wp_list_sort()` helper function, v2.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/38928">38928</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/38928","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>ocean90</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2016-10-25 21:25:25 +0000 (Tue, 25 Oct 2016)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>General: Introduce a `wp_list_sort()` helper function, v2.

In addition to `wp_list_filter()` for filtering a list of objects, and `wp_list_pluck()` for plucking a certain field out of each object in a list, this new function can be used for sorting a list of objects by specific fields. These functions are now all contained within the new `WP_List_Util()` class and `wp_list_sort()` is used in various parts of core for sorting lists.

This was previously committed in <a href="https://core.trac.wordpress.org/changeset/38859">[38859]</a> but got reverted in <a href="https://core.trac.wordpress.org/changeset/38862">[38862]</a> and <a href="https://core.trac.wordpress.org/changeset/38863">[38863]</a>. To fix the previous issues, `wp_list_sort()` supports now an additional argument to preserve array keys via `uasort()`.

Props flixos90, DrewAPicture, jorbin.
Fixes <a href="https://core.trac.wordpress.org/ticket/37128">#37128</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludescategorytemplatephp">trunk/src/wp-includes/category-template.php</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizemanagerphp">trunk/src/wp-includes/class-wp-customize-manager.php</a></li>
<li><a href="#trunksrcwpincludescustomizeclasswpcustomizenavmenuitemsettingphp">trunk/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php</a></li>
<li><a href="#trunksrcwpincludescustomizeclasswpcustomizenavmenusettingphp">trunk/src/wp-includes/customize/class-wp-customize-nav-menu-setting.php</a></li>
<li><a href="#trunksrcwpincludesdeprecatedphp">trunk/src/wp-includes/deprecated.php</a></li>
<li><a href="#trunksrcwpincludesfunctionsphp">trunk/src/wp-includes/functions.php</a></li>
<li><a href="#trunksrcwpincludeslinktemplatephp">trunk/src/wp-includes/link-template.php</a></li>
<li><a href="#trunksrcwpincludesnavmenuphp">trunk/src/wp-includes/nav-menu.php</a></li>
<li><a href="#trunksrcwpsettingsphp">trunk/src/wp-settings.php</a></li>
<li><a href="#trunktestsphpunittestscustomizemanagerphp">trunk/tests/phpunit/tests/customize/manager.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswplistutilphp">trunk/src/wp-includes/class-wp-list-util.php</a></li>
<li><a href="#trunktestsphpunittestsfunctionswpListUtilphp">trunk/tests/phpunit/tests/functions/wpListUtil.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludescategorytemplatephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/category-template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/category-template.php       2016-10-25 20:58:13 UTC (rev 38927)
+++ trunk/src/wp-includes/category-template.php 2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -100,45 +100,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Sort categories by name.
- *
- * Used by usort() as a callback, should not be used directly. Can actually be
- * used to sort any term object.
- *
- * @since 2.3.0
- * @access private
- *
- * @param object $a
- * @param object $b
- * @return int
- */
-function _usort_terms_by_name( $a, $b ) {
-       return strcmp( $a->name, $b->name );
-}
-
-/**
- * Sort categories by ID.
- *
- * Used by usort() as a callback, should not be used directly. Can actually be
- * used to sort any term object.
- *
- * @since 2.3.0
- * @access private
- *
- * @param object $a
- * @param object $b
- * @return int
- */
-function _usort_terms_by_ID( $a, $b ) {
-       if ( $a->term_id > $b->term_id )
-               return 1;
-       elseif ( $a->term_id < $b->term_id )
-               return -1;
-       else
-               return 0;
-}
-
-/**
</del><span class="cx" style="display: block; padding: 0 10px">  * Retrieve category name based on category ID.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 0.71
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizemanagerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-customize-manager.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-customize-manager.php      2016-10-25 20:58:13 UTC (rev 38927)
+++ trunk/src/wp-includes/class-wp-customize-manager.php        2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2505,12 +2505,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 3.4.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @deprecated 4.7.0 Use wp_list_sort()
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $a Object A.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $b Object B.
</span><span class="cx" style="display: block; padding: 0 10px">         * @return int
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        protected function _cmp_priority( $a, $b ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                _deprecated_function( __METHOD__, '4.7.0', 'wp_list_sort' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( $a->priority === $b->priority ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return $a->instance_number - $b->instance_number;
</span><span class="cx" style="display: block; padding: 0 10px">                } else {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2530,7 +2533,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function prepare_controls() {
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $controls = array();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                uasort( $this->controls, array( $this, '_cmp_priority' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->controls = wp_list_sort( $this->controls, array(
+                       'priority'        => 'ASC',
+                       'instance_number' => 'ASC',
+               ), 'ASC', true );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $this->controls as $id => $control ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2543,7 +2549,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->controls = $controls;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Prepare sections.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                uasort( $this->sections, array( $this, '_cmp_priority' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->sections = wp_list_sort( $this->sections, array(
+                       'priority'        => 'ASC',
+                       'instance_number' => 'ASC',
+               ), 'ASC', true );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $sections = array();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $this->sections as $section ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2551,8 +2560,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                continue;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        usort( $section->controls, array( $this, '_cmp_priority' ) );
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        $section->controls = wp_list_sort( $section->controls, array(
+                               'priority'        => 'ASC',
+                               'instance_number' => 'ASC',
+                       ) );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         if ( ! $section->panel ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                // Top-level section.
</span><span class="cx" style="display: block; padding: 0 10px">                                $sections[ $section->id ] = $section;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2566,7 +2579,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->sections = $sections;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Prepare panels.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                uasort( $this->panels, array( $this, '_cmp_priority' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->panels = wp_list_sort( $this->panels, array(
+                       'priority'        => 'ASC',
+                       'instance_number' => 'ASC',
+               ), 'ASC', true );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $panels = array();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $this->panels as $panel ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2574,14 +2590,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                continue;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        uasort( $panel->sections, array( $this, '_cmp_priority' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $panel->sections = wp_list_sort( $panel->sections, array(
+                               'priority'        => 'ASC',
+                               'instance_number' => 'ASC',
+                       ), 'ASC', true );
</ins><span class="cx" style="display: block; padding: 0 10px">                         $panels[ $panel->id ] = $panel;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">                $this->panels = $panels;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Sort panels and top-level sections together.
</span><span class="cx" style="display: block; padding: 0 10px">                $this->containers = array_merge( $this->panels, $this->sections );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                uasort( $this->containers, array( $this, '_cmp_priority' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->containers = wp_list_sort( $this->containers, array(
+                       'priority'        => 'ASC',
+                       'instance_number' => 'ASC',
+               ), 'ASC', true );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span></span></pre></div>
<a id="trunksrcwpincludesclasswplistutilphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/class-wp-list-util.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-list-util.php                              (rev 0)
+++ trunk/src/wp-includes/class-wp-list-util.php        2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,267 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * WordPress List utility class
+ *
+ * @package WordPress
+ * @since 4.7.0
+ */
+
+/**
+ * List utility.
+ *
+ * Utility class to handle operations on an array of objects.
+ *
+ * @since 4.7.0
+ */
+class WP_List_Util {
+       /**
+        * The input array.
+        *
+        * @since 4.7.0
+        * @access private
+        * @var array
+        */
+       private $input = array();
+
+       /**
+        * The output array.
+        *
+        * @since 4.7.0
+        * @access private
+        * @var array
+        */
+       private $output = array();
+
+       /**
+        * Temporary arguments for sorting.
+        *
+        * @since 4.7.0
+        * @access private
+        * @var array
+        */
+       private $orderby = array();
+
+       /**
+        * Constructor.
+        *
+        * Sets the input array.
+        *
+        * @since 4.7.0
+        *
+        * @param array $input Array to perform operations on.
+        */
+       public function __construct( $input ) {
+               $this->output = $this->input = $input;
+       }
+
+       /**
+        * Returns the original input array.
+        *
+        * @since 4.7.0
+        * @access public
+        *
+        * @return array The input array.
+        */
+       public function get_input() {
+               return $this->input;
+       }
+
+       /**
+        * Returns the output array.
+        *
+        * @since 4.7.0
+        * @access public
+        *
+        * @return array The output array.
+        */
+       public function get_output() {
+               return $this->output;
+       }
+
+       /**
+        * Filters the list, based on a set of key => value arguments.
+        *
+        * @since 4.7.0
+        *
+        * @param array  $args     Optional. An array of key => value arguments to match
+        *                         against each object. Default empty array.
+        * @param string $operator Optional. The logical operation to perform. 'AND' means
+        *                         all elements from the array must match. 'OR' means only
+        *                         one element needs to match. 'NOT' means no elements may
+        *                         match. Default 'AND'.
+        * @return array Array of found values.
+        */
+       public function filter( $args = array(), $operator = 'AND' ) {
+               if ( empty( $args ) ) {
+                       return $this->output;
+               }
+
+               $operator = strtoupper( $operator );
+
+               if ( ! in_array( $operator, array( 'AND', 'OR', 'NOT' ), true ) ) {
+                       return array();
+               }
+
+               $count = count( $args );
+               $filtered = array();
+
+               foreach ( $this->output as $key => $obj ) {
+                       $to_match = (array) $obj;
+
+                       $matched = 0;
+                       foreach ( $args as $m_key => $m_value ) {
+                               if ( array_key_exists( $m_key, $to_match ) && $m_value == $to_match[ $m_key ] ) {
+                                       $matched++;
+                               }
+                       }
+
+                       if (
+                               ( 'AND' == $operator && $matched == $count ) ||
+                               ( 'OR' == $operator && $matched > 0 ) ||
+                               ( 'NOT' == $operator && 0 == $matched )
+                       ) {
+                               $filtered[$key] = $obj;
+                       }
+               }
+
+               $this->output = $filtered;
+
+               return $this->output;
+       }
+
+       /**
+        * Plucks a certain field out of each object in the list.
+        *
+        * This has the same functionality and prototype of
+        * array_column() (PHP 5.5) but also supports objects.
+        *
+        * @since 4.7.0
+        *
+        * @param int|string $field     Field from the object to place instead of the entire object
+        * @param int|string $index_key Optional. Field from the object to use as keys for the new array.
+        *                              Default null.
+        * @return array Array of found values. If `$index_key` is set, an array of found values with keys
+        *               corresponding to `$index_key`. If `$index_key` is null, array keys from the original
+        *               `$list` will be preserved in the results.
+        */
+       public function pluck( $field, $index_key = null ) {
+               if ( ! $index_key ) {
+                       /*
+                        * This is simple. Could at some point wrap array_column()
+                        * if we knew we had an array of arrays.
+                        */
+                       foreach ( $this->output as $key => $value ) {
+                               if ( is_object( $value ) ) {
+                                       $this->output[ $key ] = $value->$field;
+                               } else {
+                                       $this->output[ $key ] = $value[ $field ];
+                               }
+                       }
+                       return $this->output;
+               }
+
+               /*
+                * When index_key is not set for a particular item, push the value
+                * to the end of the stack. This is how array_column() behaves.
+                */
+               $newlist = array();
+               foreach ( $this->output as $value ) {
+                       if ( is_object( $value ) ) {
+                               if ( isset( $value->$index_key ) ) {
+                                       $newlist[ $value->$index_key ] = $value->$field;
+                               } else {
+                                       $newlist[] = $value->$field;
+                               }
+                       } else {
+                               if ( isset( $value[ $index_key ] ) ) {
+                                       $newlist[ $value[ $index_key ] ] = $value[ $field ];
+                               } else {
+                                       $newlist[] = $value[ $field ];
+                               }
+                       }
+               }
+
+               $this->output = $newlist;
+
+               return $this->output;
+       }
+
+       /**
+        * Sorts the list, based on one or more orderby arguments.
+        *
+        * @since 4.7.0
+        *
+        * @param string|array $orderby       Optional. Either the field name to order by or an array
+        *                                    of multiple orderby fields as $orderby => $order.
+        * @param string       $order         Optional. Either 'ASC' or 'DESC'. Only used if $orderby
+        *                                    is a string.
+        * @param bool         $preserve_keys Optional. Whether to preserve keys. Default false.
+        * @return array The sorted array.
+        */
+       public function sort( $orderby = array(), $order = 'ASC', $preserve_keys = false ) {
+               if ( empty( $orderby ) ) {
+                       return $this->output;
+               }
+
+               if ( is_string( $orderby ) ) {
+                       $orderby = array( $orderby => $order );
+               }
+
+               foreach ( $orderby as $field => $direction ) {
+                       $orderby[ $field ] = 'DESC' === strtoupper( $direction ) ? 'DESC' : 'ASC';
+               }
+
+               $this->orderby = $orderby;
+
+               if ( $preserve_keys ) {
+                       uasort( $this->output, array( $this, 'sort_callback' ) );
+               } else {
+                       usort( $this->output, array( $this, 'sort_callback' ) );
+               }
+
+               $this->orderby = array();
+
+               return $this->output;
+       }
+
+       /**
+        * Callback to sort the list by specific fields.
+        *
+        * @since 4.7.0
+        * @access private
+        *
+        * @see WP_List_Util::sort()
+        *
+        * @param object|array $a One object to compare.
+        * @param object|array $b The other object to compare.
+        * @return int 0 if both objects equal. -1 if second object should come first, 1 otherwise.
+        */
+       private function sort_callback( $a, $b ) {
+               if ( empty( $this->orderby ) ) {
+                       return 0;
+               }
+
+               $a = (array) $a;
+               $b = (array) $b;
+
+               foreach ( $this->orderby as $field => $direction ) {
+                       if ( ! isset( $a[ $field ] ) || ! isset( $b[ $field ] ) ) {
+                               continue;
+                       }
+
+                       if ( $a[ $field ] == $b[ $field ] ) {
+                               continue;
+                       }
+
+                       $results = 'DESC' === $direction ? array( 1, -1 ) : array( -1, 1 );
+
+                       if ( is_numeric( $a[ $field ] ) && is_numeric( $b[ $field ] ) ) {
+                               return ( $a[ $field ] < $b[ $field ] ) ? $results[0] : $results[1];
+                       }
+
+                       return 0 > strcmp( $a[ $field ], $b[ $field ] ) ? $results[0] : $results[1];
+               }
+
+               return 0;
+       }
+}
</ins></span></pre></div>
<a id="trunksrcwpincludescustomizeclasswpcustomizenavmenuitemsettingphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php      2016-10-25 20:58:13 UTC (rev 38927)
+++ trunk/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php        2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -532,8 +532,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ARRAY_A === $args['output'] ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $GLOBALS['_menu_item_sort_prop'] = $args['output_key'];
-                       usort( $items, '_sort_nav_menu_items' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $items = wp_list_sort( $items, array(
+                               $args['output_key'] => 'ASC',
+                       ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                         $i = 1;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        foreach ( $items as $k => $item ) {
</span></span></pre></div>
<a id="trunksrcwpincludescustomizeclasswpcustomizenavmenusettingphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/customize/class-wp-customize-nav-menu-setting.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/customize/class-wp-customize-nav-menu-setting.php   2016-10-25 20:58:13 UTC (rev 38927)
+++ trunk/src/wp-includes/customize/class-wp-customize-nav-menu-setting.php     2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -287,8 +287,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Make sure the menu objects get re-sorted after an update/insert.
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! $is_delete && ! empty( $args['orderby'] ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $this->_current_menus_sort_orderby = $args['orderby'];
-                       usort( $menus, array( $this, '_sort_menus_by_orderby' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $menus = wp_list_sort( $menus, array(
+                               $args['orderby'] => 'ASC',
+                       ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">                // @todo add support for $args['hide_empty'] === true
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -313,7 +314,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * This is a workaround for a lack of closures.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 4.3.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @deprecated 4.7.0 Use wp_list_sort()
</ins><span class="cx" style="display: block; padding: 0 10px">          * @access protected
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @param object $menu1
</span><span class="cx" style="display: block; padding: 0 10px">         * @param object $menu2
</span><span class="cx" style="display: block; padding: 0 10px">         * @return int
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -321,6 +324,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus()
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        protected function _sort_menus_by_orderby( $menu1, $menu2 ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                _deprecated_function( __METHOD__, '4.7.0', 'wp_list_sort' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $key = $this->_current_menus_sort_orderby;
</span><span class="cx" style="display: block; padding: 0 10px">                return strcmp( $menu1->$key, $menu2->$key );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcwpincludesdeprecatedphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/deprecated.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/deprecated.php      2016-10-25 20:58:13 UTC (rev 38927)
+++ trunk/src/wp-includes/deprecated.php        2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3798,3 +3798,83 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        return preg_replace( '%&\s*\{[^}]*(\}\s*;?|$)%', '', $string );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Sort categories by ID.
+ *
+ * Used by usort() as a callback, should not be used directly. Can actually be
+ * used to sort any term object.
+ *
+ * @since 2.3.0
+ * @deprecated 4.7.0 Use wp_list_sort()
+ * @access private
+ *
+ * @param object $a
+ * @param object $b
+ * @return int
+ */
+function _usort_terms_by_ID( $a, $b ) {
+       _deprecated_function( __FUNCTION__, '4.7.0', 'wp_list_sort' );
+
+       if ( $a->term_id > $b->term_id )
+               return 1;
+       elseif ( $a->term_id < $b->term_id )
+               return -1;
+       else
+               return 0;
+}
+
+/**
+ * Sort categories by name.
+ *
+ * Used by usort() as a callback, should not be used directly. Can actually be
+ * used to sort any term object.
+ *
+ * @since 2.3.0
+ * @deprecated 4.7.0 Use wp_list_sort()
+ * @access private
+ *
+ * @param object $a
+ * @param object $b
+ * @return int
+ */
+function _usort_terms_by_name( $a, $b ) {
+       _deprecated_function( __FUNCTION__, '4.7.0', 'wp_list_sort' );
+
+       return strcmp( $a->name, $b->name );
+}
+
+/**
+ * Sort menu items by the desired key.
+ *
+ * @since 3.0.0
+ * @deprecated 4.7.0 Use wp_list_sort()
+ * @access private
+ *
+ * @global string $_menu_item_sort_prop
+ *
+ * @param object $a The first object to compare
+ * @param object $b The second object to compare
+ * @return int -1, 0, or 1 if $a is considered to be respectively less than, equal to, or greater than $b.
+ */
+function _sort_nav_menu_items( $a, $b ) {
+       global $_menu_item_sort_prop;
+
+       _deprecated_function( __FUNCTION__, '4.7.0', 'wp_list_sort' );
+
+       if ( empty( $_menu_item_sort_prop ) )
+               return 0;
+
+       if ( ! isset( $a->$_menu_item_sort_prop ) || ! isset( $b->$_menu_item_sort_prop ) )
+               return 0;
+
+       $_a = (int) $a->$_menu_item_sort_prop;
+       $_b = (int) $b->$_menu_item_sort_prop;
+
+       if ( $a->$_menu_item_sort_prop == $b->$_menu_item_sort_prop )
+               return 0;
+       elseif ( $_a == $a->$_menu_item_sort_prop && $_b == $b->$_menu_item_sort_prop )
+               return $_a < $_b ? -1 : 1;
+       else
+               return strcmp( $a->$_menu_item_sort_prop, $b->$_menu_item_sort_prop );
+}
</ins></span></pre></div>
<a id="trunksrcwpincludesfunctionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/functions.php       2016-10-25 20:58:13 UTC (rev 38927)
+++ trunk/src/wp-includes/functions.php 2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3489,6 +3489,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Filters a list of objects, based on a set of key => value arguments.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 3.0.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 4.7.0 Uses WP_List_Util class.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param array       $list     An array of objects to filter
</span><span class="cx" style="display: block; padding: 0 10px">  * @param array       $args     Optional. An array of key => value arguments to match
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3502,21 +3503,26 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @return array A list of objects or object fields.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function wp_filter_object_list( $list, $args = array(), $operator = 'and', $field = false ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! is_array( $list ) )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! is_array( $list ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 return array();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $list = wp_list_filter( $list, $args, $operator );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $util = new WP_List_Util( $list );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( $field )
-               $list = wp_list_pluck( $list, $field );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $util->filter( $args, $operator );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        return $list;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $field ) {
+               $util->pluck( $field );
+       }
+
+       return $util->get_output();
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Filters a list of objects, based on a set of key => value arguments.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 3.1.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 4.7.0 Uses WP_List_Util class.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param array  $list     An array of objects to filter.
</span><span class="cx" style="display: block; padding: 0 10px">  * @param array  $args     Optional. An array of key => value arguments to match
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3528,33 +3534,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @return array Array of found values.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function wp_list_filter( $list, $args = array(), $operator = 'AND' ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! is_array( $list ) )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! is_array( $list ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 return array();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-       if ( empty( $args ) )
-               return $list;
-
-       $operator = strtoupper( $operator );
-       $count = count( $args );
-       $filtered = array();
-
-       foreach ( $list as $key => $obj ) {
-               $to_match = (array) $obj;
-
-               $matched = 0;
-               foreach ( $args as $m_key => $m_value ) {
-                       if ( array_key_exists( $m_key, $to_match ) && $m_value == $to_match[ $m_key ] )
-                               $matched++;
-               }
-
-               if ( ( 'AND' == $operator && $matched == $count )
-                 || ( 'OR' == $operator && $matched > 0 )
-                 || ( 'NOT' == $operator && 0 == $matched ) ) {
-                       $filtered[$key] = $obj;
-               }
</del><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        return $filtered;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $util = new WP_List_Util( $list );
+       return $util->filter( $args, $operator );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3565,6 +3550,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 3.1.0
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 4.0.0 $index_key parameter added.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 4.7.0 Uses WP_List_Util class.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param array      $list      List of objects or arrays
</span><span class="cx" style="display: block; padding: 0 10px">  * @param int|string $field     Field from the object to place instead of the entire object
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3575,43 +3561,30 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *               `$list` will be preserved in the results.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function wp_list_pluck( $list, $field, $index_key = null ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! $index_key ) {
-               /*
-                * This is simple. Could at some point wrap array_column()
-                * if we knew we had an array of arrays.
-                */
-               foreach ( $list as $key => $value ) {
-                       if ( is_object( $value ) ) {
-                               $list[ $key ] = $value->$field;
-                       } else {
-                               $list[ $key ] = $value[ $field ];
-                       }
-               }
-               return $list;
-       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $util = new WP_List_Util( $list );
+       return $util->pluck( $field, $index_key );
+}
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /*
-        * When index_key is not set for a particular item, push the value
-        * to the end of the stack. This is how array_column() behaves.
-        */
-       $newlist = array();
-       foreach ( $list as $value ) {
-               if ( is_object( $value ) ) {
-                       if ( isset( $value->$index_key ) ) {
-                               $newlist[ $value->$index_key ] = $value->$field;
-                       } else {
-                               $newlist[] = $value->$field;
-                       }
-               } else {
-                       if ( isset( $value[ $index_key ] ) ) {
-                               $newlist[ $value[ $index_key ] ] = $value[ $field ];
-                       } else {
-                               $newlist[] = $value[ $field ];
-                       }
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * Sorts a list of objects, based on one or more orderby arguments.
+ *
+ * @since 4.7.0
+ *
+ * @param array        $list          An array of objects to filter.
+ * @param string|array $orderby       Optional. Either the field name to order by or an array
+ *                                    of multiple orderby fields as $orderby => $order.
+ * @param string       $order         Optional. Either 'ASC' or 'DESC'. Only used if $orderby
+ *                                    is a string.
+ * @param bool         $preserve_keys Optional. Whether to preserve keys. Default false.
+ * @return array The sorted array.
+ */
+function wp_list_sort( $list, $orderby = array(), $order = 'ASC', $preserve_keys = false ) {
+       if ( ! is_array( $list ) ) {
+               return array();
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        return $newlist;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $util = new WP_List_Util( $list );
+       return $util->sort( $orderby, $order, $preserve_keys );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5572,4 +5545,4 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        return $last_changed;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-}
</del><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+}
</ins></span></pre></div>
<a id="trunksrcwpincludeslinktemplatephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/link-template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/link-template.php   2016-10-25 20:58:13 UTC (rev 38927)
+++ trunk/src/wp-includes/link-template.php     2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -169,7 +169,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                if ( strpos($permalink, '%category%') !== false ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $cats = get_the_category($post->ID);
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( $cats ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                usort($cats, '_usort_terms_by_ID'); // order by ID
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $cats = wp_list_sort( $cats, array(
+                                       'term_id' => 'ASC',
+                               ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                /**
</span><span class="cx" style="display: block; padding: 0 10px">                                 * Filters the category that gets used in the %category% permalink token.
</span></span></pre></div>
<a id="trunksrcwpincludesnavmenuphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/nav-menu.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/nav-menu.php        2016-10-25 20:58:13 UTC (rev 38927)
+++ trunk/src/wp-includes/nav-menu.php  2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -558,38 +558,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Sort menu items by the desired key.
- *
- * @since 3.0.0
- * @access private
- *
- * @global string $_menu_item_sort_prop
- *
- * @param object $a The first object to compare
- * @param object $b The second object to compare
- * @return int -1, 0, or 1 if $a is considered to be respectively less than, equal to, or greater than $b.
- */
-function _sort_nav_menu_items( $a, $b ) {
-       global $_menu_item_sort_prop;
-
-       if ( empty( $_menu_item_sort_prop ) )
-               return 0;
-
-       if ( ! isset( $a->$_menu_item_sort_prop ) || ! isset( $b->$_menu_item_sort_prop ) )
-               return 0;
-
-       $_a = (int) $a->$_menu_item_sort_prop;
-       $_b = (int) $b->$_menu_item_sort_prop;
-
-       if ( $a->$_menu_item_sort_prop == $b->$_menu_item_sort_prop )
-               return 0;
-       elseif ( $_a == $a->$_menu_item_sort_prop && $_b == $b->$_menu_item_sort_prop )
-               return $_a < $_b ? -1 : 1;
-       else
-               return strcmp( $a->$_menu_item_sort_prop, $b->$_menu_item_sort_prop );
-}
-
-/**
</del><span class="cx" style="display: block; padding: 0 10px">  * Return if a menu item is valid.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @link https://core.trac.wordpress.org/ticket/13958
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -682,8 +650,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ARRAY_A == $args['output'] ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $GLOBALS['_menu_item_sort_prop'] = $args['output_key'];
-               usort($items, '_sort_nav_menu_items');
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $items = wp_list_sort( $items, array(
+                       $args['output_key'] => 'ASC',
+               ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $i = 1;
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $items as $k => $item ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $items[$k]->{$args['output_key']} = $i++;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -776,7 +745,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                $menu_item->type_label = __( 'Post Type Archive' );
</span><span class="cx" style="display: block; padding: 0 10px">                                $post_content = wp_trim_words( $menu_item->post_content, 200 );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $post_type_description = '' == $post_content ? $post_type_description : $post_content; 
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $post_type_description = '' == $post_content ? $post_type_description : $post_content;
</ins><span class="cx" style="display: block; padding: 0 10px">                                 $menu_item->url = get_post_type_archive_link( $menu_item->object );
</span><span class="cx" style="display: block; padding: 0 10px">                        } elseif ( 'taxonomy' == $menu_item->type ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $object = get_taxonomy( $menu_item->object );
</span></span></pre></div>
<a id="trunksrcwpsettingsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-settings.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-settings.php 2016-10-25 20:58:13 UTC (rev 38927)
+++ trunk/src/wp-settings.php   2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -89,6 +89,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> // Load early WordPress files.
</span><span class="cx" style="display: block; padding: 0 10px"> require( ABSPATH . WPINC . '/compat.php' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require( ABSPATH . WPINC . '/class-wp-list-util.php' );
</ins><span class="cx" style="display: block; padding: 0 10px"> require( ABSPATH . WPINC . '/functions.php' );
</span><span class="cx" style="display: block; padding: 0 10px"> require( ABSPATH . WPINC . '/class-wp-matchesmapregex.php' );
</span><span class="cx" style="display: block; padding: 0 10px"> require( ABSPATH . WPINC . '/class-wp.php' );
</span></span></pre></div>
<a id="trunktestsphpunittestscustomizemanagerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/customize/manager.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/manager.php   2016-10-25 20:58:13 UTC (rev 38927)
+++ trunk/tests/phpunit/tests/customize/manager.php     2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1862,6 +1862,73 @@
</span><span class="cx" style="display: block; padding: 0 10px">        function filter_customize_previewable_devices( $devices ) {
</span><span class="cx" style="display: block; padding: 0 10px">                return $this->filtered_device_list();
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @ticket 37128
+        */
+       function test_prepare_controls_wp_list_sort_controls() {
+               wp_set_current_user( self::$admin_user_id );
+
+               $controls = array( 'foo' => 2, 'bar' => 4, 'foobar' => 3, 'key' => 1 );
+               $controls_sorted = array( 'key', 'foo', 'foobar', 'bar' );
+
+               $this->manager->add_section( 'foosection', array() );
+
+               foreach ( $controls as $control_id => $priority ) {
+                       $this->manager->add_setting( $control_id );
+                       $this->manager->add_control( $control_id, array(
+                               'priority' => $priority,
+                               'section'  => 'foosection',
+                       ) );
+               }
+
+               $this->manager->prepare_controls();
+
+               $result = $this->manager->controls();
+               $this->assertEquals( $controls_sorted, array_keys( $result ) );
+       }
+
+       /**
+        * @ticket 37128
+        */
+       function test_prepare_controls_wp_list_sort_sections() {
+               wp_set_current_user( self::$admin_user_id );
+
+               $sections = array( 'foo' => 2, 'bar' => 4, 'foobar' => 3, 'key' => 1 );
+               $sections_sorted = array( 'key', 'foo', 'foobar', 'bar' );
+
+               foreach ( $sections as $section_id => $priority ) {
+                       $this->manager->add_section( $section_id, array(
+                               'priority' => $priority,
+                       ) );
+               }
+
+               $this->manager->prepare_controls();
+
+               $result = $this->manager->sections();
+               $this->assertEquals( $sections_sorted, array_keys( $result ) );
+       }
+
+       /**
+        * @ticket 37128
+        */
+       function test_prepare_controls_wp_list_sort_panels() {
+               wp_set_current_user( self::$admin_user_id );
+
+               $panels = array( 'foo' => 2, 'bar' => 4, 'foobar' => 3, 'key' => 1 );
+               $panels_sorted = array( 'key', 'foo', 'foobar', 'bar' );
+
+               foreach ( $panels as $panel_id => $priority ) {
+                       $this->manager->add_panel( $panel_id, array(
+                               'priority' => $priority,
+                       ) );
+               }
+
+               $this->manager->prepare_controls();
+
+               $result = $this->manager->panels();
+               $this->assertEquals( $panels_sorted, array_keys( $result ) );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> require_once ABSPATH . WPINC . '/class-wp-customize-setting.php';
</span></span></pre></div>
<a id="trunktestsphpunittestsfunctionswpListUtilphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/functions/wpListUtil.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/functions/wpListUtil.php                                (rev 0)
+++ trunk/tests/phpunit/tests/functions/wpListUtil.php  2016-10-25 21:25:25 UTC (rev 38928)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,459 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group functions.php
+ */
+class Tests_WP_List_Util extends WP_UnitTestCase {
+       public function data_test_wp_list_pluck() {
+               return array(
+                       'arrays'                         => array(
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz' ),
+                                       array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum' ),
+                                       array( 'foo' => 'baz' ),
+                               ),
+                               'foo',
+                               null,
+                               array( 'bar', 'foo', 'baz' ),
+                       ),
+                       'arrays with index key'          => array(
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz', 'key' => 'foo' ),
+                                       array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               'foo',
+                               'key',
+                               array( 'foo' => 'bar', 'bar' => 'foo', 'value' => 'baz' ),
+                       ),
+                       'arrays with index key missing'  => array(
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz' ),
+                                       array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               'foo',
+                               'key',
+                               array( 'bar' => 'foo', 'value' => 'baz', 'bar' ),
+                       ),
+                       'objects'                        => array(
+                               array(
+                                       (object) array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz' ),
+                                       (object) array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum' ),
+                                       (object) array( 'foo' => 'baz' ),
+                               ),
+                               'foo',
+                               null,
+                               array( 'bar', 'foo', 'baz' ),
+                       ),
+                       'objects with index key'         => array(
+                               array(
+                                       (object) array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz', 'key' => 'foo' ),
+                                       (object) array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       (object) array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               'foo',
+                               'key',
+                               array( 'foo' => 'bar', 'bar' => 'foo', 'value' => 'baz' ),
+                       ),
+                       'objects with index key missing' => array(
+                               array(
+                                       (object) array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz' ),
+                                       (object) array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       (object) array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               'foo',
+                               'key',
+                               array( 'bar' => 'foo', 'value' => 'baz', 'bar' ),
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider data_test_wp_list_pluck
+        *
+        * @param array      $list      List of objects or arrays.
+        * @param int|string $field     Field from the object to place instead of the entire object
+        * @param int|string $index_key Field from the object to use as keys for the new array.
+        * @param array      $expected  Expected result.
+        */
+       public function test_wp_list_pluck( $list, $field, $index_key, $expected ) {
+               $this->assertEqualSetsWithIndex( $expected, wp_list_pluck( $list, $field, $index_key ) );
+       }
+
+       public function data_test_wp_list_filter() {
+               return array(
+                       'string instead of array'  => array(
+                               'foo',
+                               array(),
+                               'AND',
+                               array(),
+                       ),
+                       'object instead of array'  => array(
+                               (object) array( 'foo' ),
+                               array(),
+                               'AND',
+                               array(),
+                       ),
+                       'empty args'               => array(
+                               array( 'foo', 'bar' ),
+                               array(),
+                               'AND',
+                               array( 'foo', 'bar' ),
+                       ),
+                       'invalid operator'         => array(
+                               array(
+                                       (object) array( 'foo' => 'bar' ),
+                                       (object) array( 'foo' => 'baz' ),
+                               ),
+                               array( 'foo' => 'bar' ),
+                               'XOR',
+                               array(),
+                       ),
+                       'single argument to match' => array(
+                               array(
+                                       (object) array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz', 'key' => 'foo' ),
+                                       (object) array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       (object) array( 'foo' => 'baz', 'key' => 'value' ),
+                                       (object) array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                               array( 'foo' => 'bar' ),
+                               'AND',
+                               array(
+                                       0 => (object) array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz', 'key' => 'foo' ),
+                                       3 => (object) array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                       ),
+                       'all must match'           => array(
+                               array(
+                                       (object) array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz', 'key' => 'foo' ),
+                                       (object) array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       (object) array( 'foo' => 'baz', 'key' => 'value', 'bar' => 'baz' ),
+                                       (object) array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                               array( 'foo' => 'bar', 'bar' => 'baz' ),
+                               'AND',
+                               array(
+                                       0 => (object) array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz', 'key' => 'foo' ),
+                               ),
+                       ),
+                       'any must match'           => array(
+                               array(
+                                       (object) array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz', 'key' => 'foo' ),
+                                       (object) array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       (object) array( 'foo' => 'baz', 'key' => 'value', 'bar' => 'baz' ),
+                                       (object) array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                               array( 'key' => 'value', 'bar' => 'baz' ),
+                               'OR',
+                               array(
+                                       0 => (object) array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz', 'key' => 'foo' ),
+                                       2 => (object) array( 'foo' => 'baz', 'key' => 'value', 'bar' => 'baz' ),
+                                       3 => (object) array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                       ),
+                       'none must match'          => array(
+                               array(
+                                       (object) array( 'foo' => 'bar', 'bar' => 'baz', 'abc' => 'xyz', 'key' => 'foo' ),
+                                       (object) array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       (object) array( 'foo' => 'baz', 'key' => 'value' ),
+                                       (object) array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                               array( 'key' => 'value', 'bar' => 'baz' ),
+                               'NOT',
+                               array(
+                                       1 => (object) array( 'foo' => 'foo', '123' => '456', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                               ),
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider data_test_wp_list_filter
+        *
+        * @param array  $list     An array of objects to filter.
+        * @param array  $args     An array of key => value arguments to match
+        *                         against each object.
+        * @param string $operator The logical operation to perform.
+        * @param array  $expected Expected result.
+        */
+       public function test_wp_list_filter( $list, $args, $operator, $expected ) {
+               $this->assertEqualSetsWithIndex( $expected, wp_list_filter( $list, $args, $operator ) );
+       }
+
+       public function data_test_wp_list_sort() {
+               return array(
+                       'single orderby ascending'        => array(
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               'foo',
+                               'ASC',
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'baz', 'key' => 'value' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                               ),
+                       ),
+                       'single orderby descending'       => array(
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               'foo',
+                               'DESC',
+                               array(
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'baz', 'key' => 'value' ),
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                               ),
+                       ),
+                       'single orderby array ascending'  => array(
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               array( 'foo' => 'ASC' ),
+                               'IGNORED',
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'baz', 'key' => 'value' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                               ),
+                       ),
+                       'single orderby array descending' => array(
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               array( 'foo' => 'DESC' ),
+                               'IGNORED',
+                               array(
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'baz', 'key' => 'value' ),
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                               ),
+                       ),
+                       'multiple orderby ascending'      => array(
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'foo', 'key' => 'key' ),
+                                       array( 'foo' => 'baz', 'key' => 'key' ),
+                                       array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                               array( 'key' => 'ASC', 'foo' => 'ASC' ),
+                               'IGNORED',
+                               array(
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'baz', 'key' => 'key' ),
+                                       array( 'foo' => 'foo', 'key' => 'key' ),
+                                       array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                       ),
+                       'multiple orderby descending'     => array(
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'foo', 'key' => 'key' ),
+                                       array( 'foo' => 'baz', 'key' => 'key' ),
+                                       array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                               array( 'key' => 'DESC', 'foo' => 'DESC' ),
+                               'IGNORED',
+                               array(
+                                       array( 'foo' => 'bar', 'key' => 'value' ),
+                                       array( 'foo' => 'foo', 'key' => 'key' ),
+                                       array( 'foo' => 'baz', 'key' => 'key' ),
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                               ),
+                       ),
+                       'multiple orderby mixed'          => array(
+                               array(
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       array( 'foo' => 'foo', 'key' => 'key' ),
+                                       array( 'foo' => 'baz', 'key' => 'key' ),
+                                       array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                               array( 'key' => 'DESC', 'foo' => 'ASC' ),
+                               'IGNORED',
+                               array(
+                                       array( 'foo' => 'bar', 'key' => 'value' ),
+                                       array( 'foo' => 'baz', 'key' => 'key' ),
+                                       array( 'foo' => 'foo', 'key' => 'key' ),
+                                       array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                               ),
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider data_test_wp_list_sort
+        *
+        * @param string|array $orderby Either the field name to order by or an array
+        *                              of multiple orderby fields as $orderby => $order.
+        * @param string       $order   Either 'ASC' or 'DESC'.
+        */
+       public function test_wp_list_sort( $list, $orderby, $order, $expected ) {
+               $this->assertEquals( $expected, wp_list_sort( $list, $orderby, $order ) );
+       }
+
+       public function data_test_wp_list_sort_preserve_keys() {
+               return array(
+                       'single orderby ascending'        => array(
+                               array(
+                                       'foobar' => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foofoo' => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       'foobaz' => array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               'foo',
+                               'ASC',
+                               array(
+                                       'foobar' => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foobaz' => array( 'foo' => 'baz', 'key' => 'value' ),
+                                       'foofoo' => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                               ),
+                       ),
+                       'single orderby descending'       => array(
+                               array(
+                                       'foobar' => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foofoo' => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       'foobaz' => array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               'foo',
+                               'DESC',
+                               array(
+                                       'foofoo' => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       'foobaz' => array( 'foo' => 'baz', 'key' => 'value' ),
+                                       'foobar' => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                               ),
+                       ),
+                       'single orderby array ascending'  => array(
+                               array(
+                                       'foobar' => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foofoo' => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       'foobaz' => array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               array( 'foo' => 'ASC' ),
+                               'IGNORED',
+                               array(
+                                       'foobar' => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foobaz' => array( 'foo' => 'baz', 'key' => 'value' ),
+                                       'foofoo' => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                               ),
+                       ),
+                       'single orderby array descending' => array(
+                               array(
+                                       'foobar' => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foofoo' => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       'foobaz' => array( 'foo' => 'baz', 'key' => 'value' ),
+                               ),
+                               array( 'foo' => 'DESC' ),
+                               'IGNORED',
+                               array(
+                                       'foofoo' => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       'foobaz' => array( 'foo' => 'baz', 'key' => 'value' ),
+                                       'foobar' => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                               ),
+                       ),
+                       'multiple orderby ascending'      => array(
+                               array(
+                                       'foobarfoo'   => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foofoobar'   => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       'foofookey'   => array( 'foo' => 'foo', 'key' => 'key' ),
+                                       'foobazkey'   => array( 'foo' => 'baz', 'key' => 'key' ),
+                                       'foobarvalue' => array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                               array( 'key' => 'ASC', 'foo' => 'ASC' ),
+                               'IGNORED',
+                               array(
+                                       'foofoobar'   => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       'foobarfoo'   => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foobazkey'   => array( 'foo' => 'baz', 'key' => 'key' ),
+                                       'foofookey'   => array( 'foo' => 'foo', 'key' => 'key' ),
+                                       'foobarvalue' => array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                       ),
+                       'multiple orderby descending'     => array(
+                               array(
+                                       'foobarfoo'   => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foofoobar'   => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       'foofookey'   => array( 'foo' => 'foo', 'key' => 'key' ),
+                                       'foobazkey'   => array( 'foo' => 'baz', 'key' => 'key' ),
+                                       'foobarvalue' => array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                               array( 'key' => 'DESC', 'foo' => 'DESC' ),
+                               'IGNORED',
+                               array(
+                                       'foobarvalue' => array( 'foo' => 'bar', 'key' => 'value' ),
+                                       'foofookey'   => array( 'foo' => 'foo', 'key' => 'key' ),
+                                       'foobazkey'   => array( 'foo' => 'baz', 'key' => 'key' ),
+                                       'foobarfoo'   => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foofoobar'   => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                               ),
+                       ),
+                       'multiple orderby mixed'          => array(
+                               array(
+                                       'foobarfoo'   => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foofoobar'   => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                                       'foofookey'   => array( 'foo' => 'foo', 'key' => 'key' ),
+                                       'foobazkey'   => array( 'foo' => 'baz', 'key' => 'key' ),
+                                       'foobarvalue' => array( 'foo' => 'bar', 'key' => 'value' ),
+                               ),
+                               array( 'key' => 'DESC', 'foo' => 'ASC' ),
+                               'IGNORED',
+                               array(
+                                       'foobarvalue' => array( 'foo' => 'bar', 'key' => 'value' ),
+                                       'foobazkey'   => array( 'foo' => 'baz', 'key' => 'key' ),
+                                       'foofookey'   => array( 'foo' => 'foo', 'key' => 'key' ),
+                                       'foobarfoo'   => array( 'foo' => 'bar', 'bar' => 'baz', 'key' => 'foo' ),
+                                       'foofoobar'   => array( 'foo' => 'foo', 'lorem' => 'ipsum', 'key' => 'bar' ),
+                               ),
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider data_test_wp_list_sort_preserve_keys
+        *
+        * @param string|array $orderby Either the field name to order by or an array
+        *                              of multiple orderby fields as $orderby => $order.
+        * @param string       $order   Either 'ASC' or 'DESC'.
+        */
+       public function test_wp_list_sort_preserve_keys( $list, $orderby, $order, $expected ) {
+               $this->assertEquals( $expected, wp_list_sort( $list, $orderby, $order, true ) );
+       }
+
+       public function test_wp_list_util_get_input() {
+               $input = array( 'foo', 'bar' );
+               $util  = new WP_List_Util( $input );
+
+               $this->assertEqualSets( $input, $util->get_input() );
+       }
+
+       public function test_wp_list_util_get_output_immediately() {
+               $input = array( 'foo', 'bar' );
+               $util  = new WP_List_Util( $input );
+
+               $this->assertEqualSets( $input, $util->get_output() );
+       }
+
+       public function test_wp_list_util_get_output() {
+               $expected = array( (object) array( 'foo' => 'bar', 'bar' => 'baz' ) );
+
+               $util   = new WP_List_Util( array( (object) array( 'foo' => 'bar', 'bar' => 'baz' ), (object) array( 'bar' => 'baz' ) ) );
+               $actual = $util->filter( array( 'foo' => 'bar' ) );
+
+               $this->assertEqualSets( $expected, $actual );
+               $this->assertEqualSets( $expected, $util->get_output() );
+       }
+}
</ins></span></pre>
</div>
</div>

</body>
</html>