PluginProbe
Elementor Website Builder – more than just a page builder / 3.0.13
Elementor Website Builder – more than just a page builder v3.0.13
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.0.13, at core/documents-manager.php

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