Ticket #27404: 27404-5-2-16.patch
File 27404-5-2-16.patch, 31.9 KB (added by , 9 years ago) |
---|
-
src/wp-admin/css/customize-controls.css
1665 1665 #available-widgets-list { 1666 1666 top: 140px; 1667 1667 } 1668 1669 .wp-customizer #available-widgets .saved-widget-controls { 1670 visibility: visible; 1671 } 1668 1672 } -
src/wp-admin/css/customize-widgets.css
212 212 display: block; 213 213 } 214 214 215 #customize-theme-controls .widget-control-remove:hover { 216 color: #0096dd; 217 } 218 #customize-theme-controls .widget-control-close { 219 color: #a00; 220 } 221 #customize-theme-controls .widget-control-close:hover { 222 color: #f00; 223 } 224 215 225 /** 216 226 * Styles for new widget addition panel 217 227 */ … … 265 275 opacity: 0.4; 266 276 } 267 277 278 #available-widgets .widget-tpl { 279 padding-right: 35px; 280 } 268 281 282 #available-widgets .saved-widget { 283 margin-left: 48px; 284 border-left: 1px #e4e4e4 solid; 285 padding: 10px 20px 10px; 286 background-color: white; 287 display: none; 288 cursor: default; 289 } 290 291 #available-widgets .saved-widget:last-of-type { 292 margin-bottom: 40px; 293 } 294 295 #available-widgets .saved-widget h3 { 296 font-size: 1em; 297 margin: 0; 298 } 299 300 #available-widgets .saved-widget a { 301 text-decoration: none; 302 } 303 304 #available-widgets .saved-widget-controls { 305 visibility: hidden; 306 } 307 308 #available-widgets .saved-widget.selected .saved-widget-controls, 309 #available-widgets .saved-widget:hover .saved-widget-controls { 310 visibility: visible; 311 } 312 313 #available-widgets .delete-widget-permanently { 314 color: #a00; 315 } 316 317 #available-widgets .delete-widget-permanently:hover { 318 color: #f00; 319 } 320 321 #available-widgets .widget-tpl.expanded a.widget-action:after { 322 content: "\f142"; 323 } 324 325 #available-widgets .has-inactive-widgets .widget-action { 326 display: block; 327 position: relative; 328 top: 50%; 329 width: 20px; 330 margin: 0 auto; 331 -webkit-transform: translateY(-50%); 332 -ms-transform: translateY(-50%); 333 transform: translateY(-50%); 334 } 335 336 #available-widgets .has-inactive-widgets .widget-title-action { 337 display: block; 338 position: absolute; 339 right: 0; 340 top: 0; 341 height:100%; 342 width: 30px; 343 background-color: #eee; 344 } 345 346 #available-widgets .saved-widget.widget-title:before { 347 display: none; 348 } 349 350 #available-widgets .widget-tpl.expanded .widget-title-action, 351 #available-widgets .has-inactive-widgets .widget-title-action:hover { 352 border-left: 1px #e4e4e4 solid; 353 background-color: #FFFFFF; 354 } 355 356 #available-widgets .has-inactive-widgets .widget-title-action:hover .widget-action { 357 color: #555d66; 358 } 359 360 #available-widgets .has-inactive-widgets .widget-top a.widget-action:after { 361 margin: 0 auto; 362 } 363 269 364 /** 270 365 * Widget Icon styling 271 366 * No plurals in naming. … … 445 540 .widget-reorder-nav span:before { 446 541 line-height: 39px; 447 542 } 543 #available-widgets .widget-top { 544 position: static; 545 } 448 546 #customize-theme-controls .widget-area-select li { 449 547 padding: 9px 15px 11px 42px; 450 548 } -
src/wp-admin/js/customize-widgets.js
153 153 'search #widgets-search': 'search', 154 154 'focus .widget-tpl' : 'focus', 155 155 'click .widget-tpl' : '_submit', 156 'click .add-saved-widget' : '_submit', 157 'click .delete-widget-permanently' : '_delete', 158 'click .widget-tpl .widget-title-action' : '_toggleInactive', 156 159 'keypress .widget-tpl' : '_submit', 157 160 'keydown' : 'keyboardAccessible' 158 161 }, 159 162 163 // Inactive widget template to be used in available widgets. 164 inactiveWidgetTpl: null, 165 160 166 // Cache current selected widget 161 167 selected: null, 162 168 … … 175 181 176 182 this.updateList(); 177 183 184 this.inactiveWidgetTpl = _.template( api.Widgets.data.tpl.inactiveWidget ); 185 186 this._createInactiveWidgets(); 187 178 188 // If the available widgets panel is open and the customize controls are 179 189 // interacted with (i.e. available widgets panel is blurred) then close the 180 190 // available widgets panel. Also close on back button click. … … 214 224 this.select( firstVisible ); 215 225 } 216 226 } 227 228 // Collapse inactive widgets upon searching. 229 this.$el.find( '.saved-widget' ).hide(); 230 this.$el.find( '.widget-tpl.expanded' ).removeClass( 'expanded' ); 231 217 232 }, 218 233 219 234 // Changes visibility of available widgets … … 227 242 } ); 228 243 }, 229 244 245 /** 246 * Create the list of inactive widgets to be added to the available widgets sidebar onload. 247 * 248 * @since 4.6.0 249 */ 250 _createInactiveWidgets : function () { 251 var self = this; 252 253 _( api.Widgets.data.availableWidgets ).each( function ( availableWidget ) { 254 _( availableWidget.saved_widgets ).each( function ( savedWidget ) { 255 self.addInactiveWidget( { 256 id: savedWidget.id, 257 type: savedWidget.type, 258 idBase: availableWidget.id_base, 259 } ); 260 }); 261 }); 262 }, 263 264 /** 265 * Adds a single inactive widget to the available widgets sidebar. 266 * 267 * @since 4.6.0 268 */ 269 addInactiveWidget : function ( templateArgs ) { 270 var $inactiveWidget = $( this.inactiveWidgetTpl( templateArgs ) ); 271 272 if ( 0 === this.$el.find( '.saved-widget[data-widget-id="' + templateArgs.id + '"]' ).length ) { 273 this.$el.find( '[data-id-base="' + templateArgs.idBase + '"]' ).first().after( $inactiveWidget ); 274 } 275 276 return $inactiveWidget; 277 }, 278 279 /** 280 * Show inactive widgets and hide active widgets. 281 * 282 * @since 4.6.0 283 */ 284 updateVisibleSavedWidgets : function () { 285 var self = this; 286 287 // Remove state classes. 288 this.$el.find( '.widget-tpl.has-inactive-widgets, .widget-tpl.expanded' ) 289 .removeClass( 'has-inactive-widgets' ) 290 .removeClass( 'expanded' ); 291 292 // Hide all inactive widgets 293 this.$el.find( '.saved-widget' ) 294 .hide() 295 .removeClass( 'inactive-widget' ); 296 297 _( api( 'sidebars_widgets[wp_inactive_widgets]' )() ).each( function( widgetId ) { 298 var $savedWidget = self.$el.find( '#saved-widget-' + widgetId ), 299 parsedWidgetId = parseWidgetId( widgetId ), 300 widgetControlId = 'widget_' + parsedWidgetId.id_base + '[' + parsedWidgetId.number + ']', 301 widgetControl = api.control( widgetControlId ), 302 widgetSetting = api( widgetControlId ), 303 title = ( widgetSetting ) ? widgetSetting().title : null, 304 $inWidgetTitle, 305 availableWidget; 306 307 if ( ! widgetControl ) { 308 return; 309 } 310 311 // If inactive widget HTML does not exist, create it. 312 if ( 0 === $savedWidget.length ) { 313 availableWidget = api.Widgets.availableWidgets.findWhere( { id_base: parsedWidgetId.id_base } ); 314 $savedWidget = self.addInactiveWidget({ 315 id: widgetId, 316 type: availableWidget.attributes.name, 317 idBase: parsedWidgetId.id_base, 318 }); 319 } 320 321 $savedWidget.addClass( 'inactive-widget' ); 322 323 self.$el.find( '.widget-tpl[data-id-base="' + parsedWidgetId.id_base + '"]' ) 324 .not( '.saved-widget' ) 325 .addClass( 'has-inactive-widgets' ); 326 327 $inWidgetTitle = $savedWidget.find( '.in-widget-title' ); 328 if ( title ) { 329 $inWidgetTitle.text( ': ' + title ); 330 } else { 331 $inWidgetTitle.text( '' ); 332 } 333 } ); 334 }, 335 336 /** 337 * Show or hide the list of inactive widgets per widget id base. 338 * 339 * @since 4.6.0 340 */ 341 toggleInactive : function ( idBase ) { 342 var $widgetTpl = this.$el.find( '.has-inactive-widgets[data-id-base="' + idBase + '"]' ), 343 $inactiveWidgets = this.$el.find( '.saved-widget[data-id-base="' + idBase + '"].inactive-widget' ); 344 345 $widgetTpl.toggleClass( 'expanded' ); 346 $inactiveWidgets.stop().slideToggle( 'fast' ); 347 348 if ( $widgetTpl.hasClass('expanded') ) { 349 this.select( $inactiveWidgets.first().focus() ); 350 } else { 351 this.select( $widgetTpl.focus() ); 352 } 353 }, 354 355 /** 356 * Slides down a list of active widgets per widget type. 357 * 358 * @since 4.6.0 359 */ 360 _toggleInactive : function ( event ) { 361 event.stopPropagation(); 362 event.preventDefault(); 363 364 var $widgetTpl = $( event.currentTarget ).closest( '.widget-tpl' ); 365 this.toggleInactive( $widgetTpl.data('id-base') ); 366 }, 367 368 /** 369 * Permanently delete a widget. 370 * 371 * @since 4.6.0 372 */ 373 _delete : function ( event ) { 374 var self = this, 375 $currentTarget = $( event.currentTarget ), 376 $savedWidgets = $currentTarget.closest( '.saved-widget' ), 377 widgetId = $savedWidgets.data( 'widget-id' ), 378 idBase = $savedWidgets.data( 'id-base' ), 379 settingId = widgetIdToSettingId( widgetId ), 380 control = api.Widgets.getWidgetFormControlForWidget( widgetId ), 381 deleteCallback, 382 inactiveSidebarWidgets, 383 inactiveWidgets, 384 i; 385 386 // Remove from sidebar. 387 inactiveWidgets = api( 'sidebars_widgets[wp_inactive_widgets]' ); 388 389 inactiveSidebarWidgets = inactiveWidgets().slice(); 390 i = _.indexOf( inactiveSidebarWidgets, widgetId ); 391 if ( -1 !== i ) { 392 inactiveSidebarWidgets.splice( i, 1 ); 393 inactiveWidgets.set( inactiveSidebarWidgets ); 394 } 395 396 // Embed control to allow unembedded controls to trigger update calls. 397 if ( ! control.widgetControlEmdedded ) { 398 control.embedWidgetControl(); 399 } 400 api( settingId ).set( false ); 401 402 deleteCallback = function () { 403 var $remainingWidgets; 404 405 $( this ).remove(); 406 api.control.remove( control.id ); 407 control.container.remove(); 408 409 // Remove drop down if widgets no longer exist 410 $remainingWidgets = self.$el.find( '.saved-widget[data-id-base="' + idBase + '"].inactive-widget' ); 411 if ( ! $remainingWidgets.length ) { 412 self.$el.find( '.has-inactive-widgets[data-id-base="' + idBase + '"]' ) 413 .removeClass( 'has-inactive-widgets expanded' ); 414 } 415 416 wp.a11y.speak( l10n.itemDeleted ); 417 }; 418 419 $currentTarget.closest( '.saved-widget' ).slideUp( 'fast' , deleteCallback ); 420 }, 421 230 422 // Highlights a widget 231 423 select: function( widgetTpl ) { 232 424 this.selected = $( widgetTpl ); … … 251 443 252 444 // Adds a selected widget to the sidebar 253 445 submit: function( widgetTpl ) { 254 var widgetId, widget, widgetFormControl ;446 var widgetId, widget, widgetFormControl, isSavedWidget, addWidgetId; 255 447 256 448 if ( ! widgetTpl ) { 257 449 widgetTpl = this.selected; … … 263 455 264 456 this.select( widgetTpl ); 265 457 266 widgetId = $( this.selected ).data( 'widget-id' ); 458 widgetId = widgetTpl.data( 'widget-id' ); 459 isSavedWidget = widgetTpl.data( 'is-saved-widget' ); 460 267 461 widget = this.collection.findWhere( { id: widgetId } ); 268 if ( ! widget) {462 if ( ( ! widget && ! isSavedWidget ) || widgetTpl.hasClass( 'saved-widget' ) ) { 269 463 return; 270 464 } 271 465 272 widgetFormControl = this.currentSidebarControl.addWidget( widget.get( 'id_base' ) ); 466 addWidgetId = ( isSavedWidget ) ? widgetId : widget.get( 'id_base' ); 467 widgetFormControl = this.currentSidebarControl.addWidget( addWidgetId ); 468 273 469 if ( widgetFormControl ) { 274 470 widgetFormControl.focus(); 275 471 } … … 281 477 open: function( sidebarControl ) { 282 478 this.currentSidebarControl = sidebarControl; 283 479 480 // Create inactive widget controls that were moved to the inactive sidebar. 481 this.updateVisibleSavedWidgets(); 482 284 483 // Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens 285 484 _( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) { 286 485 if ( control.params.is_wide ) { … … 316 515 this.$search.val( '' ); 317 516 }, 318 517 319 // Add keyboard accessib lity to the panel518 // Add keyboard accessibility to the panel 320 519 keyboardAccessible: function( event ) { 321 520 var isEnter = ( event.which === 13 ), 322 521 isEsc = ( event.which === 27 ), 323 522 isDown = ( event.which === 40 ), 324 523 isUp = ( event.which === 38 ), 524 isLeft = ( event.which === 37 ), 525 isRight = ( event.which === 39 ), 325 526 isTab = ( event.which === 9 ), 326 527 isShift = ( event.shiftKey ), 327 528 selected = null, 328 firstVisible = this.$el.find( ' >.widget-tpl:visible:first' ),329 lastVisible = this.$el.find( ' >.widget-tpl:visible:last' ),529 firstVisible = this.$el.find( '.widget-tpl:visible:first' ), 530 lastVisible = this.$el.find( '.widget-tpl:visible:last' ), 330 531 isSearchFocused = $( event.target ).is( this.$search ), 331 532 isLastWidgetFocused = $( event.target ).is( '.widget-tpl:visible:last' ); 332 533 … … 356 557 return; 357 558 } 358 559 560 // Toggle display when the user presses left or right on widget template with inactive widgets. 561 if ( isLeft || isRight ) { 562 if ( this.selected && (this.selected.hasClass( 'has-inactive-widgets' ) || this.selected.hasClass( 'saved-widget' ) ) ) { 563 var idBase = this.selected.data('id-base'); 564 this.toggleInactive( idBase ); 565 } 566 567 return; 568 } 569 359 570 // If enter pressed but nothing entered, don't do anything 360 571 if ( isEnter && ! this.$search.val() ) { 361 572 return; … … 490 701 control._setupReorderUI(); 491 702 control._setupHighlightEffects(); 492 703 control._setupUpdateUI(); 704 control._setupMoveUI(); 493 705 control._setupRemoveUI(); 494 706 }, 495 707 … … 903 1115 }, 904 1116 905 1117 /** 906 * Set up event handlers for widget removal 1118 * Set up event handlers for widget move. 1119 * 1120 * @since 4.6.0 907 1121 */ 1122 _setupMoveUI: function() { 1123 var self = this, $moveBtn, $reorderToggle, replaceDeleteWithMove; 1124 1125 // Configure move button 1126 $moveBtn = this.container.find( 'a.widget-control-remove' ); 1127 1128 $moveBtn.on( 'click', function ( e ) { 1129 e.preventDefault(); 1130 1131 $reorderToggle = $( this ).closest( '.ui-sortable' ).find( '.reorder-toggle' ); 1132 1133 $reorderToggle.trigger( 'click' ); 1134 self.openWidgetMoveArea(); 1135 } ); 1136 1137 // Replace the Delete link with Move. 1138 replaceDeleteWithMove = function () { 1139 $moveBtn.text( l10n.moveBtnLabel ); // wp_widget_control() outputs the link as "Close" 1140 $moveBtn.attr( 'title', l10n.moveBtnTooltip ); 1141 }; 1142 1143 replaceDeleteWithMove(); 1144 }, 1145 1146 /** 1147 * Set up event handlers for widget removal. 1148 */ 908 1149 _setupRemoveUI: function() { 909 var self = this, $ removeBtn, replaceDeleteWithRemove;1150 var self = this, $closeBtn; 910 1151 911 1152 // Configure remove button 912 $ removeBtn = this.container.find( 'a.widget-control-remove' );913 $ removeBtn.on( 'click', function( e ) {1153 $closeBtn = this.container.find( 'a.widget-control-close' ); 1154 $closeBtn.on( 'click', function( e ) { 914 1155 e.preventDefault(); 915 1156 916 1157 // Find an adjacent element to add focus to when this widget goes away … … 925 1166 926 1167 self.container.slideUp( function() { 927 1168 var sidebarsWidgetsControl = api.Widgets.getSidebarWidgetControlContainingWidget( self.params.widget_id ), 928 sidebarWidgetIds, i ;1169 sidebarWidgetIds, i, inactiveWidgets, inactiveWidgetSetting; 929 1170 930 1171 if ( ! sidebarsWidgetsControl ) { 931 1172 return; … … 940 1181 sidebarWidgetIds.splice( i, 1 ); 941 1182 sidebarsWidgetsControl.setting( sidebarWidgetIds ); 942 1183 1184 // Add widget to inactive widgets. 1185 inactiveWidgetSetting = api( 'sidebars_widgets[wp_inactive_widgets]' ); 1186 inactiveWidgets = inactiveWidgetSetting(); 1187 inactiveWidgets.push( self.params.widget_id ); 1188 inactiveWidgetSetting.set( [] ).set( _( inactiveWidgets ).unique() ); 1189 943 1190 $adjacentFocusTarget.focus(); // keyboard accessibility 1191 1192 // Remove display none in case its added again. 1193 self.container.show(); 944 1194 } ); 945 1195 } ); 946 1196 947 replaceDeleteWithRemove = function() { 948 $removeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Delete" 949 $removeBtn.attr( 'title', l10n.removeBtnTooltip ); 950 }; 951 952 if ( this.params.is_new ) { 953 api.bind( 'saved', replaceDeleteWithRemove ); 954 } else { 955 replaceDeleteWithRemove(); 956 } 1197 $closeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the link as "Close" 1198 $closeBtn.attr( 'title', l10n.removeBtnTooltip ); 957 1199 }, 958 1200 959 1201 /** … … 1112 1354 params.nonce = api.settings.nonce['update-widget']; 1113 1355 params.theme = api.settings.theme.stylesheet; 1114 1356 params.customized = wp.customize.previewer.query().customized; 1357 params.delete_widget = ( false === self.setting() ) ? 1 : 0; 1115 1358 1116 1359 data = $.param( params ); 1117 1360 $inputs = this._getInputs( $widgetContent ); … … 1125 1368 1126 1369 if ( instanceOverride ) { 1127 1370 data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instanceOverride ) } ); 1128 } else {1371 } else if ( $inputs.length ) { 1129 1372 data += '&' + $inputs.serialize(); 1130 1373 } 1131 1374 data += '&' + $widgetContent.find( '~ :input' ).serialize(); … … 1208 1451 * preview finishing loading. 1209 1452 */ 1210 1453 isChanged = ! isLiveUpdateAborted && ! _( self.setting() ).isEqual( r.data.instance ); 1211 if ( isChanged ) {1454 if ( isChanged || false === self.setting() ) { 1212 1455 self.isWidgetUpdating = true; // suppress triggering another updateWidget 1213 1456 self.setting( r.data.instance ); 1214 1457 self.isWidgetUpdating = false; … … 1484 1727 } 1485 1728 1486 1729 $moveWidgetArea.toggleClass( 'active', showOrHide ); 1730 self.toggleSaveForLater(); 1487 1731 }, 1488 1732 1489 1733 /** 1734 * Open widget move area if control is not active already. 1735 * 1736 * @since 4.6.0 1737 */ 1738 openWidgetMoveArea: function() { 1739 var self = this, $moveWidgetArea; 1740 1741 $moveWidgetArea = this.container.find( '.move-widget-area' ); 1742 1743 if( ! $moveWidgetArea.hasClass( 'active' ) ) { 1744 self.toggleWidgetMoveArea(); 1745 } 1746 }, 1747 1748 /** 1749 * Toggle visibility of the Save For Later/Inactive Widget Sidebar section. 1750 * 1751 * @since 4.6.0 1752 */ 1753 toggleSaveForLater: function() { 1754 var $moveWidgetArea, $saveForLater; 1755 1756 $moveWidgetArea = this.container.find( '.move-widget-area' ); 1757 $saveForLater = this.container.find( 'li[data-id="wp_inactive_widgets"]' ); 1758 1759 if( $moveWidgetArea.hasClass( 'active' ) ) { 1760 $saveForLater.show(); 1761 } else { 1762 $saveForLater.hide(); 1763 } 1764 }, 1765 1766 /** 1490 1767 * Highlight the widget control and section 1491 1768 */ 1492 1769 highlightSectionAndControl: function() { … … 1707 1984 return; 1708 1985 } 1709 1986 1710 removedControl = api.Widgets.getWidgetFormControlForWidget( removedWidgetId );1711 1712 // Detect if widget control was dragged to another sidebar1713 wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( self.$sectionContent[0], removedControl.container[0] );1714 1715 // Delete any widget form controls for removed widgets1716 if ( removedControl && ! wasDraggedToAnotherSidebar ) {1717 api.control.remove( removedControl.id );1718 removedControl.container.remove();1719 }1720 1721 1987 // Move widget to inactive widgets sidebar (move it to trash) if has been previously saved 1722 1988 // This prevents the inactive widgets sidebar from overflowing with throwaway widgets 1723 if ( api.Widgets.savedWidgetIds[removedWidgetId] ) {1989 if ( api.Widgets.savedWidgetIds[removedWidgetId] && 'sidebars_widgets[wp_inactive_widgets]' !== self.id ) { 1724 1990 inactiveWidgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice(); 1725 1991 inactiveWidgets.push( removedWidgetId ); 1726 1992 api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactiveWidgets ).unique() ); … … 1989 2255 setting.set( {} ); // mark dirty, changing from '' to {} 1990 2256 } 1991 2257 1992 controlConstructor = api.controlConstructor[controlType]; 1993 widgetFormControl = new controlConstructor( settingId, { 1994 params: { 1995 settings: { 1996 'default': settingId 2258 widgetFormControl = api.control( settingId ); 2259 if ( ! widgetFormControl ) { 2260 2261 controlConstructor = api.controlConstructor[controlType]; 2262 widgetFormControl = new controlConstructor( settingId, { 2263 params: { 2264 settings: { 2265 'default': settingId 2266 }, 2267 content: controlContainer, 2268 sidebar_id: self.params.sidebar_id, 2269 widget_id: widgetId, 2270 widget_id_base: widget.get( 'id_base' ), 2271 type: controlType, 2272 is_new: ! isExistingWidget, 2273 width: widget.get( 'width' ), 2274 height: widget.get( 'height' ), 2275 is_wide: widget.get( 'is_wide' ), 2276 active: true 1997 2277 }, 1998 content: controlContainer, 1999 sidebar_id: self.params.sidebar_id, 2000 widget_id: widgetId, 2001 widget_id_base: widget.get( 'id_base' ), 2002 type: controlType, 2003 is_new: ! isExistingWidget, 2004 width: widget.get( 'width' ), 2005 height: widget.get( 'height' ), 2006 is_wide: widget.get( 'is_wide' ), 2007 active: true 2008 }, 2009 previewer: self.setting.previewer 2010 } ); 2011 api.control.add( settingId, widgetFormControl ); 2278 previewer: self.setting.previewer 2279 } ); 2280 api.control.add( settingId, widgetFormControl ); 2281 } 2012 2282 2013 2283 // Make sure widget is removed from the other sidebars 2014 2284 api.each( function( otherSetting ) { … … 2024 2294 i = _.indexOf( otherSidebarWidgets, widgetId ); 2025 2295 2026 2296 if ( -1 !== i ) { 2027 otherSidebarWidgets.splice( i );2297 otherSidebarWidgets.splice( i, 1 ); 2028 2298 otherSetting( otherSidebarWidgets ); 2029 2299 } 2030 2300 } ); -
src/wp-includes/class-wp-customize-setting.php
782 782 783 783 $result = $this->multidimensional( $root, $keys, true ); 784 784 785 if ( isset( $result ) ) 786 $result['node'][ $result['key'] ] = $value; 785 if ( isset( $result ) ) { 787 786 787 if ( ! empty( $value['multidimensional_delete'] ) ) { 788 unset( $result['node'][ $result['key'] ] ); 789 } else { 790 $result['node'][ $result['key'] ] = $value; 791 } 792 793 } 794 788 795 return $root; 789 796 } 790 797 -
src/wp-includes/class-wp-customize-widgets.php
444 444 445 445 // Add section to contain controls. 446 446 $section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id ); 447 if ( $is_active_sidebar ) {448 447 449 $section_args = array(450 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'],451 'description' => $wp_registered_sidebars[ $sidebar_id ]['description'],452 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),453 'panel' => 'widgets',454 'sidebar_id' => $sidebar_id,455 );448 if ( $is_inactive_widgets ) { 449 $title = 'Inactive Widgets'; 450 $description = $title; 451 } else { 452 $title = $wp_registered_sidebars[ $sidebar_id ]['name']; 453 $description = $wp_registered_sidebars[ $sidebar_id ]['description']; 454 } 456 455 457 /** 458 * Filter Customizer widget section arguments for a given sidebar. 459 * 460 * @since 3.9.0 461 * 462 * @param array $section_args Array of Customizer widget section arguments. 463 * @param string $section_id Customizer section ID. 464 * @param int|string $sidebar_id Sidebar ID. 465 */ 466 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 456 $section_args = array( 457 'title' => $title, 458 'description' => $description, 459 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ), 460 'panel' => 'widgets', 461 'sidebar_id' => $sidebar_id, 462 ); 467 463 468 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 469 $this->manager->add_section( $section ); 464 /** 465 * Filter Customizer widget section arguments for a given sidebar. 466 * 467 * @since 3.9.0 468 * 469 * @param array $section_args Array of Customizer widget section arguments. 470 * @param string $section_id Customizer section ID. 471 * @param int|string $sidebar_id Sidebar ID. 472 */ 473 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id ); 470 474 471 $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 472 'section' => $section_id, 473 'sidebar_id' => $sidebar_id, 474 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 475 ) ); 476 $new_setting_ids[] = $setting_id; 475 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); 476 $this->manager->add_section( $section ); 477 477 478 $this->manager->add_control( $control ); 479 } 478 $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array( 479 'section' => $section_id, 480 'sidebar_id' => $sidebar_id, 481 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. 482 ) ); 483 $new_setting_ids[] = $setting_id; 484 485 $this->manager->add_control( $control ); 480 486 } 481 487 482 488 // Add a control for each active widget (located in a sidebar). … … 483 489 foreach ( $sidebar_widget_ids as $i => $widget_id ) { 484 490 485 491 // Skip widgets that may have gone away due to a plugin being deactivated. 486 if ( ! $is_active_sidebar || !isset( $wp_registered_widgets[$widget_id] ) ) {492 if ( ! isset( $wp_registered_widgets[$widget_id] ) ) { 487 493 continue; 488 494 } 489 495 … … 717 723 </div>' 718 724 ); 719 725 726 $inactive_widget_tpl = str_replace( 727 array( '{delete}', '{add}' ), 728 array( 729 __( 'Delete' ), 730 __( 'Add' ) 731 ), 732 '<div id="saved-widget-<%- id %>" data-id-base="<%- idBase %>" data-widget-id="<%- id %>" class="widget-title saved-widget widget-tpl" tabindex="0"> 733 <h3><%- type %><span class="in-widget-title"></span></h3> 734 <div class="saved-widget-controls"> 735 <a href="#" class="add-saved-widget" data-is-saved-widget="1" data-widget-id="<%- id %>" 736 >{add}</a> | <a href="#" class="delete-widget-permanently">{delete}</a> 737 </div> 738 </div>' 739 ); 740 741 $inactive_sidebar = array( 742 'inactive-sidebar' => array( 743 'name' => __( 'Save For Later' ), 744 'id' => 'wp_inactive_widgets', 745 'description' => __( 'Save widgets to the Inactive Sidebar to use later.' ), 746 'class' => 'inactive-sidebar', 747 'before_widget' => '<aside id="%1$s" class="widget %2$s">', 748 'after_widget' => '</aside>', 749 'before_title' => '<h1 class="widget-title">', 750 'after_title' => '</h1>', 751 ), 752 ); 753 754 $all_sidebars = array_values( array_merge( $wp_registered_sidebars, $inactive_sidebar ) ); 755 720 756 $settings = array( 721 'registeredSidebars' => array_values( $wp_registered_sidebars ),757 'registeredSidebars' => $all_sidebars, 722 758 'registeredWidgets' => $wp_registered_widgets, 723 759 'availableWidgets' => $available_widgets, // @todo Merge this with registered_widgets 724 760 'l10n' => array( … … 726 762 'saveBtnTooltip' => __( 'Save and preview changes before publishing them.' ), 727 763 'removeBtnLabel' => __( 'Remove' ), 728 764 'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ), 765 'moveBtnLabel' => __( 'Move' ), 766 'moveBtnTooltip' => __( 'Reorder the widgets in this sidebar.' ), 729 767 'error' => __( 'An error has occurred. Please reload the page and try again.' ), 730 768 'widgetMovedUp' => __( 'Widget moved up' ), 731 769 'widgetMovedDown' => __( 'Widget moved down' ), … … 734 772 'reorderModeOff' => __( 'Reorder mode closed' ), 735 773 'reorderLabelOn' => esc_attr__( 'Reorder widgets' ), 736 774 'reorderLabelOff' => esc_attr__( 'Close reorder mode' ), 775 'itemDeleted' => __( 'Widget deleted' ), 737 776 ), 738 777 'tpl' => array( 739 778 'widgetReorderNav' => $widget_reorder_nav_tpl, 740 779 'moveWidgetArea' => $move_widget_area_tpl, 780 'inactiveWidget' => $inactive_widget_tpl, 741 781 ), 742 782 'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(), 743 783 ); … … 781 821 </div> 782 822 <div id="available-widgets-list"> 783 823 <?php foreach ( $this->get_available_widgets() as $available_widget ): ?> 784 <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">824 <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"> 785 825 <?php echo $available_widget['control_tpl']; ?> 786 826 </div> 787 827 <?php endforeach; ?> … … 872 912 } 873 913 874 914 /** 915 * Create an list of saved widgets that correspond to a widget type. 916 * 917 * @since 4.6.0 918 * @access protected 919 * 920 * @global array $wp_registered_widget_controls 921 * 922 * @return array List of saved widget by type. 923 */ 924 protected function get_sorted_saved_widgets() { 925 global $wp_registered_widget_controls; 926 927 $sidebars_widgets = wp_get_sidebars_widgets(); 928 929 $sorted_saved_widgets = array(); 930 foreach ( $sidebars_widgets as $sidebars_widget ) { 931 foreach( $sidebars_widget as $widget ) { 932 933 if ( isset ( $wp_registered_widget_controls[ $widget ]['id_base'] ) ) { 934 $current_base_id = $wp_registered_widget_controls[ $widget ]['id_base']; 935 $sorted_saved_widgets[ $current_base_id ][] = array( 936 'id' => $widget, 937 'type' => $wp_registered_widget_controls[ $widget ]['name'], 938 ); 939 } 940 941 } 942 } 943 944 return $sorted_saved_widgets; 945 } 946 947 /** 875 948 * Builds up an index of all available widgets for use in Backbone models. 876 949 * 877 950 * @since 3.9.0 … … 894 967 global $wp_registered_widgets, $wp_registered_widget_controls; 895 968 require_once ABSPATH . '/wp-admin/includes/widgets.php'; // for next_widget_id_number() 896 969 970 $sorted_saved_widgets = $this->get_sorted_saved_widgets(); 897 971 $sort = $wp_registered_widgets; 898 972 usort( $sort, array( $this, '_sort_name_callback' ) ); 899 973 $done = array(); … … 950 1024 'width' => $wp_registered_widget_controls[$widget['id']]['width'], 951 1025 'height' => $wp_registered_widget_controls[$widget['id']]['height'], 952 1026 'is_wide' => $this->is_wide_widget( $widget['id'] ), 1027 'saved_widgets' => isset( $sorted_saved_widgets[ $id_base ] ) ? $sorted_saved_widgets[ $id_base ] : array(), 953 1028 ) ); 954 1029 955 1030 $available_widgets[] = $available_widget; … … 1257 1332 * @return array|void Sanitized widget instance. 1258 1333 */ 1259 1334 public function sanitize_widget_instance( $value ) { 1260 if ( $value === array() ) {1335 if ( $value === array() || false === $value ) { 1261 1336 return $value; 1262 1337 } 1263 1338 … … 1396 1471 } 1397 1472 } 1398 1473 1474 // If deleting widget. 1475 $is_widget_delete = ! empty( $this->get_post_value( 'delete_widget' ) ); 1476 1477 if ( $is_widget_delete ) { 1478 // Set post values needed in widget update_callback. 1479 $_POST[ 'sidebar' ] = 'wp_inactive_widgets'; 1480 $_POST[ 'widget-' . $parsed_id['id_base'] ] = array(); 1481 $_POST[ 'the-widget-id' ] = $widget_id; 1482 $_POST[ 'delete_widget' ] = '1'; 1483 1484 $post_values_added = array( 'sidebar', 'widget-' . $parsed_id[ 'id_base' ], 'the-widget-id', 'delete_widget' ); 1485 $added_input_vars = array_merge( $added_input_vars, $post_values_added ); 1486 1487 /** This action is documented in wp-admin/widgets.php */ 1488 do_action( 'delete_widget', $widget_id, $_POST[ 'sidebar' ], $parsed_id['id_base'] ); 1489 } 1490 1399 1491 // Invoke the widget update callback. 1400 1492 foreach ( (array) $wp_registered_widget_updates as $name => $control ) { 1401 1493 if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) { … … 1428 1520 1429 1521 // Obtain the widget instance. 1430 1522 $option = $this->get_captured_option( $option_name ); 1431 if ( null !== $parsed_id['number'] ) { 1523 if ( $is_widget_delete ) { 1524 $instance = array( 'multidimensional_delete' => true ); 1525 } else if ( null !== $parsed_id['number'] ) { 1432 1526 $instance = $option[ $parsed_id['number'] ]; 1433 1527 } else { 1434 1528 $instance = $option; … … 1445 1539 // Obtain the widget control with the updated instance in place. 1446 1540 ob_start(); 1447 1541 $form = $wp_registered_widget_controls[ $widget_id ]; 1448 if ( $form ) {1542 if ( $form && false === $is_widget_delete ) { 1449 1543 call_user_func_array( $form['callback'], $form['params'] ); 1450 1544 } 1451 1545 $form = ob_get_clean();