PluginProbe
Elementor Website Builder – more than just a page builder / 3.22.1
Elementor Website Builder – more than just a page builder v3.22.1
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / core / documents-manager.php

documents-manager.php in Elementor Website Builder – more than just a page builder 3.22.1, at core/documents-manager.php

778 lines 19.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Core;
3
4 use Elementor\Core\Base\Document;
5 use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
6 use Elementor\Core\DocumentTypes\Page;
7 use Elementor\Core\DocumentTypes\Post;
8 use Elementor\Plugin;
9 use Elementor\TemplateLibrary\Source_Local;
10 use Elementor\Utils;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly
14 }
15
16 /**
17 * Elementor documents manager.
18 *
19 * Elementor documents manager handler class is responsible for registering and
20 * managing Elementor documents.
21 *
22 * @since 2.0.0
23 */
24 class Documents_Manager {
25
26 /**
27 * Registered types.
28 *
29 * Holds the list of all the registered types.
30 *
31 * @since 2.0.0
32 * @access protected
33 *
34 * @var Document[]
35 */
36 protected $types = [];
37
38 /**
39 * Registered documents.
40 *
41 * Holds the list of all the registered documents.
42 *
43 * @since 2.0.0
44 * @access protected
45 *
46 * @var Document[]
47 */
48 protected $documents = [];
49
50 /**
51 * Current document.
52 *
53 * Holds the current document.
54 *
55 * @since 2.0.0
56 * @access protected
57 *
58 * @var Document
59 */
60 protected $current_doc;
61
62 /**
63 * Switched data.
64 *
65 * Holds the current document when changing to the requested post.
66 *
67 * @since 2.0.0
68 * @access protected
69 *
70 * @var array
71 */
72 protected $switched_data = [];
73
74 protected $cpt = [];
75
76 /**
77 * Documents manager constructor.
78 *
79 * Initializing the Elementor documents manager.
80 *
81 * @since 2.0.0
82 * @access public
83 */
84 public function __construct() {
85 add_action( 'elementor/documents/register', [ $this, 'register_default_types' ], 0 );
86 add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
87 add_filter( 'post_row_actions', [ $this, 'filter_post_row_actions' ], 11, 2 );
88 add_filter( 'page_row_actions', [ $this, 'filter_post_row_actions' ], 11, 2 );
89 add_filter( 'user_has_cap', [ $this, 'remove_user_edit_cap' ], 10, 3 );
90 add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
91 }
92
93 /**
94 * Register ajax actions.
95 *
96 * Process ajax action handles when saving data and discarding changes.
97 *
98 * Fired by `elementor/ajax/register_actions` action.
99 *
100 * @since 2.0.0
101 * @access public
102 *
103 * @param Ajax $ajax_manager An instance of the ajax manager.
104 */
105 public function register_ajax_actions( $ajax_manager ) {
106 $ajax_manager->register_ajax_action( 'save_builder', [ $this, 'ajax_save' ] );
107 $ajax_manager->register_ajax_action( 'discard_changes', [ $this, 'ajax_discard_changes' ] );
108 $ajax_manager->register_ajax_action( 'get_document_config', [ $this, 'ajax_get_document_config' ] );
109 }
110
111 /**
112 * Register default types.
113 *
114 * Registers the default document types.
115 *
116 * @since 2.0.0
117 * @access public
118 */
119 public function register_default_types() {
120 $default_types = [
121 'post' => Post::get_class_full_name(), // BC.
122 'wp-post' => Post::get_class_full_name(),
123 'wp-page' => Page::get_class_full_name(),
124 ];
125
126 foreach ( $default_types as $type => $class ) {
127 $this->register_document_type( $type, $class );
128 }
129 }
130
131 /**
132 * Register document type.
133 *
134 * Registers a single document.
135 *
136 * @since 2.0.0
137 * @access public
138 *
139 * @param string $type Document type name.
140 * @param string $class The name of the class that registers the document type.
141 * Full name with the namespace.
142 *
143 * @return Documents_Manager The updated document manager instance.
144 */
145 public function register_document_type( $type, $class ) {
146 $this->types[ $type ] = $class;
147
148 $cpt = $class::get_property( 'cpt' );
149
150 if ( $cpt ) {
151 foreach ( $cpt as $post_type ) {
152 $this->cpt[ $post_type ] = $type;
153 }
154 }
155
156 if ( $class::get_property( 'register_type' ) ) {
157 Source_Local::add_template_type( $type );
158 }
159
160 return $this;
161 }
162
163 /**
164 * Get document.
165 *
166 * Retrieve the document data based on a post ID.
167 *
168 * @since 2.0.0
169 * @access public
170 *
171 * @param int $post_id Post ID.
172 * @param bool $from_cache Optional. Whether to retrieve cached data. Default is true.
173 *
174 * @return false|Document Document data or false if post ID was not entered.
175 */
176 public function get( $post_id, $from_cache = true ) {
177 $this->register_types();
178
179 $post_id = absint( $post_id );
180
181 if ( ! $post_id || ! get_post( $post_id ) ) {
182 return false;
183 }
184
185 /**
186 * Retrieve document post ID.
187 *
188 * Filters the document post ID.
189 *
190 * @since 2.0.7
191 *
192 * @param int $post_id The post ID of the document.
193 */
194 $post_id = apply_filters( 'elementor/documents/get/post_id', $post_id );
195
196 if ( ! $from_cache || ! isset( $this->documents[ $post_id ] ) ) {
197 $doc_type = $this->get_doc_type_by_id( $post_id );
198 $doc_type_class = $this->get_document_type( $doc_type );
199
200 $this->documents[ $post_id ] = new $doc_type_class( [
201 'post_id' => $post_id,
202 ] );
203 }
204
205 return $this->documents[ $post_id ];
206 }
207
208 /**
209 * Retrieve a document after checking it exist and allowed to edit.
210 *
211 * @since 3.13.0
212 *
213 * @param int $post_id The post ID of the document.
214 *
215 * @return Document
216 * @throws \Exception
217 */
218 public function get_with_permissions( $id ): Document {
219 $document = $this->get( $id );
220
221 if ( ! $document ) {
222 throw new \Exception( 'Not found.' );
223 }
224
225 if ( ! $document->is_editable_by_current_user() ) {
226 throw new \Exception( 'Access denied.' );
227 }
228
229 return $document;
230 }
231
232 /**
233 * A `void` version for `get_with_permissions`.
234 *
235 * @param $id
236 * @return void
237 * @throws \Exception
238 */
239 public function check_permissions( $id ) {
240 $this->get_with_permissions( $id );
241 }
242
243 /**
244 * Get document or autosave.
245 *
246 * Retrieve either the document or the autosave.
247 *
248 * @since 2.0.0
249 * @access public
250 *
251 * @param int $id Optional. Post ID. Default is `0`.
252 * @param int $user_id Optional. User ID. Default is `0`.
253 *
254 * @return false|Document The document if it exist, False otherwise.
255 */
256 public function get_doc_or_auto_save( $id, $user_id = 0 ) {
257 $document = $this->get( $id );
258 if ( $document && $document->get_autosave_id( $user_id ) ) {
259 $document = $document->get_autosave( $user_id );
260 }
261
262 return $document;
263 }
264
265 /**
266 * Get document for frontend.
267 *
268 * Retrieve the document for frontend use.
269 *
270 * @since 2.0.0
271 * @access public
272 *
273 * @param int $post_id Optional. Post ID. Default is `0`.
274 *
275 * @return false|Document The document if it exist, False otherwise.
276 */
277 public function get_doc_for_frontend( $post_id ) {
278 $preview_id = (int) Utils::get_super_global_value( $_GET, 'preview_id' );
279 $is_preview = is_preview() && $post_id === $preview_id;
280 $is_nonce_verify = wp_verify_nonce( Utils::get_super_global_value( $_GET, 'preview_nonce' ), 'post_preview_' . $preview_id );
281
282 if ( ( $is_preview && $is_nonce_verify ) || Plugin::$instance->preview->is_preview_mode() ) {
283 $document = $this->get_doc_or_auto_save( $post_id, get_current_user_id() );
284 } else {
285 $document = $this->get( $post_id );
286 }
287
288 return $document;
289 }
290
291 /**
292 * Get document type.
293 *
294 * Retrieve the type of any given document.
295 *
296 * @since 2.0.0
297 * @access public
298 *
299 * @param string $type
300 *
301 * @param string $fallback
302 *
303 * @return Document|bool The type of the document.
304 */
305 public function get_document_type( $type, $fallback = 'post' ) {
306 $types = $this->get_document_types();
307
308 if ( isset( $types[ $type ] ) ) {
309 return $types[ $type ];
310 }
311
312 if ( isset( $types[ $fallback ] ) ) {
313 return $types[ $fallback ];
314 }
315
316 return false;
317 }
318
319 /**
320 * Get document types.
321 *
322 * Retrieve the all the registered document types.
323 *
324 * @since 2.0.0
325 * @access public
326 *
327 * @param array $args Optional. An array of key => value arguments to match against
328 * the properties. Default is empty array.
329 * @param string $operator Optional. The logical operation to perform. 'or' means only one
330 * element from the array needs to match; 'and' means all elements
331 * must match; 'not' means no elements may match. Default 'and'.
332 *
333 * @return Document[] All the registered document types.
334 */
335 public function get_document_types( $args = [], $operator = 'and' ) {
336 $this->register_types();
337
338 if ( ! empty( $args ) ) {
339 $types_properties = $this->get_types_properties();
340
341 $filtered = wp_filter_object_list( $types_properties, $args, $operator );
342
343 return array_intersect_key( $this->types, $filtered );
344 }
345
346 return $this->types;
347 }
348
349 /**
350 * Get document types with their properties.
351 *
352 * @return array A list of properties arrays indexed by the type.
353 */
354 public function get_types_properties() {
355 $types_properties = [];
356
357 foreach ( $this->get_document_types() as $type => $class ) {
358 $types_properties[ $type ] = $class::get_properties();
359 }
360 return $types_properties;
361 }
362
363 /**
364 * Create a document.
365 *
366 * Create a new document using any given parameters.
367 *
368 * @since 2.0.0
369 * @access public
370 *
371 * @param string $type Document type.
372 * @param array $post_data An array containing the post data.
373 * @param array $meta_data An array containing the post meta data.
374 *
375 * @return Document The type of the document.
376 */
377 public function create( $type, $post_data = [], $meta_data = [] ) {
378 $class = $this->get_document_type( $type, false );
379
380 if ( ! $class ) {
381 return new \WP_Error( 500, sprintf( 'Type %s does not exist.', $type ) );
382 }
383
384 if ( empty( $post_data['post_title'] ) ) {
385 $post_data['post_title'] = esc_html__( 'Elementor', 'elementor' );
386 if ( 'post' !== $type ) {
387 $post_data['post_title'] = sprintf(
388 /* translators: %s: Document title. */
389 __( 'Elementor %s', 'elementor' ),
390 call_user_func( [ $class, 'get_title' ] )
391 );
392 }
393 $update_title = true;
394 }
395
396 $meta_data['_elementor_edit_mode'] = 'builder';
397
398 // Save the type as-is for plugins that hooked at `wp_insert_post`.
399 $meta_data[ Document::TYPE_META_KEY ] = $type;
400
401 $post_data['meta_input'] = $meta_data;
402
403 $post_types = $class::get_property( 'cpt' );
404
405 if ( ! empty( $post_types[0] ) && empty( $post_data['post_type'] ) ) {
406 $post_data['post_type'] = $post_types[0];
407 }
408
409 $post_id = wp_insert_post( $post_data );
410
411 if ( ! empty( $update_title ) ) {
412 $post_data['ID'] = $post_id;
413 $post_data['post_title'] .= ' #' . $post_id;
414
415 // The meta doesn't need update.
416 unset( $post_data['meta_input'] );
417
418 wp_update_post( $post_data );
419 }
420
421 /** @var Document $document */
422 $document = new $class( [
423 'post_id' => $post_id,
424 ] );
425
426 // Let the $document to re-save the template type by his way + version.
427 $document->save( [] );
428
429 return $document;
430 }
431
432 /**
433 * Remove user edit capabilities if document is not editable.
434 *
435 * Filters the user capabilities to disable editing in admin.
436 *
437 * @param array $allcaps An array of all the user's capabilities.
438 * @param array $caps Actual capabilities for meta capability.
439 * @param array $args Optional parameters passed to has_cap(), typically object ID.
440 *
441 * @return array
442 */
443 public function remove_user_edit_cap( $allcaps, $caps, $args ) {
444 global $pagenow;
445
446 if ( ! in_array( $pagenow, [ 'post.php', 'edit.php' ], true ) ) {
447 return $allcaps;
448 }
449
450 // Don't touch not existing or not allowed caps.
451 if ( empty( $caps[0] ) || empty( $allcaps[ $caps[0] ] ) ) {
452 return $allcaps;
453 }
454
455 $capability = $args[0];
456
457 if ( 'edit_post' !== $capability ) {
458 return $allcaps;
459 }
460
461 if ( empty( $args[2] ) ) {
462 return $allcaps;
463 }
464
465 $post_id = $args[2];
466
467 $document = Plugin::$instance->documents->get( $post_id );
468
469 if ( ! $document ) {
470 return $allcaps;
471 }
472
473 $allcaps[ $caps[0] ] = $document::get_property( 'is_editable' );
474
475 return $allcaps;
476 }
477
478 /**
479 * Filter Post Row Actions.
480 *
481 * Let the Document to filter the array of row action links on the Posts list table.
482 *
483 * @param array $actions
484 * @param \WP_Post $post
485 *
486 * @return array
487 */
488 public function filter_post_row_actions( $actions, $post ) {
489 $document = $this->get( $post->ID );
490
491 if ( $document ) {
492 $actions = $document->filter_admin_row_actions( $actions );
493 }
494
495 return $actions;
496 }
497
498 /**
499 * Save document data using ajax.
500 *
501 * Save the document on the builder using ajax, when saving the changes, and refresh the editor.
502 *
503 * @since 2.0.0
504 * @access public
505 *
506 * @param $request Post ID.
507 *
508 * @throws \Exception If current user don't have permissions to edit the post or the post is not using Elementor.
509 *
510 * @return array The document data after saving.
511 */
512 public function ajax_save( $request ) {
513 $document = $this->get( $request['editor_post_id'] );
514
515 if ( ! $document->is_built_with_elementor() || ! $document->is_editable_by_current_user() ) {
516 throw new \Exception( 'Access denied.' );
517 }
518
519 $this->switch_to_document( $document );
520
521 // Set the post as global post.
522 Plugin::$instance->db->switch_to_post( $document->get_post()->ID );
523
524 $status = Document::STATUS_DRAFT;
525
526 if ( isset( $request['status'] ) && in_array( $request['status'], [ Document::STATUS_PUBLISH, Document::STATUS_PRIVATE, Document::STATUS_PENDING, Document::STATUS_AUTOSAVE ], true ) ) {
527 $status = $request['status'];
528 }
529
530 if ( Document::STATUS_AUTOSAVE === $status ) {
531 // If the post is a draft - save the `autosave` to the original draft.
532 // Allow a revision only if the original post is already published.
533 if ( in_array( $document->get_post()->post_status, [ Document::STATUS_PUBLISH, Document::STATUS_PRIVATE ], true ) ) {
534 $document = $document->get_autosave( 0, true );
535 }
536 }
537
538 // Set default page template because the footer-saver doesn't send default values,
539 // But if the template was changed from canvas to default - it needed to save.
540 if ( Utils::is_cpt_custom_templates_supported() && ! isset( $request['settings']['template'] ) ) {
541 $request['settings']['template'] = 'default';
542 }
543
544 $data = [
545 'elements' => $request['elements'],
546 'settings' => $request['settings'],
547 ];
548
549 $document->save( $data );
550
551 $post = $document->get_post();
552 $main_post = $document->get_main_post();
553
554 // Refresh after save.
555 $document = $this->get( $post->ID, false );
556
557 $return_data = [
558 'status' => $post->post_status,
559 'config' => [
560 'document' => [
561 'last_edited' => $document->get_last_edited(),
562 'urls' => [
563 'wp_preview' => $document->get_wp_preview_url(),
564 ],
565 ],
566 ],
567 ];
568
569 $post_status_object = get_post_status_object( $main_post->post_status );
570
571 if ( $post_status_object ) {
572 $return_data['config']['document']['status'] = [
573 'value' => $post_status_object->name,
574 'label' => $post_status_object->label,
575 ];
576 }
577
578 /**
579 * Returned documents ajax saved data.
580 *
581 * Filters the ajax data returned when saving the post on the builder.
582 *
583 * @since 2.0.0
584 *
585 * @param array $return_data The returned data.
586 * @param Document $document The document instance.
587 */
588 $return_data = apply_filters( 'elementor/documents/ajax_save/return_data', $return_data, $document );
589
590 return $return_data;
591 }
592
593 /**
594 * Ajax discard changes.
595 *
596 * Load the document data from an autosave, deleting unsaved changes.
597 *
598 * @param $request
599 *
600 * @return bool True if changes discarded, False otherwise.
601 * @throws \Exception
602 *
603 * @since 2.0.0
604 * @access public
605 *
606 */
607 public function ajax_discard_changes( $request ) {
608 $document = $this->get_with_permissions( $request['editor_post_id'] );
609
610 $autosave = $document->get_autosave();
611
612 if ( $autosave ) {
613 $success = $autosave->delete();
614 } else {
615 $success = true;
616 }
617
618 return $success;
619 }
620
621 public function ajax_get_document_config( $request ) {
622 $post_id = absint( $request['id'] );
623
624 Plugin::$instance->editor->set_post_id( $post_id );
625
626 $document = $this->get_doc_or_auto_save( $post_id );
627
628 if ( ! $document ) {
629 throw new \Exception( 'Not found.' );
630 }
631
632 if ( ! $document->is_editable_by_current_user() ) {
633 throw new \Exception( 'Access denied.' );
634 }
635
636 // Set the global data like $post, $authordata and etc
637 Plugin::$instance->db->switch_to_post( $post_id );
638
639 $this->switch_to_document( $document );
640
641 // Change mode to Builder
642 $document->set_is_built_with_elementor( true );
643
644 $doc_config = $document->get_config();
645
646 return $doc_config;
647 }
648
649 /**
650 * Switch to document.
651 *
652 * Change the document to any new given document type.
653 *
654 * @since 2.0.0
655 * @access public
656 *
657 * @param Document $document The document to switch to.
658 */
659 public function switch_to_document( $document ) {
660 // If is already switched, or is the same post, return.
661 if ( $this->current_doc === $document ) {
662 $this->switched_data[] = false;
663 return;
664 }
665
666 $this->switched_data[] = [
667 'switched_doc' => $document,
668 'original_doc' => $this->current_doc, // Note, it can be null if the global isn't set
669 ];
670
671 $this->current_doc = $document;
672 }
673
674 /**
675 * Restore document.
676 *
677 * Rollback to the original document.
678 *
679 * @since 2.0.0
680 * @access public
681 */
682 public function restore_document() {
683 $data = array_pop( $this->switched_data );
684
685 // If not switched, return.
686 if ( ! $data ) {
687 return;
688 }
689
690 $this->current_doc = $data['original_doc'];
691 }
692
693 /**
694 * Get current document.
695 *
696 * Retrieve the current document.
697 *
698 * @since 2.0.0
699 * @access public
700 *
701 * @return Document The current document.
702 */
703 public function get_current() {
704 return $this->current_doc;
705 }
706
707 public function localize_settings( $settings ) {
708 $translations = [];
709
710 foreach ( $this->get_document_types() as $type => $class ) {
711 $translations[ $type ] = $class::get_title();
712 }
713
714 return array_replace_recursive( $settings, [
715 'i18n' => $translations,
716 ] );
717 }
718
719 private function register_types() {
720 if ( ! did_action( 'elementor/documents/register' ) ) {
721 /**
722 * Register Elementor documents.
723 *
724 * @since 2.0.0
725 *
726 * @param Documents_Manager $this The document manager instance.
727 */
728 do_action( 'elementor/documents/register', $this );
729 }
730 }
731
732 /**
733 * Get create new post URL.
734 *
735 * Retrieve a custom URL for creating a new post/page using Elementor.
736 *
737 * @param string $post_type Optional. Post type slug. Default is 'page'.
738 * @param string|null $template_type Optional. Query arg 'template_type'. Default is null.
739 *
740 * @return string A URL for creating new post using Elementor.
741 */
742 public static function get_create_new_post_url( $post_type = 'page', $template_type = null ) {
743 $query_args = [
744 'action' => 'elementor_new_post',
745 'post_type' => $post_type,
746 ];
747
748 if ( $template_type ) {
749 $query_args['template_type'] = $template_type;
750 }
751
752 $new_post_url = add_query_arg( $query_args, admin_url( 'edit.php' ) );
753
754 $new_post_url = add_query_arg( '_wpnonce', wp_create_nonce( 'elementor_action_new_post' ), $new_post_url );
755
756 return $new_post_url;
757 }
758
759 private function get_doc_type_by_id( $post_id ) {
760 // Auto-save inherits from the original post.
761 if ( wp_is_post_autosave( $post_id ) ) {
762 $post_id = wp_get_post_parent_id( $post_id );
763 }
764
765 // Content built with Elementor.
766 $template_type = get_post_meta( $post_id, Document::TYPE_META_KEY, true );
767
768 if ( $template_type && isset( $this->types[ $template_type ] ) ) {
769 return $template_type;
770 }
771
772 // Elementor installation on a site with existing content (which doesn't contain Elementor's meta).
773 $post_type = get_post_type( $post_id );
774
775 return $this->cpt[ $post_type ] ?? 'post';
776 }
777 }
778