PluginProbe
Elementor Website Builder – more than just a page builder / 3.1.0-dev2
Elementor Website Builder – more than just a page builder v3.1.0-dev2
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 / sources / local.php

local.php in Elementor Website Builder – more than just a page builder 3.1.0-dev2, at includes/template-library/sources/local.php

1,719 lines 45.5 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\Core\Base\Document;
5 use Elementor\Core\Editor\Editor;
6 use Elementor\DB;
7 use Elementor\Core\Settings\Manager as SettingsManager;
8 use Elementor\Core\Settings\Page\Model;
9 use Elementor\Modules\Library\Documents\Library_Document;
10 use Elementor\Plugin;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly.
14 }
15
16 /**
17 * Elementor template library local source.
18 *
19 * Elementor template library local source handler class is responsible for
20 * handling local Elementor templates saved by the user locally on his site.
21 *
22 * @since 1.0.0
23 */
24 class Source_Local extends Source_Base {
25
26 /**
27 * Elementor template-library post-type slug.
28 */
29 const CPT = 'elementor_library';
30
31 /**
32 * Elementor template-library taxonomy slug.
33 */
34 const TAXONOMY_TYPE_SLUG = 'elementor_library_type';
35
36 /**
37 * Elementor template-library category slug.
38 */
39 const TAXONOMY_CATEGORY_SLUG = 'elementor_library_category';
40
41 /**
42 * Elementor template-library meta key.
43 * @deprecated 2.3.0 Use \Elementor\Core\Base\Document::TYPE_META_KEY instead
44 */
45 const TYPE_META_KEY = '_elementor_template_type';
46
47 /**
48 * Elementor template-library temporary files folder.
49 */
50 const TEMP_FILES_DIR = 'elementor/tmp';
51
52 /**
53 * Elementor template-library bulk export action name.
54 */
55 const BULK_EXPORT_ACTION = 'elementor_export_multiple_templates';
56
57 const ADMIN_MENU_SLUG = 'edit.php?post_type=elementor_library';
58
59 const ADMIN_SCREEN_ID = 'edit-elementor_library';
60
61 /**
62 * Template types.
63 *
64 * Holds the list of supported template types that can be displayed.
65 *
66 * @access private
67 * @static
68 *
69 * @var array
70 */
71 private static $template_types = [];
72
73 /**
74 * Post type object.
75 *
76 * Holds the post type object of the current post.
77 *
78 * @access private
79 *
80 * @var \WP_Post_Type
81 */
82 private $post_type_object;
83
84 /**
85 * @since 2.3.0
86 * @access public
87 * @static
88 * @return array
89 */
90 public static function get_template_types() {
91 return self::$template_types;
92 }
93
94 /**
95 * Get local template type.
96 *
97 * Retrieve the template type from the post meta.
98 *
99 * @since 1.0.0
100 * @access public
101 * @static
102 *
103 * @param int $template_id The template ID.
104 *
105 * @return mixed The value of meta data field.
106 */
107 public static function get_template_type( $template_id ) {
108 return get_post_meta( $template_id, Document::TYPE_META_KEY, true );
109 }
110
111 /**
112 * Is base templates screen.
113 *
114 * Whether the current screen base is edit and the post type is template.
115 *
116 * @since 1.0.0
117 * @access public
118 * @static
119 *
120 * @return bool True on base templates screen, False otherwise.
121 */
122 public static function is_base_templates_screen() {
123 global $current_screen;
124
125 if ( ! $current_screen ) {
126 return false;
127 }
128
129 return 'edit' === $current_screen->base && self::CPT === $current_screen->post_type;
130 }
131
132 /**
133 * Add template type.
134 *
135 * Register new template type to the list of supported local template types.
136 *
137 * @since 1.0.3
138 * @access public
139 * @static
140 *
141 * @param string $type Template type.
142 */
143 public static function add_template_type( $type ) {
144 self::$template_types[ $type ] = $type;
145 }
146
147 /**
148 * Remove template type.
149 *
150 * Remove existing template type from the list of supported local template
151 * types.
152 *
153 * @since 1.8.0
154 * @access public
155 * @static
156 *
157 * @param string $type Template type.
158 */
159 public static function remove_template_type( $type ) {
160 if ( isset( self::$template_types[ $type ] ) ) {
161 unset( self::$template_types[ $type ] );
162 }
163 }
164
165 public static function get_admin_url( $relative = false ) {
166 $base_url = self::ADMIN_MENU_SLUG;
167 if ( ! $relative ) {
168 $base_url = admin_url( $base_url );
169 }
170
171 return add_query_arg( 'tabs_group', 'library', $base_url );
172 }
173
174 /**
175 * Get local template ID.
176 *
177 * Retrieve the local template ID.
178 *
179 * @since 1.0.0
180 * @access public
181 *
182 * @return string The local template ID.
183 */
184 public function get_id() {
185 return 'local';
186 }
187
188 /**
189 * Get local template title.
190 *
191 * Retrieve the local template title.
192 *
193 * @since 1.0.0
194 * @access public
195 *
196 * @return string The local template title.
197 */
198 public function get_title() {
199 return __( 'Local', 'elementor' );
200 }
201
202 /**
203 * Register local template data.
204 *
205 * Used to register custom template data like a post type, a taxonomy or any
206 * other data.
207 *
208 * The local template class registers a new `elementor_library` post type
209 * and an `elementor_library_type` taxonomy. They are used to store data for
210 * local templates saved by the user on his site.
211 *
212 * @since 1.0.0
213 * @access public
214 */
215 public function register_data() {
216 $labels = [
217 'name' => _x( 'My Templates', 'Template Library', 'elementor' ),
218 'singular_name' => _x( 'Template', 'Template Library', 'elementor' ),
219 'add_new' => _x( 'Add New', 'Template Library', 'elementor' ),
220 'add_new_item' => _x( 'Add New Template', 'Template Library', 'elementor' ),
221 'edit_item' => _x( 'Edit Template', 'Template Library', 'elementor' ),
222 'new_item' => _x( 'New Template', 'Template Library', 'elementor' ),
223 'all_items' => _x( 'All Templates', 'Template Library', 'elementor' ),
224 'view_item' => _x( 'View Template', 'Template Library', 'elementor' ),
225 'search_items' => _x( 'Search Template', 'Template Library', 'elementor' ),
226 'not_found' => _x( 'No Templates found', 'Template Library', 'elementor' ),
227 'not_found_in_trash' => _x( 'No Templates found in Trash', 'Template Library', 'elementor' ),
228 'parent_item_colon' => '',
229 'menu_name' => _x( 'Templates', 'Template Library', 'elementor' ),
230 ];
231
232 $args = [
233 'labels' => $labels,
234 'public' => true,
235 'rewrite' => false,
236 'menu_icon' => 'dashicons-admin-page',
237 'show_ui' => true,
238 'show_in_menu' => true,
239 'show_in_nav_menus' => false,
240 'exclude_from_search' => true,
241 'capability_type' => 'post',
242 'hierarchical' => false,
243 'supports' => [ 'title', 'thumbnail', 'author', 'elementor' ],
244 ];
245
246 /**
247 * Register template library post type args.
248 *
249 * Filters the post type arguments when registering elementor template library post type.
250 *
251 * @since 1.0.0
252 *
253 * @param array $args Arguments for registering a post type.
254 */
255 $args = apply_filters( 'elementor/template_library/sources/local/register_post_type_args', $args );
256
257 $this->post_type_object = register_post_type( self::CPT, $args );
258
259 $args = [
260 'hierarchical' => false,
261 'show_ui' => false,
262 'show_in_nav_menus' => false,
263 'show_admin_column' => true,
264 'query_var' => is_admin(),
265 'rewrite' => false,
266 'public' => false,
267 'label' => _x( 'Type', 'Template Library', 'elementor' ),
268 ];
269
270 /**
271 * Register template library taxonomy args.
272 *
273 * Filters the taxonomy arguments when registering elementor template library taxonomy.
274 *
275 * @since 1.0.0
276 *
277 * @param array $args Arguments for registering a taxonomy.
278 */
279 $args = apply_filters( 'elementor/template_library/sources/local/register_taxonomy_args', $args );
280
281 $cpts_to_associate = [ self::CPT ];
282
283 // If the Landing Page feature is active, associate pages with this taxonomy as well.
284 if ( Plugin::$instance->experiments->is_feature_active( 'landing-pages' ) ) {
285 $cpts_to_associate[] = 'page';
286 }
287
288 register_taxonomy( self::TAXONOMY_TYPE_SLUG, $cpts_to_associate, $args );
289
290 /**
291 * Categories
292 */
293 $args = [
294 'hierarchical' => true,
295 'show_ui' => true,
296 'show_in_nav_menus' => false,
297 'show_admin_column' => true,
298 'query_var' => is_admin(),
299 'rewrite' => false,
300 'public' => false,
301 'labels' => [
302 'name' => _x( 'Categories', 'Template Library', 'elementor' ),
303 'singular_name' => _x( 'Category', 'Template Library', 'elementor' ),
304 'all_items' => _x( 'All Categories', 'Template Library', 'elementor' ),
305 ],
306 ];
307
308 /**
309 * Register template library category args.
310 *
311 * Filters the category arguments when registering elementor template library category.
312 *
313 * @since 2.4.0
314 *
315 * @param array $args Arguments for registering a category.
316 */
317 $args = apply_filters( 'elementor/template_library/sources/local/register_category_args', $args );
318
319 register_taxonomy( self::TAXONOMY_CATEGORY_SLUG, self::CPT, $args );
320 }
321
322 /**
323 * Remove Add New item from admin menu.
324 *
325 * Fired by `admin_menu` action.
326 *
327 * @since 2.4.0
328 * @access public
329 */
330 public function admin_menu_reorder() {
331 global $submenu;
332
333 if ( ! isset( $submenu[ self::ADMIN_MENU_SLUG ] ) ) {
334 return;
335 }
336 $library_submenu = &$submenu[ self::ADMIN_MENU_SLUG ];
337
338 // Remove 'All Templates' menu.
339 unset( $library_submenu[5] );
340
341 // If current use can 'Add New' - move the menu to end, and add the '#add_new' anchor.
342 if ( isset( $library_submenu[10][2] ) ) {
343 $library_submenu[700] = $library_submenu[10];
344 unset( $library_submenu[10] );
345 $library_submenu[700][2] = admin_url( self::ADMIN_MENU_SLUG . '#add_new' );
346 }
347
348 // Move the 'Categories' menu to end.
349 if ( isset( $library_submenu[15] ) ) {
350 $library_submenu[800] = $library_submenu[15];
351 unset( $library_submenu[15] );
352 }
353
354 if ( $this->is_current_screen() ) {
355 $library_title = $this->get_library_title();
356
357 foreach ( $library_submenu as &$item ) {
358 if ( $library_title === $item[0] ) {
359 if ( ! isset( $item[4] ) ) {
360 $item[4] = '';
361 }
362 $item[4] .= ' current';
363 }
364 }
365 }
366 }
367
368 public function admin_menu() {
369 add_submenu_page( self::ADMIN_MENU_SLUG, '', __( 'Saved Templates', 'elementor' ), Editor::EDITING_CAPABILITY, self::get_admin_url( true ) );
370 }
371
372 public function admin_title( $admin_title, $title ) {
373 $library_title = $this->get_library_title();
374
375 if ( $library_title ) {
376 $admin_title = str_replace( $title, $library_title, $admin_title );
377 }
378
379 return $admin_title;
380 }
381
382 public function replace_admin_heading() {
383 $library_title = $this->get_library_title();
384
385 if ( $library_title ) {
386 global $post_type_object;
387
388 $post_type_object->labels->name = $library_title;
389 }
390 }
391
392 /**
393 * Get local templates.
394 *
395 * Retrieve local templates saved by the user on his site.
396 *
397 * @since 1.0.0
398 * @access public
399 *
400 * @param array $args Optional. Filter templates based on a set of
401 * arguments. Default is an empty array.
402 *
403 * @return array Local templates.
404 */
405 public function get_items( $args = [] ) {
406 $template_types = array_values( self::$template_types );
407
408 if ( ! empty( $args['type'] ) ) {
409 $template_types = $args['type'];
410 unset( $args['type'] );
411 }
412
413 $defaults = [
414 'post_type' => self::CPT,
415 'post_status' => 'publish',
416 'posts_per_page' => -1,
417 'orderby' => 'title',
418 'order' => 'ASC',
419 'meta_query' => [
420 [
421 'key' => Document::TYPE_META_KEY,
422 'value' => $template_types,
423 ],
424 ],
425 ];
426
427 $query_args = wp_parse_args( $args, $defaults );
428
429 $templates_query = new \WP_Query( $query_args );
430
431 $templates = [];
432
433 if ( $templates_query->have_posts() ) {
434 foreach ( $templates_query->get_posts() as $post ) {
435 $templates[] = $this->get_item( $post->ID );
436 }
437 }
438
439 return $templates;
440 }
441
442 /**
443 * Save local template.
444 *
445 * Save new or update existing template on the database.
446 *
447 * @since 1.0.0
448 * @access public
449 *
450 * @param array $template_data Local template data.
451 *
452 * @return \WP_Error|int The ID of the saved/updated template, `WP_Error` otherwise.
453 */
454 public function save_item( $template_data ) {
455 if ( ! current_user_can( $this->post_type_object->cap->edit_posts ) ) {
456 return new \WP_Error( 'save_error', __( 'Access denied.', 'elementor' ) );
457 }
458
459 $defaults = [
460 'title' => __( '(no title)', 'elementor' ),
461 'page_settings' => [],
462 'status' => current_user_can( 'publish_posts' ) ? 'publish' : 'pending',
463 ];
464
465 $template_data = wp_parse_args( $template_data, $defaults );
466
467 $document = Plugin::$instance->documents->create(
468 $template_data['type'],
469 [
470 'post_title' => $template_data['title'],
471 'post_status' => $template_data['status'],
472 'post_type' => self::CPT,
473 ]
474 );
475
476 if ( is_wp_error( $document ) ) {
477 /**
478 * @var \WP_Error $document
479 */
480 return $document;
481 }
482
483 if ( ! empty( $template_data['content'] ) ) {
484 $template_data['content'] = $this->replace_elements_ids( $template_data['content'] );
485 }
486
487 $document->save( [
488 'elements' => $template_data['content'],
489 'settings' => $template_data['page_settings'],
490 ] );
491
492 $template_id = $document->get_main_id();
493
494 /**
495 * After template library save.
496 *
497 * Fires after Elementor template library was saved.
498 *
499 * @since 1.0.1
500 *
501 * @param int $template_id The ID of the template.
502 * @param array $template_data The template data.
503 */
504 do_action( 'elementor/template-library/after_save_template', $template_id, $template_data );
505
506 /**
507 * After template library update.
508 *
509 * Fires after Elementor template library was updated.
510 *
511 * @since 1.0.1
512 *
513 * @param int $template_id The ID of the template.
514 * @param array $template_data The template data.
515 */
516 do_action( 'elementor/template-library/after_update_template', $template_id, $template_data );
517
518 return $template_id;
519 }
520
521 /**
522 * Update local template.
523 *
524 * Update template on the database.
525 *
526 * @since 1.0.0
527 * @access public
528 *
529 * @param array $new_data New template data.
530 *
531 * @return \WP_Error|true True if template updated, `WP_Error` otherwise.
532 */
533 public function update_item( $new_data ) {
534 if ( ! current_user_can( $this->post_type_object->cap->edit_post, $new_data['id'] ) ) {
535 return new \WP_Error( 'save_error', __( 'Access denied.', 'elementor' ) );
536 }
537
538 $document = Plugin::$instance->documents->get( $new_data['id'] );
539
540 if ( ! $document ) {
541 return new \WP_Error( 'save_error', __( 'Template not exist.', 'elementor' ) );
542 }
543
544 $document->save( [
545 'elements' => $new_data['content'],
546 ] );
547
548 /**
549 * After template library update.
550 *
551 * Fires after Elementor template library was updated.
552 *
553 * @since 1.0.0
554 *
555 * @param int $new_data_id The ID of the new template.
556 * @param array $new_data The new template data.
557 */
558 do_action( 'elementor/template-library/after_update_template', $new_data['id'], $new_data );
559
560 return true;
561 }
562
563 /**
564 * Get local template.
565 *
566 * Retrieve a single local template saved by the user on his site.
567 *
568 * @since 1.0.0
569 * @access public
570 *
571 * @param int $template_id The template ID.
572 *
573 * @return array Local template.
574 */
575 public function get_item( $template_id ) {
576 $post = get_post( $template_id );
577
578 $user = get_user_by( 'id', $post->post_author );
579
580 $page = SettingsManager::get_settings_managers( 'page' )->get_model( $template_id );
581
582 $page_settings = $page->get_data( 'settings' );
583
584 $date = strtotime( $post->post_date );
585
586 $data = [
587 'template_id' => $post->ID,
588 'source' => $this->get_id(),
589 'type' => self::get_template_type( $post->ID ),
590 'title' => $post->post_title,
591 'thumbnail' => get_the_post_thumbnail_url( $post ),
592 'date' => $date,
593 'human_date' => date_i18n( get_option( 'date_format' ), $date ),
594 'human_modified_date' => date_i18n( get_option( 'date_format' ), strtotime( $post->post_modified ) ),
595 'author' => $user->display_name,
596 'status' => $post->post_status,
597 'hasPageSettings' => ! empty( $page_settings ),
598 'tags' => [],
599 'export_link' => $this->get_export_link( $template_id ),
600 'url' => get_permalink( $post->ID ),
601 ];
602
603 /**
604 * Get template library template.
605 *
606 * Filters the template data when retrieving a single template from the
607 * template library.
608 *
609 * @since 1.0.0
610 *
611 * @param array $data Template data.
612 */
613 $data = apply_filters( 'elementor/template-library/get_template', $data );
614
615 return $data;
616 }
617
618 /**
619 * Get template data.
620 *
621 * Retrieve the data of a single local template saved by the user on his site.
622 *
623 * @since 1.5.0
624 * @access public
625 *
626 * @param array $args Custom template arguments.
627 *
628 * @return array Local template data.
629 */
630 public function get_data( array $args ) {
631 $template_id = $args['template_id'];
632
633 $document = Plugin::$instance->documents->get( $template_id );
634 $content = [];
635
636 if ( $document ) {
637 // TODO: Validate the data (in JS too!).
638 if ( ! empty( $args['display'] ) ) {
639 $content = $document->get_elements_raw_data( null, true );
640 } else {
641 $content = $document->get_elements_data();
642 }
643
644 if ( ! empty( $content ) ) {
645 $content = $this->replace_elements_ids( $content );
646 }
647 }
648
649 $data = [
650 'content' => $content,
651 ];
652
653 if ( ! empty( $args['with_page_settings'] ) ) {
654 $page = SettingsManager::get_settings_managers( 'page' )->get_model( $args['template_id'] );
655
656 $data['page_settings'] = $page->get_data( 'settings' );
657 }
658
659 return $data;
660 }
661
662 /**
663 * Delete local template.
664 *
665 * Delete template from the database.
666 *
667 * @since 1.0.0
668 * @access public
669 *
670 * @param int $template_id The template ID.
671 *
672 * @return \WP_Post|\WP_Error|false|null Post data on success, false or null
673 * or 'WP_Error' on failure.
674 */
675 public function delete_template( $template_id ) {
676 if ( ! current_user_can( $this->post_type_object->cap->delete_post, $template_id ) ) {
677 return new \WP_Error( 'template_error', __( 'Access denied.', 'elementor' ) );
678 }
679
680 return wp_delete_post( $template_id, true );
681 }
682
683 /**
684 * Export local template.
685 *
686 * Export template to a file.
687 *
688 * @since 1.0.0
689 * @access public
690 *
691 * @param int $template_id The template ID.
692 *
693 * @return \WP_Error WordPress error if template export failed.
694 */
695 public function export_template( $template_id ) {
696 $file_data = $this->prepare_template_export( $template_id );
697
698 if ( is_wp_error( $file_data ) ) {
699 return $file_data;
700 }
701
702 $this->send_file_headers( $file_data['name'], strlen( $file_data['content'] ) );
703
704 // Clear buffering just in case.
705 @ob_end_clean();
706
707 flush();
708
709 // Output file contents.
710 echo $file_data['content'];
711
712 die;
713 }
714
715 /**
716 * Export multiple local templates.
717 *
718 * Export multiple template to a ZIP file.
719 *
720 * @since 1.6.0
721 * @access public
722 *
723 * @param array $template_ids An array of template IDs.
724 *
725 * @return \WP_Error WordPress error if export failed.
726 */
727 public function export_multiple_templates( array $template_ids ) {
728 $files = [];
729
730 $wp_upload_dir = wp_upload_dir();
731
732 $temp_path = $wp_upload_dir['basedir'] . '/' . self::TEMP_FILES_DIR;
733
734 // Create temp path if it doesn't exist
735 wp_mkdir_p( $temp_path );
736
737 // Create all json files
738 foreach ( $template_ids as $template_id ) {
739 $file_data = $this->prepare_template_export( $template_id );
740
741 if ( is_wp_error( $file_data ) ) {
742 continue;
743 }
744
745 $complete_path = $temp_path . '/' . $file_data['name'];
746
747 $put_contents = file_put_contents( $complete_path, $file_data['content'] );
748
749 if ( ! $put_contents ) {
750 return new \WP_Error( '404', sprintf( 'Cannot create file "%s".', $file_data['name'] ) );
751 }
752
753 $files[] = [
754 'path' => $complete_path,
755 'name' => $file_data['name'],
756 ];
757 }
758
759 if ( ! $files ) {
760 return new \WP_Error( 'empty_files', 'There is no files to export (probably all the requested templates are empty).' );
761 }
762
763 // Create temporary .zip file
764 $zip_archive_filename = 'elementor-templates-' . gmdate( 'Y-m-d' ) . '.zip';
765
766 $zip_archive = new \ZipArchive();
767
768 $zip_complete_path = $temp_path . '/' . $zip_archive_filename;
769
770 $zip_archive->open( $zip_complete_path, \ZipArchive::CREATE );
771
772 foreach ( $files as $file ) {
773 $zip_archive->addFile( $file['path'], $file['name'] );
774 }
775
776 $zip_archive->close();
777
778 foreach ( $files as $file ) {
779 unlink( $file['path'] );
780 }
781
782 $this->send_file_headers( $zip_archive_filename, filesize( $zip_complete_path ) );
783
784 @ob_end_flush();
785
786 @readfile( $zip_complete_path );
787
788 unlink( $zip_complete_path );
789
790 die;
791 }
792
793 /**
794 * Find temporary files.
795 *
796 * Recursively finds a list of temporary files from the extracted zip file.
797 *
798 * Example return data:
799 *
800 * [
801 * 0 => '/www/wp-content/uploads/elementor/tmp/5eb3a7a411d44/templates/block-2-col-marble-title.json',
802 * 1 => '/www/wp-content/uploads/elementor/tmp/5eb3a7a411d44/templates/block-2-col-text-and-photo.json',
803 * ]
804 *
805 * @since 2.9.8
806 * @access private
807 *
808 * @param string $temp_path - The temporary file path to scan for template files
809 *
810 * @return array An array of temporary files on the filesystem
811 */
812 private function find_temp_files( $temp_path ) {
813
814 $file_names = [];
815
816 $possible_file_names = array_diff( scandir( $temp_path ), [ '.', '..' ] );
817
818 // Find nested files in the unzipped path. This happens for example when the user imports a Template Kit.
819 foreach ( $possible_file_names as $possible_file_name ) {
820 $full_possible_file_name = $temp_path . '/' . $possible_file_name;
821 if ( is_dir( $full_possible_file_name ) ) {
822 $file_names = $file_names + $this->find_temp_files( $full_possible_file_name );
823 } else {
824 $file_names[] = $full_possible_file_name;
825 }
826 }
827
828 return $file_names;
829 }
830
831 /**
832 * Import local template.
833 *
834 * Import template from a file.
835 *
836 * @since 1.0.0
837 * @access public
838 *
839 * @param string $name - The file name
840 * @param string $path - The file path
841 *
842 * @return \WP_Error|array An array of items on success, 'WP_Error' on failure.
843 */
844 public function import_template( $name, $path ) {
845 if ( empty( $path ) ) {
846 return new \WP_Error( 'file_error', 'Please upload a file to import' );
847 }
848
849 $items = [];
850
851 $file_extension = pathinfo( $name, PATHINFO_EXTENSION );
852
853 if ( 'zip' === $file_extension ) {
854 if ( ! class_exists( '\ZipArchive' ) ) {
855 return new \WP_Error( 'zip_error', 'PHP Zip extension not loaded' );
856 }
857
858 $zip = new \ZipArchive();
859
860 $wp_upload_dir = wp_upload_dir();
861
862 $temp_path = $wp_upload_dir['basedir'] . '/' . self::TEMP_FILES_DIR . '/' . uniqid();
863
864 $zip->open( $path );
865
866 $valid_entries = [];
867
868 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
869 for ( $i = 0; $i < $zip->numFiles; $i++ ) {
870 $zipped_file_name = $zip->getNameIndex( $i );
871 $zipped_extension = pathinfo( $zipped_file_name, PATHINFO_EXTENSION );
872 // Template Kit zip files contain a `manifest.json` file, this is not a valid Elementor template so ensure we skip it.
873 if ( 'json' === $zipped_extension && 'manifest.json' !== $zipped_file_name ) {
874 $valid_entries[] = $zipped_file_name;
875 }
876 }
877
878 if ( ! empty( $valid_entries ) ) {
879 $zip->extractTo( $temp_path, $valid_entries );
880 }
881
882 $zip->close();
883
884 $file_names = $this->find_temp_files( $temp_path );
885
886 foreach ( $file_names as $full_file_name ) {
887 $import_result = $this->import_single_template( $full_file_name );
888
889 unlink( $full_file_name );
890
891 if ( is_wp_error( $import_result ) ) {
892 return $import_result;
893 }
894
895 $items[] = $import_result;
896 }
897
898 rmdir( $temp_path );
899 } else {
900 $import_result = $this->import_single_template( $path );
901
902 if ( is_wp_error( $import_result ) ) {
903 return $import_result;
904 }
905
906 $items[] = $import_result;
907 }
908
909 return $items;
910 }
911
912 /**
913 * Post row actions.
914 *
915 * Add an export link to the template library action links table list.
916 *
917 * Fired by `post_row_actions` filter.
918 *
919 * @since 1.0.0
920 * @access public
921 *
922 * @param array $actions An array of row action links.
923 * @param \WP_Post $post The post object.
924 *
925 * @return array An updated array of row action links.
926 */
927 public function post_row_actions( $actions, \WP_Post $post ) {
928 if ( self::is_base_templates_screen() ) {
929 if ( $this->is_template_supports_export( $post->ID ) ) {
930 $actions['export-template'] = sprintf( '<a href="%1$s">%2$s</a>', $this->get_export_link( $post->ID ), __( 'Export Template', 'elementor' ) );
931 }
932 }
933
934 return $actions;
935 }
936
937 /**
938 * Admin import template form.
939 *
940 * The import form displayed in "My Library" screen in WordPress dashboard.
941 *
942 * The form allows the user to import template in json/zip format to the site.
943 *
944 * Fired by `admin_footer` action.
945 *
946 * @since 1.0.0
947 * @access public
948 */
949 public function admin_import_template_form() {
950 if ( ! self::is_base_templates_screen() ) {
951 return;
952 }
953
954 /** @var \Elementor\Core\Common\Modules\Ajax\Module $ajax */
955 $ajax = Plugin::$instance->common->get_component( 'ajax' );
956 ?>
957 <div id="elementor-hidden-area">
958 <a id="elementor-import-template-trigger" class="page-title-action"><?php echo __( 'Import Templates', 'elementor' ); ?></a>
959 <div id="elementor-import-template-area">
960 <div id="elementor-import-template-title"><?php echo __( 'Choose an Elementor template JSON file or a .zip archive of Elementor templates, and add them to the list of templates available in your library.', 'elementor' ); ?></div>
961 <form id="elementor-import-template-form" method="post" action="<?php echo admin_url( 'admin-ajax.php' ); ?>" enctype="multipart/form-data">
962 <input type="hidden" name="action" value="elementor_library_direct_actions">
963 <input type="hidden" name="library_action" value="direct_import_template">
964 <input type="hidden" name="_nonce" value="<?php echo $ajax->create_nonce(); ?>">
965 <fieldset id="elementor-import-template-form-inputs">
966 <input type="file" name="file" accept=".json,application/json,.zip,application/octet-stream,application/zip,application/x-zip,application/x-zip-compressed" required>
967 <input type="submit" class="button" value="<?php echo esc_attr__( 'Import Now', 'elementor' ); ?>">
968 </fieldset>
969 </form>
970 </div>
971 </div>
972 <?php
973 }
974
975 /**
976 * Block template frontend
977 *
978 * Don't display the single view of the template library post type in the
979 * frontend, for users that don't have the proper permissions.
980 *
981 * Fired by `template_redirect` action.
982 *
983 * @since 1.0.0
984 * @access public
985 */
986 public function block_template_frontend() {
987 if ( is_singular( self::CPT ) && ! current_user_can( Editor::EDITING_CAPABILITY ) ) {
988 wp_safe_redirect( site_url(), 301 );
989 die;
990 }
991 }
992
993 /**
994 * Is template library supports export.
995 *
996 * whether the template library supports export.
997 *
998 * Template saved by the user locally on his site, support export by default
999 * but this can be changed using a filter.
1000 *
1001 * @since 1.0.0
1002 * @access public
1003 *
1004 * @param int $template_id The template ID.
1005 *
1006 * @return bool Whether the template library supports export.
1007 */
1008 public function is_template_supports_export( $template_id ) {
1009 $export_support = true;
1010
1011 /**
1012 * Is template library supports export.
1013 *
1014 * Filters whether the template library supports export.
1015 *
1016 * @since 1.0.0
1017 *
1018 * @param bool $export_support Whether the template library supports export.
1019 * Default is true.
1020 * @param int $template_id Post ID.
1021 */
1022 $export_support = apply_filters( 'elementor/template_library/is_template_supports_export', $export_support, $template_id );
1023
1024 return $export_support;
1025 }
1026
1027 /**
1028 * Remove Elementor post state.
1029 *
1030 * Remove the 'elementor' post state from the display states of the post.
1031 *
1032 * Used to remove the 'elementor' post state from the template library items.
1033 *
1034 * Fired by `display_post_states` filter.
1035 *
1036 * @since 1.8.0
1037 * @access public
1038 *
1039 * @param array $post_states An array of post display states.
1040 * @param \WP_Post $post The current post object.
1041 *
1042 * @return array Updated array of post display states.
1043 */
1044 public function remove_elementor_post_state_from_library( $post_states, $post ) {
1045 if ( self::CPT === $post->post_type && isset( $post_states['elementor'] ) ) {
1046 unset( $post_states['elementor'] );
1047 }
1048 return $post_states;
1049 }
1050
1051 /**
1052 * Get template export link.
1053 *
1054 * Retrieve the link used to export a single template based on the template
1055 * ID.
1056 *
1057 * @since 2.0.0
1058 * @access private
1059 *
1060 * @param int $template_id The template ID.
1061 *
1062 * @return string Template export URL.
1063 */
1064 private function get_export_link( $template_id ) {
1065 // TODO: BC since 2.3.0 - Use `$ajax->create_nonce()`
1066 /** @var \Elementor\Core\Common\Modules\Ajax\Module $ajax */
1067 // $ajax = Plugin::$instance->common->get_component( 'ajax' );
1068
1069 return add_query_arg(
1070 [
1071 'action' => 'elementor_library_direct_actions',
1072 'library_action' => 'export_template',
1073 'source' => $this->get_id(),
1074 '_nonce' => wp_create_nonce( 'elementor_ajax' ),
1075 'template_id' => $template_id,
1076 ],
1077 admin_url( 'admin-ajax.php' )
1078 );
1079 }
1080
1081 /**
1082 * On template save.
1083 *
1084 * Run this method when template is being saved.
1085 *
1086 * Fired by `save_post` action.
1087 *
1088 * @since 1.0.1
1089 * @access public
1090 *
1091 * @param int $post_id Post ID.
1092 * @param \WP_Post $post The current post object.
1093 */
1094 public function on_save_post( $post_id, \WP_Post $post ) {
1095 if ( self::CPT !== $post->post_type ) {
1096 return;
1097 }
1098
1099 if ( self::get_template_type( $post_id ) ) { // It's already with a type
1100 return;
1101 }
1102
1103 // Don't save type on import, the importer will do it.
1104 if ( did_action( 'import_start' ) ) {
1105 return;
1106 }
1107
1108 $this->save_item_type( $post_id, 'page' );
1109 }
1110
1111 /**
1112 * Save item type.
1113 *
1114 * When saving/updating templates, this method is used to update the post
1115 * meta data and the taxonomy.
1116 *
1117 * @since 1.0.1
1118 * @access private
1119 *
1120 * @param int $post_id Post ID.
1121 * @param string $type Item type.
1122 */
1123 private function save_item_type( $post_id, $type ) {
1124 update_post_meta( $post_id, Document::TYPE_META_KEY, $type );
1125
1126 wp_set_object_terms( $post_id, $type, self::TAXONOMY_TYPE_SLUG );
1127 }
1128
1129 /**
1130 * Bulk export action.
1131 *
1132 * Adds an 'Export' action to the Bulk Actions drop-down in the template
1133 * library.
1134 *
1135 * Fired by `bulk_actions-edit-elementor_library` filter.
1136 *
1137 * @since 1.6.0
1138 * @access public
1139 *
1140 * @param array $actions An array of the available bulk actions.
1141 *
1142 * @return array An array of the available bulk actions.
1143 */
1144 public function admin_add_bulk_export_action( $actions ) {
1145 $actions[ self::BULK_EXPORT_ACTION ] = __( 'Export', 'elementor' );
1146
1147 return $actions;
1148 }
1149
1150 /**
1151 * Add bulk export action.
1152 *
1153 * Handles the template library bulk export action.
1154 *
1155 * Fired by `handle_bulk_actions-edit-elementor_library` filter.
1156 *
1157 * @since 1.6.0
1158 * @access public
1159 *
1160 * @param string $redirect_to The redirect URL.
1161 * @param string $action The action being taken.
1162 * @param array $post_ids The items to take the action on.
1163 */
1164 public function admin_export_multiple_templates( $redirect_to, $action, $post_ids ) {
1165 if ( self::BULK_EXPORT_ACTION === $action ) {
1166 $result = $this->export_multiple_templates( $post_ids );
1167
1168 // If you reach this line, the export failed
1169 wp_die( $result->get_error_message() );
1170 }
1171 }
1172
1173 /**
1174 * Print admin tabs.
1175 *
1176 * Used to output the template library tabs with their labels.
1177 *
1178 * Fired by `views_edit-elementor_library` filter.
1179 *
1180 * @since 2.0.0
1181 * @access public
1182 *
1183 * @param array $views An array of available list table views.
1184 *
1185 * @return array An updated array of available list table views.
1186 */
1187 public function admin_print_tabs( $views ) {
1188 $current_type = '';
1189 $active_class = ' nav-tab-active';
1190 $current_tabs_group = $this->get_current_tab_group();
1191
1192 if ( ! empty( $_REQUEST[ self::TAXONOMY_TYPE_SLUG ] ) ) {
1193 $current_type = $_REQUEST[ self::TAXONOMY_TYPE_SLUG ];
1194 $active_class = '';
1195 }
1196
1197 $url_args = [
1198 'post_type' => self::CPT,
1199 'tabs_group' => $current_tabs_group,
1200 ];
1201
1202 $baseurl = add_query_arg( $url_args, admin_url( 'edit.php' ) );
1203
1204 $filter = [
1205 'admin_tab_group' => $current_tabs_group,
1206 ];
1207 $operator = 'and';
1208
1209 if ( empty( $current_tabs_group ) ) {
1210 // Don't include 'not-supported' or other templates that don't set their `admin_tab_group`.
1211 $operator = 'NOT';
1212 }
1213
1214 $doc_types = Plugin::$instance->documents->get_document_types( $filter, $operator );
1215
1216 if ( 1 >= count( $doc_types ) ) {
1217 return $views;
1218 }
1219
1220 ?>
1221 <div id="elementor-template-library-tabs-wrapper" class="nav-tab-wrapper">
1222 <a class="nav-tab<?php echo $active_class; ?>" href="<?php echo $baseurl; ?>">
1223 <?php
1224 $all_title = $this->get_library_title();
1225 if ( ! $all_title ) {
1226 $all_title = __( 'All', 'elementor' );
1227 }
1228 echo $all_title; ?>
1229 </a>
1230 <?php
1231 foreach ( $doc_types as $type => $class_name ) :
1232 $active_class = '';
1233
1234 if ( $current_type === $type ) {
1235 $active_class = ' nav-tab-active';
1236 }
1237
1238 $type_url = add_query_arg( self::TAXONOMY_TYPE_SLUG, $type, $baseurl );
1239 $type_label = $this->get_template_label_by_type( $type );
1240
1241 echo "<a class='nav-tab{$active_class}' href='{$type_url}'>{$type_label}</a>";
1242 endforeach;
1243 ?>
1244 </div>
1245 <?php
1246 return $views;
1247 }
1248
1249 /**
1250 * Maybe render blank state.
1251 *
1252 * When the template library has no saved templates, display a blank admin page offering
1253 * to create the very first template.
1254 *
1255 * Fired by `manage_posts_extra_tablenav` action.
1256 *
1257 * @since 2.0.0
1258 * @access public
1259 *
1260 * @param string $which The location of the extra table nav markup: 'top' or 'bottom'.
1261 * @param array $args
1262 */
1263 public function maybe_render_blank_state( $which, array $args = [] ) {
1264 global $post_type;
1265
1266 $args = wp_parse_args( $args, [
1267 'cpt' => self::CPT,
1268 'post_type' => get_query_var( 'elementor_library_type' ),
1269 ] );
1270
1271 if ( $args['cpt'] !== $post_type || 'bottom' !== $which ) {
1272 return;
1273 }
1274
1275 global $wp_list_table;
1276
1277 $total_items = $wp_list_table->get_pagination_arg( 'total_items' );
1278
1279 if ( ! empty( $total_items ) || ! empty( $_REQUEST['s'] ) ) {
1280 return;
1281 }
1282
1283 $current_type = $args['post_type'];
1284
1285 $document_types = Plugin::instance()->documents->get_document_types();
1286
1287 if ( empty( $document_types[ $current_type ] ) ) {
1288 return;
1289 }
1290
1291 // TODO: Better way to exclude widget type.
1292 if ( 'widget' === $current_type ) {
1293 return;
1294 }
1295
1296 // TODO: This code maybe unreachable see if above `if ( empty( $document_types[ $current_type ] ) )`.
1297 if ( empty( $current_type ) ) {
1298 $counts = (array) wp_count_posts( self::CPT );
1299 unset( $counts['auto-draft'] );
1300 $count = array_sum( $counts );
1301
1302 if ( 0 < $count ) {
1303 return;
1304 }
1305
1306 $current_type = 'template';
1307
1308 $args['additional_inline_style'] = '#elementor-template-library-tabs-wrapper {display: none;}';
1309 }
1310
1311 $this->render_blank_state( $current_type, $args );
1312 }
1313
1314 private function render_blank_state( $current_type, array $args = [] ) {
1315 $current_type_label = $this->get_template_label_by_type( $current_type );
1316 $inline_style = '#posts-filter .wp-list-table, #posts-filter .tablenav.top, .tablenav.bottom .actions, .wrap .subsubsub { display:none;}';
1317
1318 $args = wp_parse_args( $args, [
1319 'additional_inline_style' => '',
1320 'href' => '',
1321 'description' => __( 'Add templates and reuse them across your website. Easily export and import them to any other project, for an optimized workflow.', 'elementor' ),
1322 ] );
1323 $inline_style .= $args['additional_inline_style'];
1324 ?>
1325 <style type="text/css"><?php echo $inline_style; ?></style>
1326 <div class="elementor-template_library-blank_state">
1327 <?php $this->print_blank_state_template( $current_type_label, $args['href'], $args['description'] ); ?>
1328 </div>
1329 <?php
1330 }
1331
1332 /**
1333 * Print Blank State Template
1334 *
1335 * When the an entity (CPT, Taxonomy...etc) has no saved items, print a blank admin page offering
1336 * to create the very first item.
1337 *
1338 * This method is public because it needs to be accessed from outside the Source_Local
1339 *
1340 * @since 3.1.0
1341 * @access public
1342 *
1343 * @param string $current_type_label The Entity title
1344 * @param string $href The URL for the 'Add New' button
1345 * @param string $description The sub title describing the Entity (Post Type, Taxonomy, etc.)
1346 */
1347 public function print_blank_state_template( $current_type_label, $href, $description ) {
1348 ?>
1349 <div class="elementor-blank_state">
1350 <i class="eicon-folder"></i>
1351 <h2>
1352 <?php
1353 /* translators: %s: Template type label. */
1354 printf( __( 'Create Your First %s', 'elementor' ), $current_type_label );
1355 ?>
1356 </h2>
1357 <p><?php echo $description; ?></p>
1358 <a id="elementor-template-library-add-new" class="elementor-button elementor-button-success" href="<?php echo $href; ?>">
1359 <?php
1360 /* translators: %s: Template type label. */
1361 printf( __( 'Add New %s', 'elementor' ), $current_type_label );
1362 ?>
1363 </a>
1364 </div>
1365 <?php
1366 }
1367
1368 public function add_filter_by_category( $post_type ) {
1369 if ( self::CPT !== $post_type ) {
1370 return;
1371 }
1372
1373 $all_items = get_taxonomy( self::TAXONOMY_CATEGORY_SLUG )->labels->all_items;
1374
1375 $dropdown_options = array(
1376 'show_option_all' => $all_items,
1377 'show_option_none' => $all_items,
1378 'hide_empty' => 0,
1379 'hierarchical' => 1,
1380 'show_count' => 0,
1381 'orderby' => 'name',
1382 'value_field' => 'slug',
1383 'taxonomy' => self::TAXONOMY_CATEGORY_SLUG,
1384 'name' => self::TAXONOMY_CATEGORY_SLUG,
1385 'selected' => empty( $_GET[ self::TAXONOMY_CATEGORY_SLUG ] ) ? '' : $_GET[ self::TAXONOMY_CATEGORY_SLUG ],
1386 );
1387
1388 echo '<label class="screen-reader-text" for="cat">' . _x( 'Filter by category', 'Template Library', 'elementor' ) . '</label>';
1389 wp_dropdown_categories( $dropdown_options );
1390 }
1391
1392 /**
1393 * Import single template.
1394 *
1395 * Import template from a file to the database.
1396 *
1397 * @since 1.6.0
1398 * @access private
1399 *
1400 * @param string $file_name File name.
1401 *
1402 * @return \WP_Error|int|array Local template array, or template ID, or
1403 * `WP_Error`.
1404 */
1405 private function import_single_template( $file_name ) {
1406 $data = json_decode( file_get_contents( $file_name ), true );
1407
1408 if ( empty( $data ) ) {
1409 return new \WP_Error( 'file_error', 'Invalid File' );
1410 }
1411
1412 $content = $data['content'];
1413
1414 if ( ! is_array( $content ) ) {
1415 return new \WP_Error( 'file_error', 'Invalid Content In File' );
1416 }
1417
1418 $content = $this->process_export_import_content( $content, 'on_import' );
1419
1420 $page_settings = [];
1421
1422 if ( ! empty( $data['page_settings'] ) ) {
1423 $page = new Model( [
1424 'id' => 0,
1425 'settings' => $data['page_settings'],
1426 ] );
1427
1428 $page_settings_data = $this->process_element_export_import_content( $page, 'on_import' );
1429
1430 if ( ! empty( $page_settings_data['settings'] ) ) {
1431 $page_settings = $page_settings_data['settings'];
1432 }
1433 }
1434
1435 $template_id = $this->save_item( [
1436 'content' => $content,
1437 'title' => $data['title'],
1438 'type' => $data['type'],
1439 'page_settings' => $page_settings,
1440 ] );
1441
1442 if ( is_wp_error( $template_id ) ) {
1443 return $template_id;
1444 }
1445
1446 return $this->get_item( $template_id );
1447 }
1448
1449 /**
1450 * Prepare template to export.
1451 *
1452 * Retrieve the relevant template data and return them as an array.
1453 *
1454 * @since 1.6.0
1455 * @access private
1456 *
1457 * @param int $template_id The template ID.
1458 *
1459 * @return \WP_Error|array Exported template data.
1460 */
1461 private function prepare_template_export( $template_id ) {
1462 $template_data = $this->get_data( [
1463 'template_id' => $template_id,
1464 ] );
1465
1466 if ( empty( $template_data['content'] ) ) {
1467 return new \WP_Error( 'empty_template', 'The template is empty' );
1468 }
1469
1470 $template_data['content'] = $this->process_export_import_content( $template_data['content'], 'on_export' );
1471
1472 if ( get_post_meta( $template_id, '_elementor_page_settings', true ) ) {
1473 $page = SettingsManager::get_settings_managers( 'page' )->get_model( $template_id );
1474
1475 $page_settings_data = $this->process_element_export_import_content( $page, 'on_export' );
1476
1477 if ( ! empty( $page_settings_data['settings'] ) ) {
1478 $template_data['page_settings'] = $page_settings_data['settings'];
1479 }
1480 }
1481
1482 $export_data = [
1483 'version' => DB::DB_VERSION,
1484 'title' => get_the_title( $template_id ),
1485 'type' => self::get_template_type( $template_id ),
1486 ];
1487
1488 $export_data += $template_data;
1489
1490 return [
1491 'name' => 'elementor-' . $template_id . '-' . gmdate( 'Y-m-d' ) . '.json',
1492 'content' => wp_json_encode( $export_data ),
1493 ];
1494 }
1495
1496 /**
1497 * Send file headers.
1498 *
1499 * Set the file header when export template data to a file.
1500 *
1501 * @since 1.6.0
1502 * @access private
1503 *
1504 * @param string $file_name File name.
1505 * @param int $file_size File size.
1506 */
1507 private function send_file_headers( $file_name, $file_size ) {
1508 header( 'Content-Type: application/octet-stream' );
1509 header( 'Content-Disposition: attachment; filename=' . $file_name );
1510 header( 'Expires: 0' );
1511 header( 'Cache-Control: must-revalidate' );
1512 header( 'Pragma: public' );
1513 header( 'Content-Length: ' . $file_size );
1514 }
1515
1516 /**
1517 * Get template label by type.
1518 *
1519 * Retrieve the template label for any given template type.
1520 *
1521 * @since 2.0.0
1522 * @access private
1523 *
1524 * @param string $template_type Template type.
1525 *
1526 * @return string Template label.
1527 */
1528 private function get_template_label_by_type( $template_type ) {
1529 $document_types = Plugin::instance()->documents->get_document_types();
1530
1531 if ( isset( $document_types[ $template_type ] ) ) {
1532 $template_label = call_user_func( [ $document_types[ $template_type ], 'get_title' ] );
1533 } else {
1534 $template_label = ucwords( str_replace( [ '_', '-' ], ' ', $template_type ) );
1535 }
1536
1537 /**
1538 * Template label by template type.
1539 *
1540 * Filters the template label by template type in the template library .
1541 *
1542 * @since 2.0.0
1543 *
1544 * @param string $template_label Template label.
1545 * @param string $template_type Template type.
1546 */
1547 $template_label = apply_filters( 'elementor/template-library/get_template_label_by_type', $template_label, $template_type );
1548
1549 return $template_label;
1550 }
1551
1552 /**
1553 * Filter template types in admin query.
1554 *
1555 * Update the template types in the main admin query.
1556 *
1557 * Fired by `parse_query` action.
1558 *
1559 * @since 2.4.0
1560 * @access public
1561 *
1562 * @param \WP_Query $query The `WP_Query` instance.
1563 */
1564 public function admin_query_filter_types( \WP_Query $query ) {
1565 if ( ! $this->is_current_screen() || ! empty( $query->query_vars['meta_key'] ) ) {
1566 return;
1567 }
1568
1569 $current_tabs_group = $this->get_current_tab_group();
1570
1571 if ( isset( $query->query_vars[ self::TAXONOMY_CATEGORY_SLUG ] ) && '-1' === $query->query_vars[ self::TAXONOMY_CATEGORY_SLUG ] ) {
1572 unset( $query->query_vars[ self::TAXONOMY_CATEGORY_SLUG ] );
1573 }
1574
1575 if ( empty( $current_tabs_group ) ) {
1576 return;
1577 }
1578
1579 $doc_types = Plugin::$instance->documents->get_document_types( [
1580 'admin_tab_group' => $current_tabs_group,
1581 ] );
1582
1583 $query->query_vars['meta_key'] = Document::TYPE_META_KEY;
1584 $query->query_vars['meta_value'] = array_keys( $doc_types );
1585 }
1586
1587 /**
1588 * Add template library actions.
1589 *
1590 * Register filters and actions for the template library.
1591 *
1592 * @since 2.0.0
1593 * @access private
1594 */
1595 private function add_actions() {
1596 if ( is_admin() ) {
1597 add_action( 'admin_menu', [ $this, 'admin_menu' ] );
1598 add_action( 'admin_menu', [ $this, 'admin_menu_reorder' ], 800 );
1599 add_filter( 'admin_title', [ $this, 'admin_title' ], 10, 2 );
1600 add_action( 'all_admin_notices', [ $this, 'replace_admin_heading' ] );
1601 add_filter( 'post_row_actions', [ $this, 'post_row_actions' ], 10, 2 );
1602 add_action( 'admin_footer', [ $this, 'admin_import_template_form' ] );
1603 add_action( 'save_post', [ $this, 'on_save_post' ], 10, 2 );
1604 add_filter( 'display_post_states', [ $this, 'remove_elementor_post_state_from_library' ], 11, 2 );
1605
1606 add_action( 'parse_query', [ $this, 'admin_query_filter_types' ] );
1607
1608 // Template filter by category.
1609 add_action( 'restrict_manage_posts', [ $this, 'add_filter_by_category' ] );
1610
1611 // Template type column.
1612 add_action( 'manage_' . self::CPT . '_posts_columns', [ $this, 'admin_columns_headers' ] );
1613 add_action( 'manage_' . self::CPT . '_posts_custom_column', [ $this, 'admin_columns_content' ], 10, 2 );
1614
1615 // Template library bulk actions.
1616 add_filter( 'bulk_actions-edit-elementor_library', [ $this, 'admin_add_bulk_export_action' ] );
1617 add_filter( 'handle_bulk_actions-edit-elementor_library', [ $this, 'admin_export_multiple_templates' ], 10, 3 );
1618
1619 // Print template library tabs.
1620 add_filter( 'views_edit-' . self::CPT, [ $this, 'admin_print_tabs' ] );
1621
1622 // Show blank state.
1623 add_action( 'manage_posts_extra_tablenav', [ $this, 'maybe_render_blank_state' ] );
1624 }
1625
1626 add_action( 'template_redirect', [ $this, 'block_template_frontend' ] );
1627 }
1628
1629 /**
1630 * @since 2.0.6
1631 * @access public
1632 */
1633 public function admin_columns_content( $column_name, $post_id ) {
1634 if ( 'elementor_library_type' === $column_name ) {
1635 /** @var Document $document */
1636 $document = Plugin::$instance->documents->get( $post_id );
1637
1638 if ( $document && $document instanceof Library_Document ) {
1639 $document->print_admin_column_type();
1640 }
1641 }
1642 }
1643
1644 /**
1645 * @since 2.0.6
1646 * @access public
1647 */
1648 public function admin_columns_headers( $posts_columns ) {
1649 // Replace original column that bind to the taxonomy - with another column.
1650 unset( $posts_columns['taxonomy-elementor_library_type'] );
1651
1652 $offset = 2;
1653
1654 $posts_columns = array_slice( $posts_columns, 0, $offset, true ) + [
1655 'elementor_library_type' => __( 'Type', 'elementor' ),
1656 ] + array_slice( $posts_columns, $offset, null, true );
1657
1658 return $posts_columns;
1659 }
1660
1661 public function get_current_tab_group( $default = '' ) {
1662 $current_tabs_group = $default;
1663
1664 if ( ! empty( $_REQUEST[ self::TAXONOMY_TYPE_SLUG ] ) ) {
1665 $doc_type = Plugin::$instance->documents->get_document_type( $_REQUEST[ self::TAXONOMY_TYPE_SLUG ], '' );
1666 if ( $doc_type ) {
1667 $current_tabs_group = $doc_type::get_property( 'admin_tab_group' );
1668 }
1669 } elseif ( ! empty( $_REQUEST['tabs_group'] ) ) {
1670 $current_tabs_group = $_REQUEST['tabs_group'];
1671 }
1672
1673 return $current_tabs_group;
1674 }
1675
1676 private function get_library_title() {
1677 $title = '';
1678
1679 if ( $this->is_current_screen() ) {
1680 $current_tab_group = $this->get_current_tab_group();
1681
1682 if ( $current_tab_group ) {
1683 $titles = [
1684 'library' => __( 'Saved Templates', 'elementor' ),
1685 'theme' => __( 'Theme Builder', 'elementor' ),
1686 'popup' => __( 'Popups', 'elementor' ),
1687 ];
1688
1689 if ( ! empty( $titles[ $current_tab_group ] ) ) {
1690 $title = $titles[ $current_tab_group ];
1691 }
1692 }
1693 }
1694
1695 return $title;
1696 }
1697
1698 private function is_current_screen() {
1699 global $pagenow, $typenow;
1700
1701 return 'edit.php' === $pagenow && self::CPT === $typenow;
1702 }
1703
1704 /**
1705 * Template library local source constructor.
1706 *
1707 * Initializing the template library local source base by registering custom
1708 * template data and running custom actions.
1709 *
1710 * @since 1.0.0
1711 * @access public
1712 */
1713 public function __construct() {
1714 parent::__construct();
1715
1716 $this->add_actions();
1717 }
1718 }
1719