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

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