/wp-admin/includes/revision.php

https://bitbucket.org/acipriani/madeinapulia.com · PHP · 378 lines · 244 code · 37 blank · 97 comment · 39 complexity · 6b8ccd8d31b47d8c367bc69afd38d127 MD5 · raw file

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