Make WordPress Core

Ticket #25272: 25272.4.patch

File 25272.4.patch, 57.9 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

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

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

     
    724724                if ( 2 === wp_verify_nonce( $received['post_nonce'], 'update-post_' . $post_id ) ) {
    725725                        $response['wp-refresh-post-nonces'] = array(
    726726                                'replace' => array(
    727                                         'autosavenonce' => wp_create_nonce('autosave'),
    728727                                        'getpermalinknonce' => wp_create_nonce('getpermalink'),
    729728                                        'samplepermalinknonce' => wp_create_nonce('samplepermalink'),
    730729                                        'closedpostboxesnonce' => wp_create_nonce('closedpostboxes'),
     
    739738        return $response;
    740739}
    741740add_filter( 'heartbeat_received', 'wp_refresh_post_nonces', 10, 3 );
     741
     742/**
     743 * Autosave with heartbeat
     744 *
     745 * @since 3.8
     746 */
     747function heartbeat_autosave( $response, $data ) {
     748        if ( ! empty( $data['wp_autosave'] ) ) {
     749                $saved = wp_autosave( $data['wp_autosave'] );
     750
     751                if ( is_wp_error( $saved ) ) {
     752                        $response['wp_autosave'] = array( 'success' => false, 'message' => $saved->get_error_message() );
     753                } elseif ( empty( $saved ) ) {
     754                        $response['wp_autosave'] = array( 'success' => false, 'message' => __( 'Error while saving.' ) );
     755                } else {
     756                        /* translators: draft saved date format, see http://php.net/date */
     757                        $draft_saved_date_format = __( 'g:i:s a' );
     758                        /* translators: %s: date and time */
     759                        $response['wp_autosave'] = array( 'success' => true, 'message' => sprintf( __( 'Draft saved at %s.' ), date_i18n( $draft_saved_date_format ) ) );
     760                }
     761        }
     762
     763        return $response;
     764}
     765// Run later as we have to set DOING_AUTOSAVE for back-compat
     766add_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

     
    1 var tagBox, commentsBox, editPermalink, makeSlugeditClickable, WPSetThumbnailHTML, WPSetThumbnailID, WPRemoveThumbnail, wptitlehint;
     1var tagBox, commentsBox, WPSetThumbnailHTML, WPSetThumbnailID, WPRemoveThumbnail, wptitlehint;
     2// Prevent fatal errors (back-compat)
     3makeSlugeditClickable = editPermalink = function(){};
    24
    35// return an array with any duplicate, whitespace or values removed
    46function array_unique_noempty(a) {
     
    265267                send['lock'] = lock;
    266268
    267269        data['wp-refresh-post-lock'] = send;
    268 });
    269270
    270 // Post locks: update the lock string or show the dialog if somebody has taken over editing
    271 $(document).on( 'heartbeat-tick.refresh-lock', function( e, data ) {
     271}).on( 'heartbeat-tick.refresh-lock', function( e, data ) {
     272        // Post locks: update the lock string or show the dialog if somebody has taken over editing
    272273        var received, wrap, avatar;
    273274
    274275        if ( data['wp-refresh-post-lock'] ) {
     
    279280                        wrap = $('#post-lock-dialog');
    280281
    281282                        if ( wrap.length && ! wrap.is(':visible') ) {
    282                                 if ( typeof autosave == 'function' ) {
    283                                         $(document).on('autosave-disable-buttons.post-lock', function() {
    284                                                 wrap.addClass('saving');
    285                                         }).on('autosave-enable-buttons.post-lock', function() {
     283                                if ( typeof wp != 'undefined' && wp.autosave ) {
     284                                        // Save the latest changes and disable
     285                                        $(document).one( 'heartbeat-tick', function() {
     286                                                wp.autosave.server.disable();
    286287                                                wrap.removeClass('saving').addClass('saved');
    287                                                 window.onbeforeunload = null;
     288                                                $(window).off( 'beforeunload.autosave' );
    288289                                        });
    289290
    290                                         // Save the latest changes and disable
    291                                         if ( ! autosave() )
    292                                                 window.onbeforeunload = null;
    293 
    294                                         autosave = function(){};
     291                                        wrap.addClass('saving');
     292                                        wp.autosave.server.triggerSave();
    295293                                }
    296294
    297295                                if ( received.lock_error.avatar_src ) {
     
    306304                        $('#active_post_lock').val( received.new_lock );
    307305                }
    308306        }
     307}).on( 'after-autosave.update-post-slug', function() {
     308        // create slug area only if not already there
     309        if ( ! $('#edit-slug-box > *').length ) {
     310                $.post( ajaxurl, {
     311                                action: 'sample-permalink',
     312                                post_id: $('#post_ID').val(),
     313                                new_title: typeof fullscreen != 'undefined' && fullscreen.settings.visible ? $('#wp-fullscreen-title').val() : $('#title').val(),
     314                                samplepermalinknonce: $('#samplepermalinknonce').val()
     315                        },
     316                        function( data ) {
     317                                if ( data != '-1' ) {
     318                                        $('#edit-slug-box').html(data);
     319                                }
     320                        }
     321                );
     322        }
    309323});
    310324
    311325}(jQuery));
     
    351365}(jQuery));
    352366
    353367jQuery(document).ready( function($) {
    354         var stamp, visibility, sticky = '', last = 0, co = $('#content');
     368        var stamp, visibility, sticky = '', last = 0, co = $('#content'), $editSlugWrap = $('#edit-slug-box'),
     369                postId = $('#post_ID').val() || 0, $submitpost = $('#submitpost'), releaseLock = true;
    355370
    356371        postboxes.add_postbox_toggles(pagenow);
    357372
     
    376391                wp.heartbeat.setInterval( 15 );
    377392        }
    378393
     394        // The form is being submitted by the user
     395        $submitpost.find( 'input[type="submit"], a.submitdelete' ).on( 'click.autosave', function( event ) {
     396                var $button = $(this);
     397
     398                if ( $button.prop('disabled') ) {
     399                        event.preventDefault();
     400                        return;
     401                }
     402
     403                if ( $button.hasClass('submitdelete') ) {
     404                        return;
     405                }
     406
     407                // The form submission can be blocked from JS or by using HTML 5.0 validation on some fields.
     408                // Run this only on an actual 'submit'.
     409                $('form#post').off( 'submit.edit-post' ).on( 'submit.edit-post', function( event ) {
     410                        if ( event.isDefaultPrevented() )
     411                                return;
     412
     413                        wp.autosave.server.disable();
     414                        releaseLock = false;
     415                        $(window).off( 'beforeunload.edit-post' );
     416
     417                        $submitpost.find('a.submitdelete, #post-preview').addClass('button-disabled');
     418
     419                        if ( $button.attr('id') == 'publish' )
     420                                $submitpost.find('#major-publishing-actions .spinner').show();
     421                        else
     422                                $submitpost.find('#minor-publishing .spinner').show();
     423                });
     424        });
     425
     426        // Submit the form saving a draft or an autosave, and show a preview in a new tab
     427        $('#post-preview').on( 'click.post-preview', function( event ) {
     428                var $this = $(this),
     429                        $form = $('form#post'),
     430                        $previewField = $('input#wp-preview'),
     431                        target = $this.attr('target') || 'wp-preview';
     432               
     433                event.preventDefault();
     434
     435                if ( $this.prop('disabled') ) {
     436                        return;
     437                }
     438
     439                wp.autosave.server.blockSave();
     440                $previewField.val('dopreview');
     441                $form.attr( 'target', target ).submit().attr( 'target', '' );
     442
     443                // Workaround for WebKit bug preventing a form submitting twice to the same action.
     444                // https://bugs.webkit.org/show_bug.cgi?id=28633
     445                var ua = navigator.userAgent.toLowerCase();
     446                if ( ua.indexOf('safari') != -1 && ua.indexOf('chrome') == -1 ) {
     447                        $form.attr( 'action', function( index, value ) {
     448                                return value + '?t=' + new Date().getTime();
     449                        });
     450                }
     451
     452                $previewField.val('');
     453        });
     454
     455        // This code is meant to allow tabbing from Title to Post content.
     456        $('#title').on( 'keydown.editor-focus', function( event ) {
     457                var editor;
     458
     459                if ( event.which == 9 && ! event.ctrlKey && ! event.altKey && ! event.shiftKey ) {
     460                        editor = typeof tinymce != 'undefined' && tinymce.get('content');
     461
     462                        if ( editor && ! editor.isHidden() ) {
     463                                $(this).one( 'keyup', function() {
     464                                        $('#content_tbl td.mceToolbar > a').focus();
     465                                });
     466                        } else {
     467                                $('#content').focus();
     468                        }
     469
     470                        event.preventDefault();
     471                }
     472        });
     473
     474        $(window).on( 'beforeunload.edit-post', function() {
     475                var editor = typeof tinymce != 'undefined' && tinymce.activeEditor, compareString;
     476
     477                if ( editor && ! editor.isHidden() ) {
     478                        if ( editor.isDirty() )
     479                                return autosaveL10n.saveAlert;
     480                } else {
     481                        if ( fullscreen && fullscreen.settings.visible ) {
     482                                compareString = wp.autosave.getCompareString({
     483                                        post_title: $('#wp-fullscreen-title').val() || '',
     484                                        content: $('#wp_mce_fullscreen').val() || '',
     485                                        excerpt: $('#excerpt').val() || ''
     486                                });
     487                        } else {
     488                                compareString = wp.autosave.getCompareString();
     489                        }
     490
     491                        if ( compareString !== wp.autosave.server.autosaveLast )
     492                                return autosaveL10n.saveAlert;
     493                }
     494        }).on( 'unload.edit-post', function( event ) {
     495                if ( ! releaseLock )
     496                        return;
     497
     498                // Unload is triggered (by hand) on removing the Thickbox iframe.
     499                // Make sure we process only the main document unload.
     500                if ( event.target && event.target.nodeName != '#document' )
     501                        return;
     502
     503                $.ajax({
     504                        type: 'POST',
     505                        url: ajaxurl,
     506                        async: false,
     507                        data: {
     508                                action: 'wp-remove-post-lock',
     509                                _wpnonce: $('#_wpnonce').val(),
     510                                post_ID: $('#post_ID').val(),
     511                                active_post_lock: $('#active_post_lock').val()
     512                        }
     513                });
     514        });
     515
    379516        // multi-taxonomies
    380517        if ( $('#tagsdiv-post_tag').length ) {
    381518                tagBox.init();
     
    685822        } // end submitdiv
    686823
    687824        // permalink
    688         if ( $('#edit-slug-box').length ) {
    689                 editPermalink = function(post_id) {
    690                         var i, c = 0, e = $('#editable-post-name'), revert_e = e.html(), real_slug = $('#post_name'), revert_slug = real_slug.val(), b = $('#edit-slug-buttons'), revert_b = b.html(), full = $('#editable-post-name-full').html();
     825        if ( $editSlugWrap.length ) {
     826                function editPermalink() {
     827                        var i, c = 0, e = $('#editable-post-name'), revert_e = e.html(), real_slug = $('#post_name'),
     828                                revert_slug = real_slug.val(), b = $('#edit-slug-buttons'), revert_b = b.html(),
     829                                full = $('#editable-post-name-full').html();
    691830
    692831                        $('#view-post-btn').hide();
    693832                        b.html('<a href="#" class="save button button-small">'+postL10n.ok+'</a> <a class="cancel" href="#">'+postL10n.cancel+'</a>');
     
    694833                        b.children('.save').click(function() {
    695834                                var new_slug = e.children('input').val();
    696835                                if ( new_slug == $('#editable-post-name-full').text() ) {
    697                                         return $('.cancel', '#edit-slug-buttons').click();
     836                                        return $('#edit-slug-buttons .cancel').click();
    698837                                }
    699838                                $.post(ajaxurl, {
    700839                                        action: 'sample-permalink',
    701                                         post_id: post_id,
     840                                        post_id: postId,
    702841                                        new_slug: new_slug,
    703842                                        new_title: $('#title').val(),
    704843                                        samplepermalinknonce: $('#samplepermalinknonce').val()
     
    712851                                        }
    713852                                        b.html(revert_b);
    714853                                        real_slug.val(new_slug);
    715                                         makeSlugeditClickable();
    716854                                        $('#view-post-btn').show();
    717855                                });
    718856                                return false;
    719857                        });
    720858
    721                         $('.cancel', '#edit-slug-buttons').click(function() {
     859                        $('#edit-slug-buttons .cancel').click(function() {
    722860                                $('#view-post-btn').show();
    723861                                e.html(revert_e);
    724862                                b.html(revert_b);
     
    748886                        }).focus();
    749887                }
    750888
    751                 makeSlugeditClickable = function() {
    752                         $('#editable-post-name').click(function() {
    753                                 $('#edit-slug-buttons').children('.edit-slug').click();
    754                         });
    755                 }
    756                 makeSlugeditClickable();
     889                $editSlugWrap.on( 'click', function( event ) {
     890                        var $target = $( event.target );
     891
     892                        if ( $target.is('#editable-post-name') || $target.hasClass('edit-slug') ) {
     893                                editPermalink();
     894                        }
     895                });
    757896        }
    758897
    759898        // word count
  • src/wp-admin/post.php

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

     
    1 var autosave, autosaveLast = '', autosavePeriodical, autosaveDelayPreview = false, notSaved = true, blockSave = false, fullscreen, autosaveLockRelease = true;
     1/* global tinymce, wpCookies, autosaveL10n, fullscreen, switchEditors */
     2window.wp = window.wp || {};
    23
    3 jQuery(document).ready( function($) {
     4( function( $, window ) {
     5        var _disabled, _blockSave, _blockSaveTimer,
     6                $document = $(document),
     7                nextRun = 0;
    48
    5         if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' ) {
    6                 autosaveLast = wp.autosave.getCompareString({
    7                         post_title : $('#title').val() || '',
    8                         content : switchEditors.pre_wpautop( $('#content').val() ) || '',
    9                         excerpt : $('#excerpt').val() || ''
    10                 });
    11         } else {
    12                 autosaveLast = wp.autosave.getCompareString();
    13         }
     9/**
     10 * Returns the data saved in both local and remote autosave
     11 *
     12 * @return mixed Object containing the post data or false
     13 */
     14wp.autosave = {
     15        getPostData: function( type ) {
     16                var post_name, parent_id, data,
     17                        cats = [],
     18                        editor = typeof tinymce !== 'undefined' && tinymce.get('content');
    1419
    15         autosavePeriodical = $.schedule({time: autosaveL10n.autosaveInterval * 1000, func: function() { autosave(); }, repeat: true, protect: true});
    16 
    17         //Disable autosave after the form has been submitted
    18         $("#post").submit(function() {
    19                 $.cancel(autosavePeriodical);
    20                 autosaveLockRelease = false;
    21         });
    22 
    23         $('input[type="submit"], a.submitdelete', '#submitpost').click(function(){
    24                 blockSave = true;
    25                 window.onbeforeunload = null;
    26                 $(':button, :submit', '#submitpost').each(function(){
    27                         var t = $(this);
    28                         if ( t.hasClass('button-primary') )
    29                                 t.addClass('button-primary-disabled');
    30                         else
    31                                 t.addClass('button-disabled');
    32                 });
    33                 if ( $(this).attr('id') == 'publish' )
    34                         $('#major-publishing-actions .spinner').show();
    35                 else
    36                         $('#minor-publishing .spinner').show();
    37         });
    38 
    39         window.onbeforeunload = function(){
    40                 var editor = typeof(tinymce) != 'undefined' ? tinymce.activeEditor : false, compareString;
    41 
    4220                if ( editor && ! editor.isHidden() ) {
    43                         if ( editor.isDirty() )
    44                                 return autosaveL10n.saveAlert;
    45                 } else {
    46                         if ( fullscreen && fullscreen.settings.visible ) {
    47                                 compareString = wp.autosave.getCompareString({
    48                                         post_title: $('#wp-fullscreen-title').val() || '',
    49                                         content: $('#wp_mce_fullscreen').val() || '',
    50                                         excerpt: $('#excerpt').val() || ''
    51                                 });
     21                        // Don't run while the tinymce spellcheck is on. It resets all found words.
     22                        if ( editor.plugins.spellchecker && editor.plugins.spellchecker.active ) {
     23                                return false;
    5224                        } else {
    53                                 compareString = wp.autosave.getCompareString();
     25                                tinymce.triggerSave();
    5426                        }
    55 
    56                         if ( compareString != autosaveLast )
    57                                 return autosaveL10n.saveAlert;
    5827                }
    59         };
    6028
    61         $(window).unload( function(e) {
    62                 if ( ! autosaveLockRelease )
    63                         return;
     29                data = {
     30                        post_id: $( '#post_ID' ).val() || 0,
     31                        post_type: $( '#post_type' ).val() || '',
     32                        post_author: $( '#post_author' ).val() || '',
     33                        excerpt: $( '#excerpt' ).val() || ''
     34                };
    6435
    65                 // unload fires (twice) on removing the Thickbox iframe. Make sure we process only the main document unload.
    66                 if ( e.target && e.target.nodeName != '#document' )
    67                         return;
    68 
    69                 $.ajax({
    70                         type: 'POST',
    71                         url: ajaxurl,
    72                         async: false,
    73                         data: {
    74                                 action: 'wp-remove-post-lock',
    75                                 _wpnonce: $('#_wpnonce').val(),
    76                                 post_ID: $('#post_ID').val(),
    77                                 active_post_lock: $('#active_post_lock').val()
    78                         }
    79                 });
    80         } );
    81 
    82         // preview
    83         $('#post-preview').click(function(){
    84                 if ( $('#auto_draft').val() == '1' && notSaved ) {
    85                         autosaveDelayPreview = true;
    86                         autosave();
    87                         return false;
     36                if ( typeof fullscreen !== 'undefined' && fullscreen.settings.visible ) {
     37                        data.post_title = $( '#wp-fullscreen-title' ).val() || '';
     38                        data.content = $( '#wp_mce_fullscreen' ).val() || '';
     39                } else {
     40                        data.post_title = $( '#title' ).val() || '';
     41                        data.content = $( '#content' ).val() || '';
    8842                }
    89                 doPreview();
    90                 return false;
    91         });
    9243
    93         doPreview = function() {
    94                 $('input#wp-preview').val('dopreview');
    95                 $('form#post').attr('target', 'wp-preview').submit().attr('target', '');
    96 
    97                 /*
    98                  * Workaround for WebKit bug preventing a form submitting twice to the same action.
    99                  * https://bugs.webkit.org/show_bug.cgi?id=28633
    100                  */
    101                 var ua = navigator.userAgent.toLowerCase();
    102                 if ( ua.indexOf('safari') != -1 && ua.indexOf('chrome') == -1 ) {
    103                         $('form#post').attr('action', function(index, value) {
    104                                 return value + '?t=' + new Date().getTime();
    105                         });
     44                if ( type === 'local' ) {
     45                        return data;
    10646                }
    10747
    108                 $('input#wp-preview').val('');
    109         }
     48                $( 'input[id^="in-category-"]:checked' ).each( function() {
     49                        cats.push( this.value );
     50                });
     51                data.catslist = cats.join(',');
    11052
    111         // This code is meant to allow tabbing from Title to Post content.
    112         $('#title').on('keydown.editor-focus', function(e) {
    113                 var ed;
    114 
    115                 if ( e.which != 9 )
    116                         return;
    117 
    118                 if ( !e.ctrlKey && !e.altKey && !e.shiftKey ) {
    119                         if ( typeof(tinymce) != 'undefined' )
    120                                 ed = tinymce.get('content');
    121 
    122                         if ( ed && !ed.isHidden() ) {
    123                                 $(this).one('keyup', function(e){
    124                                         $('#content_tbl td.mceToolbar > a').focus();
    125                                 });
    126                         } else {
    127                                 $('#content').focus();
    128                         }
    129 
    130                         e.preventDefault();
     53                if ( post_name = $( '#post_name' ).val() ) {
     54                        data.post_name = post_name;
    13155                }
    132         });
    13356
    134         // autosave new posts after a title is typed but not if Publish or Save Draft is clicked
    135         if ( '1' == $('#auto_draft').val() ) {
    136                 $('#title').blur( function() {
    137                         if ( !this.value || $('#auto_draft').val() != '1' )
    138                                 return;
    139                         delayed_autosave();
    140                 });
    141         }
    142 
    143         // When connection is lost, keep user from submitting changes.
    144         $(document).on('heartbeat-connection-lost.autosave', function( e, error, status ) {
    145                 if ( 'timeout' === error || 503 == status ) {
    146                         var notice = $('#lost-connection-notice');
    147                         if ( ! wp.autosave.local.hasStorage ) {
    148                                 notice.find('.hide-if-no-sessionstorage').hide();
    149                         }
    150                         notice.show();
    151                         autosave_disable_buttons();
     57                if ( parent_id = $( '#parent_id' ).val() ) {
     58                        data.parent_id = parent_id;
    15259                }
    153         }).on('heartbeat-connection-restored.autosave', function() {
    154                 $('#lost-connection-notice').hide();
    155                 autosave_enable_buttons();
    156         });
    157 });
    15860
    159 function autosave_parse_response( response ) {
    160         var res = wpAjax.parseAjaxResponse(response, 'autosave'), post_id, sup;
    161 
    162         if ( res && res.responses && res.responses.length ) {
    163                 if ( res.responses[0].supplemental ) {
    164                         sup = res.responses[0].supplemental;
    165 
    166                         jQuery.each( sup, function( selector, value ) {
    167                                 if ( selector.match(/^replace-/) )
    168                                         jQuery( '#' + selector.replace('replace-', '') ).val( value );
    169                         });
     61                if ( $( '#comment_status' ).prop( 'checked' ) ) {
     62                        data.comment_status = 'open';
    17063                }
    17164
    172                 // if no errors: add slug UI and update autosave-message
    173                 if ( !res.errors ) {
    174                         if ( post_id = parseInt( res.responses[0].id, 10 ) )
    175                                 autosave_update_slug( post_id );
    176 
    177                         if ( res.responses[0].data ) // update autosave message
    178                                 jQuery('.autosave-message').text( res.responses[0].data );
     65                if ( $( '#ping_status' ).prop( 'checked' ) ) {
     66                        data.ping_status = 'open';
    17967                }
    180         }
    18168
    182         return res;
    183 }
    184 
    185 // called when autosaving pre-existing post
    186 function autosave_saved(response) {
    187         blockSave = false;
    188         autosave_parse_response(response); // parse the ajax response
    189         autosave_enable_buttons(); // re-enable disabled form buttons
    190 }
    191 
    192 // called when autosaving new post
    193 function autosave_saved_new(response) {
    194         blockSave = false;
    195         var res = autosave_parse_response(response), post_id;
    196 
    197         if ( res && res.responses.length && !res.errors ) {
    198                 // An ID is sent only for real auto-saves, not for autosave=0 "keepalive" saves
    199                 post_id = parseInt( res.responses[0].id, 10 );
    200 
    201                 if ( post_id ) {
    202                         notSaved = false;
    203                         jQuery('#auto_draft').val('0'); // No longer an auto-draft
     69                if ( $( '#auto_draft' ).val() === '1' ) {
     70                        data.auto_draft = '1';
    20471                }
    20572
    206                 autosave_enable_buttons();
     73                return data;
     74        },
    20775
    208                 if ( autosaveDelayPreview ) {
    209                         autosaveDelayPreview = false;
    210                         doPreview();
     76        // Concatenate title, content and excerpt. Used to track changes when auto-saving.
     77        getCompareString: function( post_data ) {
     78                if ( typeof post_data === 'object' ) {
     79                        return ( post_data.post_title || '' ) + '::' + ( post_data.content || '' ) + '::' + ( post_data.excerpt || '' );
    21180                }
    212         } else {
    213                 autosave_enable_buttons(); // re-enable disabled form buttons
    214         }
    215 }
    21681
    217 function autosave_update_slug(post_id) {
    218         // create slug area only if not already there
    219         if ( 'undefined' != makeSlugeditClickable && jQuery.isFunction(makeSlugeditClickable) && !jQuery('#edit-slug-box > *').size() ) {
    220                 jQuery.post( ajaxurl, {
    221                                 action: 'sample-permalink',
    222                                 post_id: post_id,
    223                                 new_title: fullscreen && fullscreen.settings.visible ? jQuery('#wp-fullscreen-title').val() : jQuery('#title').val(),
    224                                 samplepermalinknonce: jQuery('#samplepermalinknonce').val()
    225                         },
    226                         function(data) {
    227                                 if ( data !== '-1' ) {
    228                                         var box = jQuery('#edit-slug-box');
    229                                         box.html(data);
    230                                         if (box.hasClass('hidden')) {
    231                                                 box.fadeIn('fast', function () {
    232                                                         box.removeClass('hidden');
    233                                                 });
    234                                         }
    235                                         makeSlugeditClickable();
    236                                 }
    237                         }
    238                 );
    239         }
    240 }
     82                return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' );
     83        },
    24184
    242 function autosave_loading() {
    243         jQuery('.autosave-message').html(autosaveL10n.savingText);
    244 }
     85        enableButtons: function() {
     86                $document.trigger( 'autosave-enable-buttons' );
    24587
    246 function autosave_enable_buttons() {
    247         jQuery(document).trigger('autosave-enable-buttons');
    248         if ( ! wp.heartbeat || ! wp.heartbeat.hasConnectionError() ) {
    249                 // delay that a bit to avoid some rare collisions while the DOM is being updated.
    250                 setTimeout(function(){
    251                         var parent = jQuery('#submitpost');
    252                         parent.find(':button, :submit').removeAttr('disabled');
    253                         parent.find('.spinner').hide();
    254                 }, 500);
    255         }
    256 }
     88                if ( ! wp.heartbeat || ! wp.heartbeat.hasConnectionError() ) {
     89                        $( '#submitpost' ).find( ':button, :submit, a.submitdelete, #post-preview' ).prop( 'disabled', false ).removeClass( 'button-disabled' );
     90                }
     91        },
    25792
    258 function autosave_disable_buttons() {
    259         jQuery(document).trigger('autosave-disable-buttons');
    260         jQuery('#submitpost').find(':button, :submit').prop('disabled', true);
    261         // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
    262         setTimeout( autosave_enable_buttons, 5000 );
    263 }
    264 
    265 function delayed_autosave() {
    266         setTimeout(function(){
    267                 if ( blockSave )
    268                         return;
    269                 autosave();
    270         }, 200);
    271 }
    272 
    273 autosave = function() {
    274         var post_data = wp.autosave.getPostData(),
    275                 compareString,
    276                 successCallback;
    277 
    278         blockSave = true;
    279 
    280         // post_data.content cannot be retrieved at the moment
    281         if ( ! post_data.autosave )
    282                 return false;
    283 
    284         // No autosave while thickbox is open (media buttons)
    285         if ( jQuery("#TB_window").css('display') == 'block' )
    286                 return false;
    287 
    288         compareString = wp.autosave.getCompareString( post_data );
    289 
    290         // Nothing to save or no change.
    291         if ( compareString == autosaveLast )
    292                 return false;
    293 
    294         autosaveLast = compareString;
    295         jQuery(document).triggerHandler('wpcountwords', [ post_data["content"] ]);
    296 
    297         // Disable buttons until we know the save completed.
    298         autosave_disable_buttons();
    299 
    300         if ( post_data["auto_draft"] == '1' ) {
    301                 successCallback = autosave_saved_new; // new post
    302         } else {
    303                 successCallback = autosave_saved; // pre-existing post
     93        disableButtons: function() {
     94                $document.trigger('autosave-disable-buttons');
     95                $( '#submitpost' ).find( ':button, :submit, a.submitdelete, #post-preview' ).prop( 'disabled', true ).addClass( 'button-disabled' );
     96                // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
     97                setTimeout( wp.autosave.enableButtons, 5000 );
    30498        }
     99};
    305100
    306         jQuery.ajax({
    307                 data: post_data,
    308                 beforeSend: autosave_loading,
    309                 type: "POST",
    310                 url: ajaxurl,
    311                 success: successCallback
    312         });
    313 
    314         return true;
    315 }
    316 
    317101// Autosave in localStorage
    318102// set as simple object/mixin for now
    319 window.wp = window.wp || {};
    320 wp.autosave = wp.autosave || {};
    321 
    322 (function($){
    323 // Returns the data for saving in both localStorage and autosaves to the server
    324 wp.autosave.getPostData = function() {
    325         var ed = typeof tinymce != 'undefined' ? tinymce.activeEditor : null, post_name, parent_id, cats = [],
    326                 data = {
    327                         action: 'autosave',
    328                         autosave: true,
    329                         post_id: $('#post_ID').val() || 0,
    330                         autosavenonce: $('#autosavenonce').val() || '',
    331                         post_type: $('#post_type').val() || '',
    332                         post_author: $('#post_author').val() || '',
    333                         excerpt: $('#excerpt').val() || ''
    334                 };
    335 
    336         if ( ed && !ed.isHidden() ) {
    337                 // Don't run while the tinymce spellcheck is on. It resets all found words.
    338                 if ( ed.plugins.spellchecker && ed.plugins.spellchecker.active ) {
    339                         data.autosave = false;
    340                         return data;
    341                 } else {
    342                         if ( 'mce_fullscreen' == ed.id )
    343                                 tinymce.get('content').setContent(ed.getContent({format : 'raw'}), {format : 'raw'});
    344 
    345                         tinymce.triggerSave();
    346                 }
    347         }
    348 
    349         if ( typeof fullscreen != 'undefined' && fullscreen.settings.visible ) {
    350                 data['post_title'] = $('#wp-fullscreen-title').val() || '';
    351                 data['content'] = $('#wp_mce_fullscreen').val() || '';
    352         } else {
    353                 data['post_title'] = $('#title').val() || '';
    354                 data['content'] = $('#content').val() || '';
    355         }
    356 
    357         /*
    358         // We haven't been saving tags with autosave since 2.8... Start again?
    359         $('.the-tags').each( function() {
    360                 data[this.name] = this.value;
    361         });
    362         */
    363 
    364         $('input[id^="in-category-"]:checked').each( function() {
    365                 cats.push(this.value);
    366         });
    367         data['catslist'] = cats.join(',');
    368 
    369         if ( post_name = $('#post_name').val() )
    370                 data['post_name'] = post_name;
    371 
    372         if ( parent_id = $('#parent_id').val() )
    373                 data['parent_id'] = parent_id;
    374 
    375         if ( $('#comment_status').prop('checked') )
    376                 data['comment_status'] = 'open';
    377 
    378         if ( $('#ping_status').prop('checked') )
    379                 data['ping_status'] = 'open';
    380 
    381         if ( $('#auto_draft').val() == '1' )
    382                 data['auto_draft'] = '1';
    383 
    384         return data;
    385 };
    386 
    387 // Concatenate title, content and excerpt. Used to track changes when auto-saving.
    388 wp.autosave.getCompareString = function( post_data ) {
    389         if ( typeof post_data === 'object' ) {
    390                 return ( post_data.post_title || '' ) + '::' + ( post_data.content || '' ) + '::' + ( post_data.excerpt || '' );
    391         }
    392 
    393         return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' );
    394 };
    395 
    396103wp.autosave.local = {
    397 
    398104        lastSavedData: '',
    399105        blog_id: 0,
    400106        hasStorage: false,
     
    404110                var test = Math.random(), result = false;
    405111
    406112                try {
    407                         sessionStorage.setItem('wp-test', test);
    408                         result = sessionStorage.getItem('wp-test') == test;
    409                         sessionStorage.removeItem('wp-test');
     113                        sessionStorage.setItem( 'wp-test', test );
     114                        result = sessionStorage.getItem( 'wp-test' ) === test;
     115                        sessionStorage.removeItem( 'wp-test' );
    410116                } catch(e) {}
    411117
    412118                this.hasStorage = result;
     
    424130                if ( this.hasStorage && this.blog_id ) {
    425131                        stored_obj = sessionStorage.getItem( 'wp-autosave-' + this.blog_id );
    426132
    427                         if ( stored_obj )
     133                        if ( stored_obj ) {
    428134                                stored_obj = JSON.parse( stored_obj );
    429                         else
     135                        } else {
    430136                                stored_obj = {};
     137                        }
    431138                }
    432139
    433140                return stored_obj;
     
    460167        getData: function() {
    461168                var stored = this.getStorage(), post_id = $('#post_ID').val();
    462169
    463                 if ( !stored || !post_id )
     170                if ( !stored || !post_id ) {
    464171                        return false;
     172                }
    465173
    466174                return stored[ 'post_' + post_id ] || false;
    467175        },
     
    477185        setData: function( stored_data ) {
    478186                var stored = this.getStorage(), post_id = $('#post_ID').val();
    479187
    480                 if ( !stored || !post_id )
     188                if ( !stored || !post_id ) {
    481189                        return false;
     190                }
    482191
    483                 if ( stored_data )
     192                if ( stored_data ) {
    484193                        stored[ 'post_' + post_id ] = stored_data;
    485                 else if ( stored.hasOwnProperty( 'post_' + post_id ) )
     194                } else if ( stored.hasOwnProperty( 'post_' + post_id ) ) {
    486195                        delete stored[ 'post_' + post_id ];
    487                 else
     196                } else {
    488197                        return false;
     198                }
    489199
    490200                return this.setStorage(stored);
    491201        },
     
    502212        save: function( data ) {
    503213                var result = false, post_data, compareString;
    504214
    505                 if ( ! data ) {
    506                         post_data = wp.autosave.getPostData();
    507                 } else {
     215                if ( data ) {
    508216                        post_data = this.getData() || {};
    509217                        $.extend( post_data, data );
    510                         post_data.autosave = true;
     218                } else {
     219                        post_data = wp.autosave.getPostData('local');
     220                        // Cannot get the post data at the moment
     221                        if ( ! post_data ) {
     222                                return false;
     223                        }
    511224                }
    512225
    513                 // Cannot get the post data at the moment
    514                 if ( ! post_data.autosave )
    515                         return false;
    516 
    517226                compareString = wp.autosave.getCompareString( post_data );
    518227
    519228                // If the content, title and excerpt did not change since the last save, don't save again
    520                 if ( compareString == this.lastSavedData )
     229                if ( compareString === this.lastSavedData ) {
    521230                        return false;
     231                }
    522232
    523                 post_data['save_time'] = (new Date()).getTime();
    524                 post_data['status'] = $('#post_status').val() || '';
     233                post_data.save_time = ( new Date() ).getTime();
     234                post_data.status = $( '#post_status' ).val() || '';
    525235                result = this.setData( post_data );
    526236
    527                 if ( result )
     237                if ( result ) {
    528238                        this.lastSavedData = compareString;
     239                }
    529240
    530241                return result;
    531242        },
     
    535246                var self = this;
    536247
    537248                // Check if the browser supports sessionStorage and it's not disabled
    538                 if ( ! this.checkStorage() )
     249                if ( ! this.checkStorage() ) {
    539250                        return;
     251                }
    540252
    541253                // Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'.
    542                 if ( ! $('#content').length && ! $('#excerpt').length )
     254                if ( ! $('#content').length && ! $('#excerpt').length ) {
    543255                        return;
     256                }
    544257
    545                 if ( settings )
     258                if ( settings ) {
    546259                        $.extend( this, settings );
     260                }
    547261
    548                 if ( !this.blog_id )
    549                         this.blog_id = typeof window.autosaveL10n != 'undefined' ? window.autosaveL10n.blog_id : 0;
     262                if ( !this.blog_id ) {
     263                        this.blog_id = typeof window.autosaveL10n !== 'undefined' ? window.autosaveL10n.blog_id : 0;
     264                }
    550265
    551266                $(document).ready( function(){ self.run(); } );
    552267        },
     
    566281                        protect: true
    567282                });
    568283
    569                 $('form#post').on('submit.autosave-local', function() {
    570                         var editor = typeof tinymce != 'undefined' && tinymce.get('content'), post_id = $('#post_ID').val() || 0;
     284                $( 'form#post' ).on( 'submit.autosave-local', function() {
     285                        var editor = typeof tinymce !== 'undefined' && tinymce.get('content'),
     286                                post_id = $('#post_ID').val() || 0;
    571287
    572288                        if ( editor && ! editor.isHidden() ) {
    573289                                // Last onSubmit event in the editor, needs to run after the content has been moved to the textarea.
    574290                                editor.onSubmit.add( function() {
    575291                                        wp.autosave.local.save({
    576                                                 post_title: $('#title').val() || '',
    577                                                 content: $('#content').val() || '',
    578                                                 excerpt: $('#excerpt').val() || ''
     292                                                post_title: $( '#title' ).val() || '',
     293                                                content: $( '#content' ).val() || '',
     294                                                excerpt: $( '#excerpt' ).val() || ''
    579295                                        });
    580296                                });
    581297                        } else {
    582298                                self.save({
    583                                         post_title: $('#title').val() || '',
    584                                         content: $('#content').val() || '',
    585                                         excerpt: $('#excerpt').val() || ''
     299                                        post_title: $( '#title' ).val() || '',
     300                                        content: $( '#content' ).val() || '',
     301                                        excerpt: $( '#excerpt' ).val() || ''
    586302                                });
    587303                        }
    588304
     
    592308
    593309        // Strip whitespace and compare two strings
    594310        compare: function( str1, str2 ) {
    595                 function remove( string ) {
     311                function removeSpaces( string ) {
    596312                        return string.toString().replace(/[\x20\t\r\n\f]+/g, '');
    597313                }
    598314
    599                 return ( remove( str1 || '' ) == remove( str2 || '' ) );
     315                return ( removeSpaces( str1 || '' ) === removeSpaces( str2 || '' ) );
    600316        },
    601317
    602318        /**
     
    607323         * @return void
    608324         */
    609325        checkPost: function() {
    610                 var self = this, post_data = this.getData(), content, post_title, excerpt, notice,
    611                         post_id = $('#post_ID').val() || 0, cookie = wpCookies.get( 'wp-saving-post-' + post_id );
     326                var content, post_title, excerpt, notice,
     327                        self = this,
     328                        post_data = this.getData(),
     329                        post_id = $( '#post_ID' ).val() || 0,
     330                        cookie = wpCookies.get( 'wp-saving-post-' + post_id );
    612331
    613                 if ( ! post_data )
     332                if ( ! post_data ) {
    614333                        return;
     334                }
    615335
    616336                if ( cookie ) {
    617337                        wpCookies.remove( 'wp-saving-post-' + post_id );
    618338
    619                         if ( cookie == 'saved' ) {
     339                        if ( cookie === 'saved' ) {
    620340                                // The post was saved properly, remove old data and bail
    621341                                this.setData( false );
    622342                                return;
     
    624344                }
    625345
    626346                // There is a newer autosave. Don't show two "restore" notices at the same time.
    627                 if ( $('#has-newer-autosave').length )
     347                if ( $( '#has-newer-autosave' ).length ) {
    628348                        return;
     349                }
    629350
    630                 content = $('#content').val() || '';
    631                 post_title = $('#title').val() || '';
    632                 excerpt = $('#excerpt').val() || '';
     351                content = $( '#content' ).val() || '';
     352                post_title = $( '#title' ).val() || '';
     353                excerpt = $( '#excerpt' ).val() || '';
    633354
    634                 if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' )
     355                if ( $( '#wp-content-wrap' ).hasClass( 'tmce-active' ) && typeof switchEditors !== 'undefined' ) {
    635356                        content = switchEditors.pre_wpautop( content );
     357                }
    636358
    637359                // cookie == 'check' means the post was not saved properly, always show #local-storage-notice
    638                 if ( cookie != 'check' && this.compare( content, post_data.content ) && this.compare( post_title, post_data.post_title ) && this.compare( excerpt, post_data.excerpt ) ) {
     360                if ( cookie !== 'check' && this.compare( content, post_data.content ) &&
     361                                this.compare( post_title, post_data.post_title ) && this.compare( excerpt, post_data.excerpt ) ) {
    639362                        return;
    640363                }
    641364
     
    646369                        excerpt: excerpt
    647370                };
    648371
    649                 notice = $('#local-storage-notice');
    650                 $('.wrap h2').first().after( notice.addClass('updated').show() );
     372                notice = $( '#local-storage-notice' );
     373                $('.wrap h2').first().after( notice.addClass( 'updated' ).show() );
    651374
    652                 notice.on( 'click', function(e) {
     375                notice.on( 'click.autosae-local', function(e) {
    653376                        var target = $( e.target );
    654377
    655                         if ( target.hasClass('restore-backup') ) {
     378                        if ( target.hasClass( 'restore-backup' ) ) {
    656379                                self.restorePost( self.restore_post_data );
    657380                                target.parent().hide();
    658                                 $(this).find('p.undo-restore').show();
    659                         } else if ( target.hasClass('undo-restore-backup') ) {
     381                                $(this).find( 'p.undo-restore' ).show();
     382                        } else if ( target.hasClass( 'undo-restore-backup' ) ) {
    660383                                self.restorePost( self.undo_post_data );
    661384                                target.parent().hide();
    662                                 $(this).find('p.local-restore').show();
     385                                $(this).find( 'p.local-restore' ).show();
    663386                        }
    664387
    665388                        e.preventDefault();
     
    674397                        // Set the last saved data
    675398                        this.lastSavedData = wp.autosave.getCompareString( post_data );
    676399
    677                         if ( $('#title').val() != post_data.post_title )
    678                                 $('#title').focus().val( post_data.post_title || '' );
     400                        if ( $( '#title' ).val() !== post_data.post_title ) {
     401                                $( '#title' ).focus().val( post_data.post_title || '' );
     402                        }
    679403
    680                         $('#excerpt').val( post_data.excerpt || '' );
    681                         editor = typeof tinymce != 'undefined' && tinymce.get('content');
     404                        $( '#excerpt' ).val( post_data.excerpt || '' );
     405                        editor = typeof tinymce !== 'undefined' && tinymce.get('content');
    682406
    683                         if ( editor && ! editor.isHidden() && typeof switchEditors != 'undefined' ) {
     407                        if ( editor && ! editor.isHidden() && typeof switchEditors !== 'undefined' ) {
    684408                                // Make sure there's an undo level in the editor
    685409                                editor.undoManager.add();
    686410                                editor.setContent( post_data.content ? switchEditors.wpautop( post_data.content ) : '' );
    687411                        } else {
    688412                                // Make sure the Text editor is selected
    689                                 $('#content-html').click();
    690                                 $('#content').val( post_data.content );
     413                                $( '#content-html' ).click();
     414                                $( '#content' ).val( post_data.content );
    691415                        }
    692416
    693417                        return true;
     
    697421        }
    698422};
    699423
     424// Autosave on the server
     425wp.autosave.server = {
     426        autosaveLast: '',
     427
     428        init: function() {
     429                var self = this;
     430
     431                $document.on( 'heartbeat-send.autosave', function( event, data ) {
     432                        var autosaveData = self.getData();
     433
     434                        if ( autosaveData ) {
     435                                data.wp_autosave = autosaveData;
     436                        }
     437                }).on( 'heartbeat-tick.autosave', function( event, data ) {
     438                        if ( data.wp_autosave ) {
     439                                self.response( data.wp_autosave );
     440                        }
     441                }).on( 'heartbeat-connection-lost.autosave', function( event, error, status ) {
     442                        // When connection is lost, keep user from submitting changes.
     443                        if ( 'timeout' === error || 603 === status ) {
     444                                var notice = $('#lost-connection-notice');
     445
     446                                if ( ! wp.autosave.local.hasStorage ) {
     447                                        notice.find('.hide-if-no-sessionstorage').hide();
     448                                }
     449
     450                                notice.show();
     451                                wp.autosave.disableButtons();
     452                        }
     453                }).on( 'heartbeat-connection-restored.autosave', function() {
     454                        $('#lost-connection-notice').hide();
     455                        wp.autosave.enableButtons();
     456                }).ready( function() {
     457                        self._schedule();
     458
     459                        if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors !== 'undefined' ) {
     460                                self.autosaveLast = wp.autosave.getCompareString({
     461                                        post_title : $( '#title' ).val() || '',
     462                                        content : switchEditors.pre_wpautop( $( '#content' ).val() ) || '',
     463                                        excerpt : $( '#excerpt' ).val() || ''
     464                                });
     465                        } else {
     466                                self.autosaveLast = wp.autosave.getCompareString();
     467                        }
     468
     469                        // Autosave new posts after a title is typed
     470                        if ( $( '#auto_draft' ).val() ) {
     471                                $( '#title' ).blur( function() {
     472                                        if ( ! this.value || $( '#auto_draft' ).val() !== '1' ) {
     473                                                return;
     474                                        }
     475
     476                                        self.triggerSave();
     477                                });
     478                        }
     479                });
     480        },
     481
     482        // Block saving for the next 10 sec.
     483        blockSave: function() {
     484                _blockSave = true;
     485                window.clearTimeout( _blockSaveTimer );
     486                _blockSaveTimer = window.setTimeout( function() {
     487                        _blockSave = false;
     488                }, 10000 );
     489        },
     490
     491        // Runs on heartbeat-response
     492        response: function( data ) {
     493                this._schedule();
     494                _blockSave = false;
     495                this.autosaveLast = this.lastCompareString;
     496                this.lastCompareString = '';
     497
     498                $document.trigger( 'after-autosave', [data] );
     499                $( '.autosave-message' ).text( data.message );
     500                wp.autosave.enableButtons();
     501
     502                if ( data.success ) {
     503                        // No longer an auto-draft
     504                        $( '#auto_draft' ).val('');
     505                }
     506        },
     507
     508        disable: function() {
     509                _disabled = true;
     510        },
     511
     512        _schedule: function() {
     513                nextRun = ( new Date() ).getTime() + ( autosaveL10n.autosaveInterval * 1000 ) || 60000;
     514        },
     515
     516        // Reset the timing and tell heartbeat to connect now
     517        triggerSave: function() {
     518                nextRun = 0;
     519                wp.heartbeat.connectNow();
     520        },
     521
     522        // Runs on 'heartbeat-send'
     523        getData: function() {
     524                var compareString, postData;
     525
     526                if ( _disabled || _blockSave ) {
     527                        return false;
     528                }
     529
     530                if ( ( new Date() ).getTime() < nextRun ) {
     531                        return false;
     532                }
     533
     534                postData = wp.autosave.getPostData();
     535
     536                // post_data.content cannot be retrieved at the moment?
     537                if ( ! postData ) {
     538                        return false;
     539                }
     540
     541                compareString = wp.autosave.getCompareString( postData );
     542
     543                // Nothing to save or no change.
     544                if ( compareString === this.autosaveLast ) {
     545                        return false;
     546                }
     547
     548                this.blockSave();
     549                wp.autosave.disableButtons();
     550                this.lastCompareString = compareString;
     551
     552                $document.trigger( 'wpcountwords', [ postData.content ] )
     553                        .trigger( 'before-autosave', [ postData ] );
     554
     555                $( '.autosave-message' ).text( autosaveL10n.savingText );
     556                postData._wpnonce = $( '#_wpnonce' ).val() || '';
     557
     558                return postData;
     559        }
     560};
     561
    700562wp.autosave.local.init();
     563wp.autosave.server.init();
    701564
    702 }(jQuery));
     565}( jQuery, window ));
  • 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 );
     104               
     105                wp_set_current_user( $this->user_id );
    77106
    78                 // Become an administrator
    79                 $this->_setRole( 'administrator' );
     107                // Ensure post is locked
     108                $this->assertEquals( $another_user_id, wp_check_post_lock( $this->_post->ID ) );
    80109
    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}