revisions-manager.php
422 lines
| 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 | } |
| 422 |