Ticket #18515: locking_first_pass.diff

File locking_first_pass.diff, 18.2 KB (added by benbalter, 20 months 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 );