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

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