Make WordPress Core


Ignore:
Timestamp:
09/29/2017 08:12:19 PM (7 years ago)
Author:
westonruter
Message:

Customize: Introduce a new experience for discovering, installing, and previewing themes within the customizer.

Unify the theme-browsing and theme-customization experiences by introducing a comprehensive theme browser and installer directly accessible in the customizer. Replaces the customizer theme switcher with a full-screen panel for discovering/browsing and installing themes available on WordPress.org. Themes can now be installed and previewed directly in the customizer without entering the wp-admin context. Also includes an extensible framework for browsing and installing themes from other sources.

Also includes CSS auto-prefixing added via grunt precommit:css.

For details, see: https://make.wordpress.org/core/2016/10/03/feature-proposal-a-new-experience-for-discovering-installing-and-previewing-themes-in-the-customizer/

Previously [38813] but reverted in [39140].
Fixes #37661, #34843, #38666.
Props celloexpressions, folletto, westonruter, karmatosed, melchoyce, afercia.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-customize-manager.php

    r41640 r41648  
    321321        require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' );
    322322
     323        require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' );
    323324        require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' );
    324325        require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' );
     
    376377        add_action( 'wp_ajax_customize_save',           array( $this, 'save' ) );
    377378        add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
     379        add_action( 'wp_ajax_customize-load-themes',    array( $this, 'load_themes_ajax' ) );
    378380        add_action( 'wp_ajax_dismiss_customize_changeset_autosave', array( $this, 'handle_dismiss_changeset_autosave_request' ) );
    379381
     
    393395        // Export the settings to JS via the _wpCustomizeSettings variable.
    394396        add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
     397
     398        // Add theme update notices.
     399        if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) {
     400            require_once ABSPATH . '/wp-admin/includes/update.php';
     401            add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' );
     402        }
    395403    }
    396404
     
    36863694            $control->enqueue();
    36873695        }
     3696
     3697        if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) {
     3698            wp_enqueue_script( 'updates' );
     3699        }
    36883700    }
    36893701
     
    38903902            'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
    38913903            'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
     3904            'switch-themes' => wp_create_nonce( 'switch-themes' ),
    38923905            'dismiss_autosave' => wp_create_nonce( 'dismiss_customize_changeset_autosave' ),
    38933906        );
     
    39964009            'documentTitleTmpl' => $this->get_document_title_template(),
    39974010            'previewableDevices' => $this->get_previewable_devices(),
     4011            'l10n' => array(
     4012                'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ),
     4013                /* translators: %d is the number of theme search results, which cannot currently consider singular vs. plural forms */
     4014                'themeSearchResults' => __( '%d themes found' ),
     4015                /* translators: %d is the number of themes being displayed, which cannot currently consider singular vs. plural forms */
     4016                'announceThemeCount' => __( 'Displaying %d themes' ),
     4017                /* translators: %s is the theme name */
     4018                'announceThemeDetails' => __( 'Showing details for theme: %s' ),
     4019            ),
    39984020        );
    39994021
     
    40994121        /* Panel, Section, and Control Types */
    41004122        $this->register_panel_type( 'WP_Customize_Panel' );
     4123        $this->register_panel_type( 'WP_Customize_Themes_Panel' );
    41014124        $this->register_section_type( 'WP_Customize_Section' );
    41024125        $this->register_section_type( 'WP_Customize_Sidebar_Section' );
     4126        $this->register_section_type( 'WP_Customize_Themes_Section' );
    41034127        $this->register_control_type( 'WP_Customize_Color_Control' );
    41044128        $this->register_control_type( 'WP_Customize_Media_Control' );
     
    41604184        ) ) );
    41614185
    4162         /* Themes */
    4163 
    4164         $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
    4165             'title'      => $this->theme()->display( 'Name' ),
    4166             'capability' => 'switch_themes',
    4167             'priority'   => 0,
     4186        /* Themes (controls are loaded via ajax) */
     4187
     4188        $this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array(
     4189            'title'       => $this->theme()->display( 'Name' ),
     4190            'description' => __( 'Once themes are installed, you can live-preview them on your site, customize them, and publish your new design. Browse available themes via the filters in this menu.' ),
     4191            'capability'  => 'switch_themes',
     4192            'priority'    => 0,
    41684193        ) ) );
     4194
     4195        $this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array(
     4196            'title'       => __( 'Installed themes' ),
     4197            'action'      => 'installed',
     4198            'capability'  => 'switch_themes',
     4199            'panel'       => 'themes',
     4200            'priority'    => 0,
     4201        ) ) );
     4202
     4203        if ( ! is_multisite() ) {
     4204            $this->add_section( new WP_Customize_Themes_Section( $this, 'wporg_themes', array(
     4205                'title'       => __( 'WordPress.org themes' ),
     4206                'action'      => 'wporg',
     4207                'capability'  => 'install_themes',
     4208                'panel'       => 'themes',
     4209                'priority'    => 5,
     4210            ) ) );
     4211        }
    41694212
    41704213        // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
     
    41724215            'capability' => 'switch_themes',
    41734216        ) ) );
    4174 
    4175         require_once( ABSPATH . 'wp-admin/includes/theme.php' );
    4176 
    4177         // Theme Controls.
    4178 
    4179         // Add a control for the active/original theme.
    4180         if ( ! $this->is_theme_active() ) {
    4181             $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) );
    4182             $active_theme = current( $themes );
    4183             $active_theme['isActiveTheme'] = true;
    4184             $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array(
    4185                 'theme'    => $active_theme,
    4186                 'section'  => 'themes',
    4187                 'settings' => 'active_theme',
    4188             ) ) );
    4189         }
    4190 
    4191         $themes = wp_prepare_themes_for_js();
    4192         foreach ( $themes as $theme ) {
    4193             if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) {
    4194                 continue;
    4195             }
    4196 
    4197             $theme_id = 'theme_' . $theme['id'];
    4198             $theme['isActiveTheme'] = false;
    4199             $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
    4200                 'theme'    => $theme,
    4201                 'section'  => 'themes',
    4202                 'settings' => 'active_theme',
    4203             ) ) );
    4204         }
    42054217
    42064218        /* Site Identity */
     
    47084720
    47094721    /**
     4722     * Load themes into the theme browsing/installation UI.
     4723     *
     4724     * @since 4.9.0
     4725     */
     4726    public function load_themes_ajax() {
     4727        check_ajax_referer( 'switch-themes', 'switch-themes-nonce' );
     4728
     4729        if ( ! current_user_can( 'switch_themes' ) ) {
     4730            wp_die( -1 );
     4731        }
     4732
     4733        if ( empty( $_POST['theme_action'] ) ) {
     4734            wp_send_json_error( 'missing_theme_action' );
     4735        }
     4736        $theme_action = sanitize_key( $_POST['theme_action'] );
     4737        $themes = array();
     4738
     4739        require_once ABSPATH . 'wp-admin/includes/theme.php';
     4740        if ( 'installed' === $theme_action ) {
     4741            $themes = array( 'themes' => wp_prepare_themes_for_js() );
     4742            foreach ( $themes['themes'] as &$theme ) {
     4743                $theme['type'] = 'installed';
     4744                $theme['active'] = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme['id'] );
     4745            }
     4746        } elseif ( 'wporg' === $theme_action ) {
     4747            if ( ! current_user_can( 'install_themes' ) ) {
     4748                wp_die( -1 );
     4749            }
     4750
     4751            // Arguments for all queries.
     4752            $args = array(
     4753                'per_page' => 100,
     4754                'page' => isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1,
     4755                'fields' => array(
     4756                    'screenshot_url' => true,
     4757                    'description' => true,
     4758                    'rating' => true,
     4759                    'downloaded' => true,
     4760                    'downloadlink' => true,
     4761                    'last_updated' => true,
     4762                    'homepage' => true,
     4763                    'num_ratings' => true,
     4764                    'tags' => true,
     4765                    'parent' => true,
     4766                    // 'extended_author' => true, @todo: WordPress.org throws a 500 server error when this is here.
     4767                ),
     4768            );
     4769
     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            }
     4776
     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
     4783            if ( '' === $args['search'] && '' === $args['tag'] ) {
     4784                $args['browse'] = 'new'; // Sort by latest themes by default.
     4785            }
     4786
     4787            // Load themes from the .org API.
     4788            $themes = themes_api( 'query_themes', $args );
     4789            if ( is_wp_error( $themes ) ) {
     4790                wp_send_json_error();
     4791            }
     4792
     4793            // This list matches the allowed tags in wp-admin/includes/theme-install.php.
     4794            $themes_allowedtags = array_fill_keys(
     4795                array( 'a', 'abbr', 'acronym', 'code', 'pre', 'em', 'strong', 'div', 'p', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img' ),
     4796                array()
     4797            );
     4798            $themes_allowedtags['a'] = array_fill_keys( array( 'href', 'title', 'target' ), true );
     4799            $themes_allowedtags['acronym']['title'] = true;
     4800            $themes_allowedtags['abbr']['title'] = true;
     4801            $themes_allowedtags['img'] = array_fill_keys( array( 'src', 'class', 'alt' ), true );
     4802
     4803            // Prepare a list of installed themes to check against before the loop.
     4804            $installed_themes = array();
     4805            $wp_themes = wp_get_themes();
     4806            foreach ( $wp_themes as $theme ) {
     4807                $installed_themes[] = $theme->get_stylesheet();
     4808            }
     4809            $update_php = network_admin_url( 'update.php?action=install-theme' );
     4810
     4811            // Set up properties for themes available on WordPress.org.
     4812            foreach ( $themes->themes as &$theme ) {
     4813                $theme->install_url = add_query_arg( array(
     4814                    'theme'    => $theme->slug,
     4815                    '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
     4816                ), $update_php );
     4817
     4818                $theme->name        = wp_kses( $theme->name, $themes_allowedtags );
     4819                $theme->author      = wp_kses( $theme->author, $themes_allowedtags );
     4820                $theme->version     = wp_kses( $theme->version, $themes_allowedtags );
     4821                $theme->description = wp_kses( $theme->description, $themes_allowedtags );
     4822                $theme->tags        = implode( ', ', $theme->tags );
     4823                $theme->stars       = wp_star_rating( array(
     4824                    'rating' => $theme->rating,
     4825                    'type' => 'percent',
     4826                    'number' => $theme->num_ratings,
     4827                    'echo' => false,
     4828                ) );
     4829                $theme->num_ratings = number_format_i18n( $theme->num_ratings );
     4830                $theme->preview_url = set_url_scheme( $theme->preview_url );
     4831
     4832                // Handle themes that are already installed as installed themes.
     4833                if ( in_array( $theme->slug, $installed_themes, true ) ) {
     4834                    $theme->type = 'installed';
     4835                } else {
     4836                    $theme->type = $theme_action;
     4837                }
     4838
     4839                // Set active based on customized theme.
     4840                $theme->active = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme->slug );
     4841
     4842                // Map available theme properties to installed theme properties.
     4843                $theme->id           = $theme->slug;
     4844                $theme->screenshot   = array( $theme->screenshot_url );
     4845                $theme->authorAndUri = $theme->author;
     4846                $theme->parent       = ( $theme->slug === $theme->template ) ? false : $theme->template; // The .org API does not seem to return the parent in a documented way; however, this check should yield a similar result in most cases.
     4847                unset( $theme->slug );
     4848                unset( $theme->screenshot_url );
     4849                unset( $theme->author );
     4850            } // End foreach().
     4851        } // End if().
     4852        wp_send_json_success( $themes );
     4853    }
     4854
     4855
     4856    /**
    47104857     * Callback for validating the header_textcolor value.
    47114858     *
Note: See TracChangeset for help on using the changeset viewer.