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

1,241 lines 31.8 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\Settings\Manager as SettingsManager;
7 use Elementor\Includes\TemplateLibrary\Data\Controller;
8 use Elementor\TemplateLibrary\Classes\Import_Images;
9 use Elementor\Core\Utils\Template_Library_Import_Export_Utils;
10 use Elementor\Plugin;
11 use Elementor\User;
12 use Elementor\Utils;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly.
16 }
17
18 /**
19 * Elementor template library manager.
20 *
21 * Elementor template library manager handler class is responsible for
22 * initializing the template library.
23 *
24 * @since 1.0.0
25 */
26 class Manager {
27
28 const ERROR_TEMPLATE_SOURCE_NOT_FOUND = 'Template source not found.';
29 const ERROR_TEMPLATE_IDS_MISSING = 'Template IDs are missing.';
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 * Template library manager constructor.
55 *
56 * Initializing the template library manager by registering default template
57 * sources and initializing ajax calls.
58 *
59 * @since 1.0.0
60 * @access public
61 */
62 public function __construct() {
63 Plugin::$instance->data_manager_v2->register_controller( new Controller() );
64
65 $this->register_default_sources();
66
67 $this->add_actions();
68 }
69
70 /**
71 * @since 2.3.0
72 * @access public
73 */
74 public function add_actions() {
75 add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
76 add_action( 'wp_ajax_elementor_library_direct_actions', [ $this, 'handle_direct_actions' ] );
77 }
78
79 /**
80 * Get `Import_Images` instance.
81 *
82 * Retrieve the instance of the `Import_Images` class.
83 *
84 * @since 1.0.0
85 * @access public
86 *
87 * @return Import_Images Imported images instance.
88 */
89 public function get_import_images_instance() {
90 if ( null === $this->_import_images ) {
91 $this->_import_images = new Import_Images();
92 }
93
94 return $this->_import_images;
95 }
96
97 /**
98 * Register template source.
99 *
100 * Used to register new template sources displayed in the template library.
101 *
102 * @since 1.0.0
103 * @access public
104 *
105 * @param string $source_class The name of source class.
106 * @param array $args Optional. Class arguments. Default is an
107 * empty array.
108 *
109 * @return \WP_Error|true True if the source was registered, `WP_Error`
110 * otherwise.
111 */
112 public function register_source( $source_class, $args = [] ) {
113 if ( ! class_exists( $source_class ) ) {
114 return new \WP_Error( 'source_class_name_not_exists' );
115 }
116
117 $source_instance = new $source_class( $args );
118
119 if ( ! $source_instance instanceof Source_Base ) {
120 return new \WP_Error( 'wrong_instance_source' );
121 }
122
123 $source_id = $source_instance->get_id();
124
125 if ( isset( $this->_registered_sources[ $source_id ] ) ) {
126 return new \WP_Error( 'source_exists' );
127 }
128
129 $this->_registered_sources[ $source_id ] = $source_instance;
130
131 return true;
132 }
133
134 /**
135 * Unregister template source.
136 *
137 * Remove an existing template sources from the list of registered template
138 * sources.
139 *
140 * @since 1.0.0
141 * @deprecated 2.7.0
142 * @access public
143 *
144 * @param string $id The source ID.
145 *
146 * @return bool Whether the source was unregistered.
147 */
148 public function unregister_source( $id ) {
149 return true;
150 }
151
152 /**
153 * Get registered template sources.
154 *
155 * Retrieve registered template sources.
156 *
157 * @since 1.0.0
158 * @access public
159 *
160 * @return Source_Base[] Registered template sources.
161 */
162 public function get_registered_sources() {
163 return $this->_registered_sources;
164 }
165
166 /**
167 * Get template source.
168 *
169 * Retrieve single template sources for a given template ID.
170 *
171 * @since 1.0.0
172 * @access public
173 *
174 * @param string $id The source ID.
175 *
176 * @return false|Source_Base Template sources if one exist, False otherwise.
177 */
178 public function get_source( $id ) {
179 $sources = $this->get_registered_sources();
180
181 if ( ! isset( $sources[ $id ] ) ) {
182 return false;
183 }
184
185 return $sources[ $id ];
186 }
187
188 /**
189 * Get templates.
190 *
191 * Retrieve all the templates from all the registered sources.
192 *
193 * @param array $filter_sources
194 * @param bool $force_update
195 * @return array
196 */
197 public function get_templates( array $filter_sources = [], bool $force_update = false ): array {
198 $templates = [];
199
200 foreach ( $this->get_registered_sources() as $source ) {
201 if ( ! empty( $filter_sources ) && ! in_array( $source->get_id(), $filter_sources, true ) ) {
202 continue;
203 }
204
205 $templates = array_merge( $templates, $source->get_items( [ 'force_update' => $force_update ] ) );
206 }
207
208 return $templates;
209 }
210
211 /**
212 * Get library data.
213 *
214 * Retrieve the library data.
215 *
216 * @since 1.9.0
217 * @access public
218 *
219 * @param array $args Library arguments.
220 *
221 * @return array Library data.
222 */
223 public function get_library_data( array $args ) {
224 $force_update = ! empty( $args['sync'] );
225
226 $library_data = Api::get_library_data( $force_update );
227
228 if ( empty( $library_data ) ) {
229 return $library_data;
230 }
231
232 // Ensure all document are registered.
233 Plugin::$instance->documents->get_document_types();
234
235 $filter_sources = ! empty( $args['filter_sources'] ) ? $args['filter_sources'] : [];
236
237 $full_library_data = [
238 'templates' => $this->get_templates( $filter_sources, $force_update ),
239 'config' => $library_data['types_data'],
240 ];
241
242 /**
243 * Filter the full library data.
244 *
245 * @since 3.32.2
246 * @param-out $full_library_data - 'templates' and 'config' data ('config' holds the list of categories).
247 */
248 return apply_filters( 'elementor/library/full-data', $full_library_data );
249 }
250
251 /**
252 * Save template.
253 *
254 * Save new or update existing template on the database.
255 *
256 * @since 1.0.0
257 * @access public
258 *
259 * @param array $args Template arguments.
260 *
261 * @return \WP_Error|int The ID of the saved/updated template.
262 */
263 public function save_template( array $args ) {
264 $validate_args = $this->ensure_args( [ 'post_id', 'source', 'content', 'type' ], $args );
265
266 if ( is_wp_error( $validate_args ) ) {
267 return $validate_args;
268 }
269
270 $sources = (array) $args['source']; // BC
271 $results = [];
272
273 foreach ( $sources as $source ) {
274 $args_copy = $args;
275 $args_copy['source'] = $source;
276 $results[] = $this->save_template_item( $args_copy );
277 }
278
279 return 1 === count( $results ) ? $results[0] : $results;
280 }
281
282 private function save_template_item( array $args ) {
283 $source = $this->get_source( $args['source'] );
284
285 if ( ! $source ) {
286 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
287 }
288
289 $args['content'] = json_decode( $args['content'], true );
290
291 $page = SettingsManager::get_settings_managers( 'page' )->get_model( $args['post_id'] );
292
293 $args['page_settings'] = $page->get_data( 'settings' );
294
295 $template_id = $source->save_item( $args );
296
297 if ( is_wp_error( $template_id ) ) {
298 return $template_id;
299 }
300
301 return $source->get_item( $template_id );
302 }
303
304 public function move_template( array $args ) {
305 $validate_args = $this->ensure_args( [ 'source', 'from_source', 'from_template_id' ], $args );
306
307 if ( is_wp_error( $validate_args ) ) {
308 return $validate_args;
309 }
310
311 $args['source'] = $args['source'][0];
312
313 $result = $this->move_template_item( $args );
314
315 if ( ! $this->is_action_to_same_source( $args ) ) {
316 $this->delete_template( [
317 'source' => $args['from_source'],
318 'template_id' => $args['from_template_id'],
319 ] );
320 }
321
322 return $result;
323 }
324
325 private function move_template_item( array $args ) {
326 $validate_args = $this->ensure_args( [ 'source', 'from_source', 'from_template_id' ], $args );
327
328 if ( is_wp_error( $validate_args ) ) {
329 return $validate_args;
330 }
331
332 $source = $this->get_source( $args['source'] );
333
334 if ( ! $source ) {
335 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
336 }
337
338 if ( $this->is_action_to_same_source( $args ) ) {
339 return $source->move_template_to_folder( $args );
340 }
341
342 $from_source = $this->get_source( $args['from_source'] );
343
344 if ( ! $from_source ) {
345 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
346 }
347
348 $args = $from_source->format_args_for_single_action( $args );
349
350 $template_id = $source->save_item( $args );
351
352 if ( is_wp_error( $template_id ) ) {
353 return $template_id;
354 }
355
356 return $source->get_item( $template_id );
357 }
358
359 public function copy_template( array $args ) {
360 $validate_args = $this->ensure_args( [ 'source', 'from_source', 'from_template_id' ], $args );
361
362 if ( is_wp_error( $validate_args ) ) {
363 return $validate_args;
364 }
365
366 $source = $this->get_source( $args['source'][0] );
367
368 if ( ! $source ) {
369 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
370 }
371
372 $from_source = $this->get_source( $args['from_source'] );
373
374 if ( ! $from_source ) {
375 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
376 }
377
378 $args = $from_source->format_args_for_single_action( $args );
379
380 $template_id = $source->save_item( $args );
381
382 if ( is_wp_error( $template_id ) ) {
383 return $template_id;
384 }
385
386 return $source->get_item( $template_id );
387 }
388
389 private function is_action_to_same_source( $args ) {
390 return $args['source'] === $args['from_source'];
391 }
392
393 /**
394 * Update template.
395 *
396 * Update template on the database.
397 *
398 * @since 1.0.0
399 * @access public
400 *
401 * @param array $template_data New template data.
402 *
403 * @return \WP_Error|Source_Base Template sources instance if the templates
404 * was updated, `WP_Error` otherwise.
405 */
406 public function update_template( array $template_data ) {
407 $validate_args = $this->ensure_args( [ 'source', 'content', 'type' ], $template_data );
408
409 if ( is_wp_error( $validate_args ) ) {
410 return $validate_args;
411 }
412
413 $source = $this->get_source( $template_data['source'] );
414
415 if ( ! $source ) {
416 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
417 }
418
419 $template_data['content'] = json_decode( $template_data['content'], true );
420
421 $update = $source->update_item( $template_data );
422
423 if ( is_wp_error( $update ) ) {
424 return $update;
425 }
426
427 return $source->get_item( $template_data['id'] );
428 }
429
430 public function rename_template( array $template_data ) {
431 $validate_args = $this->ensure_args( [ 'source', 'title', 'id' ], $template_data );
432
433 if ( is_wp_error( $validate_args ) ) {
434 return $validate_args;
435 }
436
437 $source = $this->get_source( $template_data['source'] );
438
439 if ( ! $source ) {
440 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
441 }
442
443 $update = $source->update_item( $template_data );
444
445 if ( is_wp_error( $update ) ) {
446 return $update;
447 }
448
449 return $source->get_item( $template_data['id'] );
450 }
451
452 /**
453 * Update templates.
454 *
455 * Update template on the database.
456 *
457 * @since 1.0.0
458 * @access public
459 *
460 * @param array $args Template arguments.
461 *
462 * @return \WP_Error|true True if templates updated, `WP_Error` otherwise.
463 */
464 public function update_templates( array $args ) {
465 foreach ( $args['templates'] as $template_data ) {
466 $result = $this->update_template( $template_data );
467
468 if ( is_wp_error( $result ) ) {
469 return $result;
470 }
471 }
472
473 return true;
474 }
475
476 /**
477 * Get template data.
478 *
479 * Retrieve the template data.
480 *
481 * @since 1.5.0
482 * @access public
483 *
484 * @param array $args Template arguments.
485 *
486 * @return \WP_Error|bool|array ??
487 */
488 public function get_template_data( array $args ) {
489 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
490
491 if ( is_wp_error( $validate_args ) ) {
492 return $validate_args;
493 }
494
495 $source = $this->get_source( $args['source'] );
496
497 if ( ! $source ) {
498 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
499 }
500
501 if ( method_exists( $source, 'is_allowed_to_read_template' ) && ! $source->is_allowed_to_read_template( $args ) ) {
502 return new \WP_Error(
503 'template_error',
504 esc_html__( 'You do not have permission to access this template.', 'elementor' )
505 );
506 }
507
508 if ( isset( $args['edit_mode'] ) ) {
509 Plugin::$instance->editor->set_edit_mode( $args['edit_mode'] );
510 }
511
512 do_action( 'elementor/template-library/before_get_source_data', $args, $source );
513
514 $data = $source->get_data( $args );
515
516 do_action( 'elementor/template-library/after_get_source_data', $args, $source );
517
518 return $data;
519 }
520
521 /**
522 * Delete template.
523 *
524 * Delete template from the database.
525 *
526 * @since 1.0.0
527 * @access public
528 *
529 * @param array $args Template arguments.
530 *
531 * @return \WP_Post|\WP_Error|false|null Post data on success, false or null
532 * or 'WP_Error' on failure.
533 */
534 public function delete_template( array $args ) {
535 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
536
537 if ( is_wp_error( $validate_args ) ) {
538 return $validate_args;
539 }
540
541 $source = $this->get_source( $args['source'] );
542
543 if ( ! $source ) {
544 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
545 }
546
547 return $source->delete_template( $args['template_id'] );
548 }
549
550 /**
551 * Export template.
552 *
553 * Export template to a file after ensuring it is a valid Elementor template
554 * and checking user permissions for private posts.
555 *
556 * @since 1.0.0
557 * @access public
558 *
559 * @param array $args Template arguments.
560 *
561 * @return mixed Whether the export succeeded or failed.
562 */
563 public function export_template( array $args ) {
564 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
565
566 if ( is_wp_error( $validate_args ) ) {
567 return $validate_args;
568 }
569
570 $source = $this->get_source( $args['source'] );
571
572 if ( ! $source ) {
573 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
574 }
575
576 return $source->export_template( $args['template_id'] );
577 }
578
579 /**
580 * @since 2.3.0
581 * @access public
582 */
583 public function direct_import_template() {
584 /** @var Source_Local $source */
585 $source = $this->get_source( 'local' );
586 $file = Utils::get_super_global_value( $_FILES, 'file' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
587 return $source->import_template( $file['name'], $file['tmp_name'] );
588 }
589
590 /**
591 * Import template.
592 *
593 * Import template from a file.
594 *
595 * @since 1.0.0
596 * @access public
597 *
598 * @param array $data
599 *
600 * @return mixed Whether the export succeeded or failed.
601 */
602 public function import_template( array $data ) {
603 // If the template is a JSON file, allow uploading it.
604 add_filter( 'elementor/files/allow-file-type/json', [ $this, 'enable_json_template_upload' ] );
605 add_filter( 'elementor/files/allow_unfiltered_upload', [ $this, 'enable_json_template_upload' ] );
606
607 // Imported templates can be either JSON files, or Zip files containing multiple JSON files
608 $upload_result = Plugin::$instance->uploads_manager->handle_elementor_upload( $data, [ 'zip', 'json' ] );
609
610 remove_filter( 'elementor/files/allow-file-type/json', [ $this, 'enable_json_template_upload' ] );
611 remove_filter( 'elementor/files/allow_unfiltered_upload', [ $this, 'enable_json_template_upload' ] );
612
613 if ( is_wp_error( $upload_result ) ) {
614 return $upload_result;
615 }
616
617 $source = $this->get_source( $data['source'] ?? 'local' );
618
619 $import_mode = $data['import_mode'] ?? 'match_site';
620 $import_result = $source->import_template( $upload_result['name'], $upload_result['tmp_name'], $import_mode );
621
622 // Remove the temporary directory generated for the stream-uploaded file.
623 Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $upload_result['tmp_name'] ) );
624
625 return $import_result;
626 }
627
628 /**
629 * Enable JSON Template Upload
630 *
631 * Runs on the 'elementor/files/allow-file-type/json' Uploads Manager filter.
632 *
633 * @since 3.5.0
634 * @access public
635 *
636 * return bool
637 */
638 public function enable_json_template_upload() {
639 return true;
640 }
641
642 /**
643 * Mark template as favorite.
644 *
645 * Add the template to the user favorite templates.
646 *
647 * @since 1.9.0
648 * @access public
649 *
650 * @param array $args Template arguments.
651 *
652 * @return mixed Whether the template marked as favorite.
653 */
654 public function mark_template_as_favorite( $args ) {
655 $validate_args = $this->ensure_args( [ 'source', 'template_id', 'favorite' ], $args );
656
657 if ( is_wp_error( $validate_args ) ) {
658 return $validate_args;
659 }
660
661 $source = $this->get_source( $args['source'] );
662
663 return $source->mark_as_favorite( $args['template_id'], filter_var( $args['favorite'], FILTER_VALIDATE_BOOLEAN ) );
664 }
665
666 public function import_from_json( array $args ) {
667 $validate_args = $this->ensure_args( [ 'editor_post_id', 'elements' ], $args );
668
669 if ( is_wp_error( $validate_args ) ) {
670 return $validate_args;
671 }
672
673 $elements = json_decode( $args['elements'], true );
674
675 $document = Plugin::$instance->documents->get( $args['editor_post_id'] );
676 if ( ! $document ) {
677 return new \WP_Error( 'template_error', 'Document not found.' );
678 }
679
680 $import_data = $document->get_import_data( [ 'content' => $elements ] );
681
682 $content = $document::on_import_update_dynamic_content(
683 $import_data['content'],
684 [ 'post_ids' => [] ]
685 );
686
687 return Plugin::$instance->db->iterate_data( $content, function( $element_data ) {
688 unset( $element_data['htmlCache'] );
689 return $element_data;
690 } );
691 }
692
693 public function process_global_styles( array $args ) {
694 $validate_args = $this->ensure_args( [ 'content', 'import_mode' ], $args );
695
696 if ( is_wp_error( $validate_args ) ) {
697 return $validate_args;
698 }
699
700 $import_mode = Template_Library_Import_Export_Utils::sanitize_import_mode( $args['import_mode'] );
701
702 $content = $this->get_validated_json_arg( $args, 'content' );
703 if ( is_wp_error( $content ) ) {
704 return $content;
705 }
706
707 $global_classes = $this->get_validated_json_arg( $args, 'global_classes', true );
708 if ( is_wp_error( $global_classes ) ) {
709 return $global_classes;
710 }
711
712 $global_variables = $this->get_validated_json_arg( $args, 'global_variables', true );
713 if ( is_wp_error( $global_variables ) ) {
714 return $global_variables;
715 }
716
717 $data = [
718 'global_classes' => $global_classes,
719 'global_variables' => $global_variables,
720 ];
721
722 $result = apply_filters(
723 'elementor/template_library/import/process_content',
724 [ 'content' => $content ],
725 $import_mode,
726 $data,
727 null
728 );
729
730 $response = [
731 'content' => $result['content'],
732 ];
733
734 if ( ! empty( $result['updated_global_classes'] ) ) {
735 $response['updated_global_classes'] = $result['updated_global_classes'];
736 }
737
738 if ( ! empty( $result['updated_global_variables'] ) ) {
739 $response['updated_global_variables'] = $result['updated_global_variables'];
740 }
741
742 $classes_to_flatten = $result['classes_to_flatten'] ?? [];
743 $variables_to_flatten = $result['variables_to_flatten'] ?? [];
744
745 if ( ! empty( $classes_to_flatten ) ) {
746 $response['flattened_classes_count'] = count( $classes_to_flatten );
747 }
748
749 if ( ! empty( $variables_to_flatten ) ) {
750 $response['flattened_variables_count'] = count( $variables_to_flatten );
751 }
752
753 return $response;
754 }
755
756 private function get_validated_json_arg( array $args, string $key, bool $is_optional = false ) {
757 if ( ! array_key_exists( $key, $args ) || null === $args[ $key ] || '' === $args[ $key ] ) {
758 return $is_optional ? null : new \WP_Error( "invalid_$key", "Invalid $key." );
759 }
760
761 $value = $args[ $key ];
762 if ( is_string( $value ) ) {
763 $decoded = json_decode( $value, true );
764 if ( JSON_ERROR_NONE !== json_last_error() ) {
765 return new \WP_Error( "invalid_$key", "Invalid JSON $key." );
766 }
767 return $decoded;
768 }
769
770 if ( ! is_array( $value ) ) {
771 return new \WP_Error( "invalid_$key", "Invalid $key format." );
772 }
773
774 return $value;
775 }
776
777 public function get_item_children( array $args ) {
778 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
779
780 if ( is_wp_error( $validate_args ) ) {
781 return $validate_args;
782 }
783
784 $source = $this->get_source( $args['source'] );
785
786 if ( ! $source ) {
787 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
788 }
789
790 return $source->get_item_children( $args );
791 }
792
793 public function search_templates( array $args ) {
794 $validate_args = $this->ensure_args( [ 'source', 'search' ], $args );
795
796 if ( is_wp_error( $validate_args ) ) {
797 return $validate_args;
798 }
799
800 $source = $this->get_source( $args['source'] );
801
802 if ( ! $source ) {
803 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
804 }
805
806 return $source->search_templates( $args );
807 }
808
809 public function load_more_templates( array $args ) {
810 $validate_args = $this->ensure_args( [ 'source', 'offset' ], $args );
811
812 if ( is_wp_error( $validate_args ) ) {
813 return $validate_args;
814 }
815
816 $source = $this->get_source( $args['source'] );
817
818 if ( ! $source ) {
819 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
820 }
821
822 return $source->get_items( $args );
823 }
824
825 public function create_folder( array $args ) {
826 $validate_args = $this->ensure_args( [ 'source', 'title' ], $args );
827
828 if ( is_wp_error( $validate_args ) ) {
829 return $validate_args;
830 }
831
832 $source = $this->get_source( $args['source'] );
833
834 if ( ! $source ) {
835 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
836 }
837
838 return $source->save_folder( $args );
839 }
840
841 public function get_folders( array $args ) {
842 $validate_args = $this->ensure_args( [ 'source', 'offset' ], $args );
843
844 if ( is_wp_error( $validate_args ) ) {
845 return $validate_args;
846 }
847
848 $source = $this->get_source( $args['source'] );
849
850 if ( ! $source ) {
851 return new \WP_Error( 'template_error', 'Folder source not found.' );
852 }
853
854 $args['templateType'] = 'folder';
855
856 return $source->get_items( $args );
857 }
858
859 /**
860 * Register default template sources.
861 *
862 * Register the 'local' and 'remote' template sources that Elementor use by
863 * default.
864 *
865 * @since 1.0.0
866 * @access private
867 */
868 private function register_default_sources() {
869 $sources = [
870 'local',
871 'remote',
872 'cloud',
873 ];
874
875 foreach ( $sources as $source_filename ) {
876 $class_name = ucwords( $source_filename );
877 $class_name = str_replace( '-', '_', $class_name );
878
879 $this->register_source( __NAMESPACE__ . '\Source_' . $class_name );
880 }
881 }
882
883 /**
884 * Handle ajax request.
885 *
886 * Fire authenticated ajax actions for any given ajax request.
887 *
888 * @since 1.0.0
889 * @access private
890 *
891 * @param string $ajax_request Ajax request.
892 *
893 * @param array $data
894 *
895 * @return mixed
896 * @throws \Exception If current user has no permission or the post is not found.
897 */
898 private function handle_ajax_request( $ajax_request, array $data ) {
899 if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) {
900 throw new \Exception( 'Access denied.' );
901 }
902
903 if ( ! empty( $data['editor_post_id'] ) ) {
904 $editor_post_id = absint( $data['editor_post_id'] );
905
906 if ( ! get_post( $editor_post_id ) ) {
907 throw new \Exception( 'Post not found.' );
908 }
909
910 Plugin::$instance->db->switch_to_post( $editor_post_id );
911 }
912
913 $result = call_user_func( [ $this, $ajax_request ], $data );
914
915 if ( is_wp_error( $result ) ) {
916 throw new \Exception( esc_html( $result->get_error_message() ) );
917 }
918
919 return $result;
920 }
921
922 /**
923 * @throws \Exception If template import fails, file validation errors occur, or processing encounters issues.
924 */
925 public function save_template_screenshot( $data ): string {
926 $validate_args = $this->ensure_args( [ 'template_id', 'screenshot' ], $data );
927
928 if ( is_wp_error( $validate_args ) ) {
929 return $validate_args;
930 }
931
932 $raw_binary = base64_decode( substr( $data['screenshot'], strlen( 'data:image/png;base64,' ) ) );
933
934 return $this->get_source( 'cloud' )->save_item_preview( $data['template_id'], $raw_binary );
935 }
936
937 /**
938 * @throws \Exception If template processing fails or data validation errors occur.
939 */
940 public function template_screenshot_failed( $data ): string {
941 $validate_args = $this->ensure_args( [ 'template_id' ], $data );
942
943 if ( is_wp_error( $validate_args ) ) {
944 return $validate_args;
945 }
946
947 return $this->get_source( 'cloud' )->mark_preview_as_failed( $data['template_id'], $data['error'] );
948 }
949
950 public function bulk_delete_templates( $data ) {
951 $validate_args = $this->ensure_args( [ 'template_ids', 'source' ], $data );
952
953 if ( is_wp_error( $validate_args ) ) {
954 return $validate_args;
955 }
956
957 $source = $this->get_source( $data['source'] );
958
959 if ( ! $source ) {
960 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
961 }
962
963 if ( empty( $data['template_ids'] ) || ! is_array( $data['template_ids'] ) ) {
964 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_IDS_MISSING );
965 }
966
967 return $source->bulk_delete_items( $data['template_ids'] );
968 }
969
970 public function bulk_undo_delete_items( $data ) {
971 $validate_args = $this->ensure_args( [ 'template_ids', 'source' ], $data );
972
973 if ( is_wp_error( $validate_args ) ) {
974 return $validate_args;
975 }
976
977 $source = $this->get_source( $data['source'] );
978
979 if ( ! $source ) {
980 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
981 }
982
983 if ( empty( $data['template_ids'] ) || ! is_array( $data['template_ids'] ) ) {
984 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_IDS_MISSING );
985 }
986
987 return $source->bulk_undo_delete_items( $data['template_ids'] );
988 }
989
990 /**
991 * Init ajax calls.
992 *
993 * Initialize template library ajax calls for allowed ajax requests.
994 *
995 * @since 2.3.0
996 * @access public
997 *
998 * @param Ajax $ajax
999 */
1000 public function register_ajax_actions( Ajax $ajax ) {
1001 $library_ajax_requests = [
1002 'get_library_data',
1003 'get_template_data',
1004 'save_template',
1005 'update_templates',
1006 'delete_template',
1007 'import_template',
1008 'mark_template_as_favorite',
1009 'import_from_json',
1010 'get_item_children',
1011 'search_templates',
1012 'rename_template',
1013 'load_more_templates',
1014 'create_folder',
1015 'get_folders',
1016 'save_template_screenshot',
1017 'move_template',
1018 'copy_template',
1019 'bulk_move_templates',
1020 'bulk_delete_templates',
1021 'bulk_copy_templates',
1022 'bulk_undo_delete_items',
1023 'get_templates_quota',
1024 'template_screenshot_failed',
1025 'process_global_styles',
1026 ];
1027
1028 foreach ( $library_ajax_requests as $ajax_request ) {
1029 $ajax->register_ajax_action( $ajax_request, function( $data ) use ( $ajax_request ) {
1030 return $this->handle_ajax_request( $ajax_request, $data );
1031 } );
1032 }
1033 }
1034
1035 /**
1036 * @since 2.3.0
1037 * @access public
1038 */
1039 public function handle_direct_actions() {
1040 if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) {
1041 return;
1042 }
1043
1044 /** @var Ajax $ajax */
1045 $ajax = Plugin::$instance->common->get_component( 'ajax' );
1046
1047 if ( ! $ajax->verify_request_nonce() ) {
1048 $this->handle_direct_action_error( 'Access Denied' );
1049 }
1050
1051 $action = Utils::get_super_global_value( $_REQUEST, 'library_action' ); // phpcs:ignore -- Nonce already verified.
1052
1053 $whitelist_methods = [
1054 'export_template',
1055 'direct_import_template',
1056 ];
1057
1058 if ( 'direct_import_template' === $action && ! User::is_current_user_can_upload_json() ) {
1059 return;
1060 }
1061
1062 if ( in_array( $action, $whitelist_methods, true ) ) {
1063 $result = $this->$action( $_REQUEST ); // phpcs:ignore -- Nonce already verified.
1064 } else {
1065 $result = new \WP_Error( 'method_not_exists', 'Method Not exists' );
1066 }
1067
1068 if ( is_wp_error( $result ) ) {
1069 /** @var \WP_Error $result */
1070 $this->handle_direct_action_error( $result->get_error_message() . '.' );
1071 }
1072
1073 $callback = "on_{$action}_success";
1074
1075 if ( method_exists( $this, $callback ) ) {
1076 $this->$callback( $result );
1077 }
1078
1079 die;
1080 }
1081
1082 /**
1083 * On successful template import.
1084 *
1085 * Redirect the user to the template library after template import was
1086 * successful finished.
1087 *
1088 * @since 2.3.0
1089 * @access private
1090 */
1091 private function on_direct_import_template_success() {
1092 wp_safe_redirect( admin_url( Source_Local::ADMIN_MENU_SLUG ) );
1093 }
1094
1095 /**
1096 * @since 2.3.0
1097 * @access private
1098 */
1099 private function handle_direct_action_error( $message ) {
1100 _default_wp_die_handler( $message, 'Elementor Library' );
1101 }
1102
1103 /**
1104 * Ensure arguments exist.
1105 *
1106 * Checks whether the required arguments exist in the specified arguments.
1107 *
1108 * @since 1.0.0
1109 * @access private
1110 *
1111 * @param array $required_args Required arguments to check whether they
1112 * exist.
1113 * @param array $specified_args The list of all the specified arguments to
1114 * check against.
1115 *
1116 * @return \WP_Error|true True on success, 'WP_Error' otherwise.
1117 */
1118 private function ensure_args( array $required_args, array $specified_args ) {
1119 $not_specified_args = array_diff( $required_args, array_keys( $specified_args ) );
1120
1121 if ( $not_specified_args ) {
1122 return new \WP_Error( 'arguments_not_specified', sprintf( 'The required argument(s) "%s" not specified.', implode( ', ', $not_specified_args ) ) );
1123 }
1124
1125 return true;
1126 }
1127
1128 public function bulk_move_templates( array $args ) {
1129 $validate_args = $this->ensure_args( [ 'source', 'from_source', 'from_template_id' ], $args );
1130
1131 if ( is_wp_error( $validate_args ) ) {
1132 return $validate_args;
1133 }
1134
1135 $args['source'] = $args['source'][0];
1136
1137 return $this->bulk_move_template_items( $args );
1138 }
1139
1140 private function bulk_move_template_items( array $args ) {
1141 $source = $this->get_source( $args['source'] );
1142
1143 if ( ! $source ) {
1144 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1145 }
1146
1147 if ( $this->is_action_to_same_source( $args ) ) {
1148 return $source->move_bulk_templates_to_folder( $args );
1149 }
1150
1151 $from_source = $this->get_source( $args['from_source'] );
1152
1153 if ( ! $from_source ) {
1154 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1155 }
1156
1157 $bulk_args = $from_source->format_args_for_bulk_action( $args );
1158
1159 if ( $source->supports_quota() && ! $this->is_action_to_same_source( $args ) ) {
1160 $quota_validation = $source->validate_quota( $bulk_args );
1161
1162 if ( is_wp_error( $quota_validation ) ) {
1163 return $quota_validation;
1164 }
1165
1166 if ( false === $quota_validation ) {
1167 return new \WP_Error( 'quota_error', 'The moving failed because it will pass the maximum templates you can save.' );
1168 }
1169 }
1170
1171 $bulk_save = $source->save_bulk_items( $bulk_args );
1172
1173 if ( ! empty( $bulk_save ) ) {
1174 $this->bulk_delete_templates( [
1175 'template_ids' => $args['from_template_id'],
1176 'source' => $args['from_source'],
1177 ] );
1178 }
1179
1180 return $bulk_save;
1181 }
1182
1183 public function bulk_copy_templates( array $args ) {
1184 $validate_args = $this->ensure_args( [ 'source', 'from_source', 'from_template_id' ], $args );
1185
1186 if ( is_wp_error( $validate_args ) ) {
1187 return $validate_args;
1188 }
1189
1190 $args['source'] = $args['source'][0];
1191
1192 return $this->bulk_copy_template_items( $args );
1193 }
1194
1195 private function bulk_copy_template_items( array $args ) {
1196 $source = $this->get_source( $args['source'] );
1197
1198 if ( ! $source ) {
1199 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1200 }
1201
1202 $from_source = $this->get_source( $args['from_source'] );
1203
1204 if ( ! $from_source ) {
1205 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1206 }
1207
1208 $bulk_args = $from_source->format_args_for_bulk_action( $args );
1209
1210 if ( $source->supports_quota() && ! $this->is_action_to_same_source( $args ) ) {
1211 $quota_validation = $source->validate_quota( $bulk_args );
1212
1213 if ( is_wp_error( $quota_validation ) ) {
1214 return $quota_validation;
1215 }
1216
1217 if ( false === $quota_validation ) {
1218 return new \WP_Error( 'quota_error', 'The copying failed because it will pass the maximum templates you can save.' );
1219 }
1220 }
1221
1222 return $source->save_bulk_items( $bulk_args );
1223 }
1224
1225 public function get_templates_quota( array $args ) {
1226 $validate_args = $this->ensure_args( [ 'source' ], $args );
1227
1228 if ( is_wp_error( $validate_args ) ) {
1229 return $validate_args;
1230 }
1231
1232 $source = $this->get_source( $args['source'] );
1233
1234 if ( ! $source ) {
1235 return new \WP_Error( 'template_error', 'Source not found.' );
1236 }
1237
1238 return $source->get_quota();
1239 }
1240 }
1241