revisions-manager.php (10229B)
1 <?php 2 namespace Elementor\Modules\History; 3 4 use Elementor\Core\Base\Document; 5 use Elementor\Core\Common\Modules\Ajax\Module as Ajax; 6 use Elementor\Core\Files\CSS\Post as Post_CSS; 7 use Elementor\Plugin; 8 use Elementor\Utils; 9 10 if ( ! defined( 'ABSPATH' ) ) { 11 exit; // Exit if accessed directly. 12 } 13 14 /** 15 * Elementor history revisions manager. 16 * 17 * Elementor history revisions manager handler class is responsible for 18 * registering and managing Elementor revisions manager. 19 * 20 * @since 1.7.0 21 */ 22 class Revisions_Manager { 23 24 /** 25 * Maximum number of revisions to display. 26 */ 27 const MAX_REVISIONS_TO_DISPLAY = 100; 28 29 /** 30 * Authors list. 31 * 32 * Holds all the authors. 33 * 34 * @access private 35 * 36 * @var array 37 */ 38 private static $authors = []; 39 40 /** 41 * History revisions manager constructor. 42 * 43 * Initializing Elementor history revisions manager. 44 * 45 * @since 1.7.0 46 * @access public 47 */ 48 public function __construct() { 49 self::register_actions(); 50 } 51 52 /** 53 * @since 1.7.0 54 * @access public 55 * @static 56 */ 57 public static function handle_revision() { 58 add_filter( 'wp_save_post_revision_check_for_changes', '__return_false' ); 59 } 60 61 /** 62 * @since 2.0.0 63 * @access public 64 * @static 65 * 66 * @param $post_content 67 * @param $post_id 68 * 69 * @return string 70 */ 71 public static function avoid_delete_auto_save( $post_content, $post_id ) { 72 // Add a temporary string in order the $post will not be equal to the $autosave 73 // in edit-form-advanced.php:210 74 $document = Plugin::$instance->documents->get( $post_id ); 75 76 if ( $document && $document->is_built_with_elementor() ) { 77 $post_content .= '<!-- Created with Elementor -->'; 78 } 79 80 return $post_content; 81 } 82 83 /** 84 * @since 2.0.0 85 * @access public 86 * @static 87 */ 88 public static function remove_temp_post_content() { 89 global $post; 90 91 $document = Plugin::$instance->documents->get( $post->ID ); 92 93 if ( ! $document || ! $document->is_built_with_elementor() ) { 94 return; 95 } 96 97 $post->post_content = str_replace( '<!-- Created with Elementor -->', '', $post->post_content ); 98 } 99 100 /** 101 * @since 1.7.0 102 * @access public 103 * @static 104 * 105 * @param int $post_id 106 * @param array $query_args 107 * @param bool $parse_result 108 * 109 * @return array 110 */ 111 public static function get_revisions( $post_id = 0, $query_args = [], $parse_result = true ) { 112 $post = get_post( $post_id ); 113 114 if ( ! $post || empty( $post->ID ) ) { 115 return []; 116 } 117 118 $revisions = []; 119 120 $default_query_args = [ 121 'posts_per_page' => self::MAX_REVISIONS_TO_DISPLAY, 122 'meta_key' => '_elementor_data', 123 ]; 124 125 $query_args = array_merge( $default_query_args, $query_args ); 126 127 $posts = wp_get_post_revisions( $post->ID, $query_args ); 128 129 if ( ! wp_revisions_enabled( $post ) ) { 130 $autosave = Utils::get_post_autosave( $post->ID ); 131 if ( $autosave ) { 132 if ( $parse_result ) { 133 array_unshift( $posts, $autosave ); 134 } else { 135 array_unshift( $posts, $autosave->ID ); 136 } 137 } 138 } 139 140 if ( $parse_result ) { 141 array_unshift( $posts, $post ); 142 } else { 143 array_unshift( $posts, $post->ID ); 144 return $posts; 145 } 146 147 $current_time = current_time( 'timestamp' ); 148 149 /** @var \WP_Post $revision */ 150 foreach ( $posts as $revision ) { 151 $date = date_i18n( _x( 'M j @ H:i', 'revision date format', 'elementor' ), strtotime( $revision->post_modified ) ); 152 153 $human_time = human_time_diff( strtotime( $revision->post_modified ), $current_time ); 154 155 if ( $revision->ID === $post->ID ) { 156 $type = 'current'; 157 $type_label = esc_html__( 'Current Version', 'elementor' ); 158 } elseif ( false !== strpos( $revision->post_name, 'autosave' ) ) { 159 $type = 'autosave'; 160 $type_label = esc_html__( 'Autosave', 'elementor' ); 161 } else { 162 $type = 'revision'; 163 $type_label = esc_html__( 'Revision', 'elementor' ); 164 } 165 166 if ( ! isset( self::$authors[ $revision->post_author ] ) ) { 167 self::$authors[ $revision->post_author ] = [ 168 'avatar' => get_avatar( $revision->post_author, 22 ), 169 'display_name' => get_the_author_meta( 'display_name', $revision->post_author ), 170 ]; 171 } 172 173 $revisions[] = [ 174 'id' => $revision->ID, 175 'author' => self::$authors[ $revision->post_author ]['display_name'], 176 'timestamp' => strtotime( $revision->post_modified ), 177 'date' => sprintf( 178 /* translators: 1: Human readable time difference, 2: Date */ 179 __( '%1$s ago (%2$s)', 'elementor' ), 180 $human_time, 181 $date 182 ), 183 'type' => $type, 184 'typeLabel' => $type_label, 185 'gravatar' => self::$authors[ $revision->post_author ]['avatar'], 186 ]; 187 } 188 189 return $revisions; 190 } 191 192 /** 193 * @since 1.9.2 194 * @access public 195 * @static 196 */ 197 public static function update_autosave( $autosave_data ) { 198 self::save_revision( $autosave_data['ID'] ); 199 } 200 201 /** 202 * @since 1.7.0 203 * @access public 204 * @static 205 */ 206 public static function save_revision( $revision_id ) { 207 $parent_id = wp_is_post_revision( $revision_id ); 208 209 if ( $parent_id ) { 210 Plugin::$instance->db->safe_copy_elementor_meta( $parent_id, $revision_id ); 211 } 212 } 213 214 /** 215 * @since 1.7.0 216 * @access public 217 * @static 218 */ 219 public static function restore_revision( $parent_id, $revision_id ) { 220 $parent = Plugin::$instance->documents->get( $parent_id ); 221 $revision = Plugin::$instance->documents->get( $revision_id ); 222 223 if ( ! $parent || ! $revision ) { 224 return; 225 } 226 227 $is_built_with_elementor = $revision->is_built_with_elementor(); 228 229 $parent->set_is_built_with_elementor( $is_built_with_elementor ); 230 231 if ( ! $is_built_with_elementor ) { 232 return; 233 } 234 235 Plugin::$instance->db->copy_elementor_meta( $revision_id, $parent_id ); 236 237 $post_css = Post_CSS::create( $parent_id ); 238 239 $post_css->update(); 240 } 241 242 /** 243 * @since 2.3.0 244 * @access public 245 * @static 246 * 247 * @param $data 248 * 249 * @return array 250 * @throws \Exception 251 */ 252 public static function ajax_get_revision_data( array $data ) { 253 if ( ! isset( $data['id'] ) ) { 254 throw new \Exception( 'You must set the revision ID.' ); 255 } 256 257 $revision = Plugin::$instance->documents->get( $data['id'] ); 258 259 if ( ! $revision ) { 260 throw new \Exception( 'Invalid revision.' ); 261 } 262 263 if ( ! current_user_can( 'edit_post', $revision->get_id() ) ) { 264 throw new \Exception( esc_html__( 'Access denied.', 'elementor' ) ); 265 } 266 267 $revision_data = [ 268 'settings' => $revision->get_settings(), 269 'elements' => $revision->get_elements_data(), 270 ]; 271 272 return $revision_data; 273 } 274 275 /** 276 * @since 1.7.0 277 * @access public 278 * @static 279 */ 280 public static function add_revision_support_for_all_post_types() { 281 $post_types = get_post_types_by_support( 'elementor' ); 282 foreach ( $post_types as $post_type ) { 283 add_post_type_support( $post_type, 'revisions' ); 284 } 285 } 286 287 /** 288 * @since 2.0.0 289 * @access public 290 * @static 291 * @param array $return_data 292 * @param Document $document 293 * 294 * @return array 295 */ 296 public static function on_ajax_save_builder_data( $return_data, $document ) { 297 $post_id = $document->get_main_id(); 298 299 $latest_revisions = self::get_revisions( 300 $post_id, [ 301 'posts_per_page' => 1, 302 ] 303 ); 304 305 $all_revision_ids = self::get_revisions( 306 $post_id, [ 307 'fields' => 'ids', 308 ], false 309 ); 310 311 // Send revisions data only if has revisions. 312 if ( ! empty( $latest_revisions ) ) { 313 $current_revision_id = self::current_revision_id( $post_id ); 314 315 $return_data = array_replace_recursive( $return_data, [ 316 'config' => [ 317 'document' => [ 318 'revisions' => [ 319 'current_id' => $current_revision_id, 320 ], 321 ], 322 ], 323 'latest_revisions' => $latest_revisions, 324 'revisions_ids' => $all_revision_ids, 325 ] ); 326 } 327 328 return $return_data; 329 } 330 331 /** 332 * @since 1.7.0 333 * @access public 334 * @static 335 */ 336 public static function db_before_save( $status, $has_changes ) { 337 if ( $has_changes ) { 338 self::handle_revision(); 339 } 340 } 341 342 public static function document_config( $settings, $post_id ) { 343 $settings['revisions'] = [ 344 'enabled' => ( $post_id && wp_revisions_enabled( get_post( $post_id ) ) ), 345 'current_id' => self::current_revision_id( $post_id ), 346 ]; 347 348 return $settings; 349 } 350 351 /** 352 * Localize settings. 353 * 354 * Add new localized settings for the revisions manager. 355 * 356 * Fired by `elementor/editor/editor_settings` filter. 357 * 358 * @since 1.7.0 359 * @access public 360 * @static 361 * @deprecated 3.1.0 362 */ 363 public static function editor_settings() { 364 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0' ); 365 366 return []; 367 } 368 369 public static function ajax_get_revisions() { 370 return self::get_revisions(); 371 } 372 373 /** 374 * @since 2.3.0 375 * @access public 376 * @static 377 */ 378 public static function register_ajax_actions( Ajax $ajax ) { 379 $ajax->register_ajax_action( 'get_revisions', [ __CLASS__, 'ajax_get_revisions' ] ); 380 $ajax->register_ajax_action( 'get_revision_data', [ __CLASS__, 'ajax_get_revision_data' ] ); 381 } 382 383 /** 384 * @since 1.7.0 385 * @access private 386 * @static 387 */ 388 private static function register_actions() { 389 add_action( 'wp_restore_post_revision', [ __CLASS__, 'restore_revision' ], 10, 2 ); 390 add_action( 'init', [ __CLASS__, 'add_revision_support_for_all_post_types' ], 9999 ); 391 add_filter( 'elementor/document/config', [ __CLASS__, 'document_config' ], 10, 2 ); 392 add_action( 'elementor/db/before_save', [ __CLASS__, 'db_before_save' ], 10, 2 ); 393 add_action( '_wp_put_post_revision', [ __CLASS__, 'save_revision' ] ); 394 add_action( 'wp_creating_autosave', [ __CLASS__, 'update_autosave' ] ); 395 add_action( 'elementor/ajax/register_actions', [ __CLASS__, 'register_ajax_actions' ] ); 396 397 // Hack to avoid delete the auto-save revision in WP editor. 398 add_filter( 'edit_post_content', [ __CLASS__, 'avoid_delete_auto_save' ], 10, 2 ); 399 add_action( 'edit_form_after_title', [ __CLASS__, 'remove_temp_post_content' ] ); 400 401 if ( wp_doing_ajax() ) { 402 add_filter( 'elementor/documents/ajax_save/return_data', [ __CLASS__, 'on_ajax_save_builder_data' ], 10, 2 ); 403 } 404 } 405 406 /** 407 * @since 1.9.0 408 * @access private 409 * @static 410 */ 411 private static function current_revision_id( $post_id ) { 412 $current_revision_id = $post_id; 413 $autosave = Utils::get_post_autosave( $post_id ); 414 415 if ( is_object( $autosave ) ) { 416 $current_revision_id = $autosave->ID; 417 } 418 419 return $current_revision_id; 420 } 421 }