balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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 }