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