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

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