Changeset 41839
- Timestamp:
- 10/12/2017 04:00:15 AM (7 years ago)
- Location:
- trunk
- Files:
-
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/css/customize-controls.css
r41837 r41839 19 19 #customize-controls .submit { 20 20 text-align: center; 21 } 22 23 #customize-controls #customize-notifications-area .notice.notification-overlay.notification-changeset-locked { 24 background-color: rgba( 0, 0, 0, 0.7 ); 25 padding: 25px; 26 } 27 28 #customize-controls #customize-notifications-area .notice.notification-overlay.notification-changeset-locked .customize-changeset-locked-message { 29 margin-left: auto; 30 margin-right: auto; 31 max-width: 366px; 32 min-height: 64px; 33 width: auto; 34 padding: 25px 25px 25px 109px; 35 position: relative; 36 background: #fff; 37 box-shadow: 0 3px 6px rgba( 0, 0, 0, 0.3 ); 38 line-height: 1.5; 39 overflow-y: auto; 40 text-align: left; 41 top: calc( 50% - 100px ); 42 } 43 44 #customize-controls #customize-notifications-area .notice.notification-overlay.notification-changeset-locked .currently-editing { 45 margin-top: 0; 46 } 47 #customize-controls #customize-notifications-area .notice.notification-overlay.notification-changeset-locked .action-buttons { 48 margin-bottom: 0; 49 } 50 51 .customize-changeset-locked-avatar { 52 width: 64px; 53 position: absolute; 54 left: 25px; 55 top: 25px; 56 } 57 58 .wp-core-ui.wp-customizer .customize-changeset-locked-message a.button { 59 margin-right: 10px; 60 margin-top: 0; 21 61 } 22 62 -
trunk/src/wp-admin/customize.php
r41802 r41839 88 88 do_action( 'customize_controls_init' ); 89 89 90 wp_enqueue_script( 'heartbeat' ); 90 91 wp_enqueue_script( 'customize-controls' ); 91 92 wp_enqueue_style( 'customize-controls' ); -
trunk/src/wp-admin/js/customize-controls.js
r41826 r41839 34 34 if ( notification.loading ) { 35 35 notification.containerClasses += ' notification-loading'; 36 } 37 }, 38 39 /** 40 * Render notification. 41 * 42 * @since 4.9.0 43 * 44 * @return {jQuery} Notification container. 45 */ 46 render: function() { 47 var li = api.Notification.prototype.render.call( this ); 48 li.on( 'keydown', _.bind( this.handleEscape, this ) ); 49 return li; 50 }, 51 52 /** 53 * Stop propagation on escape key presses, but also dismiss notification if it is dismissible. 54 * 55 * @since 4.9.0 56 * 57 * @param {jQuery.Event} event - Event. 58 * @returns {void} 59 */ 60 handleEscape: function( event ) { 61 var notification = this; 62 if ( 27 === event.which ) { 63 event.stopPropagation(); 64 if ( notification.dismissible && notification.parent ) { 65 notification.parent.remove( notification.code ); 66 } 36 67 } 37 68 } … … 283 314 */ 284 315 constrainFocus: function constrainFocus( event ) { 285 var collection = this; 286 if ( ! collection.focusContainer || collection.focusContainer.is( event.target ) || $.contains( collection.focusContainer[0], event.target[0] ) ) { 316 var collection = this, focusableElements; 317 318 // Prevent keys from escaping. 319 event.stopPropagation(); 320 321 if ( 9 !== event.which ) { // Tab key. 287 322 return; 288 323 } 289 collection.focusContainer.focus(); 324 325 focusableElements = collection.focusContainer.find( ':focusable' ); 326 if ( 0 === focusableElements.length ) { 327 focusableElements = collection.focusContainer; 328 } 329 330 if ( ! $.contains( collection.focusContainer[0], event.target ) || ! $.contains( collection.focusContainer[0], document.activeElement ) ) { 331 event.preventDefault(); 332 focusableElements.first().focus(); 333 } else if ( focusableElements.last().is( event.target ) && ! event.shiftKey ) { 334 event.preventDefault(); 335 focusableElements.first().focus(); 336 } else if ( focusableElements.first().is( event.target ) && event.shiftKey ) { 337 event.preventDefault(); 338 focusableElements.last().focus(); 339 } 290 340 } 291 341 }); … … 6738 6788 'remainingTimeToPublish', 6739 6789 'previewerAlive', 6740 'editShortcutVisibility' 6790 'editShortcutVisibility', 6791 'changesetLocked' 6741 6792 ], function( name ) { 6742 6793 api.state.create( name ); … … 7185 7236 if ( 'not_future_date' === response.code && api.section.has( 'publish_settings' ) && api.section( 'publish_settings' ).active.get() && api.control.has( 'changeset_scheduled_date' ) ) { 7186 7237 api.control( 'changeset_scheduled_date' ).toggleFutureDateNotification( true ).focus(); 7187 } else {7238 } else if ( 'changeset_locked' !== response.code ) { 7188 7239 notification = new api.Notification( response.code, _.extend( notificationArgs, { 7189 7240 message: response.message … … 7192 7243 } else { 7193 7244 notification = new api.Notification( 'unknown_error', _.extend( notificationArgs, { 7194 message: api.l10n. serverSaveError7245 message: api.l10n.unknownRequestFail 7195 7246 } ) ); 7196 7247 } … … 7498 7549 previewerAlive = state.instance( 'previewerAlive' ), 7499 7550 editShortcutVisibility = state.instance( 'editShortcutVisibility' ), 7551 changesetLocked = state.instance( 'changesetLocked' ), 7500 7552 populateChangesetUuidParam; 7501 7553 … … 7548 7600 * and if the theme is not active or the changeset exists but is not published. 7549 7601 */ 7550 canSave = ! saving() && ! trashing() && ( ! activated() || ! saved() || ( changesetStatus() !== selectedChangesetStatus() && '' !== changesetStatus() ) || ( 'future' === selectedChangesetStatus() && changesetDate.get() !== selectedChangesetDate.get() ) );7602 canSave = ! saving() && ! trashing() && ! changesetLocked() && ( ! activated() || ! saved() || ( changesetStatus() !== selectedChangesetStatus() && '' !== changesetStatus() ) || ( 'future' === selectedChangesetStatus() && changesetDate.get() !== selectedChangesetDate.get() ) ); 7551 7603 7552 7604 saveBtn.prop( 'disabled', ! canSave ); … … 7562 7614 // Set default states. 7563 7615 changesetStatus( api.settings.changeset.status ); 7616 changesetLocked( Boolean( api.settings.changeset.lockUser ) ); 7564 7617 changesetDate( api.settings.changeset.publishDate ); 7565 7618 selectedChangesetDate( api.settings.changeset.publishDate ); … … 7660 7713 } 7661 7714 }( api.state ) ); 7715 7716 /** 7717 * Handles lock notice and take over request. 7718 * 7719 * @since 4.9.0 7720 */ 7721 ( function checkAndDisplayLockNotice() { 7722 7723 /** 7724 * A notification that is displayed in a full-screen overlay with information about the locked changeset. 7725 * 7726 * @since 4.9.0 7727 * @class 7728 * @augments wp.customize.Notification 7729 * @augments wp.customize.OverlayNotification 7730 */ 7731 var LockedNotification = api.OverlayNotification.extend({ 7732 7733 /** 7734 * Template ID. 7735 * 7736 * @type {string} 7737 */ 7738 templateId: 'customize-changeset-locked-notification', 7739 7740 /** 7741 * Lock user. 7742 * 7743 * @type {object} 7744 */ 7745 lockUser: null, 7746 7747 /** 7748 * Initialize. 7749 * 7750 * @since 4.9.0 7751 * 7752 * @param {string} [code] - Code. 7753 * @param {object} [params] - Params. 7754 */ 7755 initialize: function( code, params ) { 7756 var notification = this, _code, _params; 7757 _code = code || 'changeset_locked'; 7758 _params = _.extend( 7759 { 7760 type: 'warning', 7761 containerClasses: '', 7762 lockUser: {} 7763 }, 7764 params 7765 ); 7766 _params.containerClasses += ' notification-changeset-locked'; 7767 api.OverlayNotification.prototype.initialize.call( notification, _code, _params ); 7768 }, 7769 7770 /** 7771 * Render notification. 7772 * 7773 * @since 4.9.0 7774 * 7775 * @return {jQuery} Notification container. 7776 */ 7777 render: function() { 7778 var notification = this, li, data, takeOverButton, request; 7779 data = _.extend( 7780 { 7781 allowOverride: false, 7782 returnUrl: api.settings.url['return'], 7783 previewUrl: api.previewer.previewUrl.get(), 7784 frontendPreviewUrl: api.previewer.getFrontendPreviewUrl() 7785 }, 7786 this 7787 ); 7788 7789 li = api.OverlayNotification.prototype.render.call( data ); 7790 7791 // Try to autosave the changeset now. 7792 api.requestChangesetUpdate( {}, { autosave: true } ).fail( function( response ) { 7793 if ( ! response.autosaved ) { 7794 li.find( '.notice-error' ).prop( 'hidden', false ).text( response.message || api.l10n.unknownRequestFail ); 7795 } 7796 } ); 7797 7798 takeOverButton = li.find( '.customize-notice-take-over-button' ); 7799 takeOverButton.on( 'click', function( event ) { 7800 event.preventDefault(); 7801 if ( request ) { 7802 return; 7803 } 7804 7805 takeOverButton.addClass( 'disabled' ); 7806 request = wp.ajax.post( 'customize_override_changeset_lock', { 7807 wp_customize: 'on', 7808 customize_theme: api.settings.theme.stylesheet, 7809 customize_changeset_uuid: api.settings.changeset.uuid, 7810 nonce: api.settings.nonce.override_lock 7811 } ); 7812 7813 request.done( function() { 7814 api.notifications.remove( notification.code ); // Remove self. 7815 api.state( 'changesetLocked' ).set( false ); 7816 } ); 7817 7818 request.fail( function( response ) { 7819 var message = response.message || api.l10n.unknownRequestFail; 7820 li.find( '.notice-error' ).prop( 'hidden', false ).text( message ); 7821 7822 request.always( function() { 7823 takeOverButton.removeClass( 'disabled' ); 7824 } ); 7825 } ); 7826 7827 request.always( function() { 7828 request = null; 7829 } ); 7830 } ); 7831 7832 return li; 7833 } 7834 }); 7835 7836 /** 7837 * Start lock. 7838 * 7839 * @since 4.9.0 7840 * 7841 * @param {object} [args] - Args. 7842 * @param {object} [args.lockUser] - Lock user data. 7843 * @param {boolean} [args.allowOverride=false] - Whether override is allowed. 7844 * @returns {void} 7845 */ 7846 function startLock( args ) { 7847 if ( args && args.lockUser ) { 7848 api.settings.changeset.lockUser = args.lockUser; 7849 } 7850 api.state( 'changesetLocked' ).set( true ); 7851 api.notifications.add( new LockedNotification( 'changeset_locked', { 7852 lockUser: api.settings.changeset.lockUser, 7853 allowOverride: Boolean( args && args.allowOverride ) 7854 } ) ); 7855 } 7856 7857 // Show initial notification. 7858 if ( api.settings.changeset.lockUser ) { 7859 startLock( { allowOverride: true } ); 7860 } 7861 7862 // Check for lock when sending heartbeat requests. 7863 $( document ).on( 'heartbeat-send.update_lock_notice', function( event, data ) { 7864 data.check_changeset_lock = true; 7865 } ); 7866 7867 // Handle heartbeat ticks. 7868 $( document ).on( 'heartbeat-tick.update_lock_notice', function( event, data ) { 7869 var notification, code = 'changeset_locked'; 7870 if ( ! data.customize_changeset_lock_user ) { 7871 return; 7872 } 7873 7874 // Update notification when a different user takes over. 7875 notification = api.notifications( code ); 7876 if ( notification && notification.lockUser.id !== api.settings.changeset.lockUser.id ) { 7877 api.notifications.remove( code ); 7878 } 7879 7880 startLock( { 7881 lockUser: data.customize_changeset_lock_user 7882 } ); 7883 } ); 7884 7885 // Handle locking in response to changeset save errors. 7886 api.bind( 'error', function( response ) { 7887 if ( 'changeset_locked' === response.code && response.lock_user ) { 7888 startLock( { 7889 lockUser: response.lock_user 7890 } ); 7891 } 7892 } ); 7893 } )(); 7662 7894 7663 7895 // Set up initial notifications. … … 7734 7966 // Handle dismissal of notice. 7735 7967 li.find( '.notice-dismiss' ).on( 'click', function() { 7736 wp.ajax.post( 'customize_dismiss_autosave ', {7968 wp.ajax.post( 'customize_dismiss_autosave_or_lock', { 7737 7969 wp_customize: 'on', 7738 7970 customize_theme: api.settings.theme.stylesheet, 7739 7971 customize_changeset_uuid: api.settings.changeset.uuid, 7740 nonce: api.settings.nonce.dismiss_autosave 7972 nonce: api.settings.nonce.dismiss_autosave_or_lock, 7973 dismiss_autosave: true 7741 7974 } ); 7742 7975 } ); … … 8168 8401 // Prompt user with AYS dialog if leaving the Customizer with unsaved changes 8169 8402 $( window ).on( 'beforeunload.customize-confirm', function() { 8170 if ( ! isCleanState() ) {8403 if ( ! isCleanState() && ! api.state( 'changesetLocked' ).get() ) { 8171 8404 setTimeout( function() { 8172 8405 overlay.removeClass( 'customize-loading' ); … … 8179 8412 8180 8413 function requestClose() { 8181 var clearedToClose = $.Deferred(); 8414 var clearedToClose = $.Deferred(), dismissAutoSave = false, dismissLock = false; 8415 8182 8416 if ( isCleanState() ) { 8183 clearedToClose.resolve();8417 dismissLock = true; 8184 8418 } else if ( confirm( api.l10n.saveAlert ) ) { 8419 8420 dismissLock = true; 8185 8421 8186 8422 // Mark all settings as clean to prevent another call to requestChangesetUpdate. … … 8192 8428 8193 8429 closeBtn.css( 'cursor', 'progress' ); 8194 if ( '' === api.state( 'changesetStatus' ).get() ) { 8195 clearedToClose.resolve(); 8196 } else { 8197 wp.ajax.send( 'customize_dismiss_autosave', { 8198 timeout: 500, // Don't wait too long. 8199 data: { 8200 wp_customize: 'on', 8201 customize_theme: api.settings.theme.stylesheet, 8202 customize_changeset_uuid: api.settings.changeset.uuid, 8203 nonce: api.settings.nonce.dismiss_autosave 8204 } 8205 } ).always( function() { 8206 clearedToClose.resolve(); 8207 } ); 8430 if ( '' !== api.state( 'changesetStatus' ).get() ) { 8431 dismissAutoSave = true; 8208 8432 } 8209 8433 } else { 8210 8434 clearedToClose.reject(); 8211 8435 } 8436 8437 if ( dismissLock || dismissAutoSave ) { 8438 wp.ajax.send( 'customize_dismiss_autosave_or_lock', { 8439 timeout: 500, // Don't wait too long. 8440 data: { 8441 wp_customize: 'on', 8442 customize_theme: api.settings.theme.stylesheet, 8443 customize_changeset_uuid: api.settings.changeset.uuid, 8444 nonce: api.settings.nonce.dismiss_autosave_or_lock, 8445 dismiss_autosave: dismissAutoSave, 8446 dismiss_lock: dismissLock 8447 } 8448 } ).always( function() { 8449 clearedToClose.resolve(); 8450 } ); 8451 } 8452 8212 8453 return clearedToClose.promise(); 8213 8454 } -
trunk/src/wp-includes/class-wp-customize-manager.php
r41825 r41839 175 175 176 176 /** 177 * Whether the autosave revision of the changeset should shouldbe loaded.177 * Whether the autosave revision of the changeset should be loaded. 178 178 * 179 179 * @since 4.9.0 … … 374 374 remove_action( 'admin_init', '_maybe_update_themes' ); 375 375 376 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); 377 add_action( 'wp_ajax_customize_trash', array( $this, 'handle_changeset_trash_request' ) ); 378 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); 379 add_action( 'wp_ajax_customize_load_themes', array( $this, 'handle_load_themes_request' ) ); 380 add_action( 'wp_ajax_customize_dismiss_autosave', array( $this, 'handle_dismiss_autosave_request' ) ); 376 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); 377 add_action( 'wp_ajax_customize_trash', array( $this, 'handle_changeset_trash_request' ) ); 378 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); 379 add_action( 'wp_ajax_customize_load_themes', array( $this, 'handle_load_themes_request' ) ); 380 add_filter( 'heartbeat_settings', array( $this, 'add_customize_screen_to_heartbeat_settings' ) ); 381 add_filter( 'heartbeat_received', array( $this, 'check_changeset_lock_with_heartbeat' ), 10, 3 ); 382 add_action( 'wp_ajax_customize_override_changeset_lock', array( $this, 'handle_override_changeset_lock_request' ) ); 383 add_action( 'wp_ajax_customize_dismiss_autosave_or_lock', array( $this, 'handle_dismiss_autosave_or_lock_request' ) ); 381 384 382 385 add_action( 'customize_register', array( $this, 'register_controls' ) ); … … 630 633 $this->_changeset_uuid = $changeset_uuid; 631 634 } 635 636 $this->set_changeset_lock( $this->changeset_post_id() ); 632 637 } 633 638 … … 1107 1112 } else { 1108 1113 if ( $this->autosaved() ) { 1109 $autosave_post = wp_get_post_autosave( $changeset_post_id );1114 $autosave_post = wp_get_post_autosave( $changeset_post_id, get_current_user_id() ); 1110 1115 if ( $autosave_post ) { 1111 1116 $data = $this->get_changeset_post_data( $autosave_post->ID ); … … 2377 2382 } 2378 2383 2384 $lock_user_id = null; 2379 2385 $autosave = ! empty( $_POST['customize_changeset_autosave'] ); 2386 if ( ! $is_new_changeset ) { 2387 $lock_user_id = wp_check_post_lock( $this->changeset_post_id() ); 2388 } 2389 2390 // Force request to autosave when changeset is locked. 2391 if ( $lock_user_id && ! $autosave ) { 2392 $autosave = true; 2393 $changeset_status = null; 2394 $changeset_date_gmt = null; 2395 } 2396 2380 2397 if ( $autosave && ! defined( 'DOING_AUTOSAVE' ) ) { // Back-compat. 2381 2398 define( 'DOING_AUTOSAVE', true ); 2382 2399 } 2383 2400 2401 $autosaved = false; 2384 2402 $r = $this->save_changeset_post( array( 2385 2403 'status' => $changeset_status, … … 2389 2407 'autosave' => $autosave, 2390 2408 ) ); 2409 if ( $autosave && ! is_wp_error( $r ) ) { 2410 $autosaved = true; 2411 } 2412 2413 // If the changeset was locked and an autosave request wasn't itself an error, then now explicitly return with a failure. 2414 if ( $lock_user_id && ! is_wp_error( $r ) ) { 2415 $r = new WP_Error( 2416 'changeset_locked', 2417 __( 'Changeset is being edited by other user.' ), 2418 array( 2419 'lock_user' => $this->get_lock_user_data( $lock_user_id ), 2420 ) 2421 ); 2422 } 2423 2391 2424 if ( is_wp_error( $r ) ) { 2392 2425 $response = array( … … 2414 2447 } 2415 2448 2449 if ( 'publish' !== $response['changeset_status'] ) { 2450 $this->set_changeset_lock( $changeset_post->ID ); 2451 } 2452 2416 2453 if ( 'future' === $response['changeset_status'] ) { 2417 2454 $response['changeset_date'] = $changeset_post->post_date; … … 2421 2458 $response['next_changeset_uuid'] = wp_generate_uuid4(); 2422 2459 } 2460 } 2461 2462 if ( $autosave ) { 2463 $response['autosaved'] = $autosaved; 2423 2464 } 2424 2465 … … 2685 2726 'type' => $setting->type, 2686 2727 'user_id' => $args['user_id'], 2728 'date_modified_gmt' => current_time( 'mysql', true ), 2687 2729 ) 2688 2730 ); … … 2799 2841 2800 2842 // Delete autosave revision when the changeset is updated. 2801 $autosave_draft = wp_get_post_autosave( $changeset_post_id );2843 $autosave_draft = wp_get_post_autosave( $changeset_post_id, get_current_user_id() ); 2802 2844 if ( $autosave_draft ) { 2803 2845 wp_delete_post( $autosave_draft->ID, true ); … … 2991 3033 2992 3034 /** 3035 * Marks the changeset post as being currently edited by the current user. 3036 * 3037 * @since 4.9.0 3038 * 3039 * @param int $changeset_post_id Changeset post id. 3040 * @param bool $take_over Take over the changeset, default is false. 3041 */ 3042 public function set_changeset_lock( $changeset_post_id, $take_over = false ) { 3043 if ( $changeset_post_id ) { 3044 $can_override = ! (bool) get_post_meta( $changeset_post_id, '_edit_lock', true ); 3045 3046 if ( $take_over ) { 3047 $can_override = true; 3048 } 3049 3050 if ( $can_override ) { 3051 $lock = sprintf( '%s:%s', time(), get_current_user_id() ); 3052 update_post_meta( $changeset_post_id, '_edit_lock', $lock ); 3053 } else { 3054 $this->refresh_changeset_lock( $changeset_post_id ); 3055 } 3056 } 3057 } 3058 3059 /** 3060 * Refreshes changeset lock with the current time if current user edited the changeset before. 3061 * 3062 * @since 4.9.0 3063 * 3064 * @param int $changeset_post_id Changeset post id. 3065 */ 3066 public function refresh_changeset_lock( $changeset_post_id ) { 3067 if ( ! $changeset_post_id ) { 3068 return; 3069 } 3070 $lock = get_post_meta( $changeset_post_id, '_edit_lock', true ); 3071 $lock = explode( ':', $lock ); 3072 3073 if ( $lock && ! empty( $lock[1] ) ) { 3074 $user_id = intval( $lock[1] ); 3075 $current_user_id = get_current_user_id(); 3076 if ( $user_id === $current_user_id ) { 3077 $lock = sprintf( '%s:%s', time(), $user_id ); 3078 update_post_meta( $changeset_post_id, '_edit_lock', $lock ); 3079 } 3080 } 3081 } 3082 3083 /** 3084 * Filter heartbeat settings for the Customizer. 3085 * 3086 * @since 4.9.0 3087 * @param array $settings Current settings to filter. 3088 * @return array Heartbeat settings. 3089 */ 3090 public function add_customize_screen_to_heartbeat_settings( $settings ) { 3091 global $pagenow; 3092 if ( 'customize.php' === $pagenow ) { 3093 $settings['screenId'] = 'customize'; 3094 } 3095 return $settings; 3096 } 3097 3098 /** 3099 * Get lock user data. 3100 * 3101 * @since 4.9.0 3102 * 3103 * @param int $user_id User ID. 3104 * @return array|null User data formatted for client. 3105 */ 3106 protected function get_lock_user_data( $user_id ) { 3107 if ( ! $user_id ) { 3108 return null; 3109 } 3110 $lock_user = get_userdata( $user_id ); 3111 if ( ! $lock_user ) { 3112 return null; 3113 } 3114 return array( 3115 'id' => $lock_user->ID, 3116 'name' => $lock_user->display_name, 3117 'avatar' => get_avatar_url( $lock_user->ID, array( 'size' => 128 ) ), 3118 ); 3119 } 3120 3121 /** 3122 * Check locked changeset with heartbeat API. 3123 * 3124 * @since 4.9.0 3125 * 3126 * @param array $response The Heartbeat response. 3127 * @param array $data The $_POST data sent. 3128 * @param string $screen_id The screen id. 3129 * @return array The Heartbeat response. 3130 */ 3131 public function check_changeset_lock_with_heartbeat( $response, $data, $screen_id ) { 3132 if ( array_key_exists( 'check_changeset_lock', $data ) && 'customize' === $screen_id && current_user_can( 'customize' ) && $this->changeset_post_id() ) { 3133 $lock_user_id = wp_check_post_lock( $this->changeset_post_id() ); 3134 3135 if ( $lock_user_id ) { 3136 $response['customize_changeset_lock_user'] = $this->get_lock_user_data( $lock_user_id ); 3137 } else { 3138 3139 // Refreshing time will ensure that the user is sitting on customizer and has not closed the customizer tab. 3140 $this->refresh_changeset_lock( $this->changeset_post_id() ); 3141 } 3142 } 3143 3144 return $response; 3145 } 3146 3147 /** 3148 * Removes changeset lock when take over request is sent via Ajax. 3149 * 3150 * @since 4.9.0 3151 */ 3152 public function handle_override_changeset_lock_request() { 3153 if ( ! $this->is_preview() ) { 3154 wp_send_json_error( 'not_preview', 400 ); 3155 } 3156 3157 if ( ! check_ajax_referer( 'customize_override_changeset_lock', 'nonce', false ) ) { 3158 wp_send_json_error( array( 3159 'code' => 'invalid_nonce', 3160 'message' => __( 'Security check failed.' ), 3161 ) ); 3162 } 3163 3164 $changeset_post_id = $this->changeset_post_id(); 3165 3166 if ( empty( $changeset_post_id ) ) { 3167 wp_send_json_error( array( 3168 'code' => 'no_changeset_found_to_take_over', 3169 'message' => __( 'No changeset found to take over' ), 3170 ) ); 3171 } 3172 3173 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id ) ) { 3174 wp_send_json_error( array( 3175 'code' => 'cannot_remove_changeset_lock', 3176 'message' => __( 'Sorry you are not allowed to take over.' ), 3177 ) ); 3178 } 3179 3180 $this->set_changeset_lock( $changeset_post_id, true ); 3181 3182 wp_send_json_success( 'changeset_taken_over' ); 3183 } 3184 3185 /** 2993 3186 * Whether a changeset revision should be made. 2994 3187 * … … 3034 3227 * @since 4.7.0 3035 3228 * @see _wp_customize_publish_changeset() 3229 * @global wpdb $wpdb 3036 3230 * 3037 3231 * @param int $changeset_post_id ID for customize_changeset post. Defaults to the changeset for the current manager instance. … … 3039 3233 */ 3040 3234 public function _publish_changeset_values( $changeset_post_id ) { 3235 global $wpdb; 3236 3041 3237 $publishing_changeset_data = $this->get_changeset_post_data( $changeset_post_id ); 3042 3238 if ( is_wp_error( $publishing_changeset_data ) ) { … … 3176 3372 $this->_changeset_uuid = $previous_changeset_uuid; 3177 3373 3374 /* 3375 * Convert all autosave revisions into their own auto-drafts so that users can be prompted to 3376 * restore them when a changeset is published, but they had been locked out from including 3377 * their changes in the changeset. 3378 */ 3379 $revisions = wp_get_post_revisions( $changeset_post_id, array( 'check_enabled' => false ) ); 3380 foreach ( $revisions as $revision ) { 3381 if ( false !== strpos( $revision->post_name, "{$changeset_post_id}-autosave" ) ) { 3382 $wpdb->update( 3383 $wpdb->posts, 3384 array( 3385 'post_status' => 'auto-draft', 3386 'post_type' => 'customize_changeset', 3387 'post_name' => wp_generate_uuid4(), 3388 'post_parent' => 0, 3389 ), 3390 array( 3391 'ID' => $revision->ID, 3392 ) 3393 ); 3394 clean_post_cache( $revision->ID ); 3395 } 3396 } 3397 3178 3398 return true; 3179 3399 } … … 3230 3450 3231 3451 /** 3232 * Delete a given auto-draft changeset or the autosave revision for a given changeset .3452 * Delete a given auto-draft changeset or the autosave revision for a given changeset or delete changeset lock. 3233 3453 * 3234 3454 * @since 4.9.0 3235 3455 */ 3236 public function handle_dismiss_autosave_ request() {3456 public function handle_dismiss_autosave_or_lock_request() { 3237 3457 if ( ! $this->is_preview() ) { 3238 3458 wp_send_json_error( 'not_preview', 400 ); 3239 3459 } 3240 3460 3241 if ( ! check_ajax_referer( 'customize_dismiss_autosave ', 'nonce', false ) ) {3461 if ( ! check_ajax_referer( 'customize_dismiss_autosave_or_lock', 'nonce', false ) ) { 3242 3462 wp_send_json_error( 'invalid_nonce', 403 ); 3243 3463 } 3244 3464 3245 3465 $changeset_post_id = $this->changeset_post_id(); 3246 3247 if ( empty( $changeset_post_id ) || 'auto-draft' === get_post_status( $changeset_post_id ) ) { 3248 $dismissed = $this->dismiss_user_auto_draft_changesets(); 3249 if ( $dismissed > 0 ) { 3250 wp_send_json_success( 'auto_draft_dismissed' ); 3251 } else { 3252 wp_send_json_error( 'no_auto_draft_to_delete', 404 ); 3253 } 3254 } else { 3255 $revision = wp_get_post_autosave( $changeset_post_id ); 3256 3257 if ( $revision ) { 3258 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->delete_post, $changeset_post_id ) ) { 3259 wp_send_json_error( 'cannot_delete_autosave_revision', 403 ); 3260 } 3261 3262 if ( ! wp_delete_post( $revision->ID, true ) ) { 3263 wp_send_json_error( 'autosave_revision_deletion_failure', 500 ); 3466 $dismiss_lock = ! empty( $_POST['dismiss_lock'] ); 3467 $dismiss_autosave = ! empty( $_POST['dismiss_autosave'] ); 3468 3469 if ( $dismiss_lock ) { 3470 if ( empty( $changeset_post_id ) && ! $dismiss_autosave ) { 3471 wp_send_json_error( 'no_changeset_to_dismiss_lock', 404 ); 3472 } 3473 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id ) && ! $dismiss_autosave ) { 3474 wp_send_json_error( 'cannot_remove_changeset_lock', 403 ); 3475 } 3476 3477 delete_post_meta( $changeset_post_id, '_edit_lock' ); 3478 3479 if ( ! $dismiss_autosave ) { 3480 wp_send_json_success( 'changeset_lock_dismissed' ); 3481 } 3482 } 3483 3484 if ( $dismiss_autosave ) { 3485 if ( empty( $changeset_post_id ) || 'auto-draft' === get_post_status( $changeset_post_id ) ) { 3486 $dismissed = $this->dismiss_user_auto_draft_changesets(); 3487 if ( $dismissed > 0 ) { 3488 wp_send_json_success( 'auto_draft_dismissed' ); 3264 3489 } else { 3265 wp_send_json_ success( 'autosave_revision_deleted');3490 wp_send_json_error( 'no_auto_draft_to_delete', 404 ); 3266 3491 } 3267 3492 } else { 3268 wp_send_json_error( 'no_autosave_revision_to_delete', 404 ); 3269 } 3270 } 3493 $revision = wp_get_post_autosave( $changeset_post_id, get_current_user_id() ); 3494 3495 if ( $revision ) { 3496 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->delete_post, $changeset_post_id ) ) { 3497 wp_send_json_error( 'cannot_delete_autosave_revision', 403 ); 3498 } 3499 3500 if ( ! wp_delete_post( $revision->ID, true ) ) { 3501 wp_send_json_error( 'autosave_revision_deletion_failure', 500 ); 3502 } else { 3503 wp_send_json_success( 'autosave_revision_deleted' ); 3504 } 3505 } else { 3506 wp_send_json_error( 'no_autosave_revision_to_delete', 404 ); 3507 } 3508 } 3509 } 3510 3271 3511 wp_send_json_error( 'unknown_error', 500 ); 3272 3512 } … … 3818 4058 </script> 3819 4059 4060 <script type="text/html" id="tmpl-customize-changeset-locked-notification"> 4061 <li class="notice notice-{{ data.type || 'info' }} {{ data.containerClasses || '' }}" data-code="{{ data.code }}" data-type="{{ data.type }}"> 4062 <div class="notification-message customize-changeset-locked-message"> 4063 <img class="customize-changeset-locked-avatar" src="{{ data.lockUser.avatar }}" alt="{{ data.lockUser.name }}"> 4064 <p class="currently-editing"> 4065 <# if ( data.message ) { #> 4066 {{{ data.message }}} 4067 <# } else if ( data.allowOverride ) { #> 4068 <?php 4069 /* translators: %s: User who is customizing the changeset in customizer. */ 4070 printf( __( '%s is already customizing this site. Do you want to take over?' ), '{{ data.lockUser.name }}' ); 4071 ?> 4072 <# } else { #> 4073 <?php 4074 /* translators: %s: User who is customizing the changeset in customizer. */ 4075 printf( __( '%s is already customizing this site. Please wait until they are done to try customizing. Your latest changes have been autosaved.' ), '{{ data.lockUser.name }}' ); 4076 ?> 4077 <# } #> 4078 </p> 4079 <p class="notice notice-error notice-alt" hidden></p> 4080 <p class="action-buttons"> 4081 <# if ( data.returnUrl !== data.previewUrl ) { #> 4082 <a class="button customize-notice-go-back-button" href="{{ data.returnUrl }}"><?php _e( 'Go back' ); ?></a> 4083 <# } #> 4084 <a class="button customize-notice-preview-button" href="{{ data.frontendPreviewUrl }}"><?php _e( 'Preview' ); ?></a> 4085 <# if ( data.allowOverride ) { #> 4086 <button class="button button-primary wp-tab-last customize-notice-take-over-button"><?php _e( 'Take over' ); ?></button> 4087 <# } #> 4088 </p> 4089 </div> 4090 </li> 4091 </script> 4092 3820 4093 <?php 3821 4094 /* The following template is obsolete in core but retained for plugins. */ … … 4189 4462 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ), 4190 4463 'switch_themes' => wp_create_nonce( 'switch_themes' ), 4191 'dismiss_autosave' => wp_create_nonce( 'customize_dismiss_autosave' ), 4464 'dismiss_autosave_or_lock' => wp_create_nonce( 'customize_dismiss_autosave_or_lock' ), 4465 'override_lock' => wp_create_nonce( 'customize_override_changeset_lock' ), 4192 4466 'trash' => wp_create_nonce( 'trash_customize_changeset' ), 4193 4467 ); … … 4232 4506 if ( ! $this->saved_starter_content_changeset && ! $this->autosaved() ) { 4233 4507 if ( $changeset_post_id ) { 4234 $autosave_revision_post = wp_get_post_autosave( $changeset_post_id );4508 $autosave_revision_post = wp_get_post_autosave( $changeset_post_id, get_current_user_id() ); 4235 4509 } else { 4236 4510 $autosave_autodraft_posts = $this->get_changeset_posts( array( … … 4276 4550 } else { 4277 4551 $initial_date = current_time( 'mysql', false ); 4552 } 4553 4554 $lock_user_id = false; 4555 if ( $this->changeset_post_id() ) { 4556 $lock_user_id = wp_check_post_lock( $this->changeset_post_id() ); 4278 4557 } 4279 4558 … … 4289 4568 'publishDate' => $initial_date, 4290 4569 'statusChoices' => $status_choices, 4570 'lockUser' => $lock_user_id ? $this->get_lock_user_data( $lock_user_id ) : null, 4291 4571 ), 4292 4572 'initialServerDate' => current_time( 'mysql', false ), -
trunk/src/wp-includes/js/heartbeat.js
r41351 r41839 368 368 }; 369 369 370 if ( 'customize' === settings.screenId ) { 371 ajaxData.wp_customize = 'on'; 372 } 373 370 374 settings.connecting = true; 371 375 settings.xhr = $.ajax({ -
trunk/src/wp-includes/script-loader.php
r41805 r41839 548 548 $scripts->add( 'customize-models', "/wp-includes/js/customize-models.js", array( 'underscore', 'backbone' ), false, 1 ); 549 549 $scripts->add( 'customize-views', "/wp-includes/js/customize-views.js", array( 'jquery', 'underscore', 'imgareaselect', 'customize-models', 'media-editor', 'media-views' ), false, 1 ); 550 $scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base', 'wp-a11y', 'wp-util' ), false, 1 );550 $scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base', 'wp-a11y', 'wp-util', 'jquery-ui-core' ), false, 1 ); 551 551 did_action( 'init' ) && $scripts->localize( 'customize-controls', '_wpCustomizeControlsL10n', array( 552 552 'activate' => __( 'Activate & Publish' ), … … 575 575 'expandSidebar' => _x( 'Show Controls', 'label for hide controls button without length constraints' ), 576 576 'untitledBlogName' => __( '(Untitled)' ), 577 ' serverSaveError' => __( 'Failed connecting to the server. Please try savingagain.' ),577 'unknownRequestFail' => __( 'Looks like something’s gone wrong. Wait a couple seconds, and then try again.' ), 578 578 'themeDownloading' => __( 'Downloading your new theme…' ), 579 579 'themePreviewWait' => __( 'Setting up your live preview. This may take a bit.' ), 580 580 'revertingChanges' => __( 'Reverting unpublished changes…' ), 581 581 'trashConfirm' => __( 'Are you sure you’d like to discard your unpublished changes?' ), 582 /* translators: %s: Display name of the user who has taken over the changeset in customizer. */ 583 'takenOverMessage' => __( '%s has taken over and is currently customizing.' ), 582 584 /* translators: %s: URL to the Customizer to load the autosaved version */ 583 585 'autosaveNotice' => __( 'There is a more recent autosave of your changes than the one you are previewing. <a href="%s">Restore the autosave</a>' ), -
trunk/tests/phpunit/tests/ajax/CustomizeManager.php
r41667 r41839 517 517 * 518 518 * @ticket 39896 519 * @covers WP_Customize_Manager::handle_dismiss_autosave_ request()519 * @covers WP_Customize_Manager::handle_dismiss_autosave_or_lock_request() 520 520 * @covers WP_Customize_Manager::dismiss_user_auto_draft_changesets() 521 521 */ 522 public function test_handle_dismiss_autosave_ request() {522 public function test_handle_dismiss_autosave_or_lock_request() { 523 523 $uuid = wp_generate_uuid4(); 524 524 $wp_customize = $this->set_up_valid_state( $uuid ); 525 525 526 $this->make_ajax_call( 'customize_dismiss_autosave ' );526 $this->make_ajax_call( 'customize_dismiss_autosave_or_lock' ); 527 527 $this->assertFalse( $this->_last_response_parsed['success'] ); 528 528 $this->assertEquals( 'invalid_nonce', $this->_last_response_parsed['data'] ); 529 529 530 $nonce = wp_create_nonce( 'customize_dismiss_autosave ' );530 $nonce = wp_create_nonce( 'customize_dismiss_autosave_or_lock' ); 531 531 $_POST['nonce'] = $_GET['nonce'] = $_REQUEST['nonce'] = $nonce; 532 $this->make_ajax_call( 'customize_dismiss_autosave' ); 532 533 $_POST['dismiss_lock'] = $_GET['dismiss_lock'] = $_REQUEST['dismiss_lock'] = true; 534 $this->make_ajax_call( 'customize_dismiss_autosave_or_lock' ); 535 $this->assertFalse( $this->_last_response_parsed['success'] ); 536 $this->assertEquals( 'no_changeset_to_dismiss_lock', $this->_last_response_parsed['data'] ); 537 538 $_POST['dismiss_autosave'] = $_GET['dismiss_autosave'] = $_REQUEST['dismiss_autosave'] = true; 539 $this->make_ajax_call( 'customize_dismiss_autosave_or_lock' ); 533 540 $this->assertFalse( $this->_last_response_parsed['success'] ); 534 541 $this->assertEquals( 'no_auto_draft_to_delete', $this->_last_response_parsed['data'] ); … … 560 567 $this->assertFalse( (bool) get_post_meta( $post_id, '_customize_restore_dismissed', true ) ); 561 568 } 562 $this->make_ajax_call( 'customize_dismiss_autosave ' );569 $this->make_ajax_call( 'customize_dismiss_autosave_or_lock' ); 563 570 $this->assertTrue( $this->_last_response_parsed['success'] ); 564 571 $this->assertEquals( 'auto_draft_dismissed', $this->_last_response_parsed['data'] ); … … 573 580 574 581 // Subsequent test results in none dismissed. 575 $this->make_ajax_call( 'customize_dismiss_autosave ' );582 $this->make_ajax_call( 'customize_dismiss_autosave_or_lock' ); 576 583 $this->assertFalse( $this->_last_response_parsed['success'] ); 577 584 $this->assertEquals( 'no_auto_draft_to_delete', $this->_last_response_parsed['data'] ); … … 586 593 'status' => 'draft', 587 594 ) ); 595 596 $_POST['dismiss_autosave'] = $_GET['dismiss_autosave'] = $_REQUEST['dismiss_autosave'] = false; 597 $this->make_ajax_call( 'customize_dismiss_autosave_or_lock' ); 598 $this->assertTrue( $this->_last_response_parsed['success'] ); 599 $this->assertEquals( 'changeset_lock_dismissed', $this->_last_response_parsed['data'] ); 600 601 $_POST['dismiss_autosave'] = $_GET['dismiss_autosave'] = $_REQUEST['dismiss_autosave'] = true; 588 602 $this->assertNotInstanceOf( 'WP_Error', $r ); 589 603 $this->assertFalse( wp_get_post_autosave( $wp_customize->changeset_post_id() ) ); … … 591 605 592 606 // Since no autosave yet, confirm no action. 593 $this->make_ajax_call( 'customize_dismiss_autosave ' );607 $this->make_ajax_call( 'customize_dismiss_autosave_or_lock' ); 594 608 $this->assertFalse( $this->_last_response_parsed['success'] ); 595 609 $this->assertEquals( 'no_autosave_revision_to_delete', $this->_last_response_parsed['data'] ); … … 611 625 612 626 // Confirm autosave gets deleted. 613 $this->make_ajax_call( 'customize_dismiss_autosave ' );627 $this->make_ajax_call( 'customize_dismiss_autosave_or_lock' ); 614 628 $this->assertTrue( $this->_last_response_parsed['success'] ); 615 629 $this->assertEquals( 'autosave_revision_deleted', $this->_last_response_parsed['data'] ); … … 617 631 618 632 // Since no autosave yet, confirm no action. 619 $this->make_ajax_call( 'customize_dismiss_autosave ' );633 $this->make_ajax_call( 'customize_dismiss_autosave_or_lock' ); 620 634 $this->assertFalse( $this->_last_response_parsed['success'] ); 621 635 $this->assertEquals( 'no_autosave_revision_to_delete', $this->_last_response_parsed['data'] ); -
trunk/tests/phpunit/tests/customize/manager.php
r41824 r41839 1456 1456 'autosave' => true, 1457 1457 ) ); 1458 $this->assertFalse( wp_get_post_autosave( $changeset_post_id ) );1458 $this->assertFalse( wp_get_post_autosave( $changeset_post_id, get_current_user_id() ) ); 1459 1459 $this->assertContains( 'Autosaved Auto-draft Title', get_post( $changeset_post_id )->post_content ); 1460 1460 … … 1494 1494 1495 1495 // Try autosave. 1496 $this->assertFalse( wp_get_post_autosave( $changeset_post_id ) );1496 $this->assertFalse( wp_get_post_autosave( $changeset_post_id, get_current_user_id() ) ); 1497 1497 $r = $wp_customize->save_changeset_post( array( 1498 1498 'data' => array( … … 1506 1506 1507 1507 // Verify that autosave happened. 1508 $autosave_revision = wp_get_post_autosave( $changeset_post_id );1508 $autosave_revision = wp_get_post_autosave( $changeset_post_id, get_current_user_id() ); 1509 1509 $this->assertInstanceOf( 'WP_Post', $autosave_revision ); 1510 1510 $this->assertContains( 'Draft Title', get_post( $changeset_post_id )->post_content ); … … 2636 2636 'publishDate', 2637 2637 'statusChoices', 2638 'lockUser', 2638 2639 ), 2639 2640 array_keys( $data['changeset'] ) -
trunk/tests/qunit/fixtures/customize-settings.js
r41626 r41839 168 168 hasAutosaveRevision: false, 169 169 latestAutoDraftUuid: '341b06f6-3c1f-454f-96df-3cf197f3e347', 170 publishDate: '' 170 publishDate: '', 171 locked: false 171 172 }, 172 173 timeouts: { -
trunk/tests/qunit/index.html
r41773 r41839 2211 2211 <script src="wp-includes/js/tinymce/tinymce-obsolete.js"></script> 2212 2212 2213 <!-- Changeset locked notice template --> 2214 <script type="text/html" id="tmpl-customize-changeset-locked-notice"> 2215 <div id="customize-changeset-lock-dialog" class="notification-dialog-wrap hidden"> 2216 <div class="notification-dialog-background"></div> 2217 <div class="notification-dialog"> 2218 <div class="customize-changeset-locked-message"> 2219 <div class="customize-changeset-locked-avatar"></div> 2220 <p class="currently-editing wp-tab-first" tabindex="0"> 2221 <span class="customize-notice-user-name"></span> <span class="customize-take-over-message">is already customizing this site. Do you want to take over?</span></p> 2222 <p> 2223 <a class="button customize-notice-go-back-button" href="/wp-admin/post.php?post=505&action=edit">Go back</a> 2224 <a class="button customize-notice-preview-button" href="http://example.org/?customize_changeset_uuid=7a796f7a-255c-49f5-9d25-cef0c315a4ba">Preview</a> 2225 <a class="button button-primary wp-tab-last customize-notice-take-over-button" href="http://example.org/wp-admin/customize.php?changeset_uuid=7a796f7a-255c-49f5-9d25-cef0c315a4ba&action=customize_take_over_changeset&nonce=e3a1df16d2&return=/wp-admin/post.php?post=505&action=edit">Take over</a> 2226 </p> 2227 </div> 2228 </div> 2229 </div> 2230 </script> 2231 2213 2232 <!-- Updates templates and HTML fixtures --> 2214 2233 <script id="tmpl-wp-updates-admin-notice" type="text/html">
Note: See TracChangeset
for help on using the changeset viewer.