PluginProbe
Elementor Website Builder – more than just a page builder / 3.25.5
Elementor Website Builder – more than just a page builder v3.25.5
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 / includes / template-library / manager.php

manager.php in Elementor Website Builder – more than just a page builder 3.25.5, at includes/template-library/manager.php

808 lines 20.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\TemplateLibrary;
3
4 use Elementor\Api;
5 use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
6 use Elementor\Core\Isolation\Wordpress_Adapter;
7 use Elementor\Core\Isolation\Wordpress_Adapter_Interface;
8 use Elementor\Core\Isolation\Elementor_Adapter;
9 use Elementor\Core\Isolation\Elementor_Adapter_Interface;
10 use Elementor\Core\Settings\Manager as SettingsManager;
11 use Elementor\Includes\TemplateLibrary\Data\Controller;
12 use Elementor\TemplateLibrary\Classes\Import_Images;
13 use Elementor\Plugin;
14 use Elementor\User;
15 use Elementor\Utils;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Elementor template library manager.
23 *
24 * Elementor template library manager handler class is responsible for
25 * initializing the template library.
26 *
27 * @since 1.0.0
28 */
29 class Manager {
30
31 /**
32 * Registered template sources.
33 *
34 * Holds a list of all the supported sources with their instances.
35 *
36 * @access protected
37 *
38 * @var Source_Base[]
39 */
40 protected $_registered_sources = []; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
41
42 /**
43 * Imported template images.
44 *
45 * Holds an instance of `Import_Images` class.
46 *
47 * @access private
48 *
49 * @var Import_Images
50 */
51 private $_import_images = null; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
52
53 /**
54 * @var Wordpress_Adapter_Interface
55 */
56 protected $wordpress_adapter = null;
57
58 /**
59 * @var Elementor_Adapter_Interface
60 */
61 protected $elementor_adapter = null;
62
63 /**
64 * Template library manager constructor.
65 *
66 * Initializing the template library manager by registering default template
67 * sources and initializing ajax calls.
68 *
69 * @since 1.0.0
70 * @access public
71 */
72 public function __construct() {
73 Plugin::$instance->data_manager_v2->register_controller( new Controller() );
74
75 $this->register_default_sources();
76
77 $this->add_actions();
78 }
79
80 /**
81 * @since 2.3.0
82 * @access public
83 */
84 public function add_actions() {
85 add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
86 add_action( 'wp_ajax_elementor_library_direct_actions', [ $this, 'handle_direct_actions' ] );
87 }
88
89 /**
90 * Get `Import_Images` instance.
91 *
92 * Retrieve the instance of the `Import_Images` class.
93 *
94 * @since 1.0.0
95 * @access public
96 *
97 * @return Import_Images Imported images instance.
98 */
99 public function get_import_images_instance() {
100 if ( null === $this->_import_images ) {
101 $this->_import_images = new Import_Images();
102 }
103
104 return $this->_import_images;
105 }
106
107 public function set_wordpress_adapter( Wordpress_Adapter_Interface $wordpress_adapter ) {
108 $this->wordpress_adapter = $wordpress_adapter;
109 }
110
111 public function set_elementor_adapter( Elementor_Adapter_Interface $elementor_adapter ): void {
112 $this->elementor_adapter = $elementor_adapter;
113 }
114 /**
115 * Register template source.
116 *
117 * Used to register new template sources displayed in the template library.
118 *
119 * @since 1.0.0
120 * @access public
121 *
122 * @param string $source_class The name of source class.
123 * @param array $args Optional. Class arguments. Default is an
124 * empty array.
125 *
126 * @return \WP_Error|true True if the source was registered, `WP_Error`
127 * otherwise.
128 */
129 public function register_source( $source_class, $args = [] ) {
130 if ( ! class_exists( $source_class ) ) {
131 return new \WP_Error( 'source_class_name_not_exists' );
132 }
133
134 $source_instance = new $source_class( $args );
135
136 if ( ! $source_instance instanceof Source_Base ) {
137 return new \WP_Error( 'wrong_instance_source' );
138 }
139
140 $source_id = $source_instance->get_id();
141
142 if ( isset( $this->_registered_sources[ $source_id ] ) ) {
143 return new \WP_Error( 'source_exists' );
144 }
145
146 $this->_registered_sources[ $source_id ] = $source_instance;
147
148 return true;
149 }
150
151 /**
152 * Unregister template source.
153 *
154 * Remove an existing template sources from the list of registered template
155 * sources.
156 *
157 * @since 1.0.0
158 * @deprecated 2.7.0
159 * @access public
160 *
161 * @param string $id The source ID.
162 *
163 * @return bool Whether the source was unregistered.
164 */
165 public function unregister_source( $id ) {
166 return true;
167 }
168
169 /**
170 * Get registered template sources.
171 *
172 * Retrieve registered template sources.
173 *
174 * @since 1.0.0
175 * @access public
176 *
177 * @return Source_Base[] Registered template sources.
178 */
179 public function get_registered_sources() {
180 return $this->_registered_sources;
181 }
182
183 /**
184 * Get template source.
185 *
186 * Retrieve single template sources for a given template ID.
187 *
188 * @since 1.0.0
189 * @access public
190 *
191 * @param string $id The source ID.
192 *
193 * @return false|Source_Base Template sources if one exist, False otherwise.
194 */
195 public function get_source( $id ) {
196 $sources = $this->get_registered_sources();
197
198 if ( ! isset( $sources[ $id ] ) ) {
199 return false;
200 }
201
202 return $sources[ $id ];
203 }
204
205 /**
206 * Get templates.
207 *
208 * Retrieve all the templates from all the registered sources.
209 *
210 * @param array $filter_sources
211 * @param bool $force_update
212 * @return array
213 */
214 public function get_templates( array $filter_sources = [], bool $force_update = false ): array {
215 $templates = [];
216
217 foreach ( $this->get_registered_sources() as $source ) {
218 if ( ! empty( $filter_sources ) && ! in_array( $source->get_id(), $filter_sources, true ) ) {
219 continue;
220 }
221
222 $templates = array_merge( $templates, $source->get_items( [ 'force_update' => $force_update ] ) );
223 }
224
225 return $templates;
226 }
227
228 /**
229 * Get library data.
230 *
231 * Retrieve the library data.
232 *
233 * @since 1.9.0
234 * @access public
235 *
236 * @param array $args Library arguments.
237 *
238 * @return array Library data.
239 */
240 public function get_library_data( array $args ) {
241 $library_data = Api::get_library_data( ! empty( $args['sync'] ) );
242
243 if ( empty( $library_data ) ) {
244 return $library_data;
245 }
246
247 // Ensure all document are registered.
248 Plugin::$instance->documents->get_document_types();
249
250 $filter_sources = ! empty( $args['filter_sources'] ) ? $args['filter_sources'] : [];
251 $force_update = ! empty( $args['sync'] );
252
253 return [
254 'templates' => $this->get_templates( $filter_sources, $force_update ),
255 'config' => $library_data['types_data'],
256 ];
257 }
258
259 /**
260 * Save template.
261 *
262 * Save new or update existing template on the database.
263 *
264 * @since 1.0.0
265 * @access public
266 *
267 * @param array $args Template arguments.
268 *
269 * @return \WP_Error|int The ID of the saved/updated template.
270 */
271 public function save_template( array $args ) {
272 $validate_args = $this->ensure_args( [ 'post_id', 'source', 'content', 'type' ], $args );
273
274 if ( is_wp_error( $validate_args ) ) {
275 return $validate_args;
276 }
277
278 $source = $this->get_source( $args['source'] );
279
280 if ( ! $source ) {
281 return new \WP_Error( 'template_error', 'Template source not found.' );
282 }
283
284 $args['content'] = json_decode( $args['content'], true );
285
286 $page = SettingsManager::get_settings_managers( 'page' )->get_model( $args['post_id'] );
287
288 $args['page_settings'] = $page->get_data( 'settings' );
289
290 $template_id = $source->save_item( $args );
291
292 if ( is_wp_error( $template_id ) ) {
293 return $template_id;
294 }
295
296 return $source->get_item( $template_id );
297 }
298
299 /**
300 * Update template.
301 *
302 * Update template on the database.
303 *
304 * @since 1.0.0
305 * @access public
306 *
307 * @param array $template_data New template data.
308 *
309 * @return \WP_Error|Source_Base Template sources instance if the templates
310 * was updated, `WP_Error` otherwise.
311 */
312 public function update_template( array $template_data ) {
313 $validate_args = $this->ensure_args( [ 'source', 'content', 'type' ], $template_data );
314
315 if ( is_wp_error( $validate_args ) ) {
316 return $validate_args;
317 }
318
319 $source = $this->get_source( $template_data['source'] );
320
321 if ( ! $source ) {
322 return new \WP_Error( 'template_error', 'Template source not found.' );
323 }
324
325 $template_data['content'] = json_decode( $template_data['content'], true );
326
327 $update = $source->update_item( $template_data );
328
329 if ( is_wp_error( $update ) ) {
330 return $update;
331 }
332
333 return $source->get_item( $template_data['id'] );
334 }
335
336 /**
337 * Update templates.
338 *
339 * Update template on the database.
340 *
341 * @since 1.0.0
342 * @access public
343 *
344 * @param array $args Template arguments.
345 *
346 * @return \WP_Error|true True if templates updated, `WP_Error` otherwise.
347 */
348 public function update_templates( array $args ) {
349 foreach ( $args['templates'] as $template_data ) {
350 $result = $this->update_template( $template_data );
351
352 if ( is_wp_error( $result ) ) {
353 return $result;
354 }
355 }
356
357 return true;
358 }
359
360 /**
361 * Get template data.
362 *
363 * Retrieve the template data.
364 *
365 * @since 1.5.0
366 * @access public
367 *
368 * @param array $args Template arguments.
369 *
370 * @return \WP_Error|bool|array ??
371 */
372 public function get_template_data( array $args ) {
373 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
374
375 if ( is_wp_error( $validate_args ) ) {
376 return $validate_args;
377 }
378
379 if ( ! $this->is_allowed_to_read_template( $args ) ) {
380 return new \WP_Error(
381 'template_error',
382 esc_html__( 'You do not have permission to access this template.', 'elementor' )
383 );
384 }
385
386 if ( isset( $args['edit_mode'] ) ) {
387 Plugin::$instance->editor->set_edit_mode( $args['edit_mode'] );
388 }
389
390 $source = $this->get_source( $args['source'] );
391
392 if ( ! $source ) {
393 return new \WP_Error( 'template_error', 'Template source not found.' );
394 }
395
396 do_action( 'elementor/template-library/before_get_source_data', $args, $source );
397
398 $data = $source->get_data( $args );
399
400 do_action( 'elementor/template-library/after_get_source_data', $args, $source );
401
402 return $data;
403 }
404
405 /**
406 * Delete template.
407 *
408 * Delete template from the database.
409 *
410 * @since 1.0.0
411 * @access public
412 *
413 * @param array $args Template arguments.
414 *
415 * @return \WP_Post|\WP_Error|false|null Post data on success, false or null
416 * or 'WP_Error' on failure.
417 */
418 public function delete_template( array $args ) {
419 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
420
421 if ( is_wp_error( $validate_args ) ) {
422 return $validate_args;
423 }
424
425 $source = $this->get_source( $args['source'] );
426
427 if ( ! $source ) {
428 return new \WP_Error( 'template_error', 'Template source not found.' );
429 }
430
431 return $source->delete_template( $args['template_id'] );
432 }
433
434 /**
435 * Export template.
436 *
437 * Export template to a file after ensuring it is a valid Elementor template
438 * and checking user permissions for private posts.
439 *
440 * @since 1.0.0
441 * @access public
442 *
443 * @param array $args Template arguments.
444 *
445 * @return mixed Whether the export succeeded or failed.
446 */
447 public function export_template( array $args ) {
448 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
449
450 if ( is_wp_error( $validate_args ) ) {
451 return $validate_args;
452 }
453
454 $post_id = intval( $args['template_id'] );
455 $post_status = get_post_status( $post_id );
456
457 if ( get_post_type( $post_id ) !== Source_Local::CPT ) {
458 return new \WP_Error( 'template_error', esc_html__( 'Invalid template type or template does not exist.', 'elementor' ) );
459 }
460
461 if ( 'private' === $post_status && ! current_user_can( 'read_private_posts', $post_id ) ) {
462 return new \WP_Error( 'template_error', esc_html__( 'You do not have permission to access this template.', 'elementor' ) );
463 }
464
465 if ( 'publish' !== $post_status && ! current_user_can( 'edit_post', $post_id ) ) {
466 return new \WP_Error( 'template_error', esc_html__( 'You do not have permission to export this template.', 'elementor' ) );
467 }
468
469 $source = $this->get_source( $args['source'] );
470
471 if ( ! $source ) {
472 return new \WP_Error( 'template_error', 'Template source not found' );
473 }
474
475 return $source->export_template( $args['template_id'] );
476 }
477
478 /**
479 * @since 2.3.0
480 * @access public
481 */
482 public function direct_import_template() {
483 /** @var Source_Local $source */
484 $source = $this->get_source( 'local' );
485 $file = Utils::get_super_global_value( $_FILES, 'file' );
486 return $source->import_template( $file['name'], $file['tmp_name'] );
487 }
488
489 /**
490 * Import template.
491 *
492 * Import template from a file.
493 *
494 * @since 1.0.0
495 * @access public
496 *
497 * @param array $data
498 *
499 * @return mixed Whether the export succeeded or failed.
500 */
501 public function import_template( array $data ) {
502 // If the template is a JSON file, allow uploading it.
503 add_filter( 'elementor/files/allow-file-type/json', [ $this, 'enable_json_template_upload' ] );
504 add_filter( 'elementor/files/allow_unfiltered_upload', [ $this, 'enable_json_template_upload' ] );
505
506 // Imported templates can be either JSON files, or Zip files containing multiple JSON files
507 $upload_result = Plugin::$instance->uploads_manager->handle_elementor_upload( $data, [ 'zip', 'json' ] );
508
509 remove_filter( 'elementor/files/allow-file-type/json', [ $this, 'enable_json_template_upload' ] );
510 remove_filter( 'elementor/files/allow_unfiltered_upload', [ $this, 'enable_json_template_upload' ] );
511
512 if ( is_wp_error( $upload_result ) ) {
513 Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $upload_result['tmp_name'] ) );
514
515 return $upload_result;
516 }
517
518 /** @var Source_Local $source_local */
519 $source_local = $this->get_source( 'local' );
520
521 $import_result = $source_local->import_template( $upload_result['name'], $upload_result['tmp_name'] );
522
523 // Remove the temporary directory generated for the stream-uploaded file.
524 Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $upload_result['tmp_name'] ) );
525
526 return $import_result;
527 }
528
529 /**
530 * Enable JSON Template Upload
531 *
532 * Runs on the 'elementor/files/allow-file-type/json' Uploads Manager filter.
533 *
534 * @since 3.5.0
535 * @access public
536 *
537 * return bool
538 */
539 public function enable_json_template_upload() {
540 return true;
541 }
542
543 /**
544 * Mark template as favorite.
545 *
546 * Add the template to the user favorite templates.
547 *
548 * @since 1.9.0
549 * @access public
550 *
551 * @param array $args Template arguments.
552 *
553 * @return mixed Whether the template marked as favorite.
554 */
555 public function mark_template_as_favorite( $args ) {
556 $validate_args = $this->ensure_args( [ 'source', 'template_id', 'favorite' ], $args );
557
558 if ( is_wp_error( $validate_args ) ) {
559 return $validate_args;
560 }
561
562 $source = $this->get_source( $args['source'] );
563
564 return $source->mark_as_favorite( $args['template_id'], filter_var( $args['favorite'], FILTER_VALIDATE_BOOLEAN ) );
565 }
566
567 public function import_from_json( array $args ) {
568 $validate_args = $this->ensure_args( [ 'editor_post_id', 'elements' ], $args );
569
570 if ( is_wp_error( $validate_args ) ) {
571 return $validate_args;
572 }
573
574 $elements = json_decode( $args['elements'], true );
575
576 $document = Plugin::$instance->documents->get( $args['editor_post_id'] );
577 if ( ! $document ) {
578 return new \WP_Error( 'template_error', 'Document not found.' );
579 }
580
581 $import_data = $document->get_import_data( [ 'content' => $elements ] );
582
583 return $import_data['content'];
584 }
585
586 /**
587 * Register default template sources.
588 *
589 * Register the 'local' and 'remote' template sources that Elementor use by
590 * default.
591 *
592 * @since 1.0.0
593 * @access private
594 */
595 private function register_default_sources() {
596 $sources = [
597 'local',
598 'remote',
599 ];
600
601 foreach ( $sources as $source_filename ) {
602 $class_name = ucwords( $source_filename );
603 $class_name = str_replace( '-', '_', $class_name );
604
605 $this->register_source( __NAMESPACE__ . '\Source_' . $class_name );
606 }
607 }
608
609 /**
610 * Handle ajax request.
611 *
612 * Fire authenticated ajax actions for any given ajax request.
613 *
614 * @since 1.0.0
615 * @access private
616 *
617 * @param string $ajax_request Ajax request.
618 *
619 * @param array $data
620 *
621 * @return mixed
622 * @throws \Exception
623 */
624 private function handle_ajax_request( $ajax_request, array $data ) {
625 if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) {
626 throw new \Exception( 'Access denied.' );
627 }
628
629 if ( ! empty( $data['editor_post_id'] ) ) {
630 $editor_post_id = absint( $data['editor_post_id'] );
631
632 if ( ! get_post( $editor_post_id ) ) {
633 throw new \Exception( 'Post not found.' );
634 }
635
636 Plugin::$instance->db->switch_to_post( $editor_post_id );
637 }
638
639 $result = call_user_func( [ $this, $ajax_request ], $data );
640
641 if ( is_wp_error( $result ) ) {
642 throw new \Exception( $result->get_error_message() );
643 }
644
645 return $result;
646 }
647
648 /**
649 * Init ajax calls.
650 *
651 * Initialize template library ajax calls for allowed ajax requests.
652 *
653 * @since 2.3.0
654 * @access public
655 *
656 * @param Ajax $ajax
657 */
658 public function register_ajax_actions( Ajax $ajax ) {
659 $library_ajax_requests = [
660 'get_library_data',
661 'get_template_data',
662 'save_template',
663 'update_templates',
664 'delete_template',
665 'import_template',
666 'mark_template_as_favorite',
667 'import_from_json',
668 ];
669
670 foreach ( $library_ajax_requests as $ajax_request ) {
671 $ajax->register_ajax_action( $ajax_request, function( $data ) use ( $ajax_request ) {
672 return $this->handle_ajax_request( $ajax_request, $data );
673 } );
674 }
675 }
676
677 /**
678 * @since 2.3.0
679 * @access public
680 */
681 public function handle_direct_actions() {
682 if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) {
683 return;
684 }
685
686 /** @var Ajax $ajax */
687 $ajax = Plugin::$instance->common->get_component( 'ajax' );
688
689 if ( ! $ajax->verify_request_nonce() ) {
690 $this->handle_direct_action_error( 'Access Denied' );
691 }
692
693 $action = Utils::get_super_global_value( $_REQUEST, 'library_action' ); // phpcs:ignore -- Nonce already verified.
694
695 $whitelist_methods = [
696 'export_template',
697 'direct_import_template',
698 ];
699
700 if ( 'direct_import_template' === $action && ! User::is_current_user_can_upload_json() ) {
701 return;
702 }
703
704 if ( in_array( $action, $whitelist_methods, true ) ) {
705 $result = $this->$action( $_REQUEST ); // phpcs:ignore -- Nonce already verified.
706 } else {
707 $result = new \WP_Error( 'method_not_exists', 'Method Not exists' );
708 }
709
710 if ( is_wp_error( $result ) ) {
711 /** @var \WP_Error $result */
712 $this->handle_direct_action_error( $result->get_error_message() . '.' );
713 }
714
715 $callback = "on_{$action}_success";
716
717 if ( method_exists( $this, $callback ) ) {
718 $this->$callback( $result );
719 }
720
721 die;
722 }
723
724 /**
725 * On successful template import.
726 *
727 * Redirect the user to the template library after template import was
728 * successful finished.
729 *
730 * @since 2.3.0
731 * @access private
732 */
733 private function on_direct_import_template_success() {
734 wp_safe_redirect( admin_url( Source_Local::ADMIN_MENU_SLUG ) );
735 }
736
737 /**
738 * @since 2.3.0
739 * @access private
740 */
741 private function handle_direct_action_error( $message ) {
742 _default_wp_die_handler( $message, 'Elementor Library' );
743 }
744
745 /**
746 * Ensure arguments exist.
747 *
748 * Checks whether the required arguments exist in the specified arguments.
749 *
750 * @since 1.0.0
751 * @access private
752 *
753 * @param array $required_args Required arguments to check whether they
754 * exist.
755 * @param array $specified_args The list of all the specified arguments to
756 * check against.
757 *
758 * @return \WP_Error|true True on success, 'WP_Error' otherwise.
759 */
760 private function ensure_args( array $required_args, array $specified_args ) {
761 $not_specified_args = array_diff( $required_args, array_keys( $specified_args ) );
762
763 if ( $not_specified_args ) {
764 return new \WP_Error( 'arguments_not_specified', sprintf( 'The required argument(s) "%s" not specified.', implode( ', ', $not_specified_args ) ) );
765 }
766
767 return true;
768 }
769
770 private function is_allowed_to_read_template( array $args ): bool {
771 if ( 'remote' === $args['source'] ) {
772 return true;
773 }
774
775 if ( null === $this->wordpress_adapter ) {
776 $this->set_wordpress_adapter( new WordPress_Adapter() );
777 }
778
779 if ( ! $this->should_check_permissions( $args ) ) {
780 return true;
781 }
782
783 $post_id = intval( $args['template_id'] );
784 $post_status = $this->wordpress_adapter->get_post_status( $post_id );
785 $is_private_or_non_published = ( 'private' === $post_status && ! $this->wordpress_adapter->current_user_can( 'read_private_posts', $post_id ) ) || ( 'publish' !== $post_status );
786
787 $can_read_template = $is_private_or_non_published || $this->wordpress_adapter->current_user_can( 'edit_post', $post_id );
788
789 return apply_filters( 'elementor/template-library/is_allowed_to_read_template', $can_read_template, $args );
790 }
791
792 private function should_check_permissions( array $args ): bool {
793 if ( null === $this->elementor_adapter ) {
794 $this->set_elementor_adapter( new Elementor_Adapter() );
795 }
796
797 // TODO: Remove $isWidgetTemplate in 3.28.0 as there is a Pro dependency
798 $check_permissions = isset( $args['check_permissions'] ) && false === $args['check_permissions'];
799 $is_widget_template = 'widget' === $this->elementor_adapter->get_template_type( $args['template_id'] );
800
801 if ( $check_permissions || $is_widget_template ) {
802 return false;
803 }
804
805 return true;
806 }
807 }
808