[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-admin/includes/ -> revision.php (source)

   1  <?php
   2  /**
   3   * WordPress Administration Revisions API
   4   *
   5   * @package WordPress
   6   * @subpackage Administration
   7   * @since 3.6.0
   8   */
   9  
  10  /**
  11   * Get the revision UI diff.
  12   *
  13   * @since 3.6.0
  14   *
  15   * @param WP_Post|int $post         The post object or post ID.
  16   * @param int         $compare_from The revision ID to compare from.
  17   * @param int         $compare_to   The revision ID to come to.
  18   * @return array|bool Associative array of a post's revisioned fields and their diffs.
  19   *                    Or, false on failure.
  20   */
  21  function wp_get_revision_ui_diff( $post, $compare_from, $compare_to ) {
  22      $post = get_post( $post );
  23      if ( ! $post ) {
  24          return false;
  25      }
  26  
  27      if ( $compare_from ) {
  28          $compare_from = get_post( $compare_from );
  29          if ( ! $compare_from ) {
  30              return false;
  31          }
  32      } else {
  33          // If we're dealing with the first revision...
  34          $compare_from = false;
  35      }
  36  
  37      $compare_to = get_post( $compare_to );
  38      if ( ! $compare_to ) {
  39          return false;
  40      }
  41  
  42      // If comparing revisions, make sure we're dealing with the right post parent.
  43      // The parent post may be a 'revision' when revisions are disabled and we're looking at autosaves.
  44      if ( $compare_from && $compare_from->post_parent !== $post->ID && $compare_from->ID !== $post->ID ) {
  45          return false;
  46      }
  47      if ( $compare_to->post_parent !== $post->ID && $compare_to->ID !== $post->ID ) {
  48          return false;
  49      }
  50  
  51      if ( $compare_from && strtotime( $compare_from->post_date_gmt ) > strtotime( $compare_to->post_date_gmt ) ) {
  52          $temp         = $compare_from;
  53          $compare_from = $compare_to;
  54          $compare_to   = $temp;
  55      }
  56  
  57      // Add default title if title field is empty.
  58      if ( $compare_from && empty( $compare_from->post_title ) ) {
  59          $compare_from->post_title = __( '(no title)' );
  60      }
  61      if ( empty( $compare_to->post_title ) ) {
  62          $compare_to->post_title = __( '(no title)' );
  63      }
  64  
  65      $return = array();
  66  
  67      foreach ( _wp_post_revision_fields( $post ) as $field => $name ) {
  68          /**
  69           * Contextually filter a post revision field.
  70           *
  71           * The dynamic portion of the hook name, `$field`, corresponds to each of the post
  72           * fields of the revision object being iterated over in a foreach statement.
  73           *
  74           * @since 3.6.0
  75           *
  76           * @param string  $revision_field The current revision field to compare to or from.
  77           * @param string  $field          The current revision field.
  78           * @param WP_Post $compare_from   The revision post object to compare to or from.
  79           * @param string  $context        The context of whether the current revision is the old
  80           *                                or the new one. Values are 'to' or 'from'.
  81           */
  82          $content_from = $compare_from ? apply_filters( "_wp_post_revision_field_{$field}", $compare_from->$field, $field, $compare_from, 'from' ) : '';
  83  
  84          /** This filter is documented in wp-admin/includes/revision.php */
  85          $content_to = apply_filters( "_wp_post_revision_field_{$field}", $compare_to->$field, $field, $compare_to, 'to' );
  86  
  87          $args = array(
  88              'show_split_view' => true,
  89          );
  90  
  91          /**
  92           * Filters revisions text diff options.
  93           *
  94           * Filters the options passed to wp_text_diff() when viewing a post revision.
  95           *
  96           * @since 4.1.0
  97           *
  98           * @param array   $args {
  99           *     Associative array of options to pass to wp_text_diff().
 100           *
 101           *     @type bool $show_split_view True for split view (two columns), false for
 102           *                                 un-split view (single column). Default true.
 103           * }
 104           * @param string  $field        The current revision field.
 105           * @param WP_Post $compare_from The revision post to compare from.
 106           * @param WP_Post $compare_to   The revision post to compare to.
 107           */
 108          $args = apply_filters( 'revision_text_diff_options', $args, $field, $compare_from, $compare_to );
 109  
 110          $diff = wp_text_diff( $content_from, $content_to, $args );
 111  
 112          if ( ! $diff && 'post_title' === $field ) {
 113              // It's a better user experience to still show the Title, even if it didn't change.
 114              // No, you didn't see this.
 115              $diff = '<table class="diff"><colgroup><col class="content diffsplit left"><col class="content diffsplit middle"><col class="content diffsplit right"></colgroup><tbody><tr>';
 116  
 117              // In split screen mode, show the title before/after side by side.
 118              if ( true === $args['show_split_view'] ) {
 119                  $diff .= '<td>' . esc_html( $compare_from->post_title ) . '</td><td></td><td>' . esc_html( $compare_to->post_title ) . '</td>';
 120              } else {
 121                  $diff .= '<td>' . esc_html( $compare_from->post_title ) . '</td>';
 122  
 123                  // In single column mode, only show the title once if unchanged.
 124                  if ( $compare_from->post_title !== $compare_to->post_title ) {
 125                      $diff .= '</tr><tr><td>' . esc_html( $compare_to->post_title ) . '</td>';
 126                  }
 127              }
 128  
 129              $diff .= '</tr></tbody>';
 130              $diff .= '</table>';
 131          }
 132  
 133          if ( $diff ) {
 134              $return[] = array(
 135                  'id'   => $field,
 136                  'name' => $name,
 137                  'diff' => $diff,
 138              );
 139          }
 140      }
 141  
 142      /**
 143       * Filters the fields displayed in the post revision diff UI.
 144       *
 145       * @since 4.1.0
 146       *
 147       * @param array[] $return       Array of revision UI fields. Each item is an array of id, name, and diff.
 148       * @param WP_Post $compare_from The revision post to compare from.
 149       * @param WP_Post $compare_to   The revision post to compare to.
 150       */
 151      return apply_filters( 'wp_get_revision_ui_diff', $return, $compare_from, $compare_to );
 152  
 153  }
 154  
 155  /**
 156   * Prepare revisions for JavaScript.
 157   *
 158   * @since 3.6.0
 159   *
 160   * @param WP_Post|int $post                 The post object or post ID.
 161   * @param int         $selected_revision_id The selected revision ID.
 162   * @param int         $from                 Optional. The revision ID to compare from.
 163   * @return array An associative array of revision data and related settings.
 164   */
 165  function wp_prepare_revisions_for_js( $post, $selected_revision_id, $from = null ) {
 166      $post    = get_post( $post );
 167      $authors = array();
 168      $now_gmt = time();
 169  
 170      $revisions = wp_get_post_revisions(
 171          $post->ID,
 172          array(
 173              'order'         => 'ASC',
 174              'check_enabled' => false,
 175          )
 176      );
 177      // If revisions are disabled, we only want autosaves and the current post.
 178      if ( ! wp_revisions_enabled( $post ) ) {
 179          foreach ( $revisions as $revision_id => $revision ) {
 180              if ( ! wp_is_post_autosave( $revision ) ) {
 181                  unset( $revisions[ $revision_id ] );
 182              }
 183          }
 184          $revisions = array( $post->ID => $post ) + $revisions;
 185      }
 186  
 187      $show_avatars = get_option( 'show_avatars' );
 188  
 189      cache_users( wp_list_pluck( $revisions, 'post_author' ) );
 190  
 191      $can_restore = current_user_can( 'edit_post', $post->ID );
 192      $current_id  = false;
 193  
 194      foreach ( $revisions as $revision ) {
 195          $modified     = strtotime( $revision->post_modified );
 196          $modified_gmt = strtotime( $revision->post_modified_gmt . ' +0000' );
 197          if ( $can_restore ) {
 198              $restore_link = str_replace(
 199                  '&amp;',
 200                  '&',
 201                  wp_nonce_url(
 202                      add_query_arg(
 203                          array(
 204                              'revision' => $revision->ID,
 205                              'action'   => 'restore',
 206                          ),
 207                          admin_url( 'revision.php' )
 208                      ),
 209                      "restore-post_{$revision->ID}"
 210                  )
 211              );
 212          }
 213  
 214          if ( ! isset( $authors[ $revision->post_author ] ) ) {
 215              $authors[ $revision->post_author ] = array(
 216                  'id'     => (int) $revision->post_author,
 217                  'avatar' => $show_avatars ? get_avatar( $revision->post_author, 32 ) : '',
 218                  'name'   => get_the_author_meta( 'display_name', $revision->post_author ),
 219              );
 220          }
 221  
 222          $autosave = (bool) wp_is_post_autosave( $revision );
 223          $current  = ! $autosave && $revision->post_modified_gmt === $post->post_modified_gmt;
 224          if ( $current && ! empty( $current_id ) ) {
 225              // If multiple revisions have the same post_modified_gmt, highest ID is current.
 226              if ( $current_id < $revision->ID ) {
 227                  $revisions[ $current_id ]['current'] = false;
 228                  $current_id                          = $revision->ID;
 229              } else {
 230                  $current = false;
 231              }
 232          } elseif ( $current ) {
 233              $current_id = $revision->ID;
 234          }
 235  
 236          $revisions_data = array(
 237              'id'         => $revision->ID,
 238              'title'      => get_the_title( $post->ID ),
 239              'author'     => $authors[ $revision->post_author ],
 240              'date'       => date_i18n( __( 'M j, Y @ H:i' ), $modified ),
 241              'dateShort'  => date_i18n( _x( 'j M @ H:i', 'revision date short format' ), $modified ),
 242              /* translators: %s: Human-readable time difference. */
 243              'timeAgo'    => sprintf( __( '%s ago' ), human_time_diff( $modified_gmt, $now_gmt ) ),
 244              'autosave'   => $autosave,
 245              'current'    => $current,
 246              'restoreUrl' => $can_restore ? $restore_link : false,
 247          );
 248  
 249          /**
 250           * Filters the array of revisions used on the revisions screen.
 251           *
 252           * @since 4.4.0
 253           *
 254           * @param array   $revisions_data {
 255           *     The bootstrapped data for the revisions screen.
 256           *
 257           *     @type int        $id         Revision ID.
 258           *     @type string     $title      Title for the revision's parent WP_Post object.
 259           *     @type int        $author     Revision post author ID.
 260           *     @type string     $date       Date the revision was modified.
 261           *     @type string     $dateShort  Short-form version of the date the revision was modified.
 262           *     @type string     $timeAgo    GMT-aware amount of time ago the revision was modified.
 263           *     @type bool       $autosave   Whether the revision is an autosave.
 264           *     @type bool       $current    Whether the revision is both not an autosave and the post
 265           *                                  modified date matches the revision modified date (GMT-aware).
 266           *     @type bool|false $restoreUrl URL if the revision can be restored, false otherwise.
 267           * }
 268           * @param WP_Post $revision       The revision's WP_Post object.
 269           * @param WP_Post $post           The revision's parent WP_Post object.
 270           */
 271          $revisions[ $revision->ID ] = apply_filters( 'wp_prepare_revision_for_js', $revisions_data, $revision, $post );
 272      }
 273  
 274      /**
 275       * If we only have one revision, the initial revision is missing; This happens
 276       * when we have an autsosave and the user has clicked 'View the Autosave'
 277       */
 278      if ( 1 === count( $revisions ) ) {
 279          $revisions[ $post->ID ] = array(
 280              'id'         => $post->ID,
 281              'title'      => get_the_title( $post->ID ),
 282              'author'     => $authors[ $post->post_author ],
 283              'date'       => date_i18n( __( 'M j, Y @ H:i' ), strtotime( $post->post_modified ) ),
 284              'dateShort'  => date_i18n( _x( 'j M @ H:i', 'revision date short format' ), strtotime( $post->post_modified ) ),
 285              /* translators: %s: Human-readable time difference. */
 286              'timeAgo'    => sprintf( __( '%s ago' ), human_time_diff( strtotime( $post->post_modified_gmt ), $now_gmt ) ),
 287              'autosave'   => false,
 288              'current'    => true,
 289              'restoreUrl' => false,
 290          );
 291          $current_id             = $post->ID;
 292      }
 293  
 294      /*
 295       * If a post has been saved since the last revision (no revisioned fields
 296       * were changed), we may not have a "current" revision. Mark the latest
 297       * revision as "current".
 298       */
 299      if ( empty( $current_id ) ) {
 300          if ( $revisions[ $revision->ID ]['autosave'] ) {
 301              $revision = end( $revisions );
 302              while ( $revision['autosave'] ) {
 303                  $revision = prev( $revisions );
 304              }
 305              $current_id = $revision['id'];
 306          } else {
 307              $current_id = $revision->ID;
 308          }
 309          $revisions[ $current_id ]['current'] = true;
 310      }
 311  
 312      // Now, grab the initial diff.
 313      $compare_two_mode = is_numeric( $from );
 314      if ( ! $compare_two_mode ) {
 315          $found = array_search( $selected_revision_id, array_keys( $revisions ), true );
 316          if ( $found ) {
 317              $from = array_keys( array_slice( $revisions, $found - 1, 1, true ) );
 318              $from = reset( $from );
 319          } else {
 320              $from = 0;
 321          }
 322      }
 323  
 324      $from = absint( $from );
 325  
 326      $diffs = array(
 327          array(
 328              'id'     => $from . ':' . $selected_revision_id,
 329              'fields' => wp_get_revision_ui_diff( $post->ID, $from, $selected_revision_id ),
 330          ),
 331      );
 332  
 333      return array(
 334          'postId'         => $post->ID,
 335          'nonce'          => wp_create_nonce( 'revisions-ajax-nonce' ),
 336          'revisionData'   => array_values( $revisions ),
 337          'to'             => $selected_revision_id,
 338          'from'           => $from,
 339          'diffData'       => $diffs,
 340          'baseUrl'        => parse_url( admin_url( 'revision.php' ), PHP_URL_PATH ),
 341          'compareTwoMode' => absint( $compare_two_mode ), // Apparently booleans are not allowed.
 342          'revisionIds'    => array_keys( $revisions ),
 343      );
 344  }
 345  
 346  /**
 347   * Print JavaScript templates required for the revisions experience.
 348   *
 349   * @since 4.1.0
 350   *
 351   * @global WP_Post $post Global post object.
 352   */
 353  function wp_print_revision_templates() {
 354      global $post;
 355      ?><script id="tmpl-revisions-frame" type="text/html">
 356          <div class="revisions-control-frame"></div>
 357          <div class="revisions-diff-frame"></div>
 358      </script>
 359  
 360      <script id="tmpl-revisions-buttons" type="text/html">
 361          <div class="revisions-previous">
 362              <input class="button" type="button" value="<?php echo esc_attr_x( 'Previous', 'Button label for a previous revision' ); ?>" />
 363          </div>
 364  
 365          <div class="revisions-next">
 366              <input class="button" type="button" value="<?php echo esc_attr_x( 'Next', 'Button label for a next revision' ); ?>" />
 367          </div>
 368      </script>
 369  
 370      <script id="tmpl-revisions-checkbox" type="text/html">
 371          <div class="revision-toggle-compare-mode">
 372              <label>
 373                  <input type="checkbox" class="compare-two-revisions"
 374                  <#
 375                  if ( 'undefined' !== typeof data && data.model.attributes.compareTwoMode ) {
 376                      #> checked="checked"<#
 377                  }
 378                  #>
 379                  />
 380                  <?php esc_html_e( 'Compare any two revisions' ); ?>
 381              </label>
 382          </div>
 383      </script>
 384  
 385      <script id="tmpl-revisions-meta" type="text/html">
 386          <# if ( ! _.isUndefined( data.attributes ) ) { #>
 387              <div class="diff-title">
 388                  <# if ( 'from' === data.type ) { #>
 389                      <strong><?php _ex( 'From:', 'Followed by post revision info' ); ?></strong>
 390                  <# } else if ( 'to' === data.type ) { #>
 391                      <strong><?php _ex( 'To:', 'Followed by post revision info' ); ?></strong>
 392                  <# } #>
 393                  <div class="author-card<# if ( data.attributes.autosave ) { #> autosave<# } #>">
 394                      {{{ data.attributes.author.avatar }}}
 395                      <div class="author-info">
 396                      <# if ( data.attributes.autosave ) { #>
 397                          <span class="byline">
 398                          <?php
 399                          printf(
 400                              /* translators: %s: User's display name. */
 401                              __( 'Autosave by %s' ),
 402                              '<span class="author-name">{{ data.attributes.author.name }}</span>'
 403                          );
 404                          ?>
 405                              </span>
 406                      <# } else if ( data.attributes.current ) { #>
 407                          <span class="byline">
 408                          <?php
 409                          printf(
 410                              /* translators: %s: User's display name. */
 411                              __( 'Current Revision by %s' ),
 412                              '<span class="author-name">{{ data.attributes.author.name }}</span>'
 413                          );
 414                          ?>
 415                              </span>
 416                      <# } else { #>
 417                          <span class="byline">
 418                          <?php
 419                          printf(
 420                              /* translators: %s: User's display name. */
 421                              __( 'Revision by %s' ),
 422                              '<span class="author-name">{{ data.attributes.author.name }}</span>'
 423                          );
 424                          ?>
 425                              </span>
 426                      <# } #>
 427                          <span class="time-ago">{{ data.attributes.timeAgo }}</span>
 428                          <span class="date">({{ data.attributes.dateShort }})</span>
 429                      </div>
 430                  <# if ( 'to' === data.type && data.attributes.restoreUrl ) { #>
 431                      <input  <?php if ( wp_check_post_lock( $post->ID ) ) { ?>
 432                          disabled="disabled"
 433                      <?php } else { ?>
 434                          <# if ( data.attributes.current ) { #>
 435                              disabled="disabled"
 436                          <# } #>
 437                      <?php } ?>
 438                      <# if ( data.attributes.autosave ) { #>
 439                          type="button" class="restore-revision button button-primary" value="<?php esc_attr_e( 'Restore This Autosave' ); ?>" />
 440                      <# } else { #>
 441                          type="button" class="restore-revision button button-primary" value="<?php esc_attr_e( 'Restore This Revision' ); ?>" />
 442                      <# } #>
 443                  <# } #>
 444              </div>
 445          <# if ( 'tooltip' === data.type ) { #>
 446              <div class="revisions-tooltip-arrow"><span></span></div>
 447          <# } #>
 448      <# } #>
 449      </script>
 450  
 451      <script id="tmpl-revisions-diff" type="text/html">
 452          <div class="loading-indicator"><span class="spinner"></span></div>
 453          <div class="diff-error"><?php _e( 'Sorry, something went wrong. The requested comparison could not be loaded.' ); ?></div>
 454          <div class="diff">
 455          <# _.each( data.fields, function( field ) { #>
 456              <h3>{{ field.name }}</h3>
 457              {{{ field.diff }}}
 458          <# }); #>
 459          </div>
 460      </script>
 461      <?php
 462  }


Generated: Wed Oct 21 01:00:04 2020 Cross-referenced by PHPXref 0.7.1