Changeset 41597
- Timestamp:
- 09/26/2017 07:37:02 AM (7 years ago)
- Location:
- trunk/src
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/js/customize-controls.js
r41586 r41597 1 /* global _wpCustomizeHeader, _wpCustomizeBackground, _wpMediaViewsL10n, MediaElementPlayer, console */1 /* global _wpCustomizeHeader, _wpCustomizeBackground, _wpMediaViewsL10n, MediaElementPlayer, console, confirm */ 2 2 (function( exports, $ ){ 3 3 var Container, focus, normalizedTransitionendEventName, api = wp.customize; … … 356 356 * @access public 357 357 * 358 * @param {object} [changes] Mapping of setting IDs to setting params each normally including a value property, or mapping to null. 359 * If not provided, then the changes will still be obtained from unsaved dirty settings. 358 * @param {object} [changes] - Mapping of setting IDs to setting params each normally including a value property, or mapping to null. 359 * If not provided, then the changes will still be obtained from unsaved dirty settings. 360 * @param {object} [args] - Additional options for the save request. 361 * @param {boolean} [args.autosave=false] - Whether changes will be stored in autosave revision if the changeset has been promoted from an auto-draft. 362 * @param {string} [args.title] - Title to update in the changeset. Optional. 363 * @param {string} [args.date] - Date to update in the changeset. Optional. 360 364 * @returns {jQuery.Promise} Promise resolving with the response data. 361 365 */ 362 api.requestChangesetUpdate = function requestChangesetUpdate( changes ) {363 var deferred, request, submittedChanges = {}, data ;366 api.requestChangesetUpdate = function requestChangesetUpdate( changes, args ) { 367 var deferred, request, submittedChanges = {}, data, submittedArgs; 364 368 deferred = new $.Deferred(); 369 370 submittedArgs = _.extend( { 371 title: null, 372 date: null, 373 autosave: false 374 }, args ); 365 375 366 376 if ( changes ) { … … 380 390 381 391 // Short-circuit when there are no pending changes. 382 if ( _.isEmpty( submittedChanges ) ) {392 if ( _.isEmpty( submittedChanges ) && null === submittedArgs.title && null === submittedArgs.date ) { 383 393 deferred.resolve( {} ); 384 394 return deferred.promise(); 395 } 396 397 // Allow plugins to attach additional params to the settings. 398 api.trigger( 'changeset-save', submittedChanges, submittedArgs ); 399 400 // A status would cause a revision to be made, and for this wp.customize.previewer.save() should be used. Status is also disallowed for revisions regardless. 401 if ( submittedArgs.status ) { 402 return deferred.reject( { code: 'illegal_status_in_changeset_update' } ).promise(); 403 } 404 405 // Dates not beung allowed for revisions are is a technical limitation of post revisions. 406 if ( submittedArgs.date && submittedArgs.autosave ) { 407 return deferred.reject( { code: 'illegal_autosave_with_date_gmt' } ).promise(); 385 408 } 386 409 … … 390 413 api.state( 'processing' ).set( api.state( 'processing' ).get() - 1 ); 391 414 } ); 392 393 // Allow plugins to attach additional params to the settings.394 api.trigger( 'changeset-save', submittedChanges );395 415 396 416 // Ensure that if any plugins add data to save requests by extending query() that they get included here. … … 402 422 customize_changeset_data: JSON.stringify( submittedChanges ) 403 423 } ); 424 if ( null !== submittedArgs.title ) { 425 data.customize_changeset_title = submittedArgs.title; 426 } 427 if ( null !== submittedArgs.date ) { 428 data.customize_changeset_date = submittedArgs.date; 429 } 430 if ( false !== submittedArgs.autosave ) { 431 data.customize_changeset_autosave = 'true'; 432 } 404 433 405 434 request = wp.ajax.post( 'customize_save', data ); … … 1706 1735 api.state( 'processing' ).unbind( onceProcessingComplete ); 1707 1736 1708 request = api.requestChangesetUpdate( );1737 request = api.requestChangesetUpdate( {}, { autosave: true } ); 1709 1738 request.done( function() { 1710 1739 $( window ).off( 'beforeunload.customize-confirm' ); 1740 1741 // Include autosaved param to load autosave revision without prompting user to restore it. 1742 if ( ! api.state( 'saved' ).get() ) { 1743 urlParser.search += '&customize_autosaved=on'; 1744 } 1745 1711 1746 top.location.href = urlParser.href; 1712 1747 deferred.resolve(); … … 4025 4060 } 4026 4061 ); 4062 if ( ! api.state( 'saved' ).get() ) { 4063 params.customize_autosaved = 'on'; 4064 } 4027 4065 4028 4066 urlParser.search = $.param( params ); … … 4261 4299 delete queryParams.customize_theme; 4262 4300 delete queryParams.customize_messenger_channel; 4301 delete queryParams.customize_autosaved; 4263 4302 if ( _.isEmpty( queryParams ) ) { 4264 4303 urlParser.search = ''; … … 4885 4924 customize_changeset_uuid: api.settings.changeset.uuid 4886 4925 }; 4926 if ( ! api.state( 'saved' ).get() ) { 4927 queryVars.customize_autosaved = 'on'; 4928 } 4887 4929 4888 4930 /* … … 5099 5141 } 5100 5142 5143 // Prevent subsequent requestChangesetUpdate() calls from including the settings that have been saved. 5144 api._lastSavedRevision = Math.max( latestRevision, api._lastSavedRevision ); 5145 5101 5146 if ( response.setting_validities ) { 5102 5147 api._handleSettingValidities( { … … 5316 5361 if ( state( 'saved' ).get() ) { 5317 5362 state( 'saved' ).set( false ); 5318 populateChangesetUuidParam( true ); 5319 } 5320 }); 5363 } 5364 }); 5365 5366 // Populate changeset UUID param when state becomes dirty. 5367 if ( api.settings.changeset.branching ) { 5368 saved.bind( function( isSaved ) { 5369 if ( ! isSaved ) { 5370 populateChangesetUuidParam( true ); 5371 } 5372 }); 5373 } 5321 5374 5322 5375 saving.bind( function( isSaving ) { … … 5372 5425 }; 5373 5426 5374 changesetStatus.bind( function( newStatus ) { 5375 populateChangesetUuidParam( '' !== newStatus && 'publish' !== newStatus ); 5376 } ); 5427 // Show changeset UUID in URL when in branching mode and there is a saved changeset. 5428 if ( api.settings.changeset.branching ) { 5429 changesetStatus.bind( function( newStatus ) { 5430 populateChangesetUuidParam( '' !== newStatus && 'publish' !== newStatus ); 5431 } ); 5432 } 5377 5433 5378 5434 // Expose states to the API. 5379 5435 api.state = state; 5380 5436 }()); 5437 5438 // Set up autosave prompt. 5439 (function() { 5440 5441 /** 5442 * Obtain the URL to restore the autosave. 5443 * 5444 * @returns {string} Customizer URL. 5445 */ 5446 function getAutosaveRestorationUrl() { 5447 var urlParser, queryParams; 5448 urlParser = document.createElement( 'a' ); 5449 urlParser.href = location.href; 5450 queryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) ); 5451 if ( api.settings.changeset.latestAutoDraftUuid ) { 5452 queryParams.changeset_uuid = api.settings.changeset.latestAutoDraftUuid; 5453 } else { 5454 queryParams.customize_autosaved = 'on'; 5455 } 5456 urlParser.search = $.param( queryParams ); 5457 return urlParser.href; 5458 } 5459 5460 /** 5461 * Remove parameter from the URL. 5462 * 5463 * @param {Array} params - Parameter names to remove. 5464 * @returns {void} 5465 */ 5466 function stripParamsFromLocation( params ) { 5467 var urlParser = document.createElement( 'a' ), queryParams, strippedParams = 0; 5468 urlParser.href = location.href; 5469 queryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) ); 5470 _.each( params, function( param ) { 5471 if ( 'undefined' !== typeof queryParams[ param ] ) { 5472 strippedParams += 1; 5473 delete queryParams[ param ]; 5474 } 5475 } ); 5476 if ( 0 === strippedParams ) { 5477 return; 5478 } 5479 5480 urlParser.search = $.param( queryParams ); 5481 history.replaceState( {}, document.title, urlParser.href ); 5482 } 5483 5484 /** 5485 * Add notification regarding the availability of an autosave to restore. 5486 * 5487 * @returns {void} 5488 */ 5489 function addAutosaveRestoreNotification() { 5490 var code = 'autosave_available', onStateChange; 5491 5492 // Since there is an autosave revision and the user hasn't loaded with autosaved, add notification to prompt to load autosaved version. 5493 api.notifications.add( code, new api.Notification( code, { 5494 message: api.l10n.autosaveNotice, 5495 type: 'warning', 5496 dismissible: true, 5497 render: function() { 5498 var li = api.Notification.prototype.render.call( this ), link; 5499 5500 // Handle clicking on restoration link. 5501 link = li.find( 'a' ); 5502 link.prop( 'href', getAutosaveRestorationUrl() ); 5503 link.on( 'click', function( event ) { 5504 event.preventDefault(); 5505 location.replace( getAutosaveRestorationUrl() ); 5506 } ); 5507 5508 // Handle dismissal of notice. 5509 li.find( '.notice-dismiss' ).on( 'click', function() { 5510 wp.ajax.post( 'dismiss_customize_changeset_autosave', { 5511 wp_customize: 'on', 5512 customize_theme: api.settings.theme.stylesheet, 5513 customize_changeset_uuid: api.settings.changeset.latestAutoDraftUuid || api.settings.changeset.uuid, 5514 nonce: api.settings.nonce.dismiss_autosave 5515 } ); 5516 } ); 5517 5518 return li; 5519 } 5520 } ) ); 5521 5522 // Remove the notification once the user starts making changes. 5523 onStateChange = function() { 5524 api.notifications.remove( code ); 5525 api.state( 'saved' ).unbind( onStateChange ); 5526 api.state( 'saving' ).unbind( onStateChange ); 5527 api.state( 'changesetStatus' ).unbind( onStateChange ); 5528 }; 5529 api.state( 'saved' ).bind( onStateChange ); 5530 api.state( 'saving' ).bind( onStateChange ); 5531 api.state( 'changesetStatus' ).bind( onStateChange ); 5532 } 5533 5534 if ( api.settings.changeset.autosaved ) { 5535 stripParamsFromLocation( [ 'customize_autosaved' ] ); // Remove param when restoring autosave revision. 5536 } else if ( ! api.settings.changeset.branching && 'auto-draft' === api.settings.changeset.status ) { 5537 stripParamsFromLocation( [ 'changeset_uuid' ] ); // Remove UUID when restoring autosave auto-draft. 5538 } 5539 if ( api.settings.changeset.latestAutoDraftUuid || api.settings.changeset.hasAutosaveRevision ) { 5540 addAutosaveRestoreNotification(); 5541 } 5542 })(); 5381 5543 5382 5544 // Check if preview url is valid and load the preview frame. … … 5743 5905 }); 5744 5906 5745 /* 5746 * If we receive a 'back' event, we're inside an iframe. 5747 * Send any clicks to the 'Return' link to the parent page. 5748 */ 5749 parent.bind( 'back', function() { 5907 // Handle exiting of Customizer. 5908 (function() { 5909 var isInsideIframe = false; 5910 5911 function isCleanState() { 5912 return api.state( 'saved' ).get() && 'auto-draft' !== api.state( 'changesetStatus' ).get(); 5913 } 5914 5915 /* 5916 * If we receive a 'back' event, we're inside an iframe. 5917 * Send any clicks to the 'Return' link to the parent page. 5918 */ 5919 parent.bind( 'back', function() { 5920 isInsideIframe = true; 5921 }); 5922 5923 // Prompt user with AYS dialog if leaving the Customizer with unsaved changes 5924 $( window ).on( 'beforeunload.customize-confirm', function() { 5925 if ( ! isCleanState() ) { 5926 setTimeout( function() { 5927 overlay.removeClass( 'customize-loading' ); 5928 }, 1 ); 5929 return api.l10n.saveAlert; 5930 } 5931 }); 5932 5750 5933 closeBtn.on( 'click.customize-controls-close', function( event ) { 5934 var clearedToClose = $.Deferred(); 5751 5935 event.preventDefault(); 5752 parent.send( 'close' ); 5753 }); 5754 }); 5755 5756 // Prompt user with AYS dialog if leaving the Customizer with unsaved changes 5757 $( window ).on( 'beforeunload.customize-confirm', function () { 5758 if ( ! api.state( 'saved' )() ) { 5759 setTimeout( function() { 5760 overlay.removeClass( 'customize-loading' ); 5761 }, 1 ); 5762 return api.l10n.saveAlert; 5763 } 5764 } ); 5936 5937 /* 5938 * The isInsideIframe condition is because Customizer is not able to use a confirm() 5939 * since customize-loader.js will also use one. So autosave restorations are disabled 5940 * when customize-loader.js is used. 5941 */ 5942 if ( isInsideIframe && isCleanState() ) { 5943 clearedToClose.resolve(); 5944 } else if ( confirm( api.l10n.saveAlert ) ) { 5945 5946 // Mark all settings as clean to prevent another call to requestChangesetUpdate. 5947 api.each( function( setting ) { 5948 setting._dirty = false; 5949 }); 5950 $( document ).off( 'visibilitychange.wp-customize-changeset-update' ); 5951 $( window ).off( 'beforeunload.wp-customize-changeset-update' ); 5952 5953 closeBtn.css( 'cursor', 'progress' ); 5954 if ( '' === api.state( 'changesetStatus' ).get() ) { 5955 clearedToClose.resolve(); 5956 } else { 5957 wp.ajax.send( 'dismiss_customize_changeset_autosave', { 5958 timeout: 500, // Don't wait too long. 5959 data: { 5960 wp_customize: 'on', 5961 customize_theme: api.settings.theme.stylesheet, 5962 customize_changeset_uuid: api.settings.changeset.uuid, 5963 nonce: api.settings.nonce.dismiss_autosave 5964 } 5965 } ).always( function() { 5966 clearedToClose.resolve(); 5967 } ); 5968 } 5969 } else { 5970 clearedToClose.reject(); 5971 } 5972 5973 clearedToClose.done( function() { 5974 $( window ).off( 'beforeunload.customize-confirm' ); 5975 if ( isInsideIframe ) { 5976 parent.send( 'close' ); 5977 } else { 5978 window.location.href = closeBtn.prop( 'href' ); 5979 } 5980 } ); 5981 }); 5982 })(); 5765 5983 5766 5984 // Pass events through to the parent. … … 6085 6303 var timeoutId, updateChangesetWithReschedule, scheduleChangesetUpdate, updatePending = false; 6086 6304 6305 api.state( 'saved' ).bind( function( isSaved ) { 6306 if ( ! isSaved && ! api.settings.changeset.autosaved ) { 6307 api.settings.changeset.autosaved = true; // Once a change is made then autosaving kicks in. 6308 api.previewer.send( 'autosaving' ); 6309 } 6310 } ); 6311 6087 6312 /** 6088 6313 * Request changeset update and then re-schedule the next changeset update time. … … 6094 6319 if ( ! updatePending ) { 6095 6320 updatePending = true; 6096 api.requestChangesetUpdate( ).always( function() {6321 api.requestChangesetUpdate( {}, { autosave: true } ).always( function() { 6097 6322 updatePending = false; 6098 6323 } ); … … 6118 6343 6119 6344 // Save changeset when focus removed from window. 6120 $( window ).on( 'blur.wp-customize-changeset-update', function() { 6121 updateChangesetWithReschedule(); 6345 $( document ).on( 'visibilitychange.wp-customize-changeset-update', function() { 6346 if ( document.hidden ) { 6347 updateChangesetWithReschedule(); 6348 } 6122 6349 } ); 6123 6350 -
trunk/src/wp-includes/class-wp-customize-manager.php
r41586 r41597 175 175 176 176 /** 177 * Whether settings should be previewed.177 * Whether the autosave revision of the changeset should should be loaded. 178 178 * 179 179 * @since 4.9.0 180 180 * @var bool 181 181 */ 182 protected $settings_previewed; 182 protected $autosaved = false; 183 184 /** 185 * Whether the changeset branching is allowed. 186 * 187 * @since 4.9.0 188 * @var bool 189 */ 190 protected $branching = true; 191 192 /** 193 * Whether settings should be previewed. 194 * 195 * @since 4.9.0 196 * @var bool 197 */ 198 protected $settings_previewed = true; 199 200 /** 201 * Whether a starter content changeset was saved. 202 * 203 * @since 4.9.0 204 * @var bool 205 */ 206 protected $saved_starter_content_changeset = false; 183 207 184 208 /** … … 222 246 * Args. 223 247 * 224 * @type string $changeset_uuid Changeset UUID, the post_name for the customize_changeset post containing the customized state. Defaults to new UUID. 225 * @type string $theme Theme to be previewed (for theme switch). Defaults to customize_theme or theme query params. 226 * @type string $messenger_channel Messenger channel. Defaults to customize_messenger_channel query param. 227 * @type bool $settings_previewed If settings should be previewed. Defaults to true. 248 * @type null|string|false $changeset_uuid Changeset UUID, the `post_name` for the customize_changeset post containing the customized state. 249 * Defaults to `null` resulting in a UUID to be immediately generated. If `false` is provided, then 250 * then the changeset UUID will be determined during `after_setup_theme`: when the 251 * `customize_changeset_branching` filter returns false, then the default UUID will be that 252 * of the most recent `customize_changeset` post that has a status other than 'auto-draft', 253 * 'publish', or 'trash'. Otherwise, if changeset branching is enabled, then a random UUID will be used. 254 * @type string $theme Theme to be previewed (for theme switch). Defaults to customize_theme or theme query params. 255 * @type string $messenger_channel Messenger channel. Defaults to customize_messenger_channel query param. 256 * @type bool $settings_previewed If settings should be previewed. Defaults to true. 257 * @type bool $branching If changeset branching is allowed; otherwise, changesets are linear. Defaults to true. 258 * @type bool $autosaved If data from a changeset's autosaved revision should be loaded if it exists. Defaults to false. 228 259 * } 229 260 */ … … 231 262 232 263 $args = array_merge( 233 array_fill_keys( array( 'changeset_uuid', 'theme', 'messenger_channel', 'settings_previewed' ), null ),264 array_fill_keys( array( 'changeset_uuid', 'theme', 'messenger_channel', 'settings_previewed', 'autosaved', 'branching' ), null ), 234 265 $args 235 266 ); … … 252 283 } 253 284 254 if ( ! isset( $args['settings_previewed'] ) ) {255 $args['settings_previewed'] = true;256 }257 258 285 $this->original_stylesheet = get_stylesheet(); 259 286 $this->theme = wp_get_theme( 0 === validate_file( $args['theme'] ) ? $args['theme'] : null ); 260 287 $this->messenger_channel = $args['messenger_channel']; 261 $this->settings_previewed = ! empty( $args['settings_previewed'] );262 288 $this->_changeset_uuid = $args['changeset_uuid']; 289 290 foreach ( array( 'settings_previewed', 'autosaved', 'branching' ) as $key ) { 291 if ( isset( $args[ $key ] ) ) { 292 $this->$key = (bool) $args[ $key ]; 293 } 294 } 263 295 264 296 require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' ); … … 344 376 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); 345 377 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); 378 add_action( 'wp_ajax_dismiss_customize_changeset_autosave', array( $this, 'handle_dismiss_changeset_autosave_request' ) ); 346 379 347 380 add_action( 'customize_register', array( $this, 'register_controls' ) ); … … 475 508 } 476 509 477 if ( ! wp_is_uuid( $this->_changeset_uuid ) ) { 510 // If a changeset was provided is invalid. 511 if ( isset( $this->_changeset_uuid ) && false !== $this->_changeset_uuid && ! wp_is_uuid( $this->_changeset_uuid ) ) { 478 512 $this->wp_die( -1, __( 'Invalid changeset UUID' ) ); 479 513 } … … 536 570 } 537 571 572 // Make sure changeset UUID is established immediately after the theme is loaded. 573 add_action( 'after_setup_theme', array( $this, 'establish_loaded_changeset' ), 5 ); 574 538 575 /* 539 576 * Import theme starter content for fresh installations when landing in the customizer. … … 546 583 547 584 $this->start_previewing_theme(); 585 } 586 587 /** 588 * Establish the loaded changeset. 589 * 590 * This method runs right at after_setup_theme and applies the 'customize_changeset_branching' filter to determine 591 * whether concurrent changesets are allowed. Then if the Customizer is not initialized with a `changeset_uuid` param, 592 * this method will determine which UUID should be used. If changeset branching is disabled, then the most saved 593 * changeset will be loaded by default. Otherwise, if there are no existing saved changesets or if changeset branching is 594 * enabled, then a new UUID will be generated. 595 * 596 * @since 4.9.0 597 */ 598 public function establish_loaded_changeset() { 599 600 /** 601 * Filters whether or not changeset branching is allowed. 602 * 603 * By default in core, when changeset branching is not allowed, changesets will operate 604 * linearly in that only one saved changeset will exist at a time (with a 'draft' or 605 * 'future' status). This makes the Customizer operate in a way that is similar to going to 606 * "edit" to one existing post: all users will be making changes to the same post, and autosave 607 * revisions will be made for that post. 608 * 609 * By contrast, when changeset branching is allowed, then the model is like users going 610 * to "add new" for a page and each user makes changes independently of each other since 611 * they are all operating on their own separate pages, each getting their own separate 612 * initial auto-drafts and then once initially saved, autosave revisions on top of that 613 * user's specific post. 614 * 615 * Since linear changesets are deemed to be more suitable for the majority of WordPress users, 616 * they are the default. For WordPress sites that have heavy site management in the Customizer 617 * by multiple users then branching changesets should be enabled by means of this filter. 618 * 619 * @since 4.9.0 620 * 621 * @param bool $allow_branching Whether branching is allowed. If `false`, the default, 622 * then only one saved changeset exists at a time. 623 * @param WP_Customize_Manager $wp_customize Manager instance. 624 */ 625 $this->branching = apply_filters( 'customize_changeset_branching', $this->branching, $this ); 626 627 if ( empty( $this->_changeset_uuid ) ) { 628 $changeset_uuid = null; 629 630 if ( ! $this->branching ) { 631 $unpublished_changeset_posts = $this->get_changeset_posts( array( 632 'post_status' => array_diff( get_post_stati(), array( 'auto-draft', 'publish', 'trash', 'inherit', 'private' ) ), 633 'exclude_restore_dismissed' => false, 634 'posts_per_page' => 1, 635 'order' => 'DESC', 636 'orderby' => 'date', 637 ) ); 638 $unpublished_changeset_post = array_shift( $unpublished_changeset_posts ); 639 if ( ! empty( $unpublished_changeset_post ) && wp_is_uuid( $unpublished_changeset_post->post_name ) ) { 640 $changeset_uuid = $unpublished_changeset_post->post_name; 641 } 642 } 643 644 // If no changeset UUID has been set yet, then generate a new one. 645 if ( empty( $changeset_uuid ) ) { 646 $changeset_uuid = wp_generate_uuid4(); 647 } 648 649 $this->_changeset_uuid = $changeset_uuid; 650 } 548 651 } 549 652 … … 653 756 * 654 757 * @since 4.7.0 655 * 758 * @since 4.9.0 An exception is thrown if the changeset UUID has not been established yet. 759 * @see WP_Customize_Manager::establish_loaded_changeset() 760 * 761 * @throws Exception When the UUID has not been set yet. 656 762 * @return string UUID. 657 763 */ 658 764 public function changeset_uuid() { 765 if ( empty( $this->_changeset_uuid ) ) { 766 throw new Exception( 'Changeset UUID has not been set.' ); // @todo Replace this with a call to `WP_Customize_Manager::establish_loaded_changeset()` during 4.9-beta2. 767 } 659 768 return $this->_changeset_uuid; 660 769 } … … 826 935 827 936 /** 937 * Get changeset posts. 938 * 939 * @since 4.9.0 940 * 941 * @param array $args { 942 * Args to pass into `get_posts()` to query changesets. 943 * 944 * @type int $posts_per_page Number of posts to return. Defaults to -1 (all posts). 945 * @type int $author Post author. Defaults to current user. 946 * @type string $post_status Status of changeset. Defaults to 'auto-draft'. 947 * @type bool $exclude_restore_dismissed Whether to exclude changeset auto-drafts that have been dismissed. Defaults to true. 948 * } 949 * @return WP_Post[] Auto-draft changesets. 950 */ 951 protected function get_changeset_posts( $args = array() ) { 952 $default_args = array( 953 'exclude_restore_dismissed' => true, 954 'posts_per_page' => -1, 955 'post_type' => 'customize_changeset', 956 'post_status' => 'auto-draft', 957 'order' => 'DESC', 958 'orderby' => 'date', 959 'no_found_rows' => true, 960 'cache_results' => true, 961 'update_post_meta_cache' => false, 962 'update_post_term_cache' => false, 963 'lazy_load_term_meta' => false, 964 ); 965 if ( get_current_user_id() ) { 966 $default_args['author'] = get_current_user_id(); 967 } 968 $args = array_merge( $default_args, $args ); 969 970 if ( ! empty( $args['exclude_restore_dismissed'] ) ) { 971 unset( $args['exclude_restore_dismissed'] ); 972 $args['meta_query'] = array( 973 array( 974 'key' => '_customize_restore_dismissed', 975 'compare' => 'NOT EXISTS', 976 ), 977 ); 978 } 979 980 return get_posts( $args ); 981 } 982 983 /** 828 984 * Get the changeset post id for the loaded changeset. 829 985 * … … 834 990 public function changeset_post_id() { 835 991 if ( ! isset( $this->_changeset_post_id ) ) { 836 $post_id = $this->find_changeset_post_id( $this-> _changeset_uuid);992 $post_id = $this->find_changeset_post_id( $this->changeset_uuid() ); 837 993 if ( ! $post_id ) { 838 994 $post_id = false; … … 862 1018 return new WP_Error( 'missing_post' ); 863 1019 } 864 if ( 'customize_changeset' !== $changeset_post->post_type ) { 1020 if ( 'revision' === $changeset_post->post_type ) { 1021 if ( 'customize_changeset' !== get_post_type( $changeset_post->post_parent ) ) { 1022 return new WP_Error( 'wrong_post_type' ); 1023 } 1024 } elseif ( 'customize_changeset' !== $changeset_post->post_type ) { 865 1025 return new WP_Error( 'wrong_post_type' ); 866 1026 } … … 879 1039 * 880 1040 * @since 4.7.0 1041 * @since 4.9.0 This will return the changeset's data with a user's autosave revision merged on top, if one exists and $autosaved is true. 881 1042 * 882 1043 * @return array Changeset data. … … 890 1051 $this->_changeset_data = array(); 891 1052 } else { 892 $data = $this->get_changeset_post_data( $changeset_post_id ); 893 if ( ! is_wp_error( $data ) ) { 894 $this->_changeset_data = $data; 895 } else { 896 $this->_changeset_data = array(); 1053 if ( $this->autosaved ) { 1054 $autosave_post = wp_get_post_autosave( $changeset_post_id ); 1055 if ( $autosave_post ) { 1056 $data = $this->get_changeset_post_data( $autosave_post->ID ); 1057 if ( ! is_wp_error( $data ) ) { 1058 $this->_changeset_data = $data; 1059 } 1060 } 1061 } 1062 1063 // Load data from the changeset if it was not loaded from an autosave. 1064 if ( ! isset( $this->_changeset_data ) ) { 1065 $data = $this->get_changeset_post_data( $changeset_post_id ); 1066 if ( ! is_wp_error( $data ) ) { 1067 $this->_changeset_data = $data; 1068 } else { 1069 $this->_changeset_data = array(); 1070 } 897 1071 } 898 1072 } … … 1375 1549 'starter_content' => true, 1376 1550 ) ); 1551 $this->saved_starter_content_changeset = true; 1377 1552 1378 1553 $this->pending_starter_content_settings_ids = array(); … … 1797 1972 $settings = array( 1798 1973 'changeset' => array( 1799 'uuid' => $this->_changeset_uuid, 1974 'uuid' => $this->changeset_uuid(), 1975 'autosaved' => $this->autosaved, 1800 1976 ), 1801 1977 'timeouts' => array( … … 2079 2255 2080 2256 $changeset_post_id = $this->changeset_post_id(); 2081 if ( empty( $changeset_post_id ) ) { 2257 $is_new_changeset = empty( $changeset_post_id ); 2258 if ( $is_new_changeset ) { 2082 2259 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->create_posts ) ) { 2083 2260 wp_send_json_error( 'cannot_create_changeset_post' ); … … 2145 2322 } 2146 2323 2324 $autosave = ! empty( $_POST['customize_changeset_autosave'] ); 2325 if ( $autosave && ! defined( 'DOING_AUTOSAVE' ) ) { // Back-compat. 2326 define( 'DOING_AUTOSAVE', true ); 2327 } 2328 2147 2329 $r = $this->save_changeset_post( array( 2148 2330 'status' => $changeset_status, … … 2150 2332 'date_gmt' => $changeset_date_gmt, 2151 2333 'data' => $input_changeset_data, 2334 'autosave' => $autosave, 2152 2335 ) ); 2153 2336 if ( is_wp_error( $r ) ) { … … 2163 2346 } else { 2164 2347 $response = $r; 2348 2349 // Dismiss all other auto-draft changeset posts for this user (they serve like autosave revisions), as there should only be one. 2350 if ( $is_new_changeset ) { 2351 $changeset_autodraft_posts = $this->get_changeset_posts( array( 2352 'post_status' => 'auto-draft', 2353 'exclude_restore_dismissed' => true, 2354 'posts_per_page' => -1, 2355 ) ); 2356 foreach ( $changeset_autodraft_posts as $autosave_autodraft_post ) { 2357 if ( $autosave_autodraft_post->ID !== $this->changeset_post_id() ) { 2358 update_post_meta( $autosave_autodraft_post->ID, '_customize_restore_dismissed', true ); 2359 } 2360 } 2361 } 2165 2362 2166 2363 // Note that if the changeset status was publish, then it will get set to trash if revisions are not supported. … … 2213 2410 * @type int $user_id ID for user who is saving the changeset. Optional, defaults to the current user ID. 2214 2411 * @type bool $starter_content Whether the data is starter content. If false (default), then $starter_content will be cleared for any $data being saved. 2412 * @type bool $autosave Whether this is a request to create an autosave revision. 2215 2413 * } 2216 2414 * … … 2227 2425 'user_id' => get_current_user_id(), 2228 2426 'starter_content' => false, 2427 'autosave' => false, 2229 2428 ), 2230 2429 $args … … 2276 2475 if ( ! empty( $is_future_dated ) && 'publish' === $args['status'] ) { 2277 2476 $args['status'] = 'future'; 2477 } 2478 2479 // Validate autosave param. See _wp_post_revision_fields() for why these fields are disallowed. 2480 if ( $args['autosave'] ) { 2481 if ( $args['date_gmt'] ) { 2482 return new WP_Error( 'illegal_autosave_with_date_gmt' ); 2483 } elseif ( $args['status'] ) { 2484 return new WP_Error( 'illegal_autosave_with_status' ); 2485 } elseif ( $args['user_id'] && get_current_user_id() !== $args['user_id'] ) { 2486 return new WP_Error( 'illegal_autosave_with_non_current_user' ); 2487 } 2278 2488 } 2279 2489 … … 2520 2730 // Note that updating a post with publish status will trigger WP_Customize_Manager::publish_changeset_values(). 2521 2731 if ( $changeset_post_id ) { 2522 $post_array['edit_date'] = true; // Prevent date clearing. 2523 $r = wp_update_post( wp_slash( $post_array ), true ); 2732 if ( $args['autosave'] && 'auto-draft' !== get_post_status( $changeset_post_id ) ) { 2733 // See _wp_translate_postdata() for why this is required as it will use the edit_post meta capability. 2734 add_filter( 'map_meta_cap', array( $this, 'grant_edit_post_capability_for_changeset' ), 10, 4 ); 2735 $post_array['post_ID'] = $post_array['ID']; 2736 $post_array['post_type'] = 'customize_changeset'; 2737 $r = wp_create_post_autosave( wp_slash( $post_array ) ); 2738 remove_filter( 'map_meta_cap', array( $this, 'grant_edit_post_capability_for_changeset' ), 10 ); 2739 } else { 2740 $post_array['edit_date'] = true; // Prevent date clearing. 2741 $r = wp_update_post( wp_slash( $post_array ), true ); 2742 2743 // Delete autosave revision when the changeset is updated. 2744 $autosave_draft = wp_get_post_autosave( $changeset_post_id ); 2745 if ( $autosave_draft ) { 2746 wp_delete_post( $autosave_draft->ID, true ); 2747 } 2748 } 2524 2749 } else { 2525 2750 $r = wp_insert_post( wp_slash( $post_array ), true ); … … 2545 2770 2546 2771 return $response; 2772 } 2773 2774 /** 2775 * Re-map 'edit_post' meta cap for a customize_changeset post to be the same as 'customize' maps. 2776 * 2777 * There is essentially a "meta meta" cap in play here, where 'edit_post' meta cap maps to 2778 * the 'customize' meta cap which then maps to 'edit_theme_options'. This is currently 2779 * required in core for `wp_create_post_autosave()` because it will call 2780 * `_wp_translate_postdata()` which in turn will check if a user can 'edit_post', but the 2781 * the caps for the customize_changeset post type are all mapping to the meta capability. 2782 * This should be able to be removed once #40922 is addressed in core. 2783 * 2784 * @since 4.9.0 2785 * @link https://core.trac.wordpress.org/ticket/40922 2786 * @see WP_Customize_Manager::save_changeset_post() 2787 * @see _wp_translate_postdata() 2788 * 2789 * @param array $caps Returns the user's actual capabilities. 2790 * @param string $cap Capability name. 2791 * @param int $user_id The user ID. 2792 * @param array $args Adds the context to the cap. Typically the object ID. 2793 * @return array Capabilities. 2794 */ 2795 public function grant_edit_post_capability_for_changeset( $caps, $cap, $user_id, $args ) { 2796 if ( 'edit_post' === $cap && ! empty( $args[0] ) && 'customize_changeset' === get_post_type( $args[0] ) ) { 2797 $post_type_obj = get_post_type_object( 'customize_changeset' ); 2798 $caps = map_meta_cap( $post_type_obj->cap->$cap, $user_id ); 2799 } 2800 return $caps; 2547 2801 } 2548 2802 … … 2787 3041 2788 3042 /** 3043 * Delete a given auto-draft changeset or the autosave revision for a given changeset. 3044 * 3045 * @since 4.9.0 3046 */ 3047 public function handle_dismiss_changeset_autosave_request() { 3048 if ( ! $this->is_preview() ) { 3049 wp_send_json_error( 'not_preview', 400 ); 3050 } 3051 3052 if ( ! check_ajax_referer( 'dismiss_customize_changeset_autosave', 'nonce', false ) ) { 3053 wp_send_json_error( 'invalid_nonce', 403 ); 3054 } 3055 3056 $changeset_post_id = $this->changeset_post_id(); 3057 if ( empty( $changeset_post_id ) ) { 3058 wp_send_json_error( 'missing_changeset', 404 ); 3059 } 3060 3061 if ( 'auto-draft' === get_post_status( $changeset_post_id ) ) { 3062 if ( ! update_post_meta( $changeset_post_id, '_customize_restore_dismissed', true ) ) { 3063 wp_send_json_error( 'auto_draft_dismissal_failure', 500 ); 3064 } else { 3065 wp_send_json_success( 'auto_draft_dismissed' ); 3066 } 3067 } else { 3068 $revision = wp_get_post_autosave( $changeset_post_id ); 3069 3070 if ( $revision ) { 3071 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->delete_post, $changeset_post_id ) ) { 3072 wp_send_json_error( 'cannot_delete_autosave_revision', 403 ); 3073 } 3074 3075 if ( ! wp_delete_post( $revision->ID, true ) ) { 3076 wp_send_json_error( 'autosave_revision_deletion_failure', 500 ); 3077 } else { 3078 wp_send_json_success( 'autosave_revision_deleted' ); 3079 } 3080 } else { 3081 wp_send_json_error( 'no_autosave_to_delete', 404 ); 3082 } 3083 } 3084 wp_send_json_error( 'unknown_error', 500 ); 3085 } 3086 3087 /** 2789 3088 * Add a customize setting. 2790 3089 * … … 3528 3827 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ), 3529 3828 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ), 3829 'dismiss_autosave' => wp_create_nonce( 'dismiss_customize_changeset_autosave' ), 3530 3830 ); 3531 3831 … … 3564 3864 } 3565 3865 3866 $autosave_revision_post = null; 3867 $autosave_autodraft_post = null; 3868 $changeset_post_id = $this->changeset_post_id(); 3869 if ( ! $this->saved_starter_content_changeset && ! $this->autosaved ) { 3870 if ( $changeset_post_id ) { 3871 $autosave_revision_post = wp_get_post_autosave( $changeset_post_id ); 3872 } else { 3873 $autosave_autodraft_posts = $this->get_changeset_posts( array( 3874 'posts_per_page' => 1, 3875 'post_status' => 'auto-draft', 3876 'exclude_restore_dismissed' => true, 3877 ) ); 3878 if ( ! empty( $autosave_autodraft_posts ) ) { 3879 $autosave_autodraft_post = array_shift( $autosave_autodraft_posts ); 3880 } 3881 } 3882 } 3883 3566 3884 // Prepare Customizer settings to pass to JavaScript. 3567 3885 $settings = array( 3568 3886 'changeset' => array( 3569 3887 'uuid' => $this->changeset_uuid(), 3570 'status' => $this->changeset_post_id() ? get_post_status( $this->changeset_post_id() ) : '', 3888 'branching' => $this->branching, 3889 'autosaved' => $this->autosaved, 3890 'hasAutosaveRevision' => ! empty( $autosave_revision_post ), 3891 'latestAutoDraftUuid' => $autosave_autodraft_post ? $autosave_autodraft_post->post_name : null, 3892 'status' => $changeset_post_id ? get_post_status( $changeset_post_id ) : '', 3571 3893 ), 3572 3894 'timeouts' => array( -
trunk/src/wp-includes/js/customize-preview.js
r41351 r41597 37 37 38 38 newQueryParams.customize_changeset_uuid = oldQueryParams.customize_changeset_uuid; 39 if ( api.settings.changeset.autosaved ) { 40 newQueryParams.customize_autosaved = 'on'; 41 } 39 42 if ( oldQueryParams.customize_theme ) { 40 43 newQueryParams.customize_theme = oldQueryParams.customize_theme; … … 365 368 queryParams = api.utils.parseQueryString( element.search.substring( 1 ) ); 366 369 queryParams.customize_changeset_uuid = api.settings.changeset.uuid; 370 if ( api.settings.changeset.autosaved ) { 371 queryParams.customize_autosaved = 'on'; 372 } 367 373 if ( ! api.settings.theme.active ) { 368 374 queryParams.customize_theme = api.settings.theme.stylesheet; … … 440 446 // Include customized state query params in URL. 441 447 queryParams.customize_changeset_uuid = api.settings.changeset.uuid; 448 if ( api.settings.changeset.autosaved ) { 449 queryParams.customize_autosaved = 'on'; 450 } 442 451 if ( ! api.settings.theme.active ) { 443 452 queryParams.customize_theme = api.settings.theme.stylesheet; … … 517 526 518 527 stateParams.customize_changeset_uuid = api.settings.changeset.uuid; 528 if ( api.settings.changeset.autosaved ) { 529 stateParams.customize_autosaved = 'on'; 530 } 519 531 if ( ! api.settings.theme.active ) { 520 532 stateParams.customize_theme = api.settings.theme.stylesheet; … … 556 568 previousQueryString = location.search.substr( 1 ), 557 569 previousQueryParams = null, 558 stateQueryParams = [ 'customize_theme', 'customize_changeset_uuid', 'customize_messenger_channel' ];570 stateQueryParams = [ 'customize_theme', 'customize_changeset_uuid', 'customize_messenger_channel', 'customize_autosaved' ]; 559 571 560 572 return function keepAliveCurrentUrl() { … … 755 767 756 768 api.preview.bind( 'saved', function( response ) { 757 758 769 if ( response.next_changeset_uuid ) { 759 770 api.settings.changeset.uuid = response.next_changeset_uuid; … … 780 791 } ); 781 792 793 // Update the URLs to reflect the fact we've started autosaving. 794 api.preview.bind( 'autosaving', function() { 795 if ( api.settings.changeset.autosaved ) { 796 return; 797 } 798 799 api.settings.changeset.autosaved = true; // Start deferring to any autosave once changeset is updated. 800 801 $( document.body ).find( 'a[href], area' ).each( function() { 802 api.prepareLinkPreview( this ); 803 } ); 804 $( document.body ).find( 'form' ).each( function() { 805 api.prepareFormPreview( this ); 806 } ); 807 if ( history.replaceState ) { 808 history.replaceState( currentHistoryState, '', location.href ); 809 } 810 } ); 811 782 812 /* 783 813 * Clear dirty flag for settings when saved to changeset so that they -
trunk/src/wp-includes/script-loader.php
r41590 r41597 562 562 'untitledBlogName' => __( '(Untitled)' ), 563 563 'serverSaveError' => __( 'Failed connecting to the server. Please try saving again.' ), 564 /* translators: placeholder is URL to the Customizer to load the autosaved version */ 565 'autosaveNotice' => __( 'There is a more recent autosave of your changes than the one you are previewing. <a href="%s">Restore the autosave</a>' ), 564 566 'videoHeaderNotice' => __( 'This theme doesn\'t support video headers on this page. Navigate to the front page or another page that supports video headers.' ), 565 567 // Used for overriding the file types allowed in plupload. -
trunk/src/wp-includes/theme.php
r41555 r41597 2788 2788 * the values should contain any characters needing slashes anyway. 2789 2789 */ 2790 $keys = array( 'changeset_uuid', 'customize_changeset_uuid', 'customize_theme', 'theme', 'customize_messenger_channel' );2790 $keys = array( 'changeset_uuid', 'customize_changeset_uuid', 'customize_theme', 'theme', 'customize_messenger_channel', 'customize_autosaved' ); 2791 2791 $input_vars = array_merge( 2792 2792 wp_array_slice_assoc( $_GET, $keys ), … … 2795 2795 2796 2796 $theme = null; 2797 $changeset_uuid = null;2797 $changeset_uuid = false; // Value false indicates UUID should be determined after_setup_theme to either re-use existing saved changeset or else generate a new UUID if none exists. 2798 2798 $messenger_channel = null; 2799 $autosaved = null; 2800 $branching = false; // Set initially fo false since defaults to true for back-compat; can be overridden via the customize_changeset_branching filter. 2799 2801 2800 2802 if ( $is_customize_admin_page && isset( $input_vars['changeset_uuid'] ) ) { … … 2810 2812 $theme = $input_vars['customize_theme']; 2811 2813 } 2814 2815 if ( ! empty( $input_vars['customize_autosaved'] ) ) { 2816 $autosaved = true; 2817 } 2818 2812 2819 if ( isset( $input_vars['customize_messenger_channel'] ) ) { 2813 2820 $messenger_channel = sanitize_key( $input_vars['customize_messenger_channel'] ); … … 2831 2838 2832 2839 require_once ABSPATH . WPINC . '/class-wp-customize-manager.php'; 2833 $GLOBALS['wp_customize'] = new WP_Customize_Manager( compact( 'changeset_uuid', 'theme', 'messenger_channel', 'settings_previewed' ) );2840 $GLOBALS['wp_customize'] = new WP_Customize_Manager( compact( 'changeset_uuid', 'theme', 'messenger_channel', 'settings_previewed', 'autosaved', 'branching' ) ); 2834 2841 } 2835 2842
Note: See TracChangeset
for help on using the changeset viewer.