<!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>[41807] trunk/src: Customize: Improve behavior and extensibility of theme loading and searching.</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/41807">41807</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/41807","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>westonruter</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2017-10-10 07:08:51 +0000 (Tue, 10 Oct 2017)</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'>Customize: Improve behavior and extensibility of theme loading and searching.

* Introduce `WP_Customize_Themes_Section::$filter_type`, which has built-in functionality for `local` and `remote` filtering. When this set to `local`, all themes are assumed to be loaded from Ajax when the section is first loaded, and subsequent searching/filtering is applied to the loaded collection of themes within the section. This is how the core "Installed" section behaves - third-party sources with limited numbers of themes may consider leveraging this implementation. When this is set to `remote`, searching and filtering always triggers a new remote query via Ajax. The core "WordPress.org" section uses this approach, as it has over 5000 themes to search.
* Refactor `filterSearch()` to accept a raw term string as input. This enables a feature filter to be used on a section where `filter_type` is `local`.
* Refactor `filter()` on a theme control to check for an array of terms. Also sort the results by the number of matches. Rather than searching for an exact match, this will now search for each word in a search distinctly, allowing things like tags to rank in search results more accurately.
* Split `loadControls()` into two functions for themes section JS: `loadThemes()` to initiate and manage an Ajax request and `loadControls()` to create theme controls based on the results of the Ajax call. If third-party sections need to change the way controls are loaded, such as by using a custom control subclass of `WP_Customize_Theme_Control`, this allows them to use the core logic for managing the Ajax call and only override the actual control-creation process.
* Introduce `customize_load_themes` filter to facilitate loading themes from third-party sources (or modifying the results of the core sections).
* Bring significant improvements to the installed themes search filter.

Props celloexpressions.
Amends <a href="https://core.trac.wordpress.org/changeset/41648">[41648]</a>.
See <a href="https://core.trac.wordpress.org/ticket/37661">#37661</a>.
Fixes <a href="https://core.trac.wordpress.org/ticket/42049">#42049</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadminjscustomizecontrolsjs">trunk/src/wp-admin/js/customize-controls.js</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizemanagerphp">trunk/src/wp-includes/class-wp-customize-manager.php</a></li>
<li><a href="#trunksrcwpincludescustomizeclasswpcustomizethemessectionphp">trunk/src/wp-includes/customize/class-wp-customize-themes-section.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadminjscustomizecontrolsjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/js/customize-controls.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/js/customize-controls.js       2017-10-10 05:33:57 UTC (rev 41806)
+++ trunk/src/wp-admin/js/customize-controls.js 2017-10-10 07:08:51 UTC (rev 41807)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1717,14 +1717,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                section.closeDetails();
</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">-                        // Filter-search all theme objects loaded in the section.
-                       section.container.on( 'input', '.wp-filter-search-themes', function( event ) {
-                                       section.filterSearch( event.currentTarget );
-                       });
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( 'local' === section.params.filter_type ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Event listeners for remote wporg queries with user-entered terms.
-                       if ( 'wporg' === section.params.action ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         // Filter-search all theme objects loaded in the section.
+                               section.container.on( 'input', '.wp-filter-search-themes', function( event ) {
+                                       section.filterSearch( event.currentTarget.value );
+                               });
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        } else if ( 'remote' === section.params.filter_type ) {
+
+                               // Event listeners for remote queries with user-entered terms.
</ins><span class="cx" style="display: block; padding: 0 10px">                                 // Search terms.
</span><span class="cx" style="display: block; padding: 0 10px">                                debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 milliseconds to initiate a search.
</span><span class="cx" style="display: block; padding: 0 10px">                                section.contentContainer.on( 'input', '.wp-filter-search', function() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1740,29 +1742,29 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        section.filtersChecked();
</span><span class="cx" style="display: block; padding: 0 10px">                                        section.checkTerm( section );
</span><span class="cx" style="display: block; padding: 0 10px">                                });
</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">-                                // Toggle feature filter sections.
-                               section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) {
-                                       $( e.currentTarget )
-                                               .toggleClass( 'open' )
-                                               .attr( 'aria-expanded', function( i, attr ) {
-                                                       return 'true' === attr ? 'false' : 'true';
-                                               })
-                                               .next( '.filter-drawer' ).slideToggle( 180, 'linear', function() {
-                                                       if ( 0 === section.filtersHeight ) {
-                                                               section.filtersHeight = $( this ).height();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Toggle feature filters.
+                       section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) {
+                               $( e.currentTarget )
+                                       .toggleClass( 'open' )
+                                       .attr( 'aria-expanded', function( i, attr ) {
+                                               return 'true' === attr ? 'false' : 'true';
+                                       })
+                                       .next( '.filter-drawer' ).slideToggle( 180, 'linear', function() {
+                                               if ( 0 === section.filtersHeight ) {
+                                                       section.filtersHeight = $( this ).height();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                                // First time, so it's opened.
-                                                               section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
-                                                       }
-                                               });
-                                       if ( $( e.currentTarget ).hasClass( 'open' ) ) {
-                                               section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
-                                       } else {
-                                               section.contentContainer.find( '.themes' ).css( 'margin-top', 0 );
-                                       }
-                               });
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                                 // First time, so it's opened.
+                                                       section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
+                                               }
+                                       });
+                               if ( $( e.currentTarget ).hasClass( 'open' ) ) {
+                                       section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
+                               } else {
+                                       section.contentContainer.find( '.themes' ).css( 'margin-top', 0 );
+                               }
+                       });
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Setup section cross-linking.
</span><span class="cx" style="display: block; padding: 0 10px">                        section.contentContainer.on( 'click', '.no-themes-local .search-dotorg-themes', function() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1806,7 +1808,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                // Try to load controls if none are loaded yet.
</span><span class="cx" style="display: block; padding: 0 10px">                                if ( 0 === section.loaded ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        section.loadControls();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 section.loadThemes();
</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">                                // Collapse any sibling sections/panels
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1821,13 +1823,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                        section.contentContainer.find( '.wp-filter-search' ).val( searchTerm );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                                        // Directly initialize an empty remote search to avoid a race condition.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                        if ( '' === searchTerm && '' !== section.term && 'installed' !== section.params.action ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                                 if ( '' === searchTerm && '' !== section.term && 'local' !== section.params.filter_type ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                                                 section.term = '';
</span><span class="cx" style="display: block; padding: 0 10px">                                                                section.initializeNewQuery( section.term, section.tags );
</span><span class="cx" style="display: block; padding: 0 10px">                                                        } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                                section.checkTerm( section );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                                         if ( 'remote' === section.params.filter_type ) {
+                                                                       section.checkTerm( section );
+                                                               } else if ( 'local' === section.params.filter_type ) {
+                                                                       section.filterSearch( searchTerm );
+                                                               }
</ins><span class="cx" style="display: block; padding: 0 10px">                                                         }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                        section.filterSearch( section.contentContainer.find( '.wp-filter-search' ).get( 0 ) );
</del><span class="cx" style="display: block; padding: 0 10px">                                                 }
</span><span class="cx" style="display: block; padding: 0 10px">                                                otherSection.collapse( { duration: args.duration } );
</span><span class="cx" style="display: block; padding: 0 10px">                                        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1877,7 +1882,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 *
</span><span class="cx" style="display: block; padding: 0 10px">                 * @returns {void}
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                loadControls: function() {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         loadThemes: function() {
</ins><span class="cx" style="display: block; padding: 0 10px">                         var section = this, params, page, request;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( section.loading ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1894,8 +1899,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'page': page
</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">-                        // Add fields for wporg actions.
-                       if ( 'wporg' === section.params.action ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Add fields for remote filtering.
+                       if ( 'remote' === section.params.filter_type ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 params.search = section.term;
</span><span class="cx" style="display: block; padding: 0 10px">                                params.tags = section.tags;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1906,7 +1911,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        section.container.find( '.no-themes' ).hide();
</span><span class="cx" style="display: block; padding: 0 10px">                        request = wp.ajax.post( 'customize_load_themes', params );
</span><span class="cx" style="display: block; padding: 0 10px">                        request.done(function( data ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                var themes = data.themes, newThemeControls;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         var themes = data.themes;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                // Stop and try again if the term changed while loading.
</span><span class="cx" style="display: block; padding: 0 10px">                                if ( '' !== section.nextTerm || '' !== section.nextTags ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1919,27 +1924,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        section.nextTerm = '';
</span><span class="cx" style="display: block; padding: 0 10px">                                        section.nextTags = '';
</span><span class="cx" style="display: block; padding: 0 10px">                                        section.loading = false;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        section.loadControls();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 section.loadThemes();
</ins><span class="cx" style="display: block; padding: 0 10px">                                         return;
</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 ( 0 !== themes.length ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        newThemeControls = [];
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        // Add controls for each theme.
-                                       _.each( themes, function( theme ) {
-                                               var themeControl = new api.controlConstructor.theme( section.params.action + '_theme_' + theme.id, {
-                                                       type: 'theme',
-                                                       section: section.params.id,
-                                                       theme: theme,
-                                                       priority: section.loaded + 1
-                                               } );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 section.loadControls( themes, page );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                api.control.add( themeControl );
-                                               newThemeControls.push( themeControl );
-                                               section.loaded = section.loaded + 1;
-                                       });
-
</del><span class="cx" style="display: block; padding: 0 10px">                                         if ( 1 === page ) {
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                                // Pre-load the first 3 theme screenshots.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1950,15 +1942,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                                img.src = src;
</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">-                                                if ( 'installed' !== section.params.action ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                         if ( 'local' !== section.params.filter_type ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                                         wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) );
</span><span class="cx" style="display: block; padding: 0 10px">                                                }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        } else {
-                                               Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue.
</del><span class="cx" style="display: block; padding: 0 10px">                                         }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">                                         _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible.
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        if ( 'installed' === section.params.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 if ( 'local' === section.params.filter_type || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list.
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 section.fullyLoaded = true;
</span><span class="cx" style="display: block; padding: 0 10px">                                        }
</span><span class="cx" style="display: block; padding: 0 10px">                                } else {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1969,7 +1960,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                section.fullyLoaded = true;
</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">-                                if ( 'installed' === section.params.action ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( 'local' === section.params.filter_type ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                         section.updateCount(); // Count of visible theme controls.
</span><span class="cx" style="display: block; padding: 0 10px">                                } else {
</span><span class="cx" style="display: block; padding: 0 10px">                                        section.updateCount( data.info.results ); // Total number of results including pages not yet loaded.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1995,6 +1986,37 @@
</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><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 * Loads controls into the section from data received from loadThemes().
+                *
+                * @since 4.9.0
+                * @param {Array} themes - Array of theme data to create controls with.
+                * @param {integer} page - Page of results being loaded.
+                * @returns {void}
+                */
+               loadControls: function( themes, page ) {
+                       var newThemeControls = [],
+                               section = this;
+
+                       // Add controls for each theme.
+                       _.each( themes, function( theme ) {
+                               var themeControl = new api.controlConstructor.theme( section.params.action + '_theme_' + theme.id, {
+                                       type: 'theme',
+                                       section: section.params.id,
+                                       theme: theme,
+                                       priority: section.loaded + 1
+                               } );
+
+                               api.control.add( themeControl );
+                               newThemeControls.push( themeControl );
+                               section.loaded = section.loaded + 1;
+                       });
+
+                       if ( 1 !== page ) {
+                               Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue.
+                       }
+               },
+
+               /**
</ins><span class="cx" style="display: block; padding: 0 10px">                  * Determines whether more themes should be loaded, and loads them.
</span><span class="cx" style="display: block; padding: 0 10px">                 *
</span><span class="cx" style="display: block; padding: 0 10px">                 * @since 4.9.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2009,7 +2031,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                threshold = container.prop( 'scrollHeight' ) - 3000; // Use a fixed distance to the bottom of loaded results to avoid unnecessarily loading results sooner when using a percentage of scroll distance.
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                if ( bottom > threshold ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        section.loadControls();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 section.loadThemes();
</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">@@ -2019,23 +2041,26 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 *
</span><span class="cx" style="display: block; padding: 0 10px">                 * @since 4.9.0
</span><span class="cx" style="display: block; padding: 0 10px">                 *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                 * @param {Element} el - The search input element as a raw JS object.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+          * @param {string} term - The raw search input value.
</ins><span class="cx" style="display: block; padding: 0 10px">                  * @returns {void}
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                filterSearch: function( el ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         filterSearch: function( term ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         var count = 0,
</span><span class="cx" style="display: block; padding: 0 10px">                                visible = false,
</span><span class="cx" style="display: block; padding: 0 10px">                                section = this,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                noFilter = ( undefined !== api.section( 'wporg_themes' ) && 'wporg' !== section.params.action ) ? '.no-themes-local' : '.no-themes',
-                               term = el.value.toLowerCase().trim().replace( '-', ' ' ),
-                               controls = section.controls();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         noFilter = ( api.section.has( 'wporg_themes' ) && 'remote' !== section.params.filter_type ) ? '.no-themes-local' : '.no-themes',
+                               controls = section.controls(),
+                               terms;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( section.loading ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                return;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        // Standardize search term format and split into an array of individual words.
+                       terms = term.toLowerCase().trim().replace( /-/g, ' ' ).split( ' ' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         _.each( controls, function( control ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                visible = control.filter( term );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         visible = control.filter( terms ); // Shows/hides and sorts control based on the applicability of the search term.
</ins><span class="cx" style="display: block; padding: 0 10px">                                 if ( visible ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                        count = count + 1;
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2049,6 +2074,7 @@
</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">                        section.renderScreenshots();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        api.reflowPaneContents();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Update theme count.
</span><span class="cx" style="display: block; padding: 0 10px">                        section.updateCount( count );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2064,7 +2090,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><span class="cx" style="display: block; padding: 0 10px">                checkTerm: function( section ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        var newTerm;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( 'wporg' === section.params.action ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( 'remote' === section.params.filter_type ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 newTerm = section.contentContainer.find( '.wp-filter-search' ).val();
</span><span class="cx" style="display: block; padding: 0 10px">                                if ( section.term !== newTerm ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                        section.initializeNewQuery( newTerm, section.tags );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2104,7 +2130,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                if ( section.loading ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                        section.nextTags = tags;
</span><span class="cx" style="display: block; padding: 0 10px">                                } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        section.initializeNewQuery( section.term, tags );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 if ( 'remote' === section.params.filter_type ) {
+                                               section.initializeNewQuery( section.term, tags );
+                                       } else if ( 'local' === section.params.filter_type ) {
+                                               section.filterSearch( tags.join( ' ' ) );
+                                       }
</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">@@ -2130,14 +2160,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        section.fullyLoaded = false;
</span><span class="cx" style="display: block; padding: 0 10px">                        section.screenshotQueue = null;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Run a new query, with loadControls handling paging, etc.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Run a new query, with loadThemes handling paging, etc.
</ins><span class="cx" style="display: block; padding: 0 10px">                         if ( ! section.loading ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                section.term = newTerm;
</span><span class="cx" style="display: block; padding: 0 10px">                                section.tags = newTags;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                section.loadControls();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         section.loadThemes();
</ins><span class="cx" style="display: block; padding: 0 10px">                         } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                section.nextTerm = newTerm; // This will reload from loadControls() with the newest term once the current batch is loaded.
-                               section.nextTags = newTags; // This will reload from loadControls() with the newest tags once the current batch is loaded.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         section.nextTerm = newTerm; // This will reload from loadThemes() with the newest term once the current batch is loaded.
+                               section.nextTags = newTags; // This will reload from loadThemes() with the newest tags once the current batch is loaded.
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( ! section.expanded() ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                section.expand(); // Expand the section if it isn't expanded.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4875,20 +4905,50 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 * Show or hide the theme based on the presence of the term in the title, description, tags, and author.
</span><span class="cx" style="display: block; padding: 0 10px">                 *
</span><span class="cx" style="display: block; padding: 0 10px">                 * @since 4.2.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 * @param {Array} terms - An array of terms to search for.
</ins><span class="cx" style="display: block; padding: 0 10px">                  * @returns {boolean} Whether a theme control was activated or not.
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                filter: function( term ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         filter: function( terms ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         var control = this,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                matchCount = 0,
</ins><span class="cx" style="display: block; padding: 0 10px">                                 haystack = control.params.theme.name + ' ' +
</span><span class="cx" style="display: block; padding: 0 10px">                                        control.params.theme.description + ' ' +
</span><span class="cx" style="display: block; padding: 0 10px">                                        control.params.theme.tags + ' ' +
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        control.params.theme.author;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 control.params.theme.author + ' ';
</ins><span class="cx" style="display: block; padding: 0 10px">                         haystack = haystack.toLowerCase().replace( '-', ' ' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( -1 !== haystack.search( term ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+                       // Back-compat for behavior in WordPress 4.2.0 to 4.8.X.
+                       if ( ! _.isArray( terms ) ) {
+                               terms = [ terms ];
+                       }
+
+                       // Always give exact name matches highest ranking.
+                       if ( control.params.theme.name.toLowerCase() === terms.join( ' ' ) ) {
+                               matchCount = 100;
+                       } else {
+
+                               // Search for and weight (by 10) complete term matches.
+                               matchCount = matchCount + 10 * ( haystack.split( terms.join( ' ' ) ).length - 1 );
+
+                               // Search for each term individually (as whole-word and partial match) and sum weighted match counts.
+                               _.each( terms, function( term ) {
+                                       matchCount = matchCount + 2 * ( haystack.split( term + ' ' ).length - 1 ); // Whole-word, double-weighted.
+                                       matchCount = matchCount + haystack.split( term ).length - 1; // Partial word, to minimize empty intermediate searches while typing.
+                               });
+
+                               // Upper limit on match ranking.
+                               if ( matchCount > 99 ) {
+                                       matchCount = 99;
+                               }
+                       }
+
+                       if ( 0 !== matchCount ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 control.activate();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                control.params.priority = 101 - matchCount; // Sort results by match count.
</ins><span class="cx" style="display: block; padding: 0 10px">                                 return true;
</span><span class="cx" style="display: block; padding: 0 10px">                        } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                control.deactivate();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         control.deactivate(); // Hide control
+                               control.params.priority = 101;
</ins><span class="cx" style="display: block; padding: 0 10px">                                 return false;
</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="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      2017-10-10 05:33:57 UTC (rev 41806)
+++ trunk/src/wp-includes/class-wp-customize-manager.php        2017-10-10 07:08:51 UTC (rev 41807)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4415,6 +4415,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->add_section( new WP_Customize_Themes_Section( $this, 'wporg_themes', array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'title'       => __( 'WordPress.org themes' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                'action'      => 'wporg',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                'filter_type' => 'remote',
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'capability'  => 'install_themes',
</span><span class="cx" style="display: block; padding: 0 10px">                                'panel'       => 'themes',
</span><span class="cx" style="display: block; padding: 0 10px">                                'priority'    => 5,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4947,23 +4948,48 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">                $theme_action = sanitize_key( $_POST['theme_action'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $themes = array();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $args = array();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Define query filters based on user input.
+               if ( ! array_key_exists( 'search', $_POST ) ) {
+                       $args['search'] = '';
+               } else {
+                       $args['search'] = sanitize_text_field( wp_unslash( $_POST['search'] ) );
+               }
+
+               if ( ! array_key_exists( 'tags', $_POST ) ) {
+                       $args['tag'] = '';
+               } else {
+                       $args['tag'] = array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['tags'] ) );
+               }
+
+               if ( ! array_key_exists( 'page', $_POST ) ) {
+                       $args['page'] = 1;
+               } else {
+                       $args['page'] = absint( $_POST['page'] );
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 require_once ABSPATH . 'wp-admin/includes/theme.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( 'installed' === $theme_action ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+                       // Load all installed themes from wp_prepare_themes_for_js().
</ins><span class="cx" style="display: block; padding: 0 10px">                         $themes = array( 'themes' => wp_prepare_themes_for_js() );
</span><span class="cx" style="display: block; padding: 0 10px">                        foreach ( $themes['themes'] as &$theme ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $theme['type'] = 'installed';
</span><span class="cx" style="display: block; padding: 0 10px">                                $theme['active'] = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme['id'] );
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">                 } elseif ( 'wporg' === $theme_action ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+                       // Load WordPress.org themes from the .org API and normalize data to match installed theme objects.
</ins><span class="cx" style="display: block; padding: 0 10px">                         if ( ! current_user_can( 'install_themes' ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                wp_die( -1 );
</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">                        // Arguments for all queries.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $args = array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $wporg_args = array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'per_page' => 100,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'page' => isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1,
</del><span class="cx" style="display: block; padding: 0 10px">                                 'fields' => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        'screenshot_url' => true,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'description' => true,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4979,19 +5005,8 @@
</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">-                        // Define query filters based on user input.
-                       if ( ! array_key_exists( 'search', $_POST ) ) {
-                               $args['search'] = '';
-                       } else {
-                               $args['search'] = sanitize_text_field( wp_unslash( $_POST['search'] ) );
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $args = array_merge( $wporg_args, $args );
</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 ( ! array_key_exists( 'tags', $_POST ) ) {
-                               $args['tag'] = '';
-                       } else {
-                               $args['tag'] = array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['tags'] ) );
-                       }
-
</del><span class="cx" style="display: block; padding: 0 10px">                         if ( '' === $args['search'] && '' === $args['tag'] ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $args['browse'] = 'new'; // Sort by latest themes by default.
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5061,6 +5076,26 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                unset( $theme->author );
</span><span class="cx" style="display: block; padding: 0 10px">                        } // End foreach().
</span><span class="cx" style="display: block; padding: 0 10px">                } // End if().
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               /**
+                * Filters the theme data loaded in the customizer.
+                *
+                * This allows theme data to be loading from an external source,
+                * or modification of data loaded from `wp_prepare_themes_for_js()`
+                * or WordPress.org via `themes_api()`.
+                *
+                * @since 4.9.0
+                *
+                * @see wp_prepare_themes_for_js()
+                * @see themes_api()
+                * @see WP_Customize_Manager::__construct()
+                *
+                * @param array                $themes  Nested array of theme data.
+                * @param array                $args    List of arguments, such as page, search term, and tags to query for.
+                * @param WP_Customize_Manager $manager Instance of Customize manager.
+                */
+               $themes = apply_filters( 'customize_load_themes', $themes, $args, $this );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 wp_send_json_success( $themes );
</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="trunksrcwpincludescustomizeclasswpcustomizethemessectionphp"></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-themes-section.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-themes-section.php     2017-10-10 05:33:57 UTC (rev 41806)
+++ trunk/src/wp-includes/customize/class-wp-customize-themes-section.php       2017-10-10 07:08:51 UTC (rev 41807)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -37,6 +37,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public $action = '';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Theme section filter type.
+        *
+        * Determines whether filters are applied to loaded (local) themes or by initiating a new remote query (remote).
+        * When filtering is local, the initial themes query is not paginated by default.
+        *
+        * @since 4.9.0
+        * @var string
+        */
+       public $filter_type = 'local';
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Get section parameters for JS.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 4.9.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -45,6 +56,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function json() {
</span><span class="cx" style="display: block; padding: 0 10px">                $exported = parent::json();
</span><span class="cx" style="display: block; padding: 0 10px">                $exported['action'] = $this->action;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $exported['filter_type'] = $this->filter_type;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return $exported;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre>
</div>
</div>

</body>
</html>