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

1,146 lines 29.1 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 return $upload_result;
614 }
615
616 $source = $this->get_source( $data['source'] ?? 'local' );
617
618 $import_result = $source->import_template( $upload_result['name'], $upload_result['tmp_name'] );
619
620 // Remove the temporary directory generated for the stream-uploaded file.
621 Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $upload_result['tmp_name'] ) );
622
623 return $import_result;
624 }
625
626 /**
627 * Enable JSON Template Upload
628 *
629 * Runs on the 'elementor/files/allow-file-type/json' Uploads Manager filter.
630 *
631 * @since 3.5.0
632 * @access public
633 *
634 * return bool
635 */
636 public function enable_json_template_upload() {
637 return true;
638 }
639
640 /**
641 * Mark template as favorite.
642 *
643 * Add the template to the user favorite templates.
644 *
645 * @since 1.9.0
646 * @access public
647 *
648 * @param array $args Template arguments.
649 *
650 * @return mixed Whether the template marked as favorite.
651 */
652 public function mark_template_as_favorite( $args ) {
653 $validate_args = $this->ensure_args( [ 'source', 'template_id', 'favorite' ], $args );
654
655 if ( is_wp_error( $validate_args ) ) {
656 return $validate_args;
657 }
658
659 $source = $this->get_source( $args['source'] );
660
661 return $source->mark_as_favorite( $args['template_id'], filter_var( $args['favorite'], FILTER_VALIDATE_BOOLEAN ) );
662 }
663
664 public function import_from_json( array $args ) {
665 $validate_args = $this->ensure_args( [ 'editor_post_id', 'elements' ], $args );
666
667 if ( is_wp_error( $validate_args ) ) {
668 return $validate_args;
669 }
670
671 $elements = json_decode( $args['elements'], true );
672
673 $document = Plugin::$instance->documents->get( $args['editor_post_id'] );
674 if ( ! $document ) {
675 return new \WP_Error( 'template_error', 'Document not found.' );
676 }
677
678 $import_data = $document->get_import_data( [ 'content' => $elements ] );
679
680 return $import_data['content'];
681 }
682
683 public function get_item_children( array $args ) {
684 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args );
685
686 if ( is_wp_error( $validate_args ) ) {
687 return $validate_args;
688 }
689
690 $source = $this->get_source( $args['source'] );
691
692 if ( ! $source ) {
693 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
694 }
695
696 return $source->get_item_children( $args );
697 }
698
699 public function search_templates( array $args ) {
700 $validate_args = $this->ensure_args( [ 'source', 'search' ], $args );
701
702 if ( is_wp_error( $validate_args ) ) {
703 return $validate_args;
704 }
705
706 $source = $this->get_source( $args['source'] );
707
708 if ( ! $source ) {
709 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
710 }
711
712 return $source->search_templates( $args );
713 }
714
715 public function load_more_templates( array $args ) {
716 $validate_args = $this->ensure_args( [ 'source', 'offset' ], $args );
717
718 if ( is_wp_error( $validate_args ) ) {
719 return $validate_args;
720 }
721
722 $source = $this->get_source( $args['source'] );
723
724 if ( ! $source ) {
725 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
726 }
727
728 return $source->get_items( $args );
729 }
730
731 public function create_folder( array $args ) {
732 $validate_args = $this->ensure_args( [ 'source', 'title' ], $args );
733
734 if ( is_wp_error( $validate_args ) ) {
735 return $validate_args;
736 }
737
738 $source = $this->get_source( $args['source'] );
739
740 if ( ! $source ) {
741 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
742 }
743
744 return $source->save_folder( $args );
745 }
746
747 public function get_folders( array $args ) {
748 $validate_args = $this->ensure_args( [ 'source', 'offset' ], $args );
749
750 if ( is_wp_error( $validate_args ) ) {
751 return $validate_args;
752 }
753
754 $source = $this->get_source( $args['source'] );
755
756 if ( ! $source ) {
757 return new \WP_Error( 'template_error', 'Folder source not found.' );
758 }
759
760 $args['templateType'] = 'folder';
761
762 return $source->get_items( $args );
763 }
764
765 /**
766 * Register default template sources.
767 *
768 * Register the 'local' and 'remote' template sources that Elementor use by
769 * default.
770 *
771 * @since 1.0.0
772 * @access private
773 */
774 private function register_default_sources() {
775 $sources = [
776 'local',
777 'remote',
778 'cloud',
779 ];
780
781 foreach ( $sources as $source_filename ) {
782 $class_name = ucwords( $source_filename );
783 $class_name = str_replace( '-', '_', $class_name );
784
785 $this->register_source( __NAMESPACE__ . '\Source_' . $class_name );
786 }
787 }
788
789 /**
790 * Handle ajax request.
791 *
792 * Fire authenticated ajax actions for any given ajax request.
793 *
794 * @since 1.0.0
795 * @access private
796 *
797 * @param string $ajax_request Ajax request.
798 *
799 * @param array $data
800 *
801 * @return mixed
802 * @throws \Exception If current user has no permission or the post is not found.
803 */
804 private function handle_ajax_request( $ajax_request, array $data ) {
805 if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) {
806 throw new \Exception( 'Access denied.' );
807 }
808
809 if ( ! empty( $data['editor_post_id'] ) ) {
810 $editor_post_id = absint( $data['editor_post_id'] );
811
812 if ( ! get_post( $editor_post_id ) ) {
813 throw new \Exception( 'Post not found.' );
814 }
815
816 Plugin::$instance->db->switch_to_post( $editor_post_id );
817 }
818
819 $result = call_user_func( [ $this, $ajax_request ], $data );
820
821 if ( is_wp_error( $result ) ) {
822 throw new \Exception( esc_html( $result->get_error_message() ) );
823 }
824
825 return $result;
826 }
827
828 /**
829 * @throws \Exception If template import fails, file validation errors occur, or processing encounters issues.
830 */
831 public function save_template_screenshot( $data ): string {
832 $validate_args = $this->ensure_args( [ 'template_id', 'screenshot' ], $data );
833
834 if ( is_wp_error( $validate_args ) ) {
835 return $validate_args;
836 }
837
838 $raw_binary = base64_decode( substr( $data['screenshot'], strlen( 'data:image/png;base64,' ) ) );
839
840 return $this->get_source( 'cloud' )->save_item_preview( $data['template_id'], $raw_binary );
841 }
842
843 /**
844 * @throws \Exception If template processing fails or data validation errors occur.
845 */
846 public function template_screenshot_failed( $data ): string {
847 $validate_args = $this->ensure_args( [ 'template_id' ], $data );
848
849 if ( is_wp_error( $validate_args ) ) {
850 return $validate_args;
851 }
852
853 return $this->get_source( 'cloud' )->mark_preview_as_failed( $data['template_id'], $data['error'] );
854 }
855
856 public function bulk_delete_templates( $data ) {
857 $validate_args = $this->ensure_args( [ 'template_ids', 'source' ], $data );
858
859 if ( is_wp_error( $validate_args ) ) {
860 return $validate_args;
861 }
862
863 $source = $this->get_source( $data['source'] );
864
865 if ( ! $source ) {
866 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
867 }
868
869 if ( empty( $data['template_ids'] ) || ! is_array( $data['template_ids'] ) ) {
870 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_IDS_MISSING );
871 }
872
873 return $source->bulk_delete_items( $data['template_ids'] );
874 }
875
876 public function bulk_undo_delete_items( $data ) {
877 $validate_args = $this->ensure_args( [ 'template_ids', 'source' ], $data );
878
879 if ( is_wp_error( $validate_args ) ) {
880 return $validate_args;
881 }
882
883 $source = $this->get_source( $data['source'] );
884
885 if ( ! $source ) {
886 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
887 }
888
889 if ( empty( $data['template_ids'] ) || ! is_array( $data['template_ids'] ) ) {
890 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_IDS_MISSING );
891 }
892
893 return $source->bulk_undo_delete_items( $data['template_ids'] );
894 }
895
896 /**
897 * Init ajax calls.
898 *
899 * Initialize template library ajax calls for allowed ajax requests.
900 *
901 * @since 2.3.0
902 * @access public
903 *
904 * @param Ajax $ajax
905 */
906 public function register_ajax_actions( Ajax $ajax ) {
907 $library_ajax_requests = [
908 'get_library_data',
909 'get_template_data',
910 'save_template',
911 'update_templates',
912 'delete_template',
913 'import_template',
914 'mark_template_as_favorite',
915 'import_from_json',
916 'get_item_children',
917 'search_templates',
918 'rename_template',
919 'load_more_templates',
920 'create_folder',
921 'get_folders',
922 'save_template_screenshot',
923 'move_template',
924 'copy_template',
925 'bulk_move_templates',
926 'bulk_delete_templates',
927 'bulk_copy_templates',
928 'bulk_undo_delete_items',
929 'get_templates_quota',
930 'template_screenshot_failed',
931 ];
932
933 foreach ( $library_ajax_requests as $ajax_request ) {
934 $ajax->register_ajax_action( $ajax_request, function( $data ) use ( $ajax_request ) {
935 return $this->handle_ajax_request( $ajax_request, $data );
936 } );
937 }
938 }
939
940 /**
941 * @since 2.3.0
942 * @access public
943 */
944 public function handle_direct_actions() {
945 if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) {
946 return;
947 }
948
949 /** @var Ajax $ajax */
950 $ajax = Plugin::$instance->common->get_component( 'ajax' );
951
952 if ( ! $ajax->verify_request_nonce() ) {
953 $this->handle_direct_action_error( 'Access Denied' );
954 }
955
956 $action = Utils::get_super_global_value( $_REQUEST, 'library_action' ); // phpcs:ignore -- Nonce already verified.
957
958 $whitelist_methods = [
959 'export_template',
960 'direct_import_template',
961 ];
962
963 if ( 'direct_import_template' === $action && ! User::is_current_user_can_upload_json() ) {
964 return;
965 }
966
967 if ( in_array( $action, $whitelist_methods, true ) ) {
968 $result = $this->$action( $_REQUEST ); // phpcs:ignore -- Nonce already verified.
969 } else {
970 $result = new \WP_Error( 'method_not_exists', 'Method Not exists' );
971 }
972
973 if ( is_wp_error( $result ) ) {
974 /** @var \WP_Error $result */
975 $this->handle_direct_action_error( $result->get_error_message() . '.' );
976 }
977
978 $callback = "on_{$action}_success";
979
980 if ( method_exists( $this, $callback ) ) {
981 $this->$callback( $result );
982 }
983
984 die;
985 }
986
987 /**
988 * On successful template import.
989 *
990 * Redirect the user to the template library after template import was
991 * successful finished.
992 *
993 * @since 2.3.0
994 * @access private
995 */
996 private function on_direct_import_template_success() {
997 wp_safe_redirect( admin_url( Source_Local::ADMIN_MENU_SLUG ) );
998 }
999
1000 /**
1001 * @since 2.3.0
1002 * @access private
1003 */
1004 private function handle_direct_action_error( $message ) {
1005 _default_wp_die_handler( $message, 'Elementor Library' );
1006 }
1007
1008 /**
1009 * Ensure arguments exist.
1010 *
1011 * Checks whether the required arguments exist in the specified arguments.
1012 *
1013 * @since 1.0.0
1014 * @access private
1015 *
1016 * @param array $required_args Required arguments to check whether they
1017 * exist.
1018 * @param array $specified_args The list of all the specified arguments to
1019 * check against.
1020 *
1021 * @return \WP_Error|true True on success, 'WP_Error' otherwise.
1022 */
1023 private function ensure_args( array $required_args, array $specified_args ) {
1024 $not_specified_args = array_diff( $required_args, array_keys( $specified_args ) );
1025
1026 if ( $not_specified_args ) {
1027 return new \WP_Error( 'arguments_not_specified', sprintf( 'The required argument(s) "%s" not specified.', implode( ', ', $not_specified_args ) ) );
1028 }
1029
1030 return true;
1031 }
1032
1033 public function bulk_move_templates( array $args ) {
1034 $validate_args = $this->ensure_args( [ 'source', 'from_source', 'from_template_id' ], $args );
1035
1036 if ( is_wp_error( $validate_args ) ) {
1037 return $validate_args;
1038 }
1039
1040 $args['source'] = $args['source'][0];
1041
1042 return $this->bulk_move_template_items( $args );
1043 }
1044
1045 private function bulk_move_template_items( array $args ) {
1046 $source = $this->get_source( $args['source'] );
1047
1048 if ( ! $source ) {
1049 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1050 }
1051
1052 if ( $this->is_action_to_same_source( $args ) ) {
1053 return $source->move_bulk_templates_to_folder( $args );
1054 }
1055
1056 $from_source = $this->get_source( $args['from_source'] );
1057
1058 if ( ! $from_source ) {
1059 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1060 }
1061
1062 $bulk_args = $from_source->format_args_for_bulk_action( $args );
1063
1064 if ( $source->supports_quota() && ! $this->is_action_to_same_source( $args ) ) {
1065 $is_quota_valid = $source->validate_quota( $bulk_args );
1066
1067 if ( is_wp_error( $is_quota_valid ) ) {
1068 return $is_quota_valid;
1069 }
1070
1071 if ( ! $is_quota_valid ) {
1072 return new \WP_Error( 'quota_error', 'The moving failed because it will pass the maximum templates you can save.' );
1073 }
1074 }
1075
1076 $bulk_save = $source->save_bulk_items( $bulk_args );
1077
1078 if ( ! empty( $bulk_save ) ) {
1079 $this->bulk_delete_templates( [
1080 'template_ids' => $args['from_template_id'],
1081 'source' => $args['from_source'],
1082 ] );
1083 }
1084
1085 return $bulk_save;
1086 }
1087
1088 public function bulk_copy_templates( array $args ) {
1089 $validate_args = $this->ensure_args( [ 'source', 'from_source', 'from_template_id' ], $args );
1090
1091 if ( is_wp_error( $validate_args ) ) {
1092 return $validate_args;
1093 }
1094
1095 $args['source'] = $args['source'][0];
1096
1097 return $this->bulk_copy_template_items( $args );
1098 }
1099
1100 private function bulk_copy_template_items( array $args ) {
1101 $source = $this->get_source( $args['source'] );
1102
1103 if ( ! $source ) {
1104 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1105 }
1106
1107 $from_source = $this->get_source( $args['from_source'] );
1108
1109 if ( ! $from_source ) {
1110 return new \WP_Error( 'template_error', self::ERROR_TEMPLATE_SOURCE_NOT_FOUND );
1111 }
1112
1113 $bulk_args = $from_source->format_args_for_bulk_action( $args );
1114
1115 if ( $source->supports_quota() && ! $this->is_action_to_same_source( $args ) ) {
1116 $is_quota_valid = $source->validate_quota( $bulk_args );
1117
1118 if ( is_wp_error( $is_quota_valid ) ) {
1119 return $is_quota_valid;
1120 }
1121
1122 if ( ! $is_quota_valid ) {
1123 return new \WP_Error( 'quota_error', 'The copying failed because it will pass the maximum templates you can save.' );
1124 }
1125 }
1126
1127 return $source->save_bulk_items( $bulk_args );
1128 }
1129
1130 public function get_templates_quota( array $args ) {
1131 $validate_args = $this->ensure_args( [ 'source' ], $args );
1132
1133 if ( is_wp_error( $validate_args ) ) {
1134 return $validate_args;
1135 }
1136
1137 $source = $this->get_source( $args['source'] );
1138
1139 if ( ! $source ) {
1140 return new \WP_Error( 'template_error', 'Source not found.' );
1141 }
1142
1143 return $source->get_quota();
1144 }
1145 }
1146