db.php (15042B)
1 <?php 2 namespace Elementor; 3 4 use Elementor\Core\Base\Document; 5 use Elementor\Core\DynamicTags\Manager; 6 7 if ( ! defined( 'ABSPATH' ) ) { 8 exit; // Exit if accessed directly. 9 } 10 11 /** 12 * Elementor database. 13 * 14 * Elementor database handler class is responsible for communicating with the 15 * DB, save and retrieve Elementor data and meta data. 16 * 17 * @since 1.0.0 18 */ 19 class DB { 20 21 /** 22 * Current DB version of the editor. 23 */ 24 const DB_VERSION = '0.4'; 25 26 /** 27 * Post publish status. 28 * 29 * @deprecated 3.1.0 Use `Document::STATUS_PUBLISH` instead 30 */ 31 const STATUS_PUBLISH = Document::STATUS_PUBLISH; 32 33 /** 34 * Post draft status. 35 * 36 * @deprecated 3.1.0 Use `Document::STATUS_DRAFT` instead 37 */ 38 const STATUS_DRAFT = Document::STATUS_DRAFT; 39 40 /** 41 * Post private status. 42 * 43 * @deprecated 3.1.0 Use `Document::STATUS_PRIVATE` instead 44 */ 45 const STATUS_PRIVATE = Document::STATUS_PRIVATE; 46 47 /** 48 * Post autosave status. 49 * 50 * @deprecated 3.1.0 Use `Document::STATUS_AUTOSAVE` instead 51 */ 52 const STATUS_AUTOSAVE = Document::STATUS_AUTOSAVE; 53 54 /** 55 * Post pending status. 56 * 57 * @deprecated 3.1.0 Use `Document::STATUS_PENDING` instead 58 */ 59 const STATUS_PENDING = Document::STATUS_PENDING; 60 61 /** 62 * Switched post data. 63 * 64 * Holds the switched post data. 65 * 66 * @since 1.5.0 67 * @access protected 68 * 69 * @var array Switched post data. Default is an empty array. 70 */ 71 protected $switched_post_data = []; 72 73 /** 74 * Switched data. 75 * 76 * Holds the switched data. 77 * 78 * @since 2.0.0 79 * @access protected 80 * 81 * @var array Switched data. Default is an empty array. 82 */ 83 protected $switched_data = []; 84 85 /** 86 * Get builder. 87 * 88 * Retrieve editor data from the database. 89 * 90 * @since 1.0.0 91 * @deprecated 3.1.0 Use `Plugin::$instance->documents->get( $post_id )->get_elements_raw_data( null, true )` OR `Plugin::$instance->documents->get_doc_or_auto_save( $post_id )->get_elements_raw_data( null, true )` instead 92 * @access public 93 * 94 * @param int $post_id Post ID. 95 * @param string $status Optional. Post status. Default is `publish`. 96 * 97 * @return array Editor data. 98 */ 99 public function get_builder( $post_id, $status = Document::STATUS_PUBLISH ) { 100 Plugin::$instance->modules_manager 101 ->get_modules( 'dev-tools' ) 102 ->deprecation 103 ->deprecated_function( 104 __METHOD__, 105 '3.1.0', 106 '`Plugin::$instance->documents->get( $post_id )->get_elements_raw_data( null, true )` OR `Plugin::$instance->documents->get_doc_or_auto_save( $post_id )->get_elements_raw_data( null, true )`' 107 ); 108 109 if ( Document::STATUS_DRAFT === $status ) { 110 $document = Plugin::$instance->documents->get_doc_or_auto_save( $post_id ); 111 } else { 112 $document = Plugin::$instance->documents->get( $post_id ); 113 } 114 115 if ( $document ) { 116 $editor_data = $document->get_elements_raw_data( null, true ); 117 } else { 118 $editor_data = []; 119 } 120 121 return $editor_data; 122 } 123 124 /** 125 * Get JSON meta. 126 * 127 * Retrieve post meta data, and return the JSON decoded data. 128 * 129 * @since 1.0.0 130 * @access protected 131 * 132 * @param int $post_id Post ID. 133 * @param string $key The meta key to retrieve. 134 * 135 * @return array Decoded JSON data from post meta. 136 */ 137 protected function _get_json_meta( $post_id, $key ) { 138 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0' ); 139 140 $meta = get_post_meta( $post_id, $key, true ); 141 142 if ( is_string( $meta ) && ! empty( $meta ) ) { 143 $meta = json_decode( $meta, true ); 144 } 145 146 if ( empty( $meta ) ) { 147 $meta = []; 148 } 149 150 return $meta; 151 } 152 153 /** 154 * Is using Elementor. 155 * 156 * Set whether the page is using Elementor or not. 157 * 158 * @since 1.5.0 159 * @deprecated 3.1.0 Use `Plugin::$instance->documents->get( $post_id )->set_is_build_with_elementor( $is_elementor )` instead 160 * @access public 161 * 162 * @param int $post_id Post ID. 163 * @param bool $is_elementor Optional. Whether the page is elementor page. 164 * Default is true. 165 */ 166 public function set_is_elementor_page( $post_id, $is_elementor = true ) { 167 Plugin::$instance->modules_manager 168 ->get_modules( 'dev-tools' ) 169 ->deprecation 170 ->deprecated_function( 171 __METHOD__, 172 '3.1.0', 173 'Plugin::$instance->documents->get( $post_id )->set_is_build_with_elementor( $is_elementor )' 174 ); 175 176 $document = Plugin::$instance->documents->get( $post_id ); 177 178 if ( ! $document ) { 179 return; 180 } 181 182 $document->set_is_built_with_elementor( $is_elementor ); 183 } 184 185 /** 186 * Render element plain content. 187 * 188 * When saving data in the editor, this method renders recursively the plain 189 * content containing only the content and the HTML. No CSS data. 190 * 191 * @since 2.0.0 192 * @access private 193 * 194 * @param array $element_data Element data. 195 */ 196 private function render_element_plain_content( $element_data ) { 197 if ( 'widget' === $element_data['elType'] ) { 198 /** @var Widget_Base $widget */ 199 $widget = Plugin::$instance->elements_manager->create_element_instance( $element_data ); 200 201 if ( $widget ) { 202 $widget->render_plain_content(); 203 } 204 } 205 206 if ( ! empty( $element_data['elements'] ) ) { 207 foreach ( $element_data['elements'] as $element ) { 208 $this->render_element_plain_content( $element ); 209 } 210 } 211 } 212 213 /** 214 * Save plain text. 215 * 216 * Retrieves the raw content, removes all kind of unwanted HTML tags and saves 217 * the content as the `post_content` field in the database. 218 * 219 * @since 1.9.0 220 * @access public 221 * 222 * @param int $post_id Post ID. 223 */ 224 public function save_plain_text( $post_id ) { 225 // Switch $dynamic_tags to parsing mode = remove. 226 $dynamic_tags = Plugin::$instance->dynamic_tags; 227 $parsing_mode = $dynamic_tags->get_parsing_mode(); 228 $dynamic_tags->set_parsing_mode( Manager::MODE_REMOVE ); 229 230 $plain_text = $this->get_plain_text( $post_id ); 231 232 wp_update_post( 233 [ 234 'ID' => $post_id, 235 'post_content' => $plain_text, 236 ] 237 ); 238 239 // Restore parsing mode. 240 $dynamic_tags->set_parsing_mode( $parsing_mode ); 241 } 242 243 /** 244 * Iterate data. 245 * 246 * Accept any type of Elementor data and a callback function. The callback 247 * function runs recursively for each element and his child elements. 248 * 249 * @since 1.0.0 250 * @access public 251 * 252 * @param array $data_container Any type of elementor data. 253 * @param callable $callback A function to iterate data by. 254 * @param array $args Array of args pointers for passing parameters in & out of the callback 255 * 256 * @return mixed Iterated data. 257 */ 258 public function iterate_data( $data_container, $callback, $args = [] ) { 259 if ( isset( $data_container['elType'] ) ) { 260 if ( ! empty( $data_container['elements'] ) ) { 261 $data_container['elements'] = $this->iterate_data( $data_container['elements'], $callback, $args ); 262 } 263 264 return call_user_func( $callback, $data_container, $args ); 265 } 266 267 foreach ( $data_container as $element_key => $element_value ) { 268 $element_data = $this->iterate_data( $data_container[ $element_key ], $callback, $args ); 269 270 if ( null === $element_data ) { 271 continue; 272 } 273 274 $data_container[ $element_key ] = $element_data; 275 } 276 277 return $data_container; 278 } 279 280 /** 281 * Safely copy Elementor meta. 282 * 283 * Make sure the original page was built with Elementor and the post is not 284 * auto-save. Only then copy elementor meta from one post to another using 285 * `copy_elementor_meta()`. 286 * 287 * @since 1.9.2 288 * @access public 289 * 290 * @param int $from_post_id Original post ID. 291 * @param int $to_post_id Target post ID. 292 */ 293 public function safe_copy_elementor_meta( $from_post_id, $to_post_id ) { 294 // It's from WP-Admin & not from Elementor. 295 if ( ! did_action( 'elementor/db/before_save' ) ) { 296 $from_document = Plugin::$instance->documents->get( $from_post_id ); 297 298 if ( ! $from_document || ! $from_document->is_built_with_elementor() ) { 299 return; 300 } 301 302 // It's an exited Elementor auto-save 303 if ( get_post_meta( $to_post_id, '_elementor_data', true ) ) { 304 return; 305 } 306 } 307 308 $this->copy_elementor_meta( $from_post_id, $to_post_id ); 309 } 310 311 /** 312 * Copy Elementor meta. 313 * 314 * Duplicate the data from one post to another. 315 * 316 * Consider using `safe_copy_elementor_meta()` method instead. 317 * 318 * @since 1.1.0 319 * @access public 320 * 321 * @param int $from_post_id Original post ID. 322 * @param int $to_post_id Target post ID. 323 */ 324 public function copy_elementor_meta( $from_post_id, $to_post_id ) { 325 $from_post_meta = get_post_meta( $from_post_id ); 326 $core_meta = [ 327 '_wp_page_template', 328 '_thumbnail_id', 329 ]; 330 331 foreach ( $from_post_meta as $meta_key => $values ) { 332 // Copy only meta with the `_elementor` prefix 333 if ( 0 === strpos( $meta_key, '_elementor' ) || in_array( $meta_key, $core_meta, true ) ) { 334 $value = $values[0]; 335 336 // The elementor JSON needs slashes before saving 337 if ( '_elementor_data' === $meta_key ) { 338 $value = wp_slash( $value ); 339 } else { 340 $value = maybe_unserialize( $value ); 341 } 342 343 // Don't use `update_post_meta` that can't handle `revision` post type 344 update_metadata( 'post', $to_post_id, $meta_key, $value ); 345 } 346 } 347 } 348 349 /** 350 * Is built with Elementor. 351 * 352 * Check whether the post was built with Elementor. 353 * 354 * @since 1.0.10 355 * @deprecated 3.2.0 Use `Plugin::$instance->documents->get( $post_id )->is_built_with_elementor()` instead 356 * @access public 357 * 358 * @param int $post_id Post ID. 359 * 360 * @return bool Whether the post was built with Elementor. 361 */ 362 public function is_built_with_elementor( $post_id ) { 363 Plugin::$instance->modules_manager 364 ->get_modules( 'dev-tools' ) 365 ->deprecation 366 ->deprecated_function( 367 __METHOD__, 368 '3.2.0', 369 'Plugin::$instance->documents->get( $post_id )->is_built_with_elementor()' 370 ); 371 372 $document = Plugin::$instance->documents->get( $post_id ); 373 374 if ( ! $document ) { 375 return false; 376 } 377 378 return $document->is_built_with_elementor(); 379 } 380 381 /** 382 * Switch to post. 383 * 384 * Change the global WordPress post to the requested post. 385 * 386 * @since 1.5.0 387 * @access public 388 * 389 * @param int $post_id Post ID to switch to. 390 */ 391 public function switch_to_post( $post_id ) { 392 $post_id = absint( $post_id ); 393 // If is already switched, or is the same post, return. 394 if ( get_the_ID() === $post_id ) { 395 $this->switched_post_data[] = false; 396 return; 397 } 398 399 $this->switched_post_data[] = [ 400 'switched_id' => $post_id, 401 'original_id' => get_the_ID(), // Note, it can be false if the global isn't set 402 ]; 403 404 $GLOBALS['post'] = get_post( $post_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited 405 406 setup_postdata( $GLOBALS['post'] ); 407 } 408 409 /** 410 * Restore current post. 411 * 412 * Rollback to the previous global post, rolling back from `DB::switch_to_post()`. 413 * 414 * @since 1.5.0 415 * @access public 416 */ 417 public function restore_current_post() { 418 $data = array_pop( $this->switched_post_data ); 419 420 // If not switched, return. 421 if ( ! $data ) { 422 return; 423 } 424 425 // It was switched from an empty global post, restore this state and unset the global post 426 if ( false === $data['original_id'] ) { 427 unset( $GLOBALS['post'] ); 428 return; 429 } 430 431 $GLOBALS['post'] = get_post( $data['original_id'] ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited 432 433 setup_postdata( $GLOBALS['post'] ); 434 } 435 436 437 /** 438 * Switch to query. 439 * 440 * Change the WordPress query to a new query with the requested 441 * query variables. 442 * 443 * @since 2.0.0 444 * @access public 445 * 446 * @param array $query_vars New query variables. 447 * @param bool $force_global_post 448 */ 449 public function switch_to_query( $query_vars, $force_global_post = false ) { 450 global $wp_query; 451 $current_query_vars = $wp_query->query; 452 453 // If is already switched, or is the same query, return. 454 if ( $current_query_vars === $query_vars ) { 455 $this->switched_data[] = false; 456 return; 457 } 458 459 $new_query = new \WP_Query( $query_vars ); 460 461 $switched_data = [ 462 'switched' => $new_query, 463 'original' => $wp_query, 464 ]; 465 466 if ( ! empty( $GLOBALS['post'] ) ) { 467 $switched_data['post'] = $GLOBALS['post']; 468 } 469 470 $this->switched_data[] = $switched_data; 471 472 $wp_query = $new_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited 473 474 // Ensure the global post is set only if needed 475 unset( $GLOBALS['post'] ); 476 477 if ( isset( $new_query->posts[0] ) ) { 478 if ( $force_global_post || $new_query->is_singular() ) { 479 $GLOBALS['post'] = $new_query->posts[0]; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited 480 setup_postdata( $GLOBALS['post'] ); 481 } 482 } 483 484 if ( $new_query->is_author() ) { 485 $GLOBALS['authordata'] = get_userdata( $new_query->get( 'author' ) ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited 486 } 487 } 488 489 /** 490 * Restore current query. 491 * 492 * Rollback to the previous query, rolling back from `DB::switch_to_query()`. 493 * 494 * @since 2.0.0 495 * @access public 496 */ 497 public function restore_current_query() { 498 $data = array_pop( $this->switched_data ); 499 500 // If not switched, return. 501 if ( ! $data ) { 502 return; 503 } 504 505 global $wp_query; 506 507 $wp_query = $data['original']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited 508 509 // Ensure the global post/authordata is set only if needed. 510 unset( $GLOBALS['post'] ); 511 unset( $GLOBALS['authordata'] ); 512 513 if ( ! empty( $data['post'] ) ) { 514 $GLOBALS['post'] = $data['post']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited 515 setup_postdata( $GLOBALS['post'] ); 516 } 517 518 if ( $wp_query->is_author() ) { 519 $GLOBALS['authordata'] = get_userdata( $wp_query->get( 'author' ) ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited 520 } 521 } 522 523 /** 524 * Get plain text. 525 * 526 * Retrieve the post plain text. 527 * 528 * @since 1.9.0 529 * @access public 530 * 531 * @param int $post_id Post ID. 532 * 533 * @return string Post plain text. 534 */ 535 public function get_plain_text( $post_id ) { 536 $document = Plugin::$instance->documents->get( $post_id ); 537 $data = $document ? $document->get_elements_data() : []; 538 539 return $this->get_plain_text_from_data( $data ); 540 } 541 542 /** 543 * Get plain text from data. 544 * 545 * Retrieve the post plain text from any given Elementor data. 546 * 547 * @since 1.9.2 548 * @access public 549 * 550 * @param array $data Post ID. 551 * 552 * @return string Post plain text. 553 */ 554 public function get_plain_text_from_data( $data ) { 555 ob_start(); 556 if ( $data ) { 557 foreach ( $data as $element_data ) { 558 $this->render_element_plain_content( $element_data ); 559 } 560 } 561 562 $plain_text = ob_get_clean(); 563 564 // Remove unnecessary tags. 565 $plain_text = preg_replace( '/<\/?div[^>]*\>/i', '', $plain_text ); 566 $plain_text = preg_replace( '/<\/?span[^>]*\>/i', '', $plain_text ); 567 $plain_text = preg_replace( '#<script(.*?)>(.*?)</script>#is', '', $plain_text ); 568 $plain_text = preg_replace( '/<i [^>]*><\\/i[^>]*>/', '', $plain_text ); 569 $plain_text = preg_replace( '/ class=".*?"/', '', $plain_text ); 570 571 // Remove empty lines. 572 $plain_text = preg_replace( '/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/', "\n", $plain_text ); 573 574 $plain_text = trim( $plain_text ); 575 576 return $plain_text; 577 } 578 }