WordPress.org

Make WordPress Core

Ticket #42049: 42049.diff

File 42049.diff, 18.6 KB (added by celloexpressions, 2 years ago)

First pass at extensibility improvements and improved filter-searching.

  • src/wp-admin/js/customize-controls.js

     
    15931593                                section.closeDetails();
    15941594                        });
    15951595
    1596                         // Filter-search all theme objects loaded in the section.
    1597                         section.container.on( 'input', '.wp-filter-search-themes', function( event ) {
    1598                                         section.filterSearch( event.currentTarget );
    1599                         });
     1596                        if ( 'local' === section.params.filter_type ) {
     1597                                // Filter-search all theme objects loaded in the section.
     1598                                section.container.on( 'input', '.wp-filter-search-themes', function( event ) {
     1599                                                section.filterSearch( event.currentTarget.value );
     1600                                });
    16001601
    1601                         // Event listeners for remote wporg queries with user-entered terms.
    1602                         if ( 'wporg' === section.params.action ) {
     1602                        } else if ( 'remote' === section.params.filter_type ) {
    16031603
     1604                                // Event listeners for remote queries with user-entered terms.
    16041605                                // Search terms.
    16051606                                debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 milliseconds to initiate a search.
    16061607                                section.contentContainer.on( 'input', '#wp-filter-search-input', function() {
     
    16161617                                        section.filtersChecked();
    16171618                                        section.checkTerm( section );
    16181619                                });
     1620                        }
    16191621
    1620                                 // Toggle feature filter sections.
    1621                                 section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) {
    1622                                         $( e.currentTarget )
    1623                                                 .toggleClass( 'open' )
    1624                                                 .attr( 'aria-expanded', function( i, attr ) {
    1625                                                         return 'true' === attr ? 'false' : 'true';
    1626                                                 })
    1627                                                 .next( '.filter-drawer' ).slideToggle( 180, 'linear', function() {
    1628                                                         if ( 0 === section.filtersHeight ) {
    1629                                                                 section.filtersHeight = $( this ).height();
     1622                        // Toggle feature filters.
     1623                        section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) {
     1624                                $( e.currentTarget )
     1625                                        .toggleClass( 'open' )
     1626                                        .attr( 'aria-expanded', function( i, attr ) {
     1627                                                return 'true' === attr ? 'false' : 'true';
     1628                                        })
     1629                                        .next( '.filter-drawer' ).slideToggle( 180, 'linear', function() {
     1630                                                if ( 0 === section.filtersHeight ) {
     1631                                                        section.filtersHeight = $( this ).height();
    16301632
    1631                                                                 // First time, so it's opened.
    1632                                                                 section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
    1633                                                         }
    1634                                                 });
    1635                                         if ( $( e.currentTarget ).hasClass( 'open' ) ) {
    1636                                                 section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
    1637                                         } else {
    1638                                                 section.contentContainer.find( '.themes' ).css( 'margin-top', 0 );
    1639                                         }
    1640                                 });
    1641                         }
     1633                                                        // First time, so it's opened.
     1634                                                        section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
     1635                                                }
     1636                                        });
     1637                                if ( $( e.currentTarget ).hasClass( 'open' ) ) {
     1638                                        section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 );
     1639                                } else {
     1640                                        section.contentContainer.find( '.themes' ).css( 'margin-top', 0 );
     1641                                }
     1642                        });
    16421643
    16431644                        // Setup section cross-linking.
    16441645                        section.contentContainer.on( 'click', '.no-themes-local .search-dotorg-themes', function() {
     
    16821683
    16831684                                // Try to load controls if none are loaded yet.
    16841685                                if ( 0 === section.loaded ) {
    1685                                         section.loadControls();
     1686                                        section.loadThemes();
    16861687                                }
    16871688
    16881689                                // Collapse any sibling sections/panels
     
    16971698                                                        section.contentContainer.find( '.wp-filter-search' ).val( searchTerm );
    16981699
    16991700                                                        // Directly initialize an empty remote search to avoid a race condition.
    1700                                                         if ( '' === searchTerm && '' !== section.term && 'installed' !== section.params.action ) {
     1701                                                        if ( '' === searchTerm && '' !== section.term && 'local' !== section.params.filter_type ) {
    17011702                                                                section.term = '';
    17021703                                                                section.initializeNewQuery( section.term, section.tags );
    17031704                                                        } else {
    1704                                                                 section.checkTerm( section );
     1705                                                                if ( 'remote' === section.params.filter_type ) {
     1706                                                                        section.checkTerm( section );
     1707                                                                } else if ( 'local' === section.params.filter_type ) {
     1708                                                                        section.filterSearch( searchTerm );
     1709                                                                }
    17051710                                                        }
    1706                                                         section.filterSearch( section.contentContainer.find( '.wp-filter-search' ).get( 0 ) );
    17071711                                                }
    17081712                                                otherSection.collapse( { duration: args.duration } );
    17091713                                        }
     
    17531757                 *
    17541758                 * @returns {void}
    17551759                 */
    1756                 loadControls: function() {
     1760                loadThemes: function() {
    17571761                        var section = this, params, page, request;
    17581762
    17591763                        if ( section.loading ) {
     
    17701774                                'page': page
    17711775                        };
    17721776
    1773                         // Add fields for wporg actions.
    1774                         if ( 'wporg' === section.params.action ) {
     1777                        // Add fields for remote filtering.
     1778                        if ( 'remote' === section.params.filter_type ) {
    17751779                                params.search = section.term;
    17761780                                params.tags = section.tags;
    17771781                        }
     
    17821786                        section.container.find( '.no-themes' ).hide();
    17831787                        request = wp.ajax.post( 'customize-load-themes', params );
    17841788                        request.done(function( data ) {
    1785                                 var themes = data.themes, themeControl, newThemeControls;
     1789                                var themes = data.themes, themeControl;
    17861790
    17871791                                // Stop and try again if the term changed while loading.
    17881792                                if ( '' !== section.nextTerm || '' !== section.nextTags ) {
     
    17951799                                        section.nextTerm = '';
    17961800                                        section.nextTags = '';
    17971801                                        section.loading = false;
    1798                                         section.loadControls();
     1802                                        section.loadThemes();
    17991803                                        return;
    18001804                                }
    18011805
    18021806                                if ( 0 !== themes.length ) {
    1803                                         newThemeControls = [];
    18041807
    1805                                         // Add controls for each theme.
    1806                                         _.each( themes, function( theme ) {
    1807                                                 var customizeId = section.params.action + '_theme_' + theme.id;
    1808                                                 themeControl = new api.controlConstructor.theme( customizeId, {
    1809                                                         params: {
    1810                                                                 type: 'theme',
    1811                                                                 content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>',
    1812                                                                 section: section.params.id,
    1813                                                                 active: true,
    1814                                                                 theme: theme,
    1815                                                                 priority: section.loaded + 1
    1816                                                         },
    1817                                                         previewer: api.previewer
    1818                                                 } );
     1808                                        section.loadControls( themes, page );
    18191809
    1820                                                 api.control.add( customizeId, themeControl );
    1821                                                 newThemeControls.push( themeControl );
    1822                                                 section.loaded = section.loaded + 1;
    1823                                         });
    1824 
    18251810                                        if ( 1 === page ) {
    18261811
    18271812                                                // Pre-load the first 3 theme screenshots.
     
    18321817                                                                img.src = src;
    18331818                                                        }
    18341819                                                });
    1835                                                 if ( 'installed' !== section.params.action ) {
     1820                                                if ( 'local' !== section.params.filter_type ) {
    18361821                                                        wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) );
    18371822                                                }
    1838                                         } else {
    1839                                                 Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue.
    18401823                                        }
     1824
    18411825                                        _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible.
    18421826
    1843                                         if ( 'installed' === section.params.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list.
     1827                                        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.
    18441828                                                section.fullyLoaded = true;
    18451829                                        }
    18461830                                } else {
     
    18511835                                                section.fullyLoaded = true;
    18521836                                        }
    18531837                                }
    1854                                 if ( 'installed' === section.params.action ) {
     1838                                if ( 'local' === section.params.filter_type ) {
    18551839                                        section.updateCount(); // Count of visible theme controls.
    18561840                                } else {
    18571841                                        section.updateCount( data.info.results ); // Total number of results including pages not yet loaded.
     
    18771861                },
    18781862
    18791863                /**
     1864                 * Loads controls into the section from data received from loadThemes().
     1865                 *
     1866                 * @since 4.9.0
     1867                 * @param {Array} themes - Array of theme data to create controls with.
     1868                 * @param {integer} page - Page of results being loaded.
     1869                 * @returns {void}
     1870                 */
     1871                loadControls: function( themes, page ) {
     1872                        var newThemeControls = [],
     1873                                section = this;
     1874
     1875                        // Add controls for each theme.
     1876                        _.each( themes, function( theme ) {
     1877                                var customizeId = section.params.action + '_theme_' + theme.id;
     1878                                themeControl = new api.controlConstructor.theme( customizeId, {
     1879                                        params: {
     1880                                                type: 'theme',
     1881                                                content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>',
     1882                                                section: section.params.id,
     1883                                                active: true,
     1884                                                theme: theme,
     1885                                                priority: section.loaded + 1
     1886                                        },
     1887                                        previewer: api.previewer
     1888                                } );
     1889
     1890                                api.control.add( customizeId, themeControl );
     1891                                newThemeControls.push( themeControl );
     1892                                section.loaded = section.loaded + 1;
     1893                        });
     1894
     1895                        if ( 1 !== page ) {
     1896                                Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue.
     1897                        }
     1898                },
     1899
     1900                /**
    18801901                 * Determines whether more themes should be loaded, and loads them.
    18811902                 *
    18821903                 * @since 4.9.0
     
    18911912                                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.
    18921913
    18931914                                if ( bottom > threshold ) {
    1894                                         section.loadControls();
     1915                                        section.loadThemes();
    18951916                                }
    18961917                        }
    18971918                },
     
    19011922                 *
    19021923                 * @since 4.9.0
    19031924                 *
    1904                  * @param {Element} el - The search input element as a raw JS object.
     1925                 * @param {string} term - The raw search input value.
    19051926                 * @returns {void}
    19061927                 */
    1907                 filterSearch: function( el ) {
     1928                filterSearch: function( term ) {
    19081929                        var count = 0,
    19091930                                visible = false,
    19101931                                section = this,
    1911                                 noFilter = ( undefined !== api.section( 'wporg_themes' ) && 'wporg' !== section.params.action ) ? '.no-themes-local' : '.no-themes',
    1912                                 term = el.value.toLowerCase().trim().replace( '-', ' ' ),
     1932                                noFilter = ( undefined !== api.section( 'wporg_themes' ) && 'remote' !== section.params.filter_type ) ? '.no-themes-local' : '.no-themes',
    19131933                                controls = section.controls();
    19141934
    19151935                        if ( section.loading ) {
     
    19161936                                return;
    19171937                        }
    19181938
     1939                        // Standardize search term format and split into an array of individual words.
     1940                        term = term.toLowerCase().trim().replace( '-', ' ' ).split( ' ' );
     1941
    19191942                        _.each( controls, function( control ) {
    1920                                 visible = control.filter( term );
     1943                                visible = control.filter( term ); // Shows/hides and sorts control based on the applicability of the search term.
    19211944                                if ( visible ) {
    19221945                                        count = count + 1;
    19231946                                }
     
    19311954                        }
    19321955
    19331956                        section.renderScreenshots();
     1957                        api.reflowPaneContents();
    19341958
    19351959                        // Update theme count.
    19361960                        section.updateCount( count );
     
    19461970                 */
    19471971                checkTerm: function( section ) {
    19481972                        var newTerm;
    1949                         if ( 'wporg' === section.params.action ) {
     1973                        if ( 'remote' === section.params.filter_type ) {
    19501974                                newTerm = $( '#wp-filter-search-input' ).val();
    19511975                                if ( section.term !== newTerm ) {
    19521976                                        section.initializeNewQuery( newTerm, section.tags );
     
    19862010                                if ( section.loading ) {
    19872011                                        section.nextTags = tags;
    19882012                                } else {
    1989                                         section.initializeNewQuery( section.term, tags );
     2013                                        if ( 'remote' === section.params.filter_type ) {
     2014                                                section.initializeNewQuery( section.term, tags );
     2015                                        } else if ( 'local' === section.params.filter_type ) {
     2016                                                section.filterSearch( tags.join( ' ' ) );
     2017                                        }
    19902018                                }
    19912019                        }
    19922020                },
     
    20122040                        section.fullyLoaded = false;
    20132041                        section.screenshotQueue = null;
    20142042
    2015                         // Run a new query, with loadControls handling paging, etc.
     2043                        // Run a new query, with loadThemes handling paging, etc.
    20162044                        if ( ! section.loading ) {
    20172045                                section.term = newTerm;
    20182046                                section.tags = newTags;
    2019                                 section.loadControls();
     2047                                section.loadThemes();
    20202048                        } else {
    2021                                 section.nextTerm = newTerm; // This will reload from loadControls() with the newest term once the current batch is loaded.
    2022                                 section.nextTags = newTags; // This will reload from loadControls() with the newest tags once the current batch is loaded.
     2049                                section.nextTerm = newTerm; // This will reload from loadThemes() with the newest term once the current batch is loaded.
     2050                                section.nextTags = newTags; // This will reload from loadThemes() with the newest tags once the current batch is loaded.
    20232051                        }
    20242052                        if ( ! section.expanded() ) {
    20252053                                section.expand(); // Expand the section if it isn't expanded.
     
    46074635                 * Show or hide the theme based on the presence of the term in the title, description, tags, and author.
    46084636                 *
    46094637                 * @since 4.2.0
     4638                 * @param {Array} terms - An array of terms to search for.
    46104639                 * @returns {boolean} Whether a theme control was activated or not.
    46114640                 */
    4612                 filter: function( term ) {
     4641                filter: function( terms ) {
    46134642                        var control = this,
     4643                                matchCount = 0,
    46144644                                haystack = control.params.theme.name + ' ' +
    46154645                                        control.params.theme.description + ' ' +
    46164646                                        control.params.theme.tags + ' ' +
    4617                                         control.params.theme.author;
     4647                                        control.params.theme.author + ' ';
    46184648                        haystack = haystack.toLowerCase().replace( '-', ' ' );
    4619                         if ( -1 !== haystack.search( term ) ) {
     4649
     4650                        // Back-compat for behavior in WordPress 4.2.0 to 4.8.X.
     4651                        if ( ! terms instanceof Array ) {
     4652                                terms = [terms];
     4653                        }
     4654
     4655                        // Always give exact name matches highest ranking.
     4656                        if ( control.params.theme.name.toLowerCase() === terms.join( ' ' ) ) {
     4657                                matchCount = 100;
     4658                        } else {
     4659
     4660                                // Search for and weight (by 10) complete term matches.
     4661                                matchCount = matchCount + 10 * ( haystack.split( terms.join( ' ' ) ).length - 1 );
     4662
     4663                                // Search for each term individually (as whole-word and partial match) and sum weighted match counts.
     4664                                _.each( terms, function( term ) {
     4665                                        matchCount = matchCount + 2 * ( haystack.split( term + ' ' ).length - 1 ); // Whole-word, double-weighted.
     4666                                        matchCount = matchCount + haystack.split( term ).length - 1; // Partial word, to minimize empty intermediate searches while typing.
     4667                                });
     4668
     4669                                // Upper limit on match ranking.
     4670                                if ( matchCount > 99 ) {
     4671                                        matchCount = 99;
     4672                                }
     4673                        }
     4674
     4675                        if ( 0 !== matchCount ) {
    46204676                                control.activate();
     4677                                control.params.priority = 101 - matchCount; // Sort results by match count.
    46214678                                return true;
    46224679                        } else {
    4623                                 control.deactivate();
     4680                                control.deactivate(); // Hide control
     4681                                control.params.priority = 101;
    46244682                                return false;
    46254683                        }
    46264684                },
  • src/wp-includes/class-wp-customize-manager.php

     
    42044204                        $this->add_section( new WP_Customize_Themes_Section( $this, 'wporg_themes', array(
    42054205                                'title'       => __( 'WordPress.org themes' ),
    42064206                                'action'      => 'wporg',
     4207                                'filter_type' => 'remote',
    42074208                                'capability'  => 'install_themes',
    42084209                                'panel'       => 'themes',
    42094210                                'priority'    => 5,
     
    47354736                }
    47364737                $theme_action = sanitize_key( $_POST['theme_action'] );
    47374738                $themes = array();
     4739                $args = array();
    47384740
     4741                // Define query filters based on user input.
     4742                if ( ! array_key_exists( 'search', $_POST ) ) {
     4743                        $args['search'] = '';
     4744                } else {
     4745                        $args['search'] = sanitize_text_field( wp_unslash( $_POST['search'] ) );
     4746                }
     4747
     4748                if ( ! array_key_exists( 'tags', $_POST ) ) {
     4749                        $args['tag'] = '';
     4750                } else {
     4751                        $args['tag'] = array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['tags'] ) );
     4752                }
     4753
     4754                if ( ! array_key_exists( 'page', $_POST ) ) {
     4755                        $args['page'] = 1;
     4756                } else {
     4757                        $args['page'] = absint( $_POST['page'] );
     4758                }
     4759
    47394760                require_once ABSPATH . 'wp-admin/includes/theme.php';
     4761
     4762                // Load all installed themes from wp_prepare_themes_for_js().
    47404763                if ( 'installed' === $theme_action ) {
    47414764                        $themes = array( 'themes' => wp_prepare_themes_for_js() );
    47424765                        foreach ( $themes['themes'] as &$theme ) {
     
    47434766                                $theme['type'] = 'installed';
    47444767                                $theme['active'] = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme['id'] );
    47454768                        }
     4769
     4770                // Load WordPress.org themes from the .org API and normalize data to match installed theme objects.
    47464771                } elseif ( 'wporg' === $theme_action ) {
    47474772                        if ( ! current_user_can( 'install_themes' ) ) {
    47484773                                wp_die( -1 );
     
    47494774                        }
    47504775
    47514776                        // Arguments for all queries.
    4752                         $args = array(
     4777                        $wporg_args = array(
    47534778                                'per_page' => 100,
    4754                                 'page' => isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1,
    47554779                                'fields' => array(
    47564780                                        'screenshot_url' => true,
    47574781                                        'description' => true,
     
    47674791                                ),
    47684792                        );
    47694793
    4770                         // Define query filters based on user input.
    4771                         if ( ! array_key_exists( 'search', $_POST ) ) {
    4772                                 $args['search'] = '';
    4773                         } else {
    4774                                 $args['search'] = sanitize_text_field( wp_unslash( $_POST['search'] ) );
    4775                         }
     4794                        $args = array_merge( $wporg_args, $args );
    47764795
    4777                         if ( ! array_key_exists( 'tags', $_POST ) ) {
    4778                                 $args['tag'] = '';
    4779                         } else {
    4780                                 $args['tag'] = array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['tags'] ) );
    4781                         }
    4782 
    47834796                        if ( '' === $args['search'] && '' === $args['tag'] ) {
    47844797                                $args['browse'] = 'new'; // Sort by latest themes by default.
    47854798                        }
     
    48494862                                unset( $theme->author );
    48504863                        } // End foreach().
    48514864                } // End if().
     4865
     4866
     4867                /**
     4868                 * Filters the theme data loaded in the customizer.
     4869                 *
     4870                 * This allows theme data to be loading from an external source,
     4871                 * or modification of data loaded from wp_prepare_themes_for_JS
     4872                 * or WordPress.org.
     4873                 *
     4874                 * @since 4.9.0
     4875                 *
     4876                 * @see WP_Customize_Manager::__construct()
     4877                 *
     4878                 * @param array $themes Nested array of theme data.
     4879                 * @param array $args   List of arguments, such as page, search term, and tags to query for.
     4880                 */
     4881                $themes = apply_filters( 'customize_load_themes_ajax', $themes, $args );
     4882
    48524883                wp_send_json_success( $themes );
    48534884        }
    48544885
  • src/wp-includes/customize/class-wp-customize-themes-section.php

     
    3737        public $action = '';
    3838
    3939        /**
     40         * Theme section filter type.
     41         *
     42         * Determines whether filters are applied to loaded (local) themes or by initiating a new remote query (remote).
     43         * When filtering is local, the initial themes query is not paginated by default.
     44         *
     45         * @since 4.9.0
     46         * @var string
     47         */
     48        public $filter_type = 'local';
     49
     50        /**
    4051         * Get section parameters for JS.
    4152         *
    4253         * @since 4.9.0
     
    4556        public function json() {
    4657                $exported = parent::json();
    4758                $exported['action'] = $this->action;
     59                $exported['filter_type'] = $this->filter_type;
    4860
    4961                return $exported;
    5062        }