PluginProbe
Elementor Website Builder – more than just a page builder / 3.2.2
Elementor Website Builder – more than just a page builder v3.2.2
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / includes / template-library / sources / local.php

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

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