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

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