Make WordPress Core

Ticket #18515: locking_first_pass.diff

File locking_first_pass.diff, 18.2 KB (added by benbalter, 12 years ago)

One approach to improved post locks

  • wp-includes/post-template.php

     
    12671267 * @since 2.6.0
    12681268 *
    12691269 * @uses date_i18n()
     1270 * @uses human_time_diff
    12701271 *
    12711272 * @param int|object $revision Revision ID or revision object.
    12721273 * @param bool $link Optional, default is true. Link to revisions's page?
     
    12781279
    12791280        if ( !in_array( $revision->post_type, array( 'post', 'page', 'revision' ) ) )
    12801281                return false;
    1281 
     1282       
     1283        //returns i18n'd string e.g., "5 min"
     1284        $diff = human_time_diff( strtotime( $revision->post_date ) );
     1285        $diff = sprintf( __( '%s ago' ), $diff );
     1286       
    12821287        /* translators: revision date format, see http://php.net/date */
    12831288        $datef = _x( 'j F, Y @ G:i', 'revision date format');
    1284         /* translators: 1: date */
    1285         $autosavef = __( '%1$s [Autosave]' );
    1286         /* translators: 1: date */
    1287         $currentf  = __( '%1$s [Current Revision]' );
    1288 
    12891289        $date = date_i18n( $datef, strtotime( $revision->post_modified ) );
     1290       
    12901291        if ( $link && current_user_can( 'edit_post', $revision->ID ) && $link = get_edit_post_link( $revision->ID ) )
    1291                 $date = "<a href='$link'>$date</a>";
     1292                $timestamp = '<a href="' . $link . '" class="timestamp" title="' . $date . '" id="' . strtotime( $revision->post_date ) .'">' . $diff . '</a>';
     1293        else
     1294                $timestamp = '<span class="timestamp" title="' . $date . '" id="' . strtotime( $revision->post_date ) .'">' . $diff . '</span>';
    12921295
    12931296        if ( !wp_is_post_revision( $revision ) )
    1294                 $date = sprintf( $currentf, $date );
     1297                $timestamp .= __( ' [Autosave]' );
    12951298        elseif ( wp_is_post_autosave( $revision ) )
    1296                 $date = sprintf( $autosavef, $date );
     1299                $timestamp .= __( ' [Current Revision]' );
     1300        elseif ( wp_is_post_conflicted( $revision ) )
     1301                $timestamp .=  __( ' [Conflicted Revision]' );
    12971302
    1298         return $date;
     1303        return $timestamp;
    12991304}
    13001305
    13011306/**
  • wp-includes/post.php

     
    47094709        $return['post_status']   = 'inherit';
    47104710        $return['post_type']     = 'revision';
    47114711        $return['post_name']     = $autosave ? "$post[ID]-autosave" : "$post[ID]-revision";
     4712       
     4713        if ( wp_check_post_lock( $post->ID ) )
     4714                $return['post_name'] .= "-conflicted";
     4715       
    47124716        $return['post_date']     = isset($post['post_modified']) ? $post['post_modified'] : '';
    47134717        $return['post_date_gmt'] = isset($post['post_modified_gmt']) ? $post['post_modified_gmt'] : '';
    47144718
     
    48564860}
    48574861
    48584862/**
     4863 * Determines if the specified post is a conflicted revision
     4864 *
     4865 * @package WordPress
     4866 * @subpackage Post_Revisions
     4867 * @since 3.3
     4868 *
     4869 * @param int|object $post Post ID or post object.
     4870 * @return bool|int False if not a revision, ID of conflicted revision's parent otherwise
     4871 */
     4872function wp_is_post_conflicted( $post ) {
     4873        if ( !$post = wp_get_post_revision( $post ) )
     4874                return false;
     4875        if ( strrpos( $post->post_name, 'conflicted' ) === false )
     4876                return false;
     4877        return (int) $post->post_parent;
     4878}
     4879
     4880/**
    48594881 * Inserts post data into the posts table as a post revision.
    48604882 *
    48614883 * @package WordPress
  • wp-includes/js/autosave.dev.js

     
    8787                        delayed_autosave();
    8888                });
    8989        }
     90
     91        //lock override toggle 
     92        $('#lock_override').click( function( event ) {
     93                event.preventDefault();
     94
     95         jQuery.post( ajaxurl, {
     96                        action: 'override_edit_lock',
     97                        post_ID: $("#post_ID").val() || 0,
     98                        autosavenonce: $('#autosavenonce').val() || 0
     99                },function(data) {
     100                                if ( data ) {
     101                                        //hide the lock notice
     102                                        $('#lock_override').parent().parent().fadeOut( 'slow' );
     103                                       
     104                                        autosaveL10n.hasLock = true;
     105                                        delayed_autosave();
     106                               
     107                                } else {
     108                                        alert( lockError );
     109                                }
     110                        }
     111                );   
     112               
     113        });
     114       
     115        //HTML5 Lock Override Notifications permission check
     116        //must be performed concurrent with a user action
     117        $('input').keyup( function() {
     118                if ( window.webkitNotifications )
     119                        window.webkitNotifications.requestPermission( );
     120        });
     121
     122
     123        //automatically refresh all timestamps every minute with actual human time diff
     124        setInterval( "updateTimestamps()", 60000 ); //60k = 1 minute
     125       
     126        // remove the post lock on unload
     127        window.onbeforeunload = function () { 
     128               
     129                if ( autosaveL10n.hasLock == false )
     130                        return;
     131               
     132                $.ajax({
     133                        type: 'POST',
     134                        url: ajaxurl,
     135                        async: false,
     136                        data: {
     137                                action: 'wp-remove-post-lock',
     138                                _wpnonce: $('#_wpnonce').val(),
     139                                post_ID: $('#post_ID').val(),
     140                                user_ID: $('#user_ID').val(),
     141                        }
     142                });     
     143               
     144        };
     145
    90146});
    91147
     148//Javscript version of the WP human time diff PHP function, allows time stamps to by dynamically updated
     149function human_time_diff( from, to  ) {
     150
     151                //allow $to to be optional; adjust to server's GMT offset so timezones stay in sync
     152                d = new Date();
     153                to = to || ( d.getTime() / 1000 ) + parseInt( autosaveL10n.offset );
     154               
     155                //caclulate difference in seconds
     156                diff = Math.abs(to - from);
     157               
     158                //less than one hour; therefore display minutes
     159                if (diff <= 3600) {
     160               
     161                        //convert seconds to minutes
     162                        mins = Math.floor(diff / 60);
     163                       
     164                        //roundup
     165                        if (mins <= 1) {
     166                                mins = 1;
     167                        }
     168                       
     169                        if ( mins == 1) //singular
     170                                return autosaveL10n.minute.replace( '%d', mins);
     171                        else //plural
     172                                return autosaveL10n.minutes.replace( '%d', mins);
     173                               
     174                //if greater than an hour but less than a day, display as hours
     175                } else if ((diff <= 86400) && (diff > 3600)) {
     176               
     177                        //convert seconds to hours
     178                        hours = Math.floor(diff / 3600);
     179       
     180                        //roundup
     181                        if (hours <= 1) {
     182                                hours = 1;
     183                        }
     184                       
     185                        if ( hours == 1) //singular
     186                                return autosaveL10n.hour.replace( '%d', hours);
     187                        else //plural
     188                                return autosaveL10n.hours.replace( '%d', hours);
     189               
     190                //if it's more than a day, display as days
     191                } else if (diff >= 86400) {
     192               
     193                        //convert seconds to days
     194                        days = Math.floor(diff / 86400);
     195                       
     196                        //roundup
     197                        if (days <= 1) {
     198                                days = 1;
     199                        }
     200                       
     201                        if ( days == 1) //singular
     202                                return autosaveL10n.day.replace( '%d', days);
     203                        else //plural
     204                                return autosaveL10n.days.replace( '%d', days);
     205                }
     206}
     207
     208//loop through all timestamps and update
     209function updateTimestamps() {
     210
     211    //loop through all timestamps and update the timestamp
     212    jQuery('.timestamp').each( function(){
     213        jQuery(this).text( human_time_diff( jQuery(this).attr('id') ) );
     214    });
     215   
     216}
     217
     218//HTML5 Lock Override Notifications
     219function lock_override_notice( notice ) {
     220    if ( window.webkitNotifications.checkPermission() > 0 ) {
     221        window.webkitNotifications.RequestPermission( notice );
     222    } else {
     223        window.webkitNotifications.createNotification(
     224        autosaveL10n.logoURL, autosaveL10n.lostLockNoticeTitle, notice ).show();
     225    }
     226}
     227
     228
    92229function autosave_parse_response(response) {
    93230        var res = wpAjax.parseAjaxResponse(response, 'autosave'), message = '', postID, sup;
    94231
     
    102239                                res = { errors: true };
    103240                        }
    104241
    105                         if ( sup['alert'] ) {
     242                        //if the #locknotice is present, this is a refresh, no need to alert again
     243                        if ( sup['alert'] && jQuery('#locknotice').length == 0 ) {
    106244                                jQuery('#autosave-alert').remove();
    107245                                jQuery('#titlediv').after('<div id="autosave-alert" class="error below-h2"><p>' + sup['alert'] + '</p></div>');
     246                       
     247                                //Lock Override alert
     248                                //todo: right now the only possible alert is file lock loss, but probably should confirm here
     249                                if ( window.webkitNotifications ) {
     250                                        //browser supports html5 Notifications
     251                                        lock_override_notice( sup['alert'] );
     252                                } else {
     253                                        //browser does not support lock override notice, send old school alert
     254                                        alert( sup['alert'] );
     255                                }
     256                       
     257                        //submit the form so that we can save whatever we've got
     258                        //and then reload the page with the lock
     259                                jQuery('#post').submit();
     260                               
    108261                        }
    109262
    110263                        jQuery.each(sup, function(selector, value) {
  • wp-includes/script-loader.php

     
    285285
    286286                $scripts->add( 'postbox', "/wp-admin/js/postbox$suffix.js", array('jquery-ui-sortable'), '20110916', 1 );
    287287
    288                 $scripts->add( 'post', "/wp-admin/js/post$suffix.js", array('suggest', 'wp-lists', 'postbox'), '20110524', 1 );
     288                $scripts->add( 'post', "/wp-admin/js/post$suffix.js", array('suggest', 'wp-lists', 'postbox'), '20110824', 1 );
    289289                $scripts->add_script_data( 'post', 'postL10n', array(
    290290                        'ok' => __('OK'),
    291291                        'cancel' => __('Cancel'),
     
    472472 * @since 2.5.0
    473473 */
    474474function wp_just_in_time_script_localization() {
    475 
     475        global $post;
     476       
    476477        wp_localize_script( 'autosave', 'autosaveL10n', array(
    477478                'autosaveInterval' => AUTOSAVE_INTERVAL,
    478479                'savingText' => __('Saving Draft&#8230;'),
    479                 'saveAlert' => __('The changes you made will be lost if you navigate away from this page.')
    480         ) );
     480                'saveAlert' => __('The changes you made will be lost if you navigate away from this page.'),
     481                'logoURL' => admin_url('images/logo.gif'),
     482                'lockOverrideError' => __('There was an error overriding the lock.'),
     483                'lostLockNoticeTitle' => __('Lost Lock'),
     484                'unlockNotice' => __('Releasing so that others can edit...'),
     485                'hasLock' => ( function_exists( 'wp_check_post_lock') && !wp_check_post_lock( $post->ID ) ) ? true : false,
     486                'offset' => get_option( 'gmt_offset' ) * 3600,
     487                'minute' => __('%d mins'),
     488                'minutes' => __('%d mins'),
     489                'hour' => __('%d hour'),
     490                'hours' => __('%d hours'),
     491                'day' => __('%d day'),
     492                'days' => __('%d days'),
     493        ) );
    481494
    482495}
    483496
  • wp-admin/admin-ajax.php

     
    958958                $data = __( 'Autosave disabled.' );
    959959
    960960                $supplemental['disable_autosave'] = 'disable';
    961                 $alert .= sprintf( __( '%s is currently editing this article. If you update it, you will overwrite the changes.' ), esc_html( $last_user_name ) );
     961               
     962                //TODO: this language needs to be updated
     963                $alert .= sprintf( __( '%1$s currently has the post "%2$s" checked out. Your changes will be saved as a conflicted revision.' ), esc_html( $last_user_name ), $post->post_title );
     964                       
    962965        }
    963966
    964967        if ( 'page' == $post->post_type ) {
     
    988991                        $id = $post->ID;
    989992        }
    990993
    991         if ( $do_lock && ( isset( $_POST['auto_draft'] ) && ( $_POST['auto_draft'] != '1' ) ) && $id && is_numeric($id) )
     994        //auto_draft will either be 1 (and thus ID ='s 0) or not POST'd at all
     995        if ( $do_lock && !isset( $_POST['auto_draft'] ) && $id && is_numeric($id) )
    992996                wp_set_post_lock( $id );
    993997
    994998        if ( $nonce_age == 2 ) {
     
    15481552
    15491553        echo json_encode( array( 'message' => $message, 'last_edited' => $last_edited ) );
    15501554        die();
     1555        break;
     1556case 'override_edit_lock':
     1557
     1558        //TODO: Does this exist if not in edit mode?
     1559        check_ajax_referer( 'autosave', 'autosavenonce' );
     1560
     1561        $post_id = isset($_POST['post_ID']) ? (int) $_POST['post_ID'] : 0;
     1562       
     1563        if ( !current_user_can('edit_post', $post_id) )
     1564                die('-1');
     1565
     1566               
     1567        if (    !$post_id ||
     1568                        !current_user_can( 'edit_post' , $post_id ) ||
     1569                        !current_user_can( 'override_edit_lock' )
     1570                        )
     1571                                wp_die( __( 'Not authorized' ) );
     1572                       
     1573        //not locked or locked to self
     1574        if ( !( $current_owner = wp_check_post_lock( $post_id) ) )
     1575                die( '-1' );
     1576               
     1577        wp_set_post_lock( $post_id );
     1578               
     1579        $current_user = wp_get_current_user();
     1580               
     1581        //if ( apply_filters( 'send_document_override_notice', $send_notice ) )
     1582        //      $this->send_override_notice( $postID, $current_owner, $current_user->ID );
     1583                       
     1584        do_action( 'edit_lock_override', $post_id, $current_user->ID, $current_owner );
     1585               
     1586        die( '1' );
     1587
    15511588        break;
     1589case 'wp-remove-post-lock' :
     1590
     1591        $post_id = isset($_POST['post_ID']) ? (int) $_POST['post_ID'] : 0;
     1592        if ( !current_user_can('edit_post', $post_id) )
     1593                die();
     1594
     1595        $post = $post_type = null;
     1596
     1597        if ( $post_id ) {
     1598                $post = get_post($post_id);
     1599                if ( $post )
     1600                        $post_type = $post->post_type;
     1601        }
     1602
     1603        check_ajax_referer('update-' . $post_type . '_' . $post_id);
     1604
     1605        sleep(4); //why are we sleeping?
     1606
     1607        //TODO: Can this be simplified with wp_check_post_lock?
     1608       
     1609        if ( !$lock = get_post_meta( $post_id, '_edit_lock', true ) )
     1610                die('-1');
     1611
     1612        $lock = explode( ':', $lock );
     1613        $user_id = isset( $lock[1] ) ? $lock[1] : get_post_meta( $post_id, '_edit_last', true );
     1614        $locked = (int) $lock[0];
     1615
     1616        if ( time() - $locked < 5 ) // give a few seconds for the lock to be refreshed if the user reloaded the page
     1617                die('0');
     1618
     1619        if ( $user_id == get_current_user_id() )
     1620                delete_post_meta( $post_id, '_edit_lock' );
     1621
     1622        die('1');
     1623        break;
    15521624default :
    15531625        do_action( 'wp_ajax_' . $_POST['action'] );
    15541626        die('0');
  • wp-admin/includes/post.php

     
    189189                                break;
    190190                }
    191191        }
    192 
     192       
     193        //above this point has been read only, starting now, we're going to begin writing,
     194        //so do checks here if we do want to break stuff
     195       
     196        //if there's a post lock, and we're not the owner, hijack the save and save a revision instead
     197        $lock_owner = wp_check_post_lock( $post_ID );
     198        if ( $lock_owner ) {
     199                $revision_ID = _wp_put_post_revision( $post_data ); //TODO: IS this properly sanitized?
     200                return false; //maybe we want to return revision_ID here?
     201        }
     202       
    193203        // Post Formats
    194204        if ( current_theme_supports( 'post-formats' ) && isset( $post_data['post_format'] ) ) {
    195205                $formats = get_theme_support( 'post-formats' );
     
    12701280                        $message = __( 'Warning: %s is currently editing this page' );
    12711281                        break;
    12721282                default:
    1273                         $message = __( 'Warning: %s is currently editing this.' );
     1283                        $cpt = get_post_type_object( $post->post_type );
     1284                        $message = __( 'Warning: %s is currently editing this ' . $cpt->labels->singular_name . '.' );
    12741285        }
     1286       
     1287        if ( current_user_can( 'override_edit_lock' ) )
     1288                $message .= '. If you believe this is in error, you can <a href="#" id="lock_override">override the lock</a>, but their changes will be lost.';
    12751289
    12761290        $message = sprintf( $message, esc_html( $last_user_name ) );
    1277         echo "<div class='error'><p>$message</p></div>";
     1291        echo "<div class='error' id=\"locknotice\"><p>$message</p></div>";
    12781292}
    12791293
    12801294/**
  • wp-admin/includes/schema.php

     
    382382        populate_roles_270();
    383383        populate_roles_280();
    384384        populate_roles_300();
     385        populate_roles_330();
    385386}
    386387
    387388/**
     
    623624        }
    624625}
    625626
     627function populate_roles_330() {
     628       
     629        $roles = array('administrator', 'editor');
     630        foreach ($roles as $role) {
     631                $role =& get_role($role);
     632                if ( empty($role) )
     633                        continue;
     634
     635                $role->add_cap('override_edit_lock');
     636        }
     637       
     638}
     639
    626640/**
    627641 * populate network settings
    628642 *
  • wp-admin/post.php

     
    5151 * @param int $post_id Optional. Post ID.
    5252 */
    5353function redirect_post($post_id = '') {
    54         if ( isset($_POST['save']) || isset($_POST['publish']) ) {
     54       
     55        if ( wp_check_post_lock( $post_id ) ) {
     56                $location = add_query_arg( 'message', 11, get_edit_post_link( $post_id, 'url' ) );
     57        } else if ( isset($_POST['save']) || isset($_POST['publish']) ) {
    5558                $status = get_post_status( $post_id );
    56 
    57                 if ( isset( $_POST['publish'] ) ) {
     59               
     60                 if ( isset( $_POST['publish'] ) ) {
    5861                        switch ( $status ) {
    5962                                case 'pending':
    6063                                        $message = 8;
     
    6972                                $message = 'draft' == $status ? 10 : 1;
    7073                }
    7174
     75               
     76
    7277                $location = add_query_arg( 'message', $message, get_edit_post_link( $post_id, 'url' ) );
    7378        } elseif ( isset($_POST['addmeta']) && $_POST['addmeta'] ) {
    7479                $location = add_query_arg( 'message', 2, wp_get_referer() );
     
    175180                add_action('admin_notices', '_admin_notice_post_locked' );
    176181        } else {
    177182                wp_set_post_lock( $post->ID );
    178                 wp_enqueue_script('autosave');
    179183        }
     184       
     185        wp_enqueue_script('autosave');
    180186
    181187        $title = $post_type_object->labels->edit_item;
    182188        $post = get_post_to_edit($post_id);
  • wp-admin/js/post.dev.js

     
    643643        }
    644644
    645645        wptitlehint();
     646       
    646647});
  • wp-admin/edit-form-advanced.php

     
    2727$user_ID = isset($user_ID) ? (int) $user_ID : 0;
    2828$action = isset($action) ? $action : '';
    2929
     30if ( $user = wp_check_post_lock( $post_ID ) )
     31        $user = get_user_by( 'id' , $user );
     32
    3033$messages = array();
    3134$messages['post'] = array(
    3235         0 => '', // Unused. Messages start at index 1.
     
    4346                // translators: Publish box date format, see http://php.net/date
    4447                date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
    4548        10 => sprintf( __('Post draft updated. <a target="_blank" href="%s">Preview post</a>'), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
     49        11 => sprintf( __( 'Your save conflicted against %s\'s version. Your copy was saved as conflicted.'), $user->display_name ), //TODO: This need better language
    4650);
    4751$messages['page'] = array(
    4852         0 => '', // Unused. Messages start at index 1.
     
    5660         8 => sprintf( __('Page submitted. <a target="_blank" href="%s">Preview page</a>'), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
    5761         9 => sprintf( __('Page scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview page</a>'), date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
    5862        10 => sprintf( __('Page draft updated. <a target="_blank" href="%s">Preview page</a>'), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
     63        11 => sprintf( __( 'Your save conflicted against %s\'s version. Your copy was saved as conflicted.'), $user->display_name ), //TODO: This need better language
    5964);
    6065
    6166$messages = apply_filters( 'post_updated_messages', $messages );