WordPress.org

Make WordPress Core

Ticket #27404: 27404-1-30-17.patch

File 27404-1-30-17.patch, 34.2 KB (added by rramo012, 12 months ago)

Refresh

  • src/wp-includes/class-wp-customize-widgets.php

     
    447447 
    448448                                // Add section to contain controls. 
    449449                                $section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id ); 
    450                                 if ( $is_active_sidebar ) { 
    451450 
    452                                         $section_args = array( 
    453                                                 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'], 
    454                                                 'description' => $wp_registered_sidebars[ $sidebar_id ]['description'], 
    455                                                 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ), 
    456                                                 'panel' => 'widgets', 
    457                                                 'sidebar_id' => $sidebar_id, 
    458                                         ); 
     451                                if ( $is_inactive_widgets ) { 
     452                                        $title = 'Inactive Widgets'; 
     453                                        $description = $title; 
     454                                } else { 
     455                                        $title = $wp_registered_sidebars[ $sidebar_id ]['name']; 
     456                                        $description = $wp_registered_sidebars[ $sidebar_id ]['description']; 
     457                                } 
    459458 
    460                                         /** 
    461                                          * Filters Customizer widget section arguments for a given sidebar. 
    462                                          * 
    463                                          * @since 3.9.0 
    464                                          * 
    465                                          * @param array      $section_args Array of Customizer widget section arguments. 
    466                                          * @param string     $section_id   Customizer section ID. 
    467                                          * @param int|string $sidebar_id   Sidebar ID. 
    468                                          */ 
    469                                         $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 
     459                                $section_args = array( 
     460                                        'title' => $title, 
     461                                        'description' => $description, 
     462                                        'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ), 
     463                                        'panel' => 'widgets', 
     464                                        'sidebar_id' => $sidebar_id, 
     465                                ); 
    470466 
    471                                         $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 
    472                                         $this->manager->add_section( $section ); 
     467                                /** 
     468                                 * Filters Customizer widget section arguments for a given sidebar. 
     469                                 * 
     470                                 * @since 3.9.0 
     471                                 * 
     472                                 * @param array      $section_args Array of Customizer widget section arguments. 
     473                                 * @param string     $section_id   Customizer section ID. 
     474                                 * @param int|string $sidebar_id   Sidebar ID. 
     475                                 */ 
     476                                $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 
    473477 
    474                                         $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 
    475                                                 'section'    => $section_id, 
    476                                                 'sidebar_id' => $sidebar_id, 
    477                                                 'priority'   => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 
    478                                         ) ); 
    479                                         $new_setting_ids[] = $setting_id; 
     478                                $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 
     479                                $this->manager->add_section( $section ); 
    480480 
    481                                         $this->manager->add_control( $control ); 
    482                                 } 
     481                                $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 
     482                                        'section'    => $section_id, 
     483                                        'sidebar_id' => $sidebar_id, 
     484                                        'priority'   => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 
     485                                ) ); 
     486                                $new_setting_ids[] = $setting_id; 
     487 
     488                                $this->manager->add_control( $control ); 
     489 
     490                                $section_args = array( 
     491                                        'title' => $title, 
     492                                        'description' => $description, 
     493                                        'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ), 
     494                                        'panel' => 'widgets', 
     495                                        'sidebar_id' => $sidebar_id, 
     496                                ); 
     497 
     498                                /** 
     499                                 * Filter Customizer widget section arguments for a given sidebar. 
     500                                 * 
     501                                 * @since 3.9.0 
     502                                 * 
     503                                 * @param array      $section_args Array of Customizer widget section arguments. 
     504                                 * @param string     $section_id   Customizer section ID. 
     505                                 * @param int|string $sidebar_id   Sidebar ID. 
     506                                 */ 
     507                                $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 
     508 
     509                                $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 
     510                                $this->manager->add_section( $section ); 
     511 
     512                                $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 
     513                                        'section'    => $section_id, 
     514                                        'sidebar_id' => $sidebar_id, 
     515                                        'priority'   => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 
     516                                ) ); 
     517                                $new_setting_ids[] = $setting_id; 
     518 
     519                                $this->manager->add_control( $control ); 
    483520                        } 
    484521 
    485522                        // Add a control for each active widget (located in a sidebar). 
    486523                        foreach ( $sidebar_widget_ids as $i => $widget_id ) { 
    487524 
    488525                                // Skip widgets that may have gone away due to a plugin being deactivated. 
    489                                 if ( ! $is_active_sidebar || ! isset( $wp_registered_widgets[$widget_id] ) ) { 
     526                                if ( ! isset( $wp_registered_widgets[$widget_id] ) ) { 
    490527                                        continue; 
    491528                                } 
    492529 
     
    505542                                        'height'         => $wp_registered_widget_controls[$widget_id]['height'], 
    506543                                        'is_wide'        => $this->is_wide_widget( $widget_id ), 
    507544                                ) ); 
     545 
    508546                                $this->manager->add_control( $control ); 
    509547                        } 
    510548                } 
     
    720758                        </div>' 
    721759                ); 
    722760 
     761                $inactive_widget_tpl = str_replace( 
     762                        array( '{delete}', '{add}' ), 
     763                        array( 
     764                                __( 'Delete' ), 
     765                                __( 'Add' ) 
     766                        ), 
     767                        '<div id="saved-widget-<%- id %>" data-id-base="<%- idBase %>" data-widget-id="<%- id %>" class="widget-title saved-widget widget-tpl" tabindex="0"> 
     768                                <h3><%- type %><span class="in-widget-title"></span></h3> 
     769                                <div class="saved-widget-controls"> 
     770                                        <a href="#" class="add-saved-widget" data-is-saved-widget="1" data-widget-id="<%- id %>" 
     771                                        >{add}</a> &#124; <a href="#" class="delete-widget-permanently">{delete}</a> 
     772                                </div> 
     773                        </div>' 
     774                ); 
     775 
     776                $inactive_sidebar = array( 
     777                        'inactive-sidebar' => array( 
     778                                'name' => __( 'Save For Later' ), 
     779                                'id' => 'wp_inactive_widgets', 
     780                                'description' => __( 'Save widgets to the Inactive Sidebar to use later.' ), 
     781                                'class' => 'inactive-sidebar', 
     782                                'before_widget' => '<aside id="%1$s" class="widget %2$s">', 
     783                                'after_widget' => '</aside>', 
     784                                'before_title' => '<h1 class="widget-title">', 
     785                                'after_title' => '</h1>', 
     786                        ), 
     787                ); 
     788 
     789                $all_sidebars = array_values( array_merge( $wp_registered_sidebars, $inactive_sidebar ) ); 
     790 
    723791                $settings = array( 
    724                         'registeredSidebars'   => array_values( $wp_registered_sidebars ), 
     792                        'registeredSidebars'   => $all_sidebars, 
    725793                        'registeredWidgets'    => $wp_registered_widgets, 
    726794                        'availableWidgets'     => $available_widgets, // @todo Merge this with registered_widgets 
    727795                        'l10n' => array( 
     
    729797                                'saveBtnTooltip'   => __( 'Save and preview changes before publishing them.' ), 
    730798                                'removeBtnLabel'   => __( 'Remove' ), 
    731799                                'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ), 
     800                                'moveBtnLabel'     => __( 'Move' ), 
     801                                'moveBtnTooltip'   => __( 'Reorder the widgets in this sidebar.' ), 
    732802                                'error'            => __( 'An error has occurred. Please reload the page and try again.' ), 
    733803                                'widgetMovedUp'    => __( 'Widget moved up' ), 
    734804                                'widgetMovedDown'  => __( 'Widget moved down' ), 
     
    736806                                'reorderModeOn'    => __( 'Reorder mode enabled' ), 
    737807                                'reorderModeOff'   => __( 'Reorder mode closed' ), 
    738808                                'reorderLabelOn'   => esc_attr__( 'Reorder widgets' ), 
     809                                'reorderLabelOff'  => esc_attr__( 'Close reorder mode' ), 
     810                                'itemDeleted'       => __( 'Widget deleted' ), 
    739811                                'widgetsFound'     => __( 'Number of widgets found: %d' ), 
    740812                                'noWidgetsFound'   => __( 'No widgets found.' ), 
    741813                        ), 
    742814                        'tpl' => array( 
    743815                                'widgetReorderNav' => $widget_reorder_nav_tpl, 
    744816                                'moveWidgetArea'   => $move_widget_area_tpl, 
     817                                'inactiveWidget'   => $inactive_widget_tpl, 
    745818                        ), 
    746819                        'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(), 
    747820                ); 
     
    788861                        </div> 
    789862                        <div id="available-widgets-list"> 
    790863                        <?php foreach ( $this->get_available_widgets() as $available_widget ): ?> 
    791                                 <div id="widget-tpl-<?php echo esc_attr( $available_widget['id'] ) ?>" data-widget-id="<?php echo esc_attr( $available_widget['id'] ) ?>" class="widget-tpl <?php echo esc_attr( $available_widget['id'] ) ?>" tabindex="0"> 
     864                                <div id="widget-tpl-<?php echo esc_attr( $available_widget['id'] ) ?>" data-id-base="<?php echo esc_attr( $available_widget['id_base'] ) ?>" data-widget-id="<?php echo esc_attr( $available_widget['id'] ) ?>" class="widget-tpl <?php echo esc_attr( $available_widget['id'] ) ?>" tabindex="0"> 
    792865                                        <?php echo $available_widget['control_tpl']; ?> 
    793866                                </div> 
    794867                        <?php endforeach; ?> 
     
    876949                foreach ( $widget_ids as $widget_id ) { 
    877950                        $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id ); 
    878951                } 
     952 
    879953                return $sanitized_widget_ids; 
    880954        } 
    881955 
    882956        /** 
     957         * Create an list of saved widgets that correspond to a widget type. 
     958         * 
     959         * @since 4.8.0 
     960         * @access protected 
     961         * 
     962         * @global array $wp_registered_widget_controls 
     963         * 
     964         * @return array List of saved widget by type. 
     965         */ 
     966        protected function get_sorted_saved_widgets() { 
     967                global $wp_registered_widget_controls; 
     968 
     969                $sidebars_widgets = wp_get_sidebars_widgets(); 
     970 
     971                $sorted_saved_widgets = array(); 
     972                foreach ( $sidebars_widgets as $sidebars_widget ) { 
     973                        foreach( $sidebars_widget as $widget ) { 
     974 
     975                                if ( isset ( $wp_registered_widget_controls[ $widget ]['id_base'] ) ) { 
     976                                        $current_base_id = $wp_registered_widget_controls[ $widget ]['id_base']; 
     977                                        $sorted_saved_widgets[ $current_base_id ][] = array( 
     978                                                'id' => $widget, 
     979                                                'type' => $wp_registered_widget_controls[ $widget ]['name'], 
     980                                        ); 
     981                                } 
     982 
     983                        } 
     984                } 
     985 
     986                return $sorted_saved_widgets; 
     987        } 
     988 
     989        /** 
    883990         * Builds up an index of all available widgets for use in Backbone models. 
    884991         * 
    885992         * @since 3.9.0 
     
    9021009                global $wp_registered_widgets, $wp_registered_widget_controls; 
    9031010                require_once ABSPATH . '/wp-admin/includes/widgets.php'; // for next_widget_id_number() 
    9041011 
     1012                $sorted_saved_widgets = $this->get_sorted_saved_widgets(); 
    9051013                $sort = $wp_registered_widgets; 
    9061014                usort( $sort, array( $this, '_sort_name_callback' ) ); 
    9071015                $done = array(); 
     
    9581066                                'width'        => $wp_registered_widget_controls[$widget['id']]['width'], 
    9591067                                'height'       => $wp_registered_widget_controls[$widget['id']]['height'], 
    9601068                                'is_wide'      => $this->is_wide_widget( $widget['id'] ), 
     1069                                'saved_widgets' => isset( $sorted_saved_widgets[ $id_base ] ) ? $sorted_saved_widgets[ $id_base ] : array(), 
    9611070                        ) ); 
    9621071 
    9631072                        $available_widgets[] = $available_widget; 
     
    12701379         * @return array|void Sanitized widget instance. 
    12711380         */ 
    12721381        public function sanitize_widget_instance( $value ) { 
    1273                 if ( $value === array() ) { 
     1382                if ( $value === array() || false === $value ) { 
    12741383                        return $value; 
    12751384                } 
    12761385 
     
    13601469                global $wp_registered_widget_updates, $wp_registered_widget_controls; 
    13611470 
    13621471                $setting_id = $this->get_setting_id( $widget_id ); 
    1363  
    13641472                /* 
    13651473                 * Make sure that other setting changes have previewed since this widget 
    13661474                 * may depend on them (e.g. Menus being present for Custom Menu widget). 
     
    14091517                        } 
    14101518                } 
    14111519 
     1520                // If deleting widget. 
     1521                $delete_widget_val = $this->get_post_value( 'delete_widget' ); 
     1522                $is_widget_delete = ! empty( $delete_widget_val ); 
     1523 
     1524                if ( $is_widget_delete ) { 
     1525                        // Set post values needed in widget update_callback. 
     1526                        $_POST[ 'sidebar' ] = 'wp_inactive_widgets'; 
     1527                        $_POST[ 'widget-' . $parsed_id['id_base'] ] = array(); 
     1528                        $_POST[ 'the-widget-id' ] = $widget_id; 
     1529                        $_POST[ 'delete_widget' ] = '1'; 
     1530 
     1531                        $post_values_added = array( 'sidebar', 'widget-' . $parsed_id[ 'id_base' ], 'the-widget-id', 'delete_widget' ); 
     1532                        $added_input_vars = array_merge( $added_input_vars, $post_values_added ); 
     1533 
     1534                        /** This action is documented in wp-admin/widgets.php */ 
     1535                        do_action( 'delete_widget', $widget_id, $_POST[ 'sidebar' ], $parsed_id['id_base'] ); 
     1536                } 
     1537 
    14121538                // Invoke the widget update callback. 
    14131539                foreach ( (array) $wp_registered_widget_updates as $name => $control ) { 
    14141540                        if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) { 
     
    14411567 
    14421568                // Obtain the widget instance. 
    14431569                $option = $this->get_captured_option( $option_name ); 
    1444                 if ( null !== $parsed_id['number'] ) { 
     1570                if ( $is_widget_delete ) { 
     1571                        $instance = array( 'multidimensional_delete' => true ); 
     1572                } else if ( null !== $parsed_id['number'] ) { 
    14451573                        $instance = $option[ $parsed_id['number'] ]; 
    14461574                } else { 
    14471575                        $instance = $option; 
     
    14581586                // Obtain the widget control with the updated instance in place. 
    14591587                ob_start(); 
    14601588                $form = $wp_registered_widget_controls[ $widget_id ]; 
    1461                 if ( $form ) { 
     1589                if ( $form && false === $is_widget_delete ) { 
    14621590                        call_user_func_array( $form['callback'], $form['params'] ); 
    14631591                } 
    14641592                $form = ob_get_clean(); 
  • src/wp-includes/class-wp-customize-setting.php

     
    868868 
    869869                $result = $this->multidimensional( $root, $keys, true ); 
    870870 
    871                 if ( isset( $result ) ) 
    872                         $result['node'][ $result['key'] ] = $value; 
     871                if ( isset( $result ) ) { 
    873872 
     873                        if ( ! empty( $value['multidimensional_delete'] ) ) { 
     874                                unset( $result['node'][ $result['key'] ] ); 
     875                        } else { 
     876                                $result['node'][ $result['key'] ] = $value; 
     877                        } 
     878 
     879                } 
     880 
    874881                return $root; 
    875882        } 
    876883 
  • src/wp-admin/js/customize-widgets.js

     
    150150                        'keyup #widgets-search': 'search', 
    151151                        'focus .widget-tpl' : 'focus', 
    152152                        'click .widget-tpl' : '_submit', 
     153                        'click .add-saved-widget' : '_submit', 
     154                        'click .delete-widget-permanently' : '_delete', 
     155                        'click .widget-tpl .widget-title-action' : '_toggleInactive', 
    153156                        'keypress .widget-tpl' : '_submit', 
    154157                        'keydown' : 'keyboardAccessible' 
    155158                }, 
    156159 
     160                // Inactive widget template to be used in available widgets. 
     161                inactiveWidgetTpl: null, 
     162 
    157163                // Cache current selected widget 
    158164                selected: null, 
    159165 
     
    176182 
    177183                        this.updateList(); 
    178184 
     185                        this.inactiveWidgetTpl = _.template( api.Widgets.data.tpl.inactiveWidget ); 
     186 
     187                        this._createInactiveWidgets(); 
     188 
    179189                        // Set the initial search count to the number of available widgets. 
    180190                        this.searchMatchesCount = this.collection.length; 
    181191 
     
    228238                                } 
    229239                        } 
    230240 
     241                        // Collapse inactive widgets upon searching. 
     242                        this.$el.find( '.saved-widget' ).hide(); 
     243                        this.$el.find( '.widget-tpl.expanded' ).removeClass( 'expanded' ); 
     244 
    231245                        // Toggle the clear search results button. 
    232246                        if ( '' !== event.target.value ) { 
    233247                                this.$clearResults.addClass( 'is-visible' ); 
     
    270284                        } ); 
    271285                }, 
    272286 
     287                /** 
     288                 * Create the list of inactive widgets to be added to the available widgets sidebar onload. 
     289                 * 
     290                 * @since 4.8.0 
     291                 */ 
     292                _createInactiveWidgets : function () { 
     293                        var self = this; 
     294 
     295                        _( api.Widgets.data.availableWidgets ).each( function ( availableWidget )  { 
     296                                _( availableWidget.saved_widgets ).each( function ( savedWidget )  { 
     297                                        self.addInactiveWidget( { 
     298                                                id: savedWidget.id, 
     299                                                type: savedWidget.type, 
     300                                                idBase: availableWidget.id_base, 
     301                                        } ); 
     302                                }); 
     303                        }); 
     304                }, 
     305 
     306                /** 
     307                 * Adds a single inactive widget to the available widgets sidebar. 
     308                 * 
     309                 * @since 4.8.0 
     310                 */ 
     311                addInactiveWidget : function ( templateArgs ) { 
     312                        var $inactiveWidget = $( this.inactiveWidgetTpl( templateArgs ) ); 
     313 
     314                        if ( 0 === this.$el.find( '.saved-widget[data-widget-id="' + templateArgs.id + '"]' ).length ) { 
     315                                this.$el.find( '[data-id-base="' + templateArgs.idBase + '"]' ).first().after( $inactiveWidget ); 
     316                        } 
     317 
     318                        return $inactiveWidget; 
     319                }, 
     320 
     321                /** 
     322                 * Show inactive widgets and hide active widgets. 
     323                 * 
     324                 * @since 4.8.0 
     325                 */ 
     326                updateVisibleSavedWidgets : function () { 
     327                        var self = this; 
     328 
     329                        // Remove state classes. 
     330                        this.$el.find( '.widget-tpl.has-inactive-widgets, .widget-tpl.expanded' ) 
     331                                .removeClass( 'has-inactive-widgets' ) 
     332                                .removeClass( 'expanded' ); 
     333 
     334                        // Hide all inactive widgets 
     335                        this.$el.find( '.saved-widget' ) 
     336                                .hide() 
     337                                .removeClass( 'inactive-widget' ); 
     338 
     339                        _( api( 'sidebars_widgets[wp_inactive_widgets]' )() ).each( function( widgetId ) { 
     340                                var $savedWidget  = self.$el.find( '#saved-widget-' + widgetId ), 
     341                                        parsedWidgetId = parseWidgetId( widgetId ), 
     342                                        widgetControlId = 'widget_' + parsedWidgetId.id_base + '[' + parsedWidgetId.number + ']', 
     343                                        widgetControl =  api.control( widgetControlId ), 
     344                                        widgetSetting =  api( widgetControlId ), 
     345                                        title = ( widgetSetting ) ? widgetSetting().title : null, 
     346                                        $inWidgetTitle, 
     347                                        availableWidget; 
     348 
     349                                if ( ! widgetControl ) { 
     350                                        return; 
     351                                } 
     352 
     353                                // If inactive widget HTML does not exist, create it. 
     354                                if ( 0 === $savedWidget.length ) { 
     355                                        availableWidget = api.Widgets.availableWidgets.findWhere( { id_base: parsedWidgetId.id_base } ); 
     356                                        $savedWidget = self.addInactiveWidget({ 
     357                                                id: widgetId, 
     358                                                type: availableWidget.attributes.name, 
     359                                                idBase: parsedWidgetId.id_base, 
     360                                        }); 
     361                                } 
     362 
     363                                $savedWidget.addClass( 'inactive-widget' ); 
     364 
     365                                self.$el.find( '.widget-tpl[data-id-base="' + parsedWidgetId.id_base + '"]' ) 
     366                                        .not( '.saved-widget' ) 
     367                                        .addClass( 'has-inactive-widgets' ); 
     368 
     369                                $inWidgetTitle = $savedWidget.find( '.in-widget-title' ); 
     370                                if ( title ) { 
     371                                        $inWidgetTitle.text( ': ' + title ); 
     372                                } else { 
     373                                        $inWidgetTitle.text( '' ); 
     374                                } 
     375                        } ); 
     376                }, 
     377 
     378                /** 
     379                 * Show or hide the list of inactive widgets per widget id base. 
     380                 * 
     381                 * @since 4.8.0 
     382                 */ 
     383                toggleInactive : function ( idBase ) { 
     384                        var $widgetTpl = this.$el.find( '.has-inactive-widgets[data-id-base="' + idBase + '"]' ), 
     385                                $inactiveWidgets = this.$el.find( '.saved-widget[data-id-base="' + idBase + '"].inactive-widget' ); 
     386 
     387                        $widgetTpl.toggleClass( 'expanded' ); 
     388                        $inactiveWidgets.stop().slideToggle( 'fast' ); 
     389 
     390                        if ( $widgetTpl.hasClass('expanded') ) { 
     391                                this.select( $inactiveWidgets.first().focus() ); 
     392                        } else { 
     393                                this.select( $widgetTpl.focus() ); 
     394                        } 
     395                }, 
     396 
     397                /** 
     398                 * Slides down a list of active widgets per widget type. 
     399                 * 
     400                 * @since 4.8.0 
     401                 */ 
     402                _toggleInactive : function ( event ) { 
     403                        event.stopPropagation(); 
     404                        event.preventDefault(); 
     405 
     406                        var $widgetTpl = $( event.currentTarget ).closest( '.widget-tpl' ); 
     407                        this.toggleInactive( $widgetTpl.data('id-base') ); 
     408                }, 
     409 
     410                /** 
     411                 * Permanently delete a widget. 
     412                 * 
     413                 * @since 4.8.0 
     414                 */ 
     415                _delete : function ( event ) { 
     416                        var self = this, 
     417                                $currentTarget = $( event.currentTarget ), 
     418                                $savedWidgets = $currentTarget.closest( '.saved-widget' ), 
     419                                widgetId = $savedWidgets.data( 'widget-id' ), 
     420                                idBase = $savedWidgets.data( 'id-base' ), 
     421                                settingId = widgetIdToSettingId( widgetId ), 
     422                                control = api.Widgets.getWidgetFormControlForWidget( widgetId ), 
     423                                deleteCallback, 
     424                                inactiveSidebarWidgets, 
     425                                inactiveWidgets, 
     426                                i; 
     427 
     428                        // Remove from sidebar. 
     429                        inactiveWidgets = api( 'sidebars_widgets[wp_inactive_widgets]' ); 
     430 
     431                        inactiveSidebarWidgets = inactiveWidgets().slice(); 
     432                        i = _.indexOf( inactiveSidebarWidgets, widgetId ); 
     433                        if ( -1 !== i ) { 
     434                                inactiveSidebarWidgets.splice( i, 1 ); 
     435                                inactiveWidgets.set( inactiveSidebarWidgets ); 
     436                        } 
     437 
     438                        // Embed control to allow unembedded controls to trigger update calls. 
     439                        if ( ! control.widgetControlEmdedded ) { 
     440                                control.embedWidgetControl(); 
     441                        } 
     442                        api( settingId ).set( false ); 
     443 
     444                        deleteCallback = function () { 
     445                                var $remainingWidgets; 
     446 
     447                                $( this ).remove(); 
     448                                api.control.remove( control.id ); 
     449                                control.container.remove(); 
     450 
     451                                // Remove drop down if widgets no longer exist 
     452                                $remainingWidgets = self.$el.find( '.saved-widget[data-id-base="' + idBase + '"].inactive-widget' ); 
     453                                if ( ! $remainingWidgets.length ) { 
     454                                        self.$el.find( '.has-inactive-widgets[data-id-base="' + idBase + '"]' ) 
     455                                                .removeClass( 'has-inactive-widgets expanded' ); 
     456                                } 
     457 
     458                                wp.a11y.speak( l10n.itemDeleted ); 
     459                        }; 
     460 
     461                        $currentTarget.closest( '.saved-widget' ).slideUp( 'fast' , deleteCallback ); 
     462                }, 
     463 
    273464                // Highlights a widget 
    274465                select: function( widgetTpl ) { 
    275466                        this.selected = $( widgetTpl ); 
     
    294485 
    295486                // Adds a selected widget to the sidebar 
    296487                submit: function( widgetTpl ) { 
    297                         var widgetId, widget, widgetFormControl; 
     488                        var widgetId, widget, widgetFormControl, isSavedWidget, addWidgetId; 
    298489 
    299490                        if ( ! widgetTpl ) { 
    300491                                widgetTpl = this.selected; 
     
    306497 
    307498                        this.select( widgetTpl ); 
    308499 
    309                         widgetId = $( this.selected ).data( 'widget-id' ); 
     500                        widgetId = widgetTpl.data( 'widget-id' ); 
     501                        isSavedWidget = widgetTpl.data( 'is-saved-widget' ); 
     502 
    310503                        widget = this.collection.findWhere( { id: widgetId } ); 
    311                         if ( ! widget ) { 
     504                        if ( ( ! widget && ! isSavedWidget ) || widgetTpl.hasClass( 'saved-widget' ) ) { 
    312505                                return; 
    313506                        } 
    314507 
    315                         widgetFormControl = this.currentSidebarControl.addWidget( widget.get( 'id_base' ) ); 
     508                        addWidgetId = ( isSavedWidget ) ? widgetId : widget.get( 'id_base' ); 
     509                        widgetFormControl = this.currentSidebarControl.addWidget( addWidgetId ); 
     510 
    316511                        if ( widgetFormControl ) { 
    317512                                widgetFormControl.focus(); 
    318513                        } 
     
    324519                open: function( sidebarControl ) { 
    325520                        this.currentSidebarControl = sidebarControl; 
    326521 
     522                        // Create inactive widget controls that were moved to the inactive sidebar. 
     523                        this.updateVisibleSavedWidgets(); 
     524 
    327525                        // Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens 
    328526                        _( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) { 
    329527                                if ( control.params.is_wide ) { 
     
    359557                        this.$search.val( '' ); 
    360558                }, 
    361559 
    362                 // Add keyboard accessiblity to the panel 
     560                // Add keyboard accessibility to the panel 
    363561                keyboardAccessible: function( event ) { 
    364562                        var isEnter = ( event.which === 13 ), 
    365563                                isEsc = ( event.which === 27 ), 
    366564                                isDown = ( event.which === 40 ), 
    367565                                isUp = ( event.which === 38 ), 
     566                                isLeft = ( event.which === 37 ), 
     567                                isRight = ( event.which === 39 ), 
    368568                                isTab = ( event.which === 9 ), 
    369569                                isShift = ( event.shiftKey ), 
    370570                                selected = null, 
    371                                 firstVisible = this.$el.find( '> .widget-tpl:visible:first' ), 
    372                                 lastVisible = this.$el.find( '> .widget-tpl:visible:last' ), 
     571                                firstVisible = this.$el.find( '.widget-tpl:visible:first' ), 
     572                                lastVisible = this.$el.find( '.widget-tpl:visible:last' ), 
    373573                                isSearchFocused = $( event.target ).is( this.$search ), 
    374574                                isLastWidgetFocused = $( event.target ).is( '.widget-tpl:visible:last' ); 
    375575 
     
    399599                                return; 
    400600                        } 
    401601 
     602                        // Toggle display when the user presses left or right on widget template with inactive widgets. 
     603                        if ( isLeft || isRight ) { 
     604                                if ( this.selected && (this.selected.hasClass( 'has-inactive-widgets' ) || this.selected.hasClass( 'saved-widget' ) ) ) { 
     605                                        var idBase = this.selected.data('id-base'); 
     606                                        this.toggleInactive( idBase ); 
     607                                } 
     608 
     609                                return; 
     610                        } 
     611 
    402612                        // If enter pressed but nothing entered, don't do anything 
    403613                        if ( isEnter && ! this.$search.val() ) { 
    404614                                return; 
     
    521731                        if ( control.widgetControlEmbedded ) { 
    522732                                return; 
    523733                        } 
     734 
    524735                        control.widgetControlEmbedded = true; 
    525736 
    526737                        widgetControl = $( control.params.widget_control ); 
     
    534745                        control._setupReorderUI(); 
    535746                        control._setupHighlightEffects(); 
    536747                        control._setupUpdateUI(); 
     748                        control._setupMoveUI(); 
    537749                        control._setupRemoveUI(); 
    538750                }, 
    539751 
     
    9471159                }, 
    9481160 
    9491161                /** 
    950                  * Set up event handlers for widget removal 
     1162                 * Set up event handlers for widget move. 
     1163                 * 
     1164                 * @since 4.8.0 
    9511165                 */ 
     1166                _setupMoveUI: function() { 
     1167                        var self = this, $moveBtn, $reorderToggle, replaceDeleteWithMove; 
     1168 
     1169                        // Configure move button 
     1170                        $moveBtn = this.container.find( 'a.widget-control-remove' ); 
     1171 
     1172                        $moveBtn.on( 'click', function ( e ) { 
     1173                                e.preventDefault(); 
     1174 
     1175                                $reorderToggle = $( this ).closest( '.ui-sortable' ).find( '.reorder-toggle' ); 
     1176 
     1177                                $reorderToggle.trigger( 'click' ); 
     1178                                self.openWidgetMoveArea(); 
     1179                        } ); 
     1180 
     1181                        // Replace the Delete link with Move. 
     1182                        replaceDeleteWithMove = function () { 
     1183                                $moveBtn.text( l10n.moveBtnLabel ); // wp_widget_control() outputs the link as "Close" 
     1184                                $moveBtn.attr( 'title', l10n.moveBtnTooltip ); 
     1185                        }; 
     1186 
     1187                        replaceDeleteWithMove(); 
     1188                }, 
     1189 
     1190                /** 
     1191                 * Set up event handlers for widget removal. 
     1192                 */ 
    9521193                _setupRemoveUI: function() { 
    953                         var self = this, $removeBtn, replaceDeleteWithRemove; 
     1194                        var self = this, $closeBtn; 
    9541195 
    9551196                        // Configure remove button 
    956                         $removeBtn = this.container.find( 'a.widget-control-remove' ); 
    957                         $removeBtn.on( 'click', function( e ) { 
     1197                        $closeBtn = this.container.find( 'a.widget-control-close' ); 
     1198                        $closeBtn.on( 'click', function( e ) { 
    9581199                                e.preventDefault(); 
    9591200 
    9601201                                // Find an adjacent element to add focus to when this widget goes away 
     
    9691210 
    9701211                                self.container.slideUp( function() { 
    9711212                                        var sidebarsWidgetsControl = api.Widgets.getSidebarWidgetControlContainingWidget( self.params.widget_id ), 
    972                                                 sidebarWidgetIds, i; 
     1213                                                sidebarWidgetIds, i, inactiveWidgets, inactiveWidgetSetting; 
    9731214 
    9741215                                        if ( ! sidebarsWidgetsControl ) { 
    9751216                                                return; 
     
    9841225                                        sidebarWidgetIds.splice( i, 1 ); 
    9851226                                        sidebarsWidgetsControl.setting( sidebarWidgetIds ); 
    9861227 
     1228                                        // Add widget to inactive widgets. 
     1229                                        inactiveWidgetSetting = api( 'sidebars_widgets[wp_inactive_widgets]' ); 
     1230                                        inactiveWidgets = inactiveWidgetSetting(); 
     1231                                        inactiveWidgets.push( self.params.widget_id ); 
     1232                                        inactiveWidgetSetting.set( [] ).set( _( inactiveWidgets ).unique() ); 
     1233 
    9871234                                        $adjacentFocusTarget.focus(); // keyboard accessibility 
     1235 
     1236                                        // Remove display none in case its added again. 
     1237                                        self.container.show(); 
    9881238                                } ); 
    9891239                        } ); 
    9901240 
    991                         replaceDeleteWithRemove = function() { 
    992                                 $removeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Delete" 
    993                                 $removeBtn.attr( 'title', l10n.removeBtnTooltip ); 
    994                         }; 
    995  
    996                         if ( this.params.is_new ) { 
    997                                 api.bind( 'saved', replaceDeleteWithRemove ); 
    998                         } else { 
    999                                 replaceDeleteWithRemove(); 
    1000                         } 
     1241                        $closeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Close" 
     1242                        $closeBtn.attr( 'title', l10n.removeBtnTooltip ); 
    10011243                }, 
    10021244 
    10031245                /** 
     
    11561398                        params.nonce = api.settings.nonce['update-widget']; 
    11571399                        params.customize_theme = api.settings.theme.stylesheet; 
    11581400                        params.customized = wp.customize.previewer.query().customized; 
     1401                        params.delete_widget = ( false === self.setting() ) ? 1 : 0; 
    11591402 
    11601403                        data = $.param( params ); 
    11611404                        $inputs = this._getInputs( $widgetContent ); 
     
    11691412 
    11701413                        if ( instanceOverride ) { 
    11711414                                data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instanceOverride ) } ); 
    1172                         } else { 
     1415                        } else if ( $inputs.length ) { 
    11731416                                data += '&' + $inputs.serialize(); 
    11741417                        } 
     1418 
    11751419                        data += '&' + $widgetContent.find( '~ :input' ).serialize(); 
    11761420 
    11771421                        if ( this._previousUpdateRequest ) { 
     
    12521496                                         * preview finishing loading. 
    12531497                                         */ 
    12541498                                        isChanged = ! isLiveUpdateAborted && ! _( self.setting() ).isEqual( r.data.instance ); 
    1255                                         if ( isChanged ) { 
     1499                                        if ( isChanged || false === self.setting() ) { 
    12561500                                                self.isWidgetUpdating = true; // suppress triggering another updateWidget 
    12571501                                                self.setting( r.data.instance ); 
    12581502                                                self.isWidgetUpdating = false; 
     
    15341778                        } 
    15351779 
    15361780                        $moveWidgetArea.toggleClass( 'active', showOrHide ); 
     1781                        self.toggleSaveForLater(); 
    15371782                }, 
    15381783 
    15391784                /** 
     1785                 * Open widget move area if control is not active already. 
     1786                 * 
     1787                 * @since 4.8.0 
     1788                 */ 
     1789                openWidgetMoveArea: function() { 
     1790                        var self = this, $moveWidgetArea; 
     1791 
     1792                        $moveWidgetArea = this.container.find( '.move-widget-area' ); 
     1793 
     1794                        if( ! $moveWidgetArea.hasClass( 'active' ) ) { 
     1795                                self.toggleWidgetMoveArea(); 
     1796                        } 
     1797                }, 
     1798 
     1799                /** 
     1800                 * Toggle visibility of the Save For Later/Inactive Widget Sidebar section. 
     1801                 * 
     1802                 * @since 4.8.0 
     1803                 */ 
     1804                toggleSaveForLater: function() { 
     1805                        var $moveWidgetArea, $saveForLater; 
     1806 
     1807                        $moveWidgetArea = this.container.find( '.move-widget-area' ); 
     1808                        $saveForLater = this.container.find( 'li[data-id="wp_inactive_widgets"]' ); 
     1809 
     1810                        if( $moveWidgetArea.hasClass( 'active' ) ) { 
     1811                                $saveForLater.show(); 
     1812                        } else { 
     1813                                $saveForLater.hide(); 
     1814                        } 
     1815                }, 
     1816 
     1817                /** 
    15401818                 * Highlight the widget control and section 
    15411819                 */ 
    15421820                highlightSectionAndControl: function() { 
     
    17572035                                                        return; 
    17582036                                                } 
    17592037 
    1760                                                 removedControl = api.Widgets.getWidgetFormControlForWidget( removedWidgetId ); 
    1761  
    1762                                                 // Detect if widget control was dragged to another sidebar 
    1763                                                 wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( self.$sectionContent[0], removedControl.container[0] ); 
    1764  
    1765                                                 // Delete any widget form controls for removed widgets 
    1766                                                 if ( removedControl && ! wasDraggedToAnotherSidebar ) { 
    1767                                                         api.control.remove( removedControl.id ); 
    1768                                                         removedControl.container.remove(); 
    1769                                                 } 
    1770  
    17712038                                                // Move widget to inactive widgets sidebar (move it to trash) if has been previously saved 
    17722039                                                // This prevents the inactive widgets sidebar from overflowing with throwaway widgets 
    1773                                                 if ( api.Widgets.savedWidgetIds[removedWidgetId] ) { 
     2040                                                if ( api.Widgets.savedWidgetIds[removedWidgetId] && 'sidebars_widgets[wp_inactive_widgets]' !== self.id ) { 
    17742041                                                        inactiveWidgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice(); 
    17752042                                                        inactiveWidgets.push( removedWidgetId ); 
    17762043                                                        api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactiveWidgets ).unique() ); 
     
    20392306                                setting.set( {} ); // mark dirty, changing from '' to {} 
    20402307                        } 
    20412308 
    2042                         controlConstructor = api.controlConstructor[controlType]; 
    2043                         widgetFormControl = new controlConstructor( settingId, { 
    2044                                 params: { 
    2045                                         settings: { 
    2046                                                 'default': settingId 
     2309                        widgetFormControl = api.control( settingId ); 
     2310                        if ( ! widgetFormControl ) { 
     2311 
     2312                                controlConstructor = api.controlConstructor[controlType]; 
     2313                                widgetFormControl = new controlConstructor( settingId, { 
     2314                                        params: { 
     2315                                                settings: { 
     2316                                                        'default': settingId 
     2317                                                }, 
     2318                                                content: controlContainer, 
     2319                                                sidebar_id: self.params.sidebar_id, 
     2320                                                widget_id: widgetId, 
     2321                                                widget_id_base: widget.get( 'id_base' ), 
     2322                                                type: controlType, 
     2323                                                is_new: ! isExistingWidget, 
     2324                                                width: widget.get( 'width' ), 
     2325                                                height: widget.get( 'height' ), 
     2326                                                is_wide: widget.get( 'is_wide' ), 
     2327                                                active: true 
    20472328                                        }, 
    2048                                         content: controlContainer, 
    2049                                         sidebar_id: self.params.sidebar_id, 
    2050                                         widget_id: widgetId, 
    2051                                         widget_id_base: widget.get( 'id_base' ), 
    2052                                         type: controlType, 
    2053                                         is_new: ! isExistingWidget, 
    2054                                         width: widget.get( 'width' ), 
    2055                                         height: widget.get( 'height' ), 
    2056                                         is_wide: widget.get( 'is_wide' ), 
    2057                                         active: true 
    2058                                 }, 
    2059                                 previewer: self.setting.previewer 
    2060                         } ); 
    2061                         api.control.add( settingId, widgetFormControl ); 
     2329                                        previewer: self.setting.previewer 
     2330                                } ); 
     2331                                api.control.add( settingId, widgetFormControl ); 
     2332                        } 
    20622333 
    20632334                        // Make sure widget is removed from the other sidebars 
    20642335                        api.each( function( otherSetting ) { 
     
    20742345                                        i = _.indexOf( otherSidebarWidgets, widgetId ); 
    20752346 
    20762347                                if ( -1 !== i ) { 
    2077                                         otherSidebarWidgets.splice( i ); 
     2348                                        otherSidebarWidgets.splice( i, 1 ); 
    20782349                                        otherSetting( otherSidebarWidgets ); 
    20792350                                } 
    20802351                        } ); 
  • src/wp-admin/css/customize-controls.css

     
    19711971        #available-menu-items-search .search-icon { 
    19721972                top: 85px; /* 70 section title height + 13 container padding +1 input margin +1 input border */ 
    19731973        } 
     1974 
     1975        .wp-customizer #available-widgets .saved-widget-controls { 
     1976                visibility: visible; 
     1977        } 
    19741978} 
  • src/wp-admin/css/customize-widgets.css

     
    212212        display: block; 
    213213} 
    214214 
     215#customize-theme-controls .widget-control-remove { 
     216        color: #0073aa; 
     217} 
     218#customize-theme-controls .widget-control-remove:hover { 
     219        color: #0096dd; 
     220} 
     221#customize-theme-controls .widget-control-close { 
     222        color: #a00; 
     223} 
     224#customize-theme-controls .widget-control-close:hover { 
     225        color: #f00; 
     226} 
     227 
    215228/** 
    216229 * Styles for new widget addition panel 
    217230 */ 
     
    267280        opacity: 0.4; 
    268281} 
    269282 
     283#available-widgets .widget-tpl { 
     284        padding-right: 35px; 
     285} 
    270286 
     287#available-widgets .saved-widget { 
     288        margin-left: 48px; 
     289        border-left: 1px #e4e4e4 solid; 
     290        padding: 10px 20px 10px; 
     291        background-color: white; 
     292        display: none; 
     293        cursor: default; 
     294} 
     295 
     296#available-widgets .saved-widget:last-of-type { 
     297        margin-bottom: 40px; 
     298} 
     299 
     300#available-widgets .saved-widget h3 { 
     301        font-size: 1em; 
     302        margin: 0; 
     303} 
     304 
     305#available-widgets .saved-widget a { 
     306        text-decoration: none; 
     307} 
     308 
     309#available-widgets .saved-widget-controls { 
     310        visibility: hidden; 
     311} 
     312 
     313#available-widgets .saved-widget.selected .saved-widget-controls, 
     314#available-widgets .saved-widget:hover .saved-widget-controls { 
     315        visibility: visible; 
     316} 
     317 
     318#available-widgets .delete-widget-permanently { 
     319        color: #a00; 
     320} 
     321 
     322#available-widgets .delete-widget-permanently:hover { 
     323        color: #f00; 
     324} 
     325 
     326#available-widgets .widget-tpl.expanded a.widget-action:after { 
     327        content: "\f142"; 
     328} 
     329 
     330#available-widgets .has-inactive-widgets .widget-action { 
     331        display: block; 
     332        position: relative; 
     333        top: 50%; 
     334        width: 20px; 
     335        margin: 0 auto; 
     336        -webkit-transform: translateY(-50%); 
     337        -ms-transform: translateY(-50%); 
     338        transform: translateY(-50%); 
     339} 
     340 
     341#available-widgets .has-inactive-widgets .widget-title-action { 
     342        display: block; 
     343        position: absolute; 
     344        right: 0; 
     345        top: 0; 
     346        height:100%; 
     347        width: 30px; 
     348        background-color: #eee; 
     349} 
     350 
     351#available-widgets .saved-widget.widget-title:before { 
     352        display: none; 
     353} 
     354 
     355#available-widgets .widget-tpl.expanded .widget-title-action, 
     356#available-widgets .has-inactive-widgets .widget-title-action:hover { 
     357        border-left: 1px #e4e4e4 solid; 
     358        background-color: #FFFFFF; 
     359} 
     360 
     361#available-widgets .has-inactive-widgets .widget-title-action:hover .widget-action { 
     362        color: #555d66; 
     363} 
     364 
     365#available-widgets .has-inactive-widgets .widget-top a.widget-action:after { 
     366        margin: 0 auto; 
     367} 
     368 
    271369/** 
    272370 * Widget Icon styling 
    273371 * No plurals in naming. 
     
    447545        .widget-reorder-nav span:before { 
    448546                line-height: 39px; 
    449547        } 
     548        #available-widgets .widget-top { 
     549                position: static; 
     550        } 
    450551        #customize-theme-controls .widget-area-select li { 
    451552                padding: 9px 15px 11px 42px; 
    452553        }