Make WordPress Core

Ticket #25272: 25272.5.patch

File 25272.5.patch, 68.1 KB (added by azaozz, 11 years ago)
  • src/wp-admin/admin-ajax.php

     
    5050        'oembed-cache', 'image-editor', 'delete-comment', 'delete-tag', 'delete-link',
    5151        'delete-meta', 'delete-post', 'trash-post', 'untrash-post', 'delete-page', 'dim-comment',
    5252        'add-link-category', 'add-tag', 'get-tagcloud', 'get-comments', 'replyto-comment',
    53         'edit-comment', 'add-menu-item', 'add-meta', 'add-user', 'autosave', 'closed-postboxes',
     53        'edit-comment', 'add-menu-item', 'add-meta', 'add-user', 'closed-postboxes',
    5454        'hidden-columns', 'update-welcome-panel', 'menu-get-metabox', 'wp-link-ajax',
    5555        'menu-locations-save', 'menu-quick-search', 'meta-box-order', 'get-permalink',
    5656        'sample-permalink', 'inline-save', 'inline-save-tax', 'find_posts', 'widgets-order',
  • src/wp-admin/edit-form-advanced.php

     
    402402
    403403echo $form_extra;
    404404
    405 wp_nonce_field( 'autosave', 'autosavenonce', false );
    406405wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false );
    407406wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false );
    408407?>
  • src/wp-admin/includes/ajax-actions.php

     
    10891089        $x->send();
    10901090}
    10911091
    1092 function wp_ajax_autosave() {
    1093         define( 'DOING_AUTOSAVE', true );
    1094 
    1095         check_ajax_referer( 'autosave', 'autosavenonce' );
    1096 
    1097         if ( ! empty( $_POST['catslist'] ) )
    1098                 $_POST['post_category'] = explode( ',', $_POST['catslist'] );
    1099         if ( $_POST['post_type'] == 'page' || empty( $_POST['post_category'] ) )
    1100                 unset( $_POST['post_category'] );
    1101 
    1102         $data = '';
    1103         $supplemental = array();
    1104         $id = $revision_id = 0;
    1105 
    1106         $post_id = (int) $_POST['post_id'];
    1107         $_POST['ID'] = $_POST['post_ID'] = $post_id;
    1108         $post = get_post( $post_id );
    1109         if ( empty( $post->ID ) || ! current_user_can( 'edit_post', $post->ID ) )
    1110                 wp_die( __( 'You are not allowed to edit this post.' ) );
    1111 
    1112         if ( 'page' == $post->post_type && ! current_user_can( 'edit_page', $post->ID ) )
    1113                 wp_die( __( 'You are not allowed to edit this page.' ) );
    1114 
    1115         if ( 'auto-draft' == $post->post_status )
    1116                 $_POST['post_status'] = 'draft';
    1117 
    1118         if ( ! empty( $_POST['autosave'] ) ) {
    1119                 if ( ! wp_check_post_lock( $post->ID ) && get_current_user_id() == $post->post_author && ( 'auto-draft' == $post->post_status || 'draft' == $post->post_status ) ) {
    1120                         // Drafts and auto-drafts are just overwritten by autosave for the same user if the post is not locked
    1121                         $id = edit_post();
    1122                 } else {
    1123                         // Non drafts or other users drafts are not overwritten. The autosave is stored in a special post revision for each user.
    1124                         $revision_id = wp_create_post_autosave( $post->ID );
    1125                         if ( is_wp_error($revision_id) )
    1126                                 $id = $revision_id;
    1127                         else
    1128                                 $id = $post->ID;
    1129                 }
    1130 
    1131                 if ( ! is_wp_error($id) ) {
    1132                         /* translators: draft saved date format, see http://php.net/date */
    1133                         $draft_saved_date_format = __('g:i:s a');
    1134                         /* translators: %s: date and time */
    1135                         $data = sprintf( __('Draft saved at %s.'), date_i18n( $draft_saved_date_format ) );
    1136                 }
    1137         } else {
    1138                 if ( ! empty( $_POST['auto_draft'] ) )
    1139                         $id = 0; // This tells us it didn't actually save
    1140                 else
    1141                         $id = $post->ID;
    1142         }
    1143 
    1144         // @todo Consider exposing any errors, rather than having 'Saving draft...'
    1145         $x = new WP_Ajax_Response( array(
    1146                 'what' => 'autosave',
    1147                 'id' => $id,
    1148                 'data' => $data,
    1149                 'supplemental' => $supplemental
    1150         ) );
    1151         $x->send();
    1152 }
    1153 
    11541092function wp_ajax_closed_postboxes() {
    11551093        check_ajax_referer( 'closedpostboxes', 'closedpostboxesnonce' );
    11561094        $closed = isset( $_POST['closed'] ) ? explode( ',', $_POST['closed']) : array();
  • src/wp-admin/includes/misc.php

     
    734734                if ( 2 === wp_verify_nonce( $received['post_nonce'], 'update-post_' . $post_id ) ) {
    735735                        $response['wp-refresh-post-nonces'] = array(
    736736                                'replace' => array(
    737                                         'autosavenonce' => wp_create_nonce('autosave'),
    738737                                        'getpermalinknonce' => wp_create_nonce('getpermalink'),
    739738                                        'samplepermalinknonce' => wp_create_nonce('samplepermalink'),
    740739                                        'closedpostboxesnonce' => wp_create_nonce('closedpostboxes'),
     
    768767        return $settings;
    769768}
    770769add_filter( 'heartbeat_settings', 'wp_heartbeat_set_suspension' );
     770/**
     771 * Autosave with heartbeat
     772 *
     773 * @since 3.8
     774 */
     775function heartbeat_autosave( $response, $data ) {
     776        if ( ! empty( $data['wp_autosave'] ) ) {
     777                $saved = wp_autosave( $data['wp_autosave'] );
     778
     779                if ( is_wp_error( $saved ) ) {
     780                        $response['wp_autosave'] = array( 'success' => false, 'message' => $saved->get_error_message() );
     781                } elseif ( empty( $saved ) ) {
     782                        $response['wp_autosave'] = array( 'success' => false, 'message' => __( 'Error while saving.' ) );
     783                } else {
     784                        /* translators: draft saved date format, see http://php.net/date */
     785                        $draft_saved_date_format = __( 'g:i:s a' );
     786                        /* translators: %s: date and time */
     787                        $response['wp_autosave'] = array( 'success' => true, 'message' => sprintf( __( 'Draft saved at %s.' ), date_i18n( $draft_saved_date_format ) ) );
     788                }
     789        }
     790
     791        return $response;
     792}
     793// Run later as we have to set DOING_AUTOSAVE for back-compat
     794add_filter( 'heartbeat_received', 'heartbeat_autosave', 50, 2 );
  • src/wp-admin/includes/post.php

     
    7979                }
    8080        }
    8181
    82         if ( ! empty( $post_data['post_status'] ) )
     82        if ( ! empty( $post_data['post_status'] ) ) {
    8383                $post_data['post_status'] = sanitize_key( $post_data['post_status'] );
    8484
     85                // No longer an auto-draft
     86                if ( 'auto-draft' == $post_data['post_status'] )
     87                        $post_data['post_status'] = 'draft';
     88        }
     89
    8590        // What to do based on which button they pressed
    8691        if ( isset($post_data['saveasdraft']) && '' != $post_data['saveasdraft'] )
    8792                $post_data['post_status'] = 'draft';
     
    190195        $post_data = _wp_translate_postdata( true, $post_data );
    191196        if ( is_wp_error($post_data) )
    192197                wp_die( $post_data->get_error_message() );
    193         if ( ( empty( $post_data['action'] ) || 'autosave' != $post_data['action'] ) && 'auto-draft' == $post_data['post_status'] ) {
    194                 $post_data['post_status'] = 'draft';
    195         }
    196198
    197199        if ( isset($post_data['visibility']) ) {
    198200                switch ( $post_data['visibility'] ) {
     
    13351337 * @uses _wp_translate_postdata()
    13361338 * @uses _wp_post_revision_fields()
    13371339 *
    1338  * @return unknown
     1340 * @param mixed $post_data Associative array containing the post data or int post ID.
     1341 * @return mixed The autosave revision ID. WP_Error or 0 on error.
    13391342 */
    1340 function wp_create_post_autosave( $post_id ) {
    1341         $translated = _wp_translate_postdata( true );
    1342         if ( is_wp_error( $translated ) )
    1343                 return $translated;
     1343function wp_create_post_autosave( $post_data ) {
     1344        if ( is_numeric( $post_data ) ) {
     1345                $post_id = $post_data;
     1346                $post_data = &$_POST;
     1347        } else {
     1348                $post_id = (int) $post_data['post_ID'];
     1349        }
    13441350
     1351        $post_data = _wp_translate_postdata( true, $post_data );
     1352        if ( is_wp_error( $post_data ) )
     1353                return $post_data;
     1354
    13451355        $post_author = get_current_user_id();
    13461356
    13471357        // Store one autosave per author. If there is already an autosave, overwrite it.
    13481358        if ( $old_autosave = wp_get_post_autosave( $post_id, $post_author ) ) {
    1349                 $new_autosave = _wp_post_revision_fields( $_POST, true );
     1359                $new_autosave = _wp_post_revision_fields( $post_data, true );
    13501360                $new_autosave['ID'] = $old_autosave->ID;
    13511361                $new_autosave['post_author'] = $post_author;
    13521362
    1353                 // If the new autosave is the same content as the post, delete the old autosave.
     1363                // If the new autosave has the same content as the post, delete the autosave.
    13541364                $post = get_post( $post_id );
    13551365                $autosave_is_different = false;
    13561366                foreach ( array_keys( _wp_post_revision_fields() ) as $field ) {
     
    13621372
    13631373                if ( ! $autosave_is_different ) {
    13641374                        wp_delete_post_revision( $old_autosave->ID );
    1365                         return;
     1375                        return 0;
    13661376                }
    13671377
    13681378                return wp_update_post( $new_autosave );
     
    13691379        }
    13701380
    13711381        // _wp_put_post_revision() expects unescaped.
    1372         $post_data = wp_unslash( $_POST );
     1382        $post_data = wp_unslash( $post_data );
    13731383
    13741384        // Otherwise create the new autosave as a special post revision
    13751385        return _wp_put_post_revision( $post_data, true );
     
    13951405function post_preview() {
    13961406
    13971407        $post_ID = (int) $_POST['post_ID'];
    1398         $status = get_post_status( $post_ID );
    1399         if ( 'auto-draft' == $status )
    1400                 wp_die( __('Preview not available. Please save as a draft first.') );
     1408        $_POST['ID'] = $post_ID;
    14011409
    1402         if ( isset($_POST['catslist']) )
    1403                 $_POST['post_category'] = explode(",", $_POST['catslist']);
     1410        if ( ! $post = get_post( $post_ID ) )
     1411                wp_die( __('You attempted to preview a non existing item.') );
    14041412
    1405         if ( isset($_POST['tags_input']) )
    1406                 $_POST['tags_input'] = explode(",", $_POST['tags_input']);
     1413        if ( ! current_user_can( 'edit_post', $post->ID ) )
     1414                wp_die( __('You are not allowed to preview this item.') );
    14071415
    1408         if ( $_POST['post_type'] == 'page' || empty($_POST['post_category']) )
    1409                 unset($_POST['post_category']);
     1416        $is_autosave = false;
    14101417
    1411         $_POST['ID'] = $post_ID;
    1412         $post = get_post($post_ID);
    1413 
    1414         if ( 'page' == $post->post_type ) {
    1415                 if ( ! current_user_can('edit_page', $post_ID) )
    1416                         wp_die( __('You are not allowed to edit this page.') );
     1418        if ( ! wp_check_post_lock( $post->ID ) && get_current_user_id() == $post->post_author && ( 'draft' == $post->post_status || 'auto-draft' == $post->post_status ) ) {
     1419                $saved_post_id = edit_post();
    14171420        } else {
    1418                 if ( ! current_user_can('edit_post', $post_ID) )
    1419                         wp_die( __('You are not allowed to edit this post.') );
    1420         }
     1421                $is_autosave = true;
    14211422
    1422         $user_id = get_current_user_id();
    1423         $locked = wp_check_post_lock( $post->ID );
    1424         if ( ! $locked && 'draft' == $post->post_status && $user_id == $post->post_author ) {
    1425                 $id = edit_post();
    1426         } else { // Non drafts are not overwritten. The autosave is stored in a special post revision.
    1427                 $id = wp_create_post_autosave( $post->ID );
    1428                 if ( ! is_wp_error($id) )
    1429                         $id = $post->ID;
     1423                if ( 'auto-draft' == $_POST['post_status'] )
     1424                        $_POST['post_status'] = 'draft';
     1425
     1426                $saved_post_id = wp_create_post_autosave( $post->ID );
    14301427        }
    14311428
    1432         if ( is_wp_error($id) )
    1433                 wp_die( $id->get_error_message() );
     1429        if ( is_wp_error( $saved_post_id ) )
     1430                wp_die( $saved_post_id->get_error_message() );
    14341431
    1435         if ( ! $locked && $_POST['post_status'] == 'draft' && $user_id == $post->post_author ) {
    1436                 $url = add_query_arg( 'preview', 'true', get_permalink($id) );
    1437         } else {
    1438                 $nonce = wp_create_nonce('post_preview_' . $id);
    1439                 $args = array(
    1440                         'preview' => 'true',
    1441                         'preview_id' => $id,
    1442                         'preview_nonce' => $nonce,
    1443                 );
     1432        $query_args = array( 'preview' => 'true' );
    14441433
     1434        if ( $is_autosave && $saved_post_id ) {
     1435                $query_args['preview_id'] = $post->ID;
     1436                $query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $post->ID );
     1437
    14451438                if ( isset( $_POST['post_format'] ) )
    1446                         $args['post_format'] = empty( $_POST['post_format'] ) ? 'standard' : sanitize_key( $_POST['post_format'] );
    1447 
    1448                 $url = add_query_arg( $args, get_permalink($id) );
     1439                        $query_args['post_format'] = empty( $_POST['post_format'] ) ? 'standard' : sanitize_key( $_POST['post_format'] );
    14491440        }
    14501441
     1442        $url = add_query_arg( $query_args, get_permalink( $post->ID ) );
    14511443        return apply_filters( 'preview_post_link', $url );
    14521444}
     1445
     1446/**
     1447 * Save a post submitted with XHR
     1448 *
     1449 * Intended for use with heartbeat and autosave.js
     1450 *
     1451 * @since 3.8
     1452 *
     1453 * @param $post_data Associative array of the submitted post data.
     1454 * @return mixed The value 0 or WP_Error on failure. The saved post ID on success.
     1455 *               Te ID can be the draft post_id or the autosave revision post_id.
     1456 */
     1457function wp_autosave( $post_data ) {
     1458        // Back-compat
     1459        if ( ! defined( 'DOING_AUTOSAVE' ) )
     1460                define( 'DOING_AUTOSAVE', true );
     1461
     1462        $post_id = (int) $post_data['post_id'];
     1463        $post_data['ID'] = $post_data['post_ID'] = $post_id;
     1464
     1465        if ( false === wp_verify_nonce( $post_data['_wpnonce'], 'update-post_' . $post_id ) )
     1466                return new WP_Error( 'invalid_nonce', __('ERROR: invalid post data.') );
     1467
     1468        $post = get_post( $post_id );
     1469
     1470        if ( ! current_user_can( 'edit_post', $post->ID ) )
     1471                return new WP_Error( 'edit_post', __('You are not allowed to edit this item.') );
     1472
     1473        if ( 'auto-draft' == $post->post_status )
     1474                $post_data['post_status'] = 'draft';
     1475
     1476        if ( $post_data['post_type'] != 'page' && ! empty( $post_data['catslist'] ) )
     1477                $post_data['post_category'] = explode( ',', $post_data['catslist'] );
     1478
     1479        if ( ! wp_check_post_lock( $post->ID ) && get_current_user_id() == $post->post_author && ( 'auto-draft' == $post->post_status || 'draft' == $post->post_status ) ) {
     1480                // Drafts and auto-drafts are just overwritten by autosave for the same user if the post is not locked
     1481                return edit_post( $post_data );
     1482        } else {
     1483                // Non drafts or other users drafts are not overwritten. The autosave is stored in a special post revision for each user.
     1484                return wp_create_post_autosave( $post_data );
     1485        }
     1486}
  • src/wp-admin/js/post.js

     
    11/* global postL10n, ajaxurl, wpAjax, setPostThumbnailL10n, postboxes, pagenow, tinymce, alert, deleteUserSetting, getUserSetting, setUserSetting */
    22/* global theList:true, theExtraList:true, autosave:true */
    33
    4 var tagBox, commentsBox, editPermalink, makeSlugeditClickable, WPSetThumbnailHTML, WPSetThumbnailID, WPRemoveThumbnail, wptitlehint;
     4var tagBox, commentsBox, WPSetThumbnailHTML, WPSetThumbnailID, WPRemoveThumbnail, wptitlehint;
     5// Back-compat: prevent fatal errors
     6makeSlugeditClickable = editPermalink = function(){};
    57
    68// return an array with any duplicate, whitespace or values removed
    79function array_unique_noempty(a) {
     
    268270                send.lock = lock;
    269271
    270272        data['wp-refresh-post-lock'] = send;
    271 });
    272273
    273 // Post locks: update the lock string or show the dialog if somebody has taken over editing
    274 $(document).on( 'heartbeat-tick.refresh-lock', function( e, data ) {
     274}).on( 'heartbeat-tick.refresh-lock', function( e, data ) {
     275        // Post locks: update the lock string or show the dialog if somebody has taken over editing
    275276        var received, wrap, avatar;
    276277
    277278        if ( data['wp-refresh-post-lock'] ) {
     
    282283                        wrap = $('#post-lock-dialog');
    283284
    284285                        if ( wrap.length && ! wrap.is(':visible') ) {
    285                                 if ( typeof autosave == 'function' ) {
    286                                         $(document).on('autosave-disable-buttons.post-lock', function() {
    287                                                 wrap.addClass('saving');
    288                                         }).on('autosave-enable-buttons.post-lock', function() {
     286                                if ( typeof wp != 'undefined' && wp.autosave ) {
     287                                        // Save the latest changes and disable
     288                                        $(document).one( 'heartbeat-tick', function() {
     289                                                wp.autosave.server.disable();
    289290                                                wrap.removeClass('saving').addClass('saved');
    290                                                 window.onbeforeunload = null;
     291                                                $(window).off( 'beforeunload.autosave' );
    291292                                        });
    292293
    293                                         // Save the latest changes and disable
    294                                         if ( ! autosave() )
    295                                                 window.onbeforeunload = null;
    296 
    297                                         autosave = function(){};
     294                                        wrap.addClass('saving');
     295                                        wp.autosave.server.triggerSave();
    298296                                }
    299297
    300298                                if ( received.lock_error.avatar_src ) {
     
    309307                        $('#active_post_lock').val( received.new_lock );
    310308                }
    311309        }
     310}).on( 'after-autosave.update-post-slug', function() {
     311        // create slug area only if not already there
     312        if ( ! $('#edit-slug-box > *').length ) {
     313                $.post( ajaxurl, {
     314                                action: 'sample-permalink',
     315                                post_id: $('#post_ID').val(),
     316                                new_title: typeof fullscreen != 'undefined' && fullscreen.settings.visible ? $('#wp-fullscreen-title').val() : $('#title').val(),
     317                                samplepermalinknonce: $('#samplepermalinknonce').val()
     318                        },
     319                        function( data ) {
     320                                if ( data != '-1' ) {
     321                                        $('#edit-slug-box').html(data);
     322                                }
     323                        }
     324                );
     325        }
    312326});
    313327
    314328}(jQuery));
     
    354368}(jQuery));
    355369
    356370jQuery(document).ready( function($) {
    357         var stamp, visibility, updateVisibility, updateText,
    358                 sticky = '', last = 0, co = $('#content');
     371        var stamp, visibility, $submitButtons,
     372                sticky = '',
     373                last = 0,
     374                co = $('#content'),
     375                $editSlugWrap = $('#edit-slug-box'),
     376                postId = $('#post_ID').val() || 0,
     377                $submitpost = $('#submitpost'),
     378                releaseLock = true;
    359379
    360380        postboxes.add_postbox_toggles(pagenow);
    361381
     
    380400                wp.heartbeat.interval( 15 );
    381401        }
    382402
     403        // The form is being submitted by the user
     404        $submitButtons = $submitpost.find( ':button, :submit, a.submitdelete, #post-preview' ).on( 'click.autosave', function( event ) {
     405                var $button = $(this);
     406
     407                if ( $button.prop('disabled') ) {
     408                        event.preventDefault();
     409                        return;
     410                }
     411
     412                if ( $button.hasClass('submitdelete') ) {
     413                        return;
     414                }
     415
     416                // The form submission can be blocked from JS or by using HTML 5.0 validation on some fields.
     417                // Run this only on an actual 'submit'.
     418                $('form#post').off( 'submit.edit-post' ).on( 'submit.edit-post', function( event ) {
     419                        if ( event.isDefaultPrevented() ) {
     420                                return;
     421                        }
     422
     423                        wp.autosave.server.disable();
     424                        releaseLock = false;
     425                        $(window).off( 'beforeunload.edit-post' );
     426
     427                        $submitButtons.prop( 'disabled', true ).addClass( 'button-disabled' );
     428
     429                        if ( $button.attr('id') === 'publish' ) {
     430                                $submitpost.find('#major-publishing-actions .spinner').show();
     431                        } else {
     432                                $submitpost.find('#minor-publishing .spinner').show();
     433                        }
     434                });
     435        });
     436
     437        // Submit the form saving a draft or an autosave, and show a preview in a new tab
     438        $('#post-preview').on( 'click.post-preview', function( event ) {
     439                var $this = $(this),
     440                        $form = $('form#post'),
     441                        $previewField = $('input#wp-preview'),
     442                        target = $this.attr('target') || 'wp-preview',
     443                        ua = navigator.userAgent.toLowerCase();
     444               
     445                event.preventDefault();
     446
     447                if ( $this.prop('disabled') ) {
     448                        return;
     449                }
     450
     451                if ( typeof wp != 'undefined' && wp.autosave ) {
     452                        wp.autosave.server.tempBlockSave();
     453                }
     454
     455                $previewField.val('dopreview');
     456                $form.attr( 'target', target ).submit().attr( 'target', '' );
     457
     458                // Workaround for WebKit bug preventing a form submitting twice to the same action.
     459                // https://bugs.webkit.org/show_bug.cgi?id=28633
     460                if ( ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1 ) {
     461                        $form.attr( 'action', function( index, value ) {
     462                                return value + '?t=' + ( new Date() ).getTime();
     463                        });
     464                }
     465
     466                $previewField.val('');
     467        });
     468
     469        // This code is meant to allow tabbing from Title to Post content.
     470        $('#title').on( 'keydown.editor-focus', function( event ) {
     471                var editor;
     472
     473                if ( event.keyCode === 9 && ! event.ctrlKey && ! event.altKey && ! event.shiftKey ) {
     474                        editor = typeof tinymce != 'undefined' && tinymce.get('content');
     475
     476                        if ( editor && ! editor.isHidden() ) {
     477                                editor.focus();
     478                        } else {
     479                                $('#content').focus();
     480                        }
     481
     482                        event.preventDefault();
     483                }
     484        });
     485
     486        // Autosave new posts after a title is typed
     487        if ( $( '#auto_draft' ).val() ) {
     488                $( '#title' ).blur( function() {
     489                        if ( ! this.value || $( '#auto_draft' ).val() !== '1' ) {
     490                                return;
     491                        }
     492
     493                        if ( typeof wp != 'undefined' && wp.autosave ) {
     494                                wp.autosave.server.triggerSave();
     495                        }
     496                });
     497        }
     498
     499        $(document).on( 'autosave-disable-buttons.edit-post', function() {
     500                $submitButtons.prop( 'disabled', true ).addClass( 'button-disabled' );
     501        }).on( 'autosave-enable-buttons.edit-post', function() {
     502                if ( ! window.wp || ! window.wp.heartbeat || ! window.wp.heartbeat.hasConnectionError() ) {
     503                        $submitButtons.prop( 'disabled', false ).removeClass( 'button-disabled' );
     504                }
     505        });
     506
     507        $(window).on( 'beforeunload.edit-post', function() {
     508                var editor = typeof tinymce != 'undefined' && tinymce.get('content');
     509
     510                if ( ( editor && ! editor.isHidden() && editor.isDirty() ) ||
     511                        ( typeof wp !== 'undefined' && wp.autosave && wp.autosave.server.textareaValueChanged() ) ) {
     512
     513                        return autosaveL10n.saveAlert;
     514                }
     515        }).on( 'unload.edit-post', function( event ) {
     516                if ( ! releaseLock ) {
     517                        return;
     518                }
     519
     520                // Unload is triggered (by hand) on removing the Thickbox iframe.
     521                // Make sure we process only the main document unload.
     522                if ( event.target && event.target.nodeName != '#document' ) {
     523                        return;
     524                }
     525
     526                $.ajax({
     527                        type: 'POST',
     528                        url: ajaxurl,
     529                        async: false,
     530                        data: {
     531                                action: 'wp-remove-post-lock',
     532                                _wpnonce: $('#_wpnonce').val(),
     533                                post_ID: $('#post_ID').val(),
     534                                active_post_lock: $('#active_post_lock').val()
     535                        }
     536                });
     537        });
     538
    383539        // multi-taxonomies
    384540        if ( $('#tagsdiv-post_tag').length ) {
    385541                tagBox.init();
     
    689845        } // end submitdiv
    690846
    691847        // permalink
    692         if ( $('#edit-slug-box').length ) {
    693                 editPermalink = function(post_id) {
    694                         var slug_value, i,
    695                                 c = 0,
    696                                 e = $( '#editable-post-name' ),
    697                                 revert_e = e.html(),
    698                                 real_slug = $( '#post_name' ),
    699                                 revert_slug = real_slug.val(),
    700                                 b = $( '#edit-slug-buttons' ),
    701                                 revert_b = b.html(),
    702                                 full = $( '#editable-post-name-full' ).html();
     848        if ( $editSlugWrap.length ) {
     849                function editPermalink() {
     850                        var i, c = 0, e = $('#editable-post-name'), revert_e = e.html(), real_slug = $('#post_name'),
     851                                revert_slug = real_slug.val(), b = $('#edit-slug-buttons'), revert_b = b.html(),
     852                                full = $('#editable-post-name-full').html();
    703853
    704854                        $('#view-post-btn').hide();
    705855                        b.html('<a href="#" class="save button button-small">'+postL10n.ok+'</a> <a class="cancel" href="#">'+postL10n.cancel+'</a>');
     
    706856                        b.children('.save').click(function() {
    707857                                var new_slug = e.children('input').val();
    708858                                if ( new_slug == $('#editable-post-name-full').text() ) {
    709                                         return $('.cancel', '#edit-slug-buttons').click();
     859                                        return $('#edit-slug-buttons .cancel').click();
    710860                                }
    711861                                $.post(ajaxurl, {
    712862                                        action: 'sample-permalink',
    713                                         post_id: post_id,
     863                                        post_id: postId,
    714864                                        new_slug: new_slug,
    715865                                        new_title: $('#title').val(),
    716866                                        samplepermalinknonce: $('#samplepermalinknonce').val()
     
    724874                                        }
    725875                                        b.html(revert_b);
    726876                                        real_slug.val(new_slug);
    727                                         makeSlugeditClickable();
    728877                                        $('#view-post-btn').show();
    729878                                });
    730879                                return false;
    731880                        });
    732881
    733                         $('.cancel', '#edit-slug-buttons').click(function() {
     882                        $('#edit-slug-buttons .cancel').click(function() {
    734883                                $('#view-post-btn').show();
    735884                                e.html(revert_e);
    736885                                b.html(revert_b);
     
    760909                        }).focus();
    761910                };
    762911
    763                 makeSlugeditClickable = function() {
    764                         $('#editable-post-name').click(function() {
    765                                 $('#edit-slug-buttons').children('.edit-slug').click();
    766                         });
    767                 };
    768                 makeSlugeditClickable();
     912                $editSlugWrap.on( 'click', function( event ) {
     913                        var $target = $( event.target );
     914
     915                        if ( $target.is('#editable-post-name') || $target.hasClass('edit-slug') ) {
     916                                editPermalink();
     917                        }
     918                });
    769919        }
    770920
    771921        // word count
  • src/wp-admin/post.php

     
    303303        break;
    304304
    305305case 'preview':
    306         check_admin_referer( 'autosave', 'autosavenonce' );
     306        check_admin_referer( 'update-post_' . $post_id );
    307307
    308308        $url = post_preview();
    309309
  • src/wp-includes/js/autosave.js

     
    1 /* global switchEditors, autosaveL10n, tinymce, ajaxurl, wpAjax, makeSlugeditClickable, wpCookies */
    2 var autosave, autosavePeriodical, fullscreen, doPreview,
    3         autosaveLast = '',
    4         autosaveDelayPreview = false,
    5         notSaved = true,
    6         blockSave = false,
    7         autosaveLockRelease = true;
     1/* global tinymce, wpCookies, autosaveL10n, switchEditors */
     2// Back-compat: prevent fatal errors
     3window.autosave = function(){};
    84
    9 jQuery(document).ready( function($) {
     5( function( $, window ) {
     6        function autosave() {
     7                var initialCompareString,
     8                lastTriggerSave = 0,
     9                isSuspended = false,
     10                $document = $(document);
    1011
    11         if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' ) {
    12                 autosaveLast = wp.autosave.getCompareString({
    13                         post_title : $('#title').val() || '',
    14                         content : switchEditors.pre_wpautop( $('#content').val() ) || '',
    15                         excerpt : $('#excerpt').val() || ''
    16                 });
    17         } else {
    18                 autosaveLast = wp.autosave.getCompareString();
    19         }
     12                /**
     13                 * Returns the data saved in both local and remote autosave
     14                 *
     15                 * @return object Object containing the post data
     16                 */
     17                function getPostData( type ) {
     18                        var post_name, parent_id, data,
     19                                time = ( new Date() ).getTime(),
     20                                cats = [],
     21                                editor = typeof tinymce !== 'undefined' && tinymce.get('content');
    2022
    21         autosavePeriodical = $.schedule({time: autosaveL10n.autosaveInterval * 1000, func: function() { autosave(); }, repeat: true, protect: true});
     23                        // Don't run editor.save() more often than every 3 sec.
     24                        // It is resource intensive and might slow down typing in long posts on slow devices.
     25                        if ( editor && ! editor.isHidden() && time - 3000 > lastTriggerSave ) {
     26                                editor.save();
     27                                lastTriggerSave = time;
     28                        }
    2229
    23         //Disable autosave after the form has been submitted
    24         $('#post').submit(function() {
    25                 $.cancel(autosavePeriodical);
    26                 autosaveLockRelease = false;
    27         });
     30                        data = {
     31                                post_id: $( '#post_ID' ).val() || 0,
     32                                post_type: $( '#post_type' ).val() || '',
     33                                post_author: $( '#post_author' ).val() || '',
     34                                post_title: $( '#title' ).val() || '',
     35                                content: $( '#content' ).val() || '',
     36                                excerpt: $( '#excerpt' ).val() || ''
     37                        };
    2838
    29         $('input[type="submit"], a.submitdelete', '#submitpost').click(function(){
    30                 blockSave = true;
    31                 window.onbeforeunload = null;
    32                 $(':button, :submit', '#submitpost').each(function(){
    33                         var t = $(this);
    34                         if ( t.hasClass('button-primary') )
    35                                 t.addClass('button-primary-disabled');
    36                         else
    37                                 t.addClass('button-disabled');
    38                 });
    39                 if ( $(this).attr('id') == 'publish' )
    40                         $('#major-publishing-actions .spinner').show();
    41                 else
    42                         $('#minor-publishing .spinner').show();
    43         });
     39                        if ( type === 'local' ) {
     40                                return data;
     41                        }
    4442
    45         window.onbeforeunload = function(){
    46                 var editor = typeof(tinymce) != 'undefined' ? tinymce.activeEditor : false;
     43                        $( 'input[id^="in-category-"]:checked' ).each( function() {
     44                                cats.push( this.value );
     45                        });
     46                        data.catslist = cats.join(',');
    4747
    48                 if ( editor && ! editor.isHidden() ) {
    49                         if ( editor.isDirty() )
    50                                 return autosaveL10n.saveAlert;
    51                 } else {
    52                         if ( wp.autosave.getCompareString() != autosaveLast )
    53                                 return autosaveL10n.saveAlert;
    54                 }
    55         };
     48                        if ( post_name = $( '#post_name' ).val() ) {
     49                                data.post_name = post_name;
     50                        }
    5651
    57         $(window).unload( function(e) {
    58                 if ( ! autosaveLockRelease )
    59                         return;
     52                        if ( parent_id = $( '#parent_id' ).val() ) {
     53                                data.parent_id = parent_id;
     54                        }
    6055
    61                 // unload fires (twice) on removing the Thickbox iframe. Make sure we process only the main document unload.
    62                 if ( e.target && e.target.nodeName != '#document' )
    63                         return;
     56                        if ( $( '#comment_status' ).prop( 'checked' ) ) {
     57                                data.comment_status = 'open';
     58                        }
    6459
    65                 $.ajax({
    66                         type: 'POST',
    67                         url: ajaxurl,
    68                         async: false,
    69                         data: {
    70                                 action: 'wp-remove-post-lock',
    71                                 _wpnonce: $('#_wpnonce').val(),
    72                                 post_ID: $('#post_ID').val(),
    73                                 active_post_lock: $('#active_post_lock').val()
     60                        if ( $( '#ping_status' ).prop( 'checked' ) ) {
     61                                data.ping_status = 'open';
    7462                        }
    75                 });
    76         } );
    7763
    78         // preview
    79         $('#post-preview').click(function(){
    80                 if ( $('#auto_draft').val() == '1' && notSaved ) {
    81                         autosaveDelayPreview = true;
    82                         autosave();
    83                         return false;
    84                 }
    85                 doPreview();
    86                 return false;
    87         });
     64                        if ( $( '#auto_draft' ).val() === '1' ) {
     65                                data.auto_draft = '1';
     66                        }
    8867
    89         doPreview = function() {
    90                 $('input#wp-preview').val('dopreview');
    91                 $('form#post').attr('target', 'wp-preview').submit().attr('target', '');
    92 
    93                 /*
    94                  * Workaround for WebKit bug preventing a form submitting twice to the same action.
    95                  * https://bugs.webkit.org/show_bug.cgi?id=28633
    96                  */
    97                 var ua = navigator.userAgent.toLowerCase();
    98                 if ( ua.indexOf('safari') != -1 && ua.indexOf('chrome') == -1 ) {
    99                         $('form#post').attr('action', function(index, value) {
    100                                 return value + '?t=' + new Date().getTime();
    101                         });
     68                        return data;
    10269                }
    10370
    104                 $('input#wp-preview').val('');
    105         };
    106 
    107         // This code is meant to allow tabbing from Title to Post content.
    108         $('#title').on( 'keydown.editor-focus', function( event ) {
    109                 var editor;
    110 
    111                 if ( event.which === 9 && ! event.ctrlKey && ! event.altKey && ! event.shiftKey ) {
    112                         if ( typeof tinymce !== 'undefined' ) {
    113                                 editor = tinymce.get('content');
     71                // Concatenate title, content and excerpt. Used to track changes when auto-saving.
     72                function getCompareString( postData ) {
     73                        if ( typeof postData === 'object' ) {
     74                                return ( postData.post_title || '' ) + '::' + ( postData.content || '' ) + '::' + ( postData.excerpt || '' );
    11475                        }
    11576
    116                         if ( editor && ! editor.isHidden() ) {
    117                                 $(this).one( 'keyup', function() {
    118                                         editor.focus();
    119                                 });
    120                         } else {
    121                                 $('#content').focus();
    122                         }
     77                        return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' );
     78                }
    12379
    124                         event.preventDefault();
     80                function disableButtons() {
     81                        $document.trigger('autosave-disable-buttons');
     82                        // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
     83                        setTimeout( enableButtons, 5000 );
    12584                }
    126         });
    12785
    128         // autosave new posts after a title is typed but not if Publish or Save Draft is clicked
    129         if ( '1' == $('#auto_draft').val() ) {
    130                 $('#title').blur( function() {
    131                         if ( !this.value || $('#auto_draft').val() != '1' )
    132                                 return;
    133                         delayed_autosave();
    134                 });
    135         }
    136 
    137         // When connection is lost, keep user from submitting changes.
    138         $(document).on('heartbeat-connection-lost.autosave', function( e, error, status ) {
    139                 if ( 'timeout' === error || 503 == status ) {
    140                         var notice = $('#lost-connection-notice');
    141                         if ( ! wp.autosave.local.hasStorage ) {
    142                                 notice.find('.hide-if-no-sessionstorage').hide();
    143                         }
    144                         notice.show();
    145                         autosave_disable_buttons();
     86                function enableButtons() {
     87                        $document.trigger( 'autosave-enable-buttons' );
    14688                }
    147         }).on('heartbeat-connection-restored.autosave', function() {
    148                 $('#lost-connection-notice').hide();
    149                 autosave_enable_buttons();
    150         });
    151 });
    15289
    153 function autosave_parse_response( response ) {
    154         var res = wpAjax.parseAjaxResponse(response, 'autosave'), post_id, sup;
    155 
    156         if ( res && res.responses && res.responses.length ) {
    157                 if ( res.responses[0].supplemental ) {
    158                         sup = res.responses[0].supplemental;
    159 
    160                         jQuery.each( sup, function( selector, value ) {
    161                                 if ( selector.match(/^replace-/) )
    162                                         jQuery( '#' + selector.replace('replace-', '') ).val( value );
    163                         });
     90                function suspend() {
     91                        isSuspended = true;
    16492                }
    16593
    166                 // if no errors: add slug UI and update autosave-message
    167                 if ( !res.errors ) {
    168                         if ( post_id = parseInt( res.responses[0].id, 10 ) )
    169                                 autosave_update_slug( post_id );
    170 
    171                         if ( res.responses[0].data ) // update autosave message
    172                                 jQuery('.autosave-message').text( res.responses[0].data );
     94                function resume() {
     95                        isSuspended = false;
    17396                }
    174         }
    17597
    176         return res;
    177 }
     98                // Autosave in localStorage
     99                function autosaveLocal() {
     100                        var restorePostData, undoPostData, blog_id, post_id, hasStorage, intervalTimer,
     101                                lastCompareString;
    178102
    179 // called when autosaving pre-existing post
    180 function autosave_saved(response) {
    181         blockSave = false;
    182         autosave_parse_response(response); // parse the ajax response
    183         autosave_enable_buttons(); // re-enable disabled form buttons
    184 }
     103                        // Check if the browser supports sessionStorage and it's not disabled
     104                        function checkStorage() {
     105                                var test = Math.random().toString(),
     106                                        result = false;
    185107
    186 // called when autosaving new post
    187 function autosave_saved_new(response) {
    188         blockSave = false;
    189         var res = autosave_parse_response(response), post_id;
     108                                try {
     109                                        window.sessionStorage.setItem( 'wp-test', test );
     110                                        result = window.sessionStorage.getItem( 'wp-test' ) === test;
     111                                        window.sessionStorage.removeItem( 'wp-test' );
     112                                } catch(e) {}
    190113
    191         if ( res && res.responses.length && !res.errors ) {
    192                 // An ID is sent only for real auto-saves, not for autosave=0 "keepalive" saves
    193                 post_id = parseInt( res.responses[0].id, 10 );
     114                                hasStorage = result;
     115                                return result;
     116                        }
    194117
    195                 if ( post_id ) {
    196                         notSaved = false;
    197                         jQuery('#auto_draft').val('0'); // No longer an auto-draft
    198                 }
     118                        /**
     119                         * Initialize the local storage
     120                         *
     121                         * @return mixed False if no sessionStorage in the browser or an Object containing all postData for this blog
     122                         */
     123                        function getStorage() {
     124                                var stored_obj = false;
     125                                // Separate local storage containers for each blog_id
     126                                if ( hasStorage && blog_id ) {
     127                                        stored_obj = sessionStorage.getItem( 'wp-autosave-' + blog_id );
    199128
    200                 autosave_enable_buttons();
    201 
    202                 if ( autosaveDelayPreview ) {
    203                         autosaveDelayPreview = false;
    204                         doPreview();
    205                 }
    206         } else {
    207                 autosave_enable_buttons(); // re-enable disabled form buttons
    208         }
    209 }
    210 
    211 function autosave_update_slug(post_id) {
    212         // create slug area only if not already there
    213         if ( 'undefined' != makeSlugeditClickable && jQuery.isFunction(makeSlugeditClickable) && !jQuery('#edit-slug-box > *').size() ) {
    214                 jQuery.post( ajaxurl, {
    215                                 action: 'sample-permalink',
    216                                 post_id: post_id,
    217                                 new_title: fullscreen && fullscreen.settings.visible ? jQuery('#wp-fullscreen-title').val() : jQuery('#title').val(),
    218                                 samplepermalinknonce: jQuery('#samplepermalinknonce').val()
    219                         },
    220                         function(data) {
    221                                 if ( data !== '-1' ) {
    222                                         var box = jQuery('#edit-slug-box');
    223                                         box.html(data);
    224                                         if (box.hasClass('hidden')) {
    225                                                 box.fadeIn('fast', function () {
    226                                                         box.removeClass('hidden');
    227                                                 });
     129                                        if ( stored_obj ) {
     130                                                stored_obj = JSON.parse( stored_obj );
     131                                        } else {
     132                                                stored_obj = {};
    228133                                        }
    229                                         makeSlugeditClickable();
    230134                                }
     135
     136                                return stored_obj;
    231137                        }
    232                 );
    233         }
    234 }
    235138
    236 function autosave_loading() {
    237         jQuery('.autosave-message').html(autosaveL10n.savingText);
    238 }
     139                        /**
     140                         * Set the storage for this blog
     141                         *
     142                         * Confirms that the data was saved successfully.
     143                         *
     144                         * @return bool
     145                         */
     146                        function setStorage( stored_obj ) {
     147                                var key;
    239148
    240 function autosave_enable_buttons() {
    241         jQuery(document).trigger('autosave-enable-buttons');
    242         if ( ! wp.heartbeat || ! wp.heartbeat.hasConnectionError() ) {
    243                 // delay that a bit to avoid some rare collisions while the DOM is being updated.
    244                 setTimeout(function(){
    245                         var parent = jQuery('#submitpost');
    246                         parent.find(':button, :submit').removeAttr('disabled');
    247                         parent.find('.spinner').hide();
    248                 }, 500);
    249         }
    250 }
     149                                if ( hasStorage && blog_id ) {
     150                                        key = 'wp-autosave-' + blog_id;
     151                                        sessionStorage.setItem( key, JSON.stringify( stored_obj ) );
     152                                        return sessionStorage.getItem( key ) !== null;
     153                                }
    251154
    252 function autosave_disable_buttons() {
    253         jQuery(document).trigger('autosave-disable-buttons');
    254         jQuery('#submitpost').find(':button, :submit').prop('disabled', true);
    255         // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
    256         setTimeout( autosave_enable_buttons, 5000 );
    257 }
     155                                return false;
     156                        }
    258157
    259 function delayed_autosave() {
    260         setTimeout(function(){
    261                 if ( blockSave )
    262                         return;
    263                 autosave();
    264         }, 200);
    265 }
     158                        /**
     159                         * Get the saved post data for the current post
     160                         *
     161                         * @return mixed False if no storage or no data or the postData as an Object
     162                         */
     163                        function getSavedPostData() {
     164                                var stored = getStorage();
    266165
    267 autosave = function() {
    268         var post_data = wp.autosave.getPostData(),
    269                 compareString,
    270                 successCallback;
     166                                if ( ! stored || ! post_id ) {
     167                                        return false;
     168                                }
    271169
    272         blockSave = true;
     170                                return stored[ 'post_' + post_id ] || false;
     171                        }
    273172
    274         // post_data.content cannot be retrieved at the moment
    275         if ( ! post_data.autosave )
    276                 return false;
     173                        /**
     174                         * Set (save or delete) post data in the storage.
     175                         *
     176                         * If stored_data evaluates to 'false' the storage key for the current post will be removed
     177                         *
     178                         * $param stored_data The post data to store or null/false/empty to delete the key
     179                         * @return bool
     180                         */
     181                        function setData( stored_data ) {
     182                                var stored = getStorage();
    277183
    278         // No autosave while thickbox is open (media buttons)
    279         if ( jQuery('#TB_window').css('display') == 'block' )
    280                 return false;
     184                                if ( ! stored || ! post_id ) {
     185                                        return false;
     186                                }
    281187
    282         compareString = wp.autosave.getCompareString( post_data );
     188                                if ( stored_data ) {
     189                                        stored[ 'post_' + post_id ] = stored_data;
     190                                } else if ( stored.hasOwnProperty( 'post_' + post_id ) ) {
     191                                        delete stored[ 'post_' + post_id ];
     192                                } else {
     193                                        return false;
     194                                }
    283195
    284         // Nothing to save or no change.
    285         if ( compareString == autosaveLast )
    286                 return false;
     196                                return setStorage( stored );
     197                        }
    287198
    288         autosaveLast = compareString;
    289         jQuery(document).triggerHandler('wpcountwords', [ post_data.content ]);
     199                        /**
     200                         * Save post data for the current post
     201                         *
     202                         * Runs on a 15 sec. interval, saves when there are differences in the post title or content.
     203                         * When the optional data is provided, updates the last saved post data.
     204                         *
     205                         * $param data optional Object The post data for saving, minimum 'post_title' and 'content'
     206                         * @return bool
     207                         */
     208                        function save( data ) {
     209                                var postData, compareString,
     210                                        result = false;
    290211
    291         // Disable buttons until we know the save completed.
    292         autosave_disable_buttons();
     212                                if ( isSuspended ) {
     213                                        return false;
     214                                }
    293215
    294         if ( post_data.auto_draft == '1' ) {
    295                 successCallback = autosave_saved_new; // new post
    296         } else {
    297                 successCallback = autosave_saved; // pre-existing post
    298         }
     216                                if ( data ) {
     217                                        postData = getSavedPostData() || {};
     218                                        $.extend( postData, data );
     219                                } else {
     220                                        postData = getPostData('local');
     221                                }
    299222
    300         jQuery.ajax({
    301                 data: post_data,
    302                 beforeSend: autosave_loading,
    303                 type: 'POST',
    304                 url: ajaxurl,
    305                 success: successCallback
    306         });
     223                                compareString = getCompareString( postData );
    307224
    308         return true;
    309 };
     225                                if ( typeof lastCompareString === 'undefined' ) {
     226                                        lastCompareString = initialCompareString;
     227                                }
    310228
    311 // Autosave in localStorage
    312 // set as simple object/mixin for now
    313 window.wp = window.wp || {};
    314 wp.autosave = wp.autosave || {};
     229                                // If the content, title and excerpt did not change since the last save, don't save again
     230                                if ( compareString === lastCompareString ) {
     231                                        return false;
     232                                }
    315233
    316 (function($){
    317 // Returns the data for saving in both localStorage and autosaves to the server
    318 wp.autosave.getPostData = function() {
    319         var ed = typeof tinymce != 'undefined' ? tinymce.activeEditor : null, post_name, parent_id, cats = [],
    320                 data = {
    321                         action: 'autosave',
    322                         autosave: true,
    323                         post_id: $('#post_ID').val() || 0,
    324                         autosavenonce: $('#autosavenonce').val() || '',
    325                         post_type: $('#post_type').val() || '',
    326                         post_author: $('#post_author').val() || '',
    327                         excerpt: $('#excerpt').val() || ''
    328                 };
     234                                postData.save_time = ( new Date() ).getTime();
     235                                postData.status = $( '#post_status' ).val() || '';
     236                                result = setData( postData );
    329237
    330         if ( ed && !ed.isHidden() ) {
    331                 // Don't run while the tinymce spellcheck is on. It resets all found words.
    332                 if ( ed.plugins.spellchecker && ed.plugins.spellchecker.active ) {
    333                         data.autosave = false;
    334                         return data;
    335                 } else {
    336                         tinymce.triggerSave();
    337                 }
    338         }
     238                                if ( result ) {
     239                                        lastCompareString = compareString;
     240                                }
    339241
    340         data.post_title = $('#title').val() || '';
    341         data.content = $('#content').val() || '';
     242                                return result;
     243                        }
    342244
    343         /*
    344         // We haven't been saving tags with autosave since 2.8... Start again?
    345         $('.the-tags').each( function() {
    346                 data[this.name] = this.value;
    347         });
    348         */
     245                        // Run on DOM ready
     246                        function run() {
     247                                post_id = $('#post_ID').val() || 0;
    349248
    350         $('input[id^="in-category-"]:checked').each( function() {
    351                 cats.push(this.value);
    352         });
    353         data.catslist = cats.join(',');
     249                                // Check if the local post data is different than the loaded post data.
     250                                if ( $( '#wp-content-wrap' ).hasClass( 'tmce-active' ) ) {
     251                                        // If TinyMCE loads first, check the post 2.5 sec. after it is ready.
     252                                        // By this time the content has been loaded in the editor and 'saved' to the textarea.
     253                                        // This prevents false positives.
     254                                        $document.on( 'tinymce-editor-init.autosave', function() {
     255                                                window.setTimeout( function() {
     256                                                        checkPost();
     257                                                }, 2500 );
     258                                        });
     259                                } else {
     260                                        checkPost();
     261                                }
    354262
    355         if ( post_name = $('#post_name').val() )
    356                 data.post_name = post_name;
     263                                // Save every 15 sec.
     264                                intervalTimer = window.setInterval( save, 15000 );
    357265
    358         if ( parent_id = $('#parent_id').val() )
    359                 data.parent_id = parent_id;
     266                                $( 'form#post' ).on( 'submit.autosave-local', function() {
     267                                        var editor = typeof tinymce !== 'undefined' && tinymce.get('content'),
     268                                                post_id = $('#post_ID').val() || 0;
    360269
    361         if ( $('#comment_status').prop('checked') )
    362                 data.comment_status = 'open';
     270                                        if ( editor && ! editor.isHidden() ) {
     271                                                // Last onSubmit event in the editor, needs to run after the content has been moved to the textarea.
     272                                                editor.on( 'submit', function() {
     273                                                        save({
     274                                                                post_title: $( '#title' ).val() || '',
     275                                                                content: $( '#content' ).val() || '',
     276                                                                excerpt: $( '#excerpt' ).val() || ''
     277                                                        });
     278                                                });
     279                                        } else {
     280                                                save({
     281                                                        post_title: $( '#title' ).val() || '',
     282                                                        content: $( '#content' ).val() || '',
     283                                                        excerpt: $( '#excerpt' ).val() || ''
     284                                                });
     285                                        }
    363286
    364         if ( $('#ping_status').prop('checked') )
    365                 data.ping_status = 'open';
     287                                        wpCookies.set( 'wp-saving-post-' + post_id, 'check' );
     288                                });
     289                        }
    366290
    367         if ( $('#auto_draft').val() == '1' )
    368                 data.auto_draft = '1';
     291                        // Strip whitespace and compare two strings
     292                        function compare( str1, str2 ) {
     293                                function removeSpaces( string ) {
     294                                        return string.toString().replace(/[\x20\t\r\n\f]+/g, '');
     295                                }
    369296
    370         return data;
    371 };
     297                                return ( removeSpaces( str1 || '' ) === removeSpaces( str2 || '' ) );
     298                        }
    372299
    373 // Concatenate title, content and excerpt. Used to track changes when auto-saving.
    374 wp.autosave.getCompareString = function( post_data ) {
    375         if ( typeof post_data === 'object' ) {
    376                 return ( post_data.post_title || '' ) + '::' + ( post_data.content || '' ) + '::' + ( post_data.excerpt || '' );
    377         }
     300                        /**
     301                         * Check if the saved data for the current post (if any) is different than the loaded post data on the screen
     302                         *
     303                         * Shows a standard message letting the user restore the post data if different.
     304                         *
     305                         * @return void
     306                         */
     307                        function checkPost() {
     308                                var content, post_title, excerpt, $notice,
     309                                        postData = getSavedPostData(),
     310                                        cookie = wpCookies.get( 'wp-saving-post-' + post_id );
    378311
    379         return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' );
    380 };
     312                                if ( ! postData ) {
     313                                        return;
     314                                }
    381315
    382 wp.autosave.local = {
     316                                if ( cookie ) {
     317                                        wpCookies.remove( 'wp-saving-post-' + post_id );
    383318
    384         lastSavedData: '',
    385         blog_id: 0,
    386         hasStorage: false,
     319                                        if ( cookie === 'saved' ) {
     320                                                // The post was saved properly, remove old data and bail
     321                                                setData( false );
     322                                                return;
     323                                        }
     324                                }
    387325
    388         // Check if the browser supports sessionStorage and it's not disabled
    389         checkStorage: function() {
    390                 var test = Math.random(), result = false;
     326                                // There is a newer autosave. Don't show two "restore" notices at the same time.
     327                                if ( $( '#has-newer-autosave' ).length ) {
     328                                        return;
     329                                }
    391330
    392                 try {
    393                         sessionStorage.setItem('wp-test', test);
    394                         result = sessionStorage.getItem('wp-test') == test;
    395                         sessionStorage.removeItem('wp-test');
    396                 } catch(e) {}
     331                                content = $( '#content' ).val() || '';
     332                                post_title = $( '#title' ).val() || '';
     333                                excerpt = $( '#excerpt' ).val() || '';
    397334
    398                 this.hasStorage = result;
    399                 return result;
    400     },
     335                                // cookie == 'check' means the post was not saved properly, always show #local-storage-notice
     336                                if ( cookie !== 'check' && compare( content, postData.content ) &&
     337                                        compare( post_title, postData.post_title ) && compare( excerpt, postData.excerpt ) ) {
    401338
    402         /**
    403          * Initialize the local storage
    404          *
    405          * @return mixed False if no sessionStorage in the browser or an Object containing all post_data for this blog
    406          */
    407         getStorage: function() {
    408                 var stored_obj = false;
    409                 // Separate local storage containers for each blog_id
    410                 if ( this.hasStorage && this.blog_id ) {
    411                         stored_obj = sessionStorage.getItem( 'wp-autosave-' + this.blog_id );
     339                                        return;
     340                                }
    412341
    413                         if ( stored_obj )
    414                                 stored_obj = JSON.parse( stored_obj );
    415                         else
    416                                 stored_obj = {};
    417                 }
     342                                restorePostData = postData;
     343                                undoPostData = {
     344                                        content: content,
     345                                        post_title: post_title,
     346                                        excerpt: excerpt
     347                                };
    418348
    419                 return stored_obj;
    420         },
     349                                $notice = $( '#local-storage-notice' );
     350                                $('.wrap h2').first().after( $notice.addClass( 'updated' ).show() );
    421351
    422         /**
    423          * Set the storage for this blog
    424          *
    425          * Confirms that the data was saved successfully.
    426          *
    427          * @return bool
    428          */
    429         setStorage: function( stored_obj ) {
    430                 var key;
     352                                $notice.on( 'click.autosae-local', function( event ) {
     353                                        var $target = $( event.target );
    431354
    432                 if ( this.hasStorage && this.blog_id ) {
    433                         key = 'wp-autosave-' + this.blog_id;
    434                         sessionStorage.setItem( key, JSON.stringify( stored_obj ) );
    435                         return sessionStorage.getItem( key ) !== null;
    436                 }
     355                                        if ( $target.hasClass( 'restore-backup' ) ) {
     356                                                restorePost( restorePostData );
     357                                                $target.parent().hide();
     358                                                $(this).find( 'p.undo-restore' ).show();
     359                                        } else if ( $target.hasClass( 'undo-restore-backup' ) ) {
     360                                                restorePost( undoPostData );
     361                                                $target.parent().hide();
     362                                                $(this).find( 'p.local-restore' ).show();
     363                                        }
    437364
    438                 return false;
    439         },
     365                                        event.preventDefault();
     366                                });
     367                        }
    440368
    441         /**
    442          * Get the saved post data for the current post
    443          *
    444          * @return mixed False if no storage or no data or the post_data as an Object
    445          */
    446         getData: function() {
    447                 var stored = this.getStorage(), post_id = $('#post_ID').val();
     369                        // Restore the current title, content and excerpt from postData.
     370                        function restorePost( postData ) {
     371                                var editor;
    448372
    449                 if ( !stored || !post_id )
    450                         return false;
     373                                if ( postData ) {
     374                                        // Set the last saved data
     375                                        lastCompareString = getCompareString( postData );
    451376
    452                 return stored[ 'post_' + post_id ] || false;
    453         },
     377                                        if ( $( '#title' ).val() !== postData.post_title ) {
     378                                                $( '#title' ).focus().val( postData.post_title || '' );
     379                                        }
    454380
    455         /**
    456          * Set (save or delete) post data in the storage.
    457          *
    458          * If stored_data evaluates to 'false' the storage key for the current post will be removed
    459          *
    460          * $param stored_data The post data to store or null/false/empty to delete the key
    461          * @return bool
    462          */
    463         setData: function( stored_data ) {
    464                 var stored = this.getStorage(), post_id = $('#post_ID').val();
     381                                        $( '#excerpt' ).val( postData.excerpt || '' );
     382                                        editor = typeof tinymce !== 'undefined' && tinymce.get('content');
    465383
    466                 if ( !stored || !post_id )
    467                         return false;
     384                                        if ( editor && ! editor.isHidden() && typeof switchEditors !== 'undefined' ) {
     385                                                // Make sure there's an undo level in the editor
     386                                                editor.undoManager.add();
     387                                                editor.setContent( postData.content ? switchEditors.wpautop( postData.content ) : '' );
     388                                        } else {
     389                                                // Make sure the Text editor is selected
     390                                                $( '#content-html' ).click();
     391                                                $( '#content' ).val( postData.content );
     392                                        }
    468393
    469                 if ( stored_data )
    470                         stored[ 'post_' + post_id ] = stored_data;
    471                 else if ( stored.hasOwnProperty( 'post_' + post_id ) )
    472                         delete stored[ 'post_' + post_id ];
    473                 else
    474                         return false;
     394                                        return true;
     395                                }
    475396
    476                 return this.setStorage(stored);
    477         },
     397                                return false;
     398                        }
    478399
    479         /**
    480          * Save post data for the current post
    481          *
    482          * Runs on a 15 sec. schedule, saves when there are differences in the post title or content.
    483          * When the optional data is provided, updates the last saved post data.
    484          *
    485          * $param data optional Object The post data for saving, minimum 'post_title' and 'content'
    486          * @return bool
    487          */
    488         save: function( data ) {
    489                 var result = false, post_data, compareString;
     400                        // Initialize and run checkPost() on loading the script (before TinyMCE init)
     401                        blog_id = typeof window.autosaveL10n !== 'undefined' && window.autosaveL10n.blog_id;
    490402
    491                 if ( ! data ) {
    492                         post_data = wp.autosave.getPostData();
    493                 } else {
    494                         post_data = this.getData() || {};
    495                         $.extend( post_data, data );
    496                         post_data.autosave = true;
    497                 }
     403                        // Check if the browser supports sessionStorage and it's not disabled
     404                        if ( ! checkStorage() ) {
     405                                return;
     406                        }
    498407
    499                 // Cannot get the post data at the moment
    500                 if ( ! post_data.autosave )
    501                         return false;
     408                        // Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'.
     409                        if ( ! blog_id || ( ! $('#content').length && ! $('#excerpt').length ) ) {
     410                                return;
     411                        }
    502412
    503                 compareString = wp.autosave.getCompareString( post_data );
     413                        $document.ready( run );
    504414
    505                 // If the content, title and excerpt did not change since the last save, don't save again
    506                 if ( compareString == this.lastSavedData )
    507                         return false;
     415                        return {
     416                                hasStorage: hasStorage,
     417                                getSavedPostData: getSavedPostData,
     418                                save: save
     419                        };
     420                }
    508421
    509                 post_data.save_time = (new Date()).getTime();
    510                 post_data.status = $('#post_status').val() || '';
    511                 result = this.setData( post_data );
     422                // Autosave on the server
     423                function autosaveServer() {
     424                        var _disabled, _blockSave, _blockSaveTimer, previousCompareString, lastCompareString,
     425                                nextRun = 0;
    512426
    513                 if ( result )
    514                         this.lastSavedData = compareString;
     427                        // Block saving for the next 10 sec.
     428                        function tempBlockSave() {
     429                                _blockSave = true;
     430                                window.clearTimeout( _blockSaveTimer );
    515431
    516                 return result;
    517         },
     432                                _blockSaveTimer = window.setTimeout( function() {
     433                                        _blockSave = false;
     434                                }, 10000 );
     435                        }
    518436
    519         // Initialize and run checkPost() on loading the script (before TinyMCE init)
    520         init: function( settings ) {
    521                 var self = this;
     437                        // Runs on heartbeat-response
     438                        function response( data ) {
     439                                _schedule();
     440                                _blockSave = false;
     441                                lastCompareString = previousCompareString;
     442                                previousCompareString = '';
    522443
    523                 // Check if the browser supports sessionStorage and it's not disabled
    524                 if ( ! this.checkStorage() )
    525                         return;
     444                                $document.trigger( 'after-autosave', [data] );
     445                                $( '.autosave-message' ).text( data.message );
     446                                enableButtons();
    526447
    527                 // Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'.
    528                 if ( ! $('#content').length && ! $('#excerpt').length )
    529                         return;
     448                                if ( data.success ) {
     449                                        // No longer an auto-draft
     450                                        $( '#auto_draft' ).val('');
     451                                }
     452                        }
    530453
    531                 if ( settings )
    532                         $.extend( this, settings );
     454                        /**
     455                         * Disable autosave
     456                         *
     457                         * Intended to run on form.submit
     458                         */
     459                        function disable() {
     460                                _disabled = true;
     461                        }
    533462
    534                 if ( !this.blog_id )
    535                         this.blog_id = typeof window.autosaveL10n != 'undefined' ? window.autosaveL10n.blog_id : 0;
     463                        /**
     464                         * Save immediately
     465                         *
     466                         * Resets the timing and tells heartbeat to connect now
     467                         *
     468                         * @return void
     469                         */
     470                        function triggerSave() {
     471                                nextRun = 0;
     472                                wp.heartbeat.connectNow();
     473                        }
    536474
    537                 $(document).ready( function(){ self.run(); } );
    538         },
     475                        /**
     476                         * Checks if the post content in the textarea has changed since page load.
     477                         *
     478                         * This also happens when TinyMCE is active and editor.save() is triggered by
     479                         * wp.autosave.getPostData().
     480                         *
     481                         * @return bool
     482                         */
     483                        function textareaValueChanged() {
     484                                return getCompareString() !== initialCompareString;
     485                        }
    539486
    540         // Run on DOM ready
    541         run: function() {
    542                 var self = this;
     487                        // Runs on 'heartbeat-send'
     488                        function save() {
     489                                var postData, compareString;
    543490
    544                 // Check if the local post data is different than the loaded post data.
    545                 this.checkPost();
     491                                if ( isSuspended || _disabled || _blockSave ) {
     492                                        return false;
     493                                }
    546494
    547                 // Set the schedule
    548                 this.schedule = $.schedule({
    549                         time: 15 * 1000,
    550                         func: function() { wp.autosave.local.save(); },
    551                         repeat: true,
    552                         protect: true
    553                 });
     495                                if ( ( new Date() ).getTime() < nextRun ) {
     496                                        return false;
     497                                }
    554498
    555                 $('form#post').on('submit.autosave-local', function() {
    556                         var editor = typeof tinymce != 'undefined' && tinymce.get('content'), post_id = $('#post_ID').val() || 0;
     499                                postData = getPostData();
     500                                compareString = getCompareString( postData );
    557501
    558                         if ( editor && ! editor.isHidden() ) {
    559                                 // Last onSubmit event in the editor, needs to run after the content has been moved to the textarea.
    560                                 editor.onSubmit.add( function() {
    561                                         wp.autosave.local.save({
    562                                                 post_title: $('#title').val() || '',
    563                                                 content: $('#content').val() || '',
    564                                                 excerpt: $('#excerpt').val() || ''
    565                                         });
    566                                 });
    567                         } else {
    568                                 self.save({
    569                                         post_title: $('#title').val() || '',
    570                                         content: $('#content').val() || '',
    571                                         excerpt: $('#excerpt').val() || ''
    572                                 });
    573                         }
     502                                // First check
     503                                if ( typeof lastCompareString === 'undefined' ) {
     504                                        lastCompareString = initialCompareString;
     505                                }
    574506
    575                         wpCookies.set( 'wp-saving-post-' + post_id, 'check' );
    576                 });
    577         },
     507                                // No change
     508                                if ( compareString === lastCompareString ) {
     509                                        return false;
     510                                }
    578511
    579         // Strip whitespace and compare two strings
    580         compare: function( str1, str2 ) {
    581                 function remove( string ) {
    582                         return string.toString().replace(/[\x20\t\r\n\f]+/g, '');
    583                 }
     512                                previousCompareString = compareString;
     513                                tempBlockSave();
     514                                disableButtons();
    584515
    585                 return ( remove( str1 || '' ) == remove( str2 || '' ) );
    586         },
     516                                $document.trigger( 'wpcountwords', [ postData.content ] )
     517                                        .trigger( 'before-autosave', [ postData ] );
    587518
    588         /**
    589          * Check if the saved data for the current post (if any) is different than the loaded post data on the screen
    590          *
    591          * Shows a standard message letting the user restore the post data if different.
    592          *
    593          * @return void
    594          */
    595         checkPost: function() {
    596                 var self = this, post_data = this.getData(), content, post_title, excerpt, notice,
    597                         post_id = $('#post_ID').val() || 0, cookie = wpCookies.get( 'wp-saving-post-' + post_id );
     519                                $( '.autosave-message' ).text( autosaveL10n.savingText );
     520                                postData._wpnonce = $( '#_wpnonce' ).val() || '';
    598521
    599                 if ( ! post_data )
    600                         return;
     522                                return postData;
     523                        }
    601524
    602                 if ( cookie ) {
    603                         wpCookies.remove( 'wp-saving-post-' + post_id );
    604 
    605                         if ( cookie == 'saved' ) {
    606                                 // The post was saved properly, remove old data and bail
    607                                 this.setData( false );
    608                                 return;
     525                        function _schedule() {
     526                                nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000;
    609527                        }
    610                 }
    611528
    612                 // There is a newer autosave. Don't show two "restore" notices at the same time.
    613                 if ( $('#has-newer-autosave').length )
    614                         return;
     529                        $document.on( 'heartbeat-send.autosave', function( event, data ) {
     530                                var autosaveData = save();
    615531
    616                 content = $('#content').val() || '';
    617                 post_title = $('#title').val() || '';
    618                 excerpt = $('#excerpt').val() || '';
     532                                if ( autosaveData ) {
     533                                        data.wp_autosave = autosaveData;
     534                                }
     535                        }).on( 'heartbeat-tick.autosave', function( event, data ) {
     536                                if ( data.wp_autosave ) {
     537                                        response( data.wp_autosave );
     538                                }
     539                        }).on( 'heartbeat-connection-lost.autosave', function( event, error, status ) {
     540                                // When connection is lost, keep user from submitting changes.
     541                                if ( 'timeout' === error || 603 === status ) {
     542                                        var $notice = $('#lost-connection-notice');
    619543
    620                 if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' )
    621                         content = switchEditors.pre_wpautop( content );
     544                                        if ( ! wp.autosave.local.hasStorage ) {
     545                                                $notice.find('.hide-if-no-sessionstorage').hide();
     546                                        }
    622547
    623                 // cookie == 'check' means the post was not saved properly, always show #local-storage-notice
    624                 if ( cookie != 'check' && this.compare( content, post_data.content ) && this.compare( post_title, post_data.post_title ) && this.compare( excerpt, post_data.excerpt ) ) {
    625                         return;
     548                                        $notice.show();
     549                                        disableButtons();
     550                                }
     551                        }).on( 'heartbeat-connection-restored.autosave', function() {
     552                                $('#lost-connection-notice').hide();
     553                                enableButtons();
     554                        }).ready( function() {
     555                                _schedule();
     556                        });
     557
     558                        return {
     559                                disable: disable,
     560                                tempBlockSave: tempBlockSave,
     561                                triggerSave: triggerSave,
     562                                textareaValueChanged: textareaValueChanged
     563                        };
    626564                }
    627565
    628                 this.restore_post_data = post_data;
    629                 this.undo_post_data = {
    630                         content: content,
    631                         post_title: post_title,
    632                         excerpt: excerpt
    633                 };
    634 
    635                 notice = $('#local-storage-notice');
    636                 $('.wrap h2').first().after( notice.addClass('updated').show() );
    637 
    638                 notice.on( 'click', function(e) {
    639                         var target = $( e.target );
    640 
    641                         if ( target.hasClass('restore-backup') ) {
    642                                 self.restorePost( self.restore_post_data );
    643                                 target.parent().hide();
    644                                 $(this).find('p.undo-restore').show();
    645                         } else if ( target.hasClass('undo-restore-backup') ) {
    646                                 self.restorePost( self.undo_post_data );
    647                                 target.parent().hide();
    648                                 $(this).find('p.local-restore').show();
     566                // Wait for TinyMCE to initialize plus 2 sec. for any external css to finish loading,
     567                // then 'save' to the textarea before setting initialCompareString.
     568                // This avoids any insignificant differences between the initial textarea content and the content
     569                // extracted from the editor.
     570                $document.on( 'tinymce-editor-init.autosave', function( event, editor ) {
     571                        if ( editor.id === 'content' ) {
     572                                window.setTimeout( function() {
     573                                        editor.save();
     574                                        initialCompareString = getCompareString();
     575                                }, 2000 );
    649576                        }
    650 
    651                         e.preventDefault();
     577                }).ready( function() {
     578                        // Set the initial compare string in case TinyMCE is not used or not loaded first
     579                        initialCompareString = getCompareString();
    652580                });
    653         },
    654581
    655         // Restore the current title, content and excerpt from post_data.
    656         restorePost: function( post_data ) {
    657                 var editor;
    658 
    659                 if ( post_data ) {
    660                         // Set the last saved data
    661                         this.lastSavedData = wp.autosave.getCompareString( post_data );
    662 
    663                         if ( $('#title').val() != post_data.post_title )
    664                                 $('#title').focus().val( post_data.post_title || '' );
    665 
    666                         $('#excerpt').val( post_data.excerpt || '' );
    667                         editor = typeof tinymce != 'undefined' && tinymce.get('content');
    668 
    669                         if ( editor && ! editor.isHidden() && typeof switchEditors != 'undefined' ) {
    670                                 // Make sure there's an undo level in the editor
    671                                 editor.undoManager.add();
    672                                 editor.setContent( post_data.content ? switchEditors.wpautop( post_data.content ) : '' );
    673                         } else {
    674                                 // Make sure the Text editor is selected
    675                                 $('#content-html').click();
    676                                 $('#content').val( post_data.content );
    677                         }
    678 
    679                         return true;
    680                 }
    681 
    682                 return false;
     582                return {
     583                        getPostData: getPostData,
     584                        getCompareString: getCompareString,
     585                        disableButtons: disableButtons,
     586                        enableButtons: enableButtons,
     587                        suspend: suspend,
     588                        resume: resume,
     589                        local: autosaveLocal(),
     590                        server: autosaveServer()
     591                };
    683592        }
    684 };
    685593
    686 wp.autosave.local.init();
     594        window.wp = window.wp || {};
     595        window.wp.autosave = autosave();
    687596
    688 }(jQuery));
     597}( jQuery, window ));
  • src/wp-includes/js/tinymce/plugins/wordpress/plugin.js

     
    278278                                e.content = e.content.replace(/<\/(p|div|ul|ol|dl|table|blockquote|h[1-6]|fieldset|pre|address)>\s*<\/p>/gi, '</$1>');
    279279                        }
    280280                });
     281               
     282                if ( typeof window.jQuery !== 'undefined' ) {
     283                        window.jQuery( document ).trigger( 'tinymce-editor-init', [editor] );
     284                }
    281285        });
    282286
    283287        // Word count
    284         if ( typeof jQuery !== 'undefined' ) {
     288        if ( typeof window.jQuery !== 'undefined' ) {
    285289                editor.on( 'keyup', function( e ) {
    286290                        var key = e.keyCode || e.charCode;
    287291
     
    290294                        }
    291295
    292296                        if ( 13 === key || 8 === last || 46 === last ) {
    293                                 jQuery(document).triggerHandler( 'wpcountwords', [ editor.getContent({ format : 'raw' }) ] );
     297                                window.jQuery( document ).triggerHandler( 'wpcountwords', [ editor.getContent({ format : 'raw' }) ] );
    294298                        }
    295299
    296300                        last = key;
  • src/wp-includes/revision.php

     
    215215 *
    216216 * @param int|object|array $post Post ID, post object OR post array.
    217217 * @param bool $autosave Optional. Is the revision an autosave?
    218  * @return mixed Null or 0 if error, new revision ID if success.
     218 * @return mixed WP_Error or 0 if error, new revision ID if success.
    219219 */
    220220function _wp_put_post_revision( $post = null, $autosave = false ) {
    221221        if ( is_object($post) )
     
    223223        elseif ( !is_array($post) )
    224224                $post = get_post($post, ARRAY_A);
    225225
    226         if ( !$post || empty($post['ID']) )
    227                 return;
     226        if ( ! $post || empty($post['ID']) )
     227                return new WP_Error( 'invalid_post', __( 'Invalid post ID' ) );
    228228
    229229        if ( isset($post['post_type']) && 'revision' == $post['post_type'] )
    230230                return new WP_Error( 'post_type', __( 'Cannot create a revision of a revision' ) );
  • tests/phpunit/includes/testcase-ajax.php

     
    4242                'oembed_cache', 'image-editor', 'delete-comment', 'delete-tag', 'delete-link',
    4343                'delete-meta', 'delete-post', 'trash-post', 'untrash-post', 'delete-page', 'dim-comment',
    4444                'add-link-category', 'add-tag', 'get-tagcloud', 'get-comments', 'replyto-comment',
    45                 'edit-comment', 'add-menu-item', 'add-meta', 'add-user', 'autosave', 'closed-postboxes',
     45                'edit-comment', 'add-menu-item', 'add-meta', 'add-user', 'closed-postboxes',
    4646                'hidden-columns', 'update-welcome-panel', 'menu-get-metabox', 'wp-link-ajax',
    4747                'menu-locations-save', 'menu-quick-search', 'meta-box-order', 'get-permalink',
    4848                'sample-permalink', 'inline-save', 'inline-save-tax', 'find_posts', 'widgets-order',
    4949                'save-widget', 'set-post-thumbnail', 'date_format', 'time_format', 'wp-fullscreen-save-post',
    50                 'wp-remove-post-lock', 'dismiss-wp-pointer', 'nopriv_autosave'
     50                'wp-remove-post-lock', 'dismiss-wp-pointer', 'heartbeat', 'nopriv_heartbeat',
    5151        );
    5252
    5353        /**
  • tests/phpunit/tests/ajax/Autosave.php

     
    2323        protected $_post = null;
    2424
    2525        /**
     26         * user_id
     27         * @var int
     28         */
     29        protected $user_id = 0;
     30
     31        /**
    2632         * Set up the test fixture
    2733         */
    2834        public function setUp() {
    2935                parent::setUp();
     36                // Set a user so the $post has 'post_author'
     37                $this->user_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
     38                wp_set_current_user( $this->user_id );
     39
    3040                $post_id = $this->factory->post->create( array( 'post_status' => 'draft' ) );
    3141                $this->_post = get_post( $post_id );
    3242        }
    3343
    3444        /**
     45         * Tear down the test fixture.
     46         * Reset the current user
     47         */
     48        public function tearDown() {
     49                parent::tearDown();
     50                wp_set_current_user( 0 );
     51        }
     52
     53        /**
    3554         * Test autosaving a post
    3655         * @return void
    3756         */
    3857        public function test_autosave_post() {
     58                // The original post_author
     59                wp_set_current_user( $this->user_id );
    3960
    40                 // Become an admin
    41                 $this->_setRole( 'administrator' );
    42 
    4361                // Set up the $_POST request
    4462                $md5 = md5( uniqid() );
    4563                $_POST = array(
    46                     'post_id'       => $this->_post->ID,
    47                     'autosavenonce' => wp_create_nonce( 'autosave' ),
    48                     'post_content'  => $this->_post->post_content . PHP_EOL . $md5,
    49                         'post_type'     => 'post',
    50                     'autosave'      => 1,
     64                        'action' =>     'heartbeat',
     65                        '_nonce' => wp_create_nonce( 'heartbeat-nonce' ),
     66                        'data' => array(
     67                                'wp_autosave' => array(
     68                                    'post_id'       => $this->_post->ID,
     69                                    '_wpnonce'      => wp_create_nonce( 'update-post_' . $this->_post->ID ),
     70                                    'post_content'  => $this->_post->post_content . PHP_EOL . $md5,
     71                                        'post_type'     => 'post',
     72                                ),
     73                        ),
    5174                );
    5275
    5376                // Make the request
    5477                try {
    55                         $this->_handleAjax( 'autosave' );
     78                        $this->_handleAjax( 'heartbeat' );
    5679                } catch ( WPAjaxDieContinueException $e ) {
    5780                        unset( $e );
    5881                }
    5982
    60                 // Get the response
    61                 $xml = simplexml_load_string( $this->_last_response, 'SimpleXMLElement', LIBXML_NOCDATA );
     83                // Get the response, it is in heartbeat's response
     84                $response = json_decode( $this->_last_response, true );
    6285
    6386                // Ensure everything is correct
    64                 $this->assertEquals( $this->_post->ID, (int) $xml->response[0]->autosave['id'] );
    65                 $this->assertEquals( 'autosave_' . $this->_post->ID, (string) $xml->response['action']);
     87                $this->assertNotEmpty( $response['wp_autosave'] );
     88                $this->assertTrue( $response['wp_autosave']['success'] );
    6689
    6790                // Check that the edit happened
    68                 $post = get_post( $this->_post->ID) ;
     91                $post = get_post( $this->_post->ID );
    6992                $this->assertGreaterThanOrEqual( 0, strpos( $post->post_content, $md5 ) );
    7093        }
    71 
     94       
    7295        /**
    73          * Test with an invalid nonce
     96         * Test autosaving a locked post
    7497         * @return void
    7598         */
    76         public function test_with_invalid_nonce( ) {
     99        public function test_autosave_locked_post() {
     100                // Lock the post to another user
     101                $another_user_id = $this->factory->user->create( array( 'role' => 'editor' ) );
     102                wp_set_current_user( $another_user_id );
     103                wp_set_post_lock( $this->_post->ID );
    77104
    78                 // Become an administrator
    79                 $this->_setRole( 'administrator' );
     105                wp_set_current_user( $this->user_id );
    80106
     107                // Ensure post is locked
     108                $this->assertEquals( $another_user_id, wp_check_post_lock( $this->_post->ID ) );
     109
    81110                // Set up the $_POST request
     111                $md5 = md5( uniqid() );
    82112                $_POST = array(
    83                     'post_id'       => $this->_post->ID,
    84                     'autosavenonce' => md5( uniqid() ),
    85                     'autosave'      => 1
     113                        'action' =>     'heartbeat',
     114                        '_nonce' => wp_create_nonce( 'heartbeat-nonce' ),
     115                        'data' => array(
     116                                'wp_autosave' => array(
     117                                    'post_id'       => $this->_post->ID,
     118                                    '_wpnonce'      => wp_create_nonce( 'update-post_' . $this->_post->ID ),
     119                                    'post_content'  => $this->_post->post_content . PHP_EOL . $md5,
     120                                        'post_type'     => 'post',
     121                                ),
     122                        ),
    86123                );
    87124
    88125                // Make the request
    89                 $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
    90                 $this->_handleAjax( 'autosave' );
     126                try {
     127                        $this->_handleAjax( 'heartbeat' );
     128                } catch ( WPAjaxDieContinueException $e ) {
     129                        unset( $e );
     130                }
     131
     132                $response = json_decode( $this->_last_response, true );
     133
     134                // Ensure everything is correct
     135                $this->assertNotEmpty( $response['wp_autosave'] );
     136                $this->assertTrue( $response['wp_autosave']['success'] );
     137
     138                // Check that the original post was NOT edited
     139                $post = get_post( $this->_post->ID );
     140                $this->assertFalse( strpos( $post->post_content, $md5 ) );
     141
     142                // Check if the autosave post was created
     143                $autosave = wp_get_post_autosave( $this->_post->ID, get_current_user_id() );
     144                $this->assertNotEmpty( $autosave );
     145                $this->assertGreaterThanOrEqual( 0, strpos( $autosave->post_content, $md5 ) );
    91146        }
    92147
    93148        /**
    94          * Test with a bad post id
     149         * Test with an invalid nonce
    95150         * @return void
    96151         */
    97         public function test_with_invalid_post_id( ) {
     152        public function test_with_invalid_nonce( ) {
    98153
    99                 // Become an administrator
    100                 $this->_setRole( 'administrator' );
     154                wp_set_current_user( $this->user_id );
    101155
    102156                // Set up the $_POST request
    103157                $_POST = array(
    104                     'post_id'       => 0,
    105                     'autosavenonce' => wp_create_nonce( 'autosave' ),
    106                     'autosave'      => 1,
    107                         'post_type'     => 'post'
     158                        'action' =>     'heartbeat',
     159                        '_nonce' => wp_create_nonce( 'heartbeat-nonce' ),
     160                        'data' => array(
     161                                'wp_autosave' => array(
     162                                    'post_id'  => $this->_post->ID,
     163                                    '_wpnonce' => substr( md5( uniqid() ), 0, 10 ),
     164                                ),
     165                        ),
    108166                );
    109167
    110168                // Make the request
    111                 $this->setExpectedException( 'WPAjaxDieStopException', 'You are not allowed to edit this post.' );
    112                 $this->_handleAjax( 'autosave' );
     169                try {
     170                        $this->_handleAjax( 'heartbeat' );
     171                } catch ( WPAjaxDieContinueException $e ) {
     172                        unset( $e );
     173                }
     174
     175                $response = json_decode( $this->_last_response, true );
     176
     177                $this->assertNotEmpty( $response['wp_autosave'] );
     178                $this->assertFalse( $response['wp_autosave']['success'] );
    113179        }
    114180}