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

1,233 lines 31.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\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 return $import_data['content'];
683 }
684
685 public function process_global_styles( array $args ) {
686 $validate_args = $this->ensure_args( [ 'content', 'import_mode' ], $args );
687
688 if ( is_wp_error( $validate_args ) ) {
689 return $validate_args;
690 }
691
692 $import_mode = Template_Library_Import_Export_Utils::sanitize_import_mode( $args['import_mode'] );
693
694 $content = $this->get_validated_json_arg( $args, 'content' );
695 if ( is_wp_error( $content ) ) {
696 return $content;
697 }
698
699 $global_classes = $this->get_validated_json_arg( $args, 'global_classes', true );
700 if ( is_wp_error( $global_classes ) ) {
701 return $global_classes;
702 }
703
704 $global_variables = $this->get_validated_json_arg( $args, 'global_variables', true );
705 if ( is_wp_error( $global_variables ) ) {
706 return $global_variables;
707 }
708
709 $data = [
710 'global_classes' => $global_classes,
711 'global_variables' => $global_variables,
712 ];
713
714 $result = apply_filters(
715 'elementor/template_library/import/process_content',
716 [ 'content' => $content ],
717 $import_mode,
718 $data,
719 null
720 );
721
722 $response = [
723 'content' => $result['content'],
724 ];
725
726 if ( ! empty( $result['updated_global_classes'] ) ) {
727 $response['updated_global_classes'] = $result['updated_global_classes'];
728 }
729
730 if ( ! empty( $result['updated_global_variables'] ) ) {
731 $response['updated_global_variables'] = $result['updated_global_variables'];
732 }
733
734 $classes_to_flatten = $result['classes_to_flatten'] ?? [];
735 $variables_to_flatten = $result['variables_to_flatten'] ?? [];
736
737 if ( ! empty( $classes_to_flatten ) ) {
738 $response['flattened_classes_count'] = count( $classes_to_flatten );
739 }
740
741 if ( ! empty( $variables_to_flatten ) ) {
742 $response['flattened_variables_count'] = count( $variables_to_flatten );
743 }
744
745 return $response;
746 }
747
748 private function get_validated_json_arg( array $args, string $key, bool $is_optional = false ) {
749 if ( ! array_key_exists( $key, $args ) || null === $args[ $key ] || '' === $args[ $key ] ) {
750 return $is_optional ? null : new \WP_Error( "invalid_$key", "Invalid $key." );
751 }
752
753 $value = $args[ $key ];
754 if ( is_string( $value ) ) {
755 $decoded = json_decode( $value, true );
756 if ( JSON_ERROR_NONE !== json_last_error() ) {
757 return new \WP_Error( "invalid_$key", "Invalid JSON $key." );
758 }
759 return $decoded;
760 }
761
762 if ( ! is_array( $value ) ) {
763 return new \WP_Error( "invalid_$key", "Invalid $key format." );
764 }
765
766 return $value;
767 }
768
769 public function get_item_children( array $args ) {
770 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
771
772 if ( is_wp_error( $validate_args ) ) {
773 return $validate_args;
774 }
775
776 $source = $this->get_source( $args['source'] );
777
778 if ( ! $source ) {
779 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
780 }
781
782 return $source->get_item_children( $args );
783 }
784
785 public function search_templates( array $args ) {
786 $validate_args = $this->ensure_args( [ 'source', 'search' ], $args );
787
788 if ( is_wp_error( $validate_args ) ) {
789 return $validate_args;
790 }
791
792 $source = $this->get_source( $args['source'] );
793
794 if ( ! $source ) {
795 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
796 }
797
798 return $source->search_templates( $args );
799 }
800
801 public function load_more_templates( array $args ) {
802 $validate_args = $this->ensure_args( [ 'source', 'offset' ], $args );
803
804 if ( is_wp_error( $validate_args ) ) {
805 return $validate_args;
806 }
807
808 $source = $this->get_source( $args['source'] );
809
810 if ( ! $source ) {
811 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
812 }
813
814 return $source->get_items( $args );
815 }
816
817 public function create_folder( array $args ) {
818 $validate_args = $this->ensure_args( [ 'source', 'title' ], $args );
819
820 if ( is_wp_error( $validate_args ) ) {
821 return $validate_args;
822 }
823
824 $source = $this->get_source( $args['source'] );
825
826 if ( ! $source ) {
827 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
828 }
829
830 return $source->save_folder( $args );
831 }
832
833 public function get_folders( array $args ) {
834 $validate_args = $this->ensure_args( [ 'source', 'offset' ], $args );
835
836 if ( is_wp_error( $validate_args ) ) {
837 return $validate_args;
838 }
839
840 $source = $this->get_source( $args['source'] );
841
842 if ( ! $source ) {
843 return new \WP_Error( 'template_error', 'Folder source not found.' );
844 }
845
846 $args['templateType'] = 'folder';
847
848 return $source->get_items( $args );
849 }
850
851 /**
852 * Register default template sources.
853 *
854 * Register the 'local' and 'remote' template sources that Elementor use by
855 * default.
856 *
857 * @since 1.0.0
858 * @access private
859 */
860 private function register_default_sources() {
861 $sources = [
862 'local',
863 'remote',
864 'cloud',
865 ];
866
867 foreach ( $sources as $source_filename ) {
868 $class_name = ucwords( $source_filename );
869 $class_name = str_replace( '-', '_', $class_name );
870
871 $this->register_source( __NAMESPACE__ . '\Source_' . $class_name );
872 }
873 }
874
875 /**
876 * Handle ajax request.
877 *
878 * Fire authenticated ajax actions for any given ajax request.
879 *
880 * @since 1.0.0
881 * @access private
882 *
883 * @param string $ajax_request Ajax request.
884 *
885 * @param array $data
886 *
887 * @return mixed
888 * @throws \Exception If current user has no permission or the post is not found.
889 */
890 private function handle_ajax_request( $ajax_request, array $data ) {
891 if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) {
892 throw new \Exception( 'Access denied.' );
893 }
894
895 if ( ! empty( $data['editor_post_id'] ) ) {
896 $editor_post_id = absint( $data['editor_post_id'] );
897
898 if ( ! get_post( $editor_post_id ) ) {
899 throw new \Exception( 'Post not found.' );
900 }
901
902 Plugin::$instance->db->switch_to_post( $editor_post_id );
903 }
904
905 $result = call_user_func( [ $this, $ajax_request ], $data );
906
907 if ( is_wp_error( $result ) ) {
908 throw new \Exception( esc_html( $result->get_error_message() ) );
909 }
910
911 return $result;
912 }
913
914 /**
915 * @throws \Exception If template import fails, file validation errors occur, or processing encounters issues.
916 */
917 public function save_template_screenshot( $data ): string {
918 $validate_args = $this->ensure_args( [ 'template_id', 'screenshot' ], $data );
919
920 if ( is_wp_error( $validate_args ) ) {
921 return $validate_args;
922 }
923
924 $raw_binary = base64_decode( substr( $data['screenshot'], strlen( 'data:image/png;base64,' ) ) );
925
926 return $this->get_source( 'cloud' )->save_item_preview( $data['template_id'], $raw_binary );
927 }
928
929 /**
930 * @throws \Exception If template processing fails or data validation errors occur.
931 */
932 public function template_screenshot_failed( $data ): string {
933 $validate_args = $this->ensure_args( [ 'template_id' ], $data );
934
935 if ( is_wp_error( $validate_args ) ) {
936 return $validate_args;
937 }
938
939 return $this->get_source( 'cloud' )->mark_preview_as_failed( $data['template_id'], $data['error'] );
940 }
941
942 public function bulk_delete_templates( $data ) {
943 $validate_args = $this->ensure_args( [ 'template_ids', 'source' ], $data );
944
945 if ( is_wp_error( $validate_args ) ) {
946 return $validate_args;
947 }
948
949 $source = $this->get_source( $data['source'] );
950
951 if ( ! $source ) {
952 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
953 }
954
955 if ( empty( $data['template_ids'] ) || ! is_array( $data['template_ids'] ) ) {
956 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_IDS_MISSING );
957 }
958
959 return $source->bulk_delete_items( $data['template_ids'] );
960 }
961
962 public function bulk_undo_delete_items( $data ) {
963 $validate_args = $this->ensure_args( [ 'template_ids', 'source' ], $data );
964
965 if ( is_wp_error( $validate_args ) ) {
966 return $validate_args;
967 }
968
969 $source = $this->get_source( $data['source'] );
970
971 if ( ! $source ) {
972 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
973 }
974
975 if ( empty( $data['template_ids'] ) || ! is_array( $data['template_ids'] ) ) {
976 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_IDS_MISSING );
977 }
978
979 return $source->bulk_undo_delete_items( $data['template_ids'] );
980 }
981
982 /**
983 * Init ajax calls.
984 *
985 * Initialize template library ajax calls for allowed ajax requests.
986 *
987 * @since 2.3.0
988 * @access public
989 *
990 * @param Ajax $ajax
991 */
992 public function register_ajax_actions( Ajax $ajax ) {
993 $library_ajax_requests = [
994 'get_library_data',
995 'get_template_data',
996 'save_template',
997 'update_templates',
998 'delete_template',
999 'import_template',
1000 'mark_template_as_favorite',
1001 'import_from_json',
1002 'get_item_children',
1003 'search_templates',
1004 'rename_template',
1005 'load_more_templates',
1006 'create_folder',
1007 'get_folders',
1008 'save_template_screenshot',
1009 'move_template',
1010 'copy_template',
1011 'bulk_move_templates',
1012 'bulk_delete_templates',
1013 'bulk_copy_templates',
1014 'bulk_undo_delete_items',
1015 'get_templates_quota',
1016 'template_screenshot_failed',
1017 'process_global_styles',
1018 ];
1019
1020 foreach ( $library_ajax_requests as $ajax_request ) {
1021 $ajax->register_ajax_action( $ajax_request, function( $data ) use ( $ajax_request ) {
1022 return $this->handle_ajax_request( $ajax_request, $data );
1023 } );
1024 }
1025 }
1026
1027 /**
1028 * @since 2.3.0
1029 * @access public
1030 */
1031 public function handle_direct_actions() {
1032 if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) {
1033 return;
1034 }
1035
1036 /** @var Ajax $ajax */
1037 $ajax = Plugin::$instance->common->get_component( 'ajax' );
1038
1039 if ( ! $ajax->verify_request_nonce() ) {
1040 $this->handle_direct_action_error( 'Access Denied' );
1041 }
1042
1043 $action = Utils::get_super_global_value( $_REQUEST, 'library_action' ); // phpcs:ignore -- Nonce already verified.
1044
1045 $whitelist_methods = [
1046 'export_template',
1047 'direct_import_template',
1048 ];
1049
1050 if ( 'direct_import_template' === $action && ! User::is_current_user_can_upload_json() ) {
1051 return;
1052 }
1053
1054 if ( in_array( $action, $whitelist_methods, true ) ) {
1055 $result = $this->$action( $_REQUEST ); // phpcs:ignore -- Nonce already verified.
1056 } else {
1057 $result = new \WP_Error( 'method_not_exists', 'Method Not exists' );
1058 }
1059
1060 if ( is_wp_error( $result ) ) {
1061 /** @var \WP_Error $result */
1062 $this->handle_direct_action_error( $result->get_error_message() . '.' );
1063 }
1064
1065 $callback = "on_{$action}_success";
1066
1067 if ( method_exists( $this, $callback ) ) {
1068 $this->$callback( $result );
1069 }
1070
1071 die;
1072 }
1073
1074 /**
1075 * On successful template import.
1076 *
1077 * Redirect the user to the template library after template import was
1078 * successful finished.
1079 *
1080 * @since 2.3.0
1081 * @access private
1082 */
1083 private function on_direct_import_template_success() {
1084 wp_safe_redirect( admin_url( Source_Local::ADMIN_MENU_SLUG ) );
1085 }
1086
1087 /**
1088 * @since 2.3.0
1089 * @access private
1090 */
1091 private function handle_direct_action_error( $message ) {
1092 _default_wp_die_handler( $message, 'Elementor Library' );
1093 }
1094
1095 /**
1096 * Ensure arguments exist.
1097 *
1098 * Checks whether the required arguments exist in the specified arguments.
1099 *
1100 * @since 1.0.0
1101 * @access private
1102 *
1103 * @param array $required_args Required arguments to check whether they
1104 * exist.
1105 * @param array $specified_args The list of all the specified arguments to
1106 * check against.
1107 *
1108 * @return \WP_Error|true True on success, 'WP_Error' otherwise.
1109 */
1110 private function ensure_args( array $required_args, array $specified_args ) {
1111 $not_specified_args = array_diff( $required_args, array_keys( $specified_args ) );
1112
1113 if ( $not_specified_args ) {
1114 return new \WP_Error( 'arguments_not_specified', sprintf( 'The required argument(s) "%s" not specified.', implode( ', ', $not_specified_args ) ) );
1115 }
1116
1117 return true;
1118 }
1119
1120 public function bulk_move_templates( array $args ) {
1121 $validate_args = $this->ensure_args( [ 'source', 'from_source', 'from_template_id' ], $args );
1122
1123 if ( is_wp_error( $validate_args ) ) {
1124 return $validate_args;
1125 }
1126
1127 $args['source'] = $args['source'][0];
1128
1129 return $this->bulk_move_template_items( $args );
1130 }
1131
1132 private function bulk_move_template_items( array $args ) {
1133 $source = $this->get_source( $args['source'] );
1134
1135 if ( ! $source ) {
1136 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1137 }
1138
1139 if ( $this->is_action_to_same_source( $args ) ) {
1140 return $source->move_bulk_templates_to_folder( $args );
1141 }
1142
1143 $from_source = $this->get_source( $args['from_source'] );
1144
1145 if ( ! $from_source ) {
1146 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1147 }
1148
1149 $bulk_args = $from_source->format_args_for_bulk_action( $args );
1150
1151 if ( $source->supports_quota() && ! $this->is_action_to_same_source( $args ) ) {
1152 $quota_validation = $source->validate_quota( $bulk_args );
1153
1154 if ( is_wp_error( $quota_validation ) ) {
1155 return $quota_validation;
1156 }
1157
1158 if ( false === $quota_validation ) {
1159 return new \WP_Error( 'quota_error', 'The moving failed because it will pass the maximum templates you can save.' );
1160 }
1161 }
1162
1163 $bulk_save = $source->save_bulk_items( $bulk_args );
1164
1165 if ( ! empty( $bulk_save ) ) {
1166 $this->bulk_delete_templates( [
1167 'template_ids' => $args['from_template_id'],
1168 'source' => $args['from_source'],
1169 ] );
1170 }
1171
1172 return $bulk_save;
1173 }
1174
1175 public function bulk_copy_templates( array $args ) {
1176 $validate_args = $this->ensure_args( [ 'source', 'from_source', 'from_template_id' ], $args );
1177
1178 if ( is_wp_error( $validate_args ) ) {
1179 return $validate_args;
1180 }
1181
1182 $args['source'] = $args['source'][0];
1183
1184 return $this->bulk_copy_template_items( $args );
1185 }
1186
1187 private function bulk_copy_template_items( array $args ) {
1188 $source = $this->get_source( $args['source'] );
1189
1190 if ( ! $source ) {
1191 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1192 }
1193
1194 $from_source = $this->get_source( $args['from_source'] );
1195
1196 if ( ! $from_source ) {
1197 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1198 }
1199
1200 $bulk_args = $from_source->format_args_for_bulk_action( $args );
1201
1202 if ( $source->supports_quota() && ! $this->is_action_to_same_source( $args ) ) {
1203 $quota_validation = $source->validate_quota( $bulk_args );
1204
1205 if ( is_wp_error( $quota_validation ) ) {
1206 return $quota_validation;
1207 }
1208
1209 if ( false === $quota_validation ) {
1210 return new \WP_Error( 'quota_error', 'The copying failed because it will pass the maximum templates you can save.' );
1211 }
1212 }
1213
1214 return $source->save_bulk_items( $bulk_args );
1215 }
1216
1217 public function get_templates_quota( array $args ) {
1218 $validate_args = $this->ensure_args( [ 'source' ], $args );
1219
1220 if ( is_wp_error( $validate_args ) ) {
1221 return $validate_args;
1222 }
1223
1224 $source = $this->get_source( $args['source'] );
1225
1226 if ( ! $source ) {
1227 return new \WP_Error( 'template_error', 'Source not found.' );
1228 }
1229
1230 return $source->get_quota();
1231 }
1232 }
1233