Файловый менеджер - Редактировать - /var/www/xthruster/html/wp-content/uploads/flags/template-library.tar
Назад
classes/class-import-images.php 0000644 00000013226 14721615050 0012577 0 ustar 00 <?php namespace Elementor\TemplateLibrary\Classes; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Files\Uploads_Manager; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor template library import images. * * Elementor template library import images handler class is responsible for * importing remote images used by the template library. * * @since 1.0.0 */ class Import_Images { /** * Replaced images IDs. * * The IDs of all the new imported images. An array containing the old * attachment ID and the new attachment ID generated after the import. * * @since 1.0.0 * @access private * * @var array */ private $_replace_image_ids = []; /** * Get image hash. * * Retrieve the sha1 hash of the image URL. * * @since 2.0.0 * @access private * * @param string $attachment_url The attachment URL. * * @return string Image hash. */ private function get_hash_image( $attachment_url ) { return sha1( $attachment_url ); } /** * Get saved image. * * Retrieve new image ID, if the image has a new ID after the import. * * @since 2.0.0 * @access private * * @param array $attachment The attachment. * * @return false|array New image ID or false. */ private function get_saved_image( $attachment ) { global $wpdb; if ( isset( $this->_replace_image_ids[ $attachment['id'] ] ) ) { return $this->_replace_image_ids[ $attachment['id'] ]; } $post_id = $wpdb->get_var( $wpdb->prepare( 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` WHERE `meta_key` = \'_elementor_source_image_hash\' AND `meta_value` = %s ;', $this->get_hash_image( $attachment['url'] ) ) ); if ( $post_id ) { $new_attachment = [ 'id' => $post_id, 'url' => wp_get_attachment_url( $post_id ), ]; $this->_replace_image_ids[ $attachment['id'] ] = $new_attachment; return $new_attachment; } return false; } /** * Import image. * * Import a single image from a remote server, upload the image WordPress * uploads folder, create a new attachment in the database and updates the * attachment metadata. * * @since 1.0.0 * @since 3.2.0 New `$parent_post_id` option added * @access public * * @param array $attachment The attachment. * @param int $parent_post_id Optional * * @return false|array Imported image data, or false. */ public function import( $attachment, $parent_post_id = null ) { if ( isset( $attachment['tmp_name'] ) ) { // Used when called to import a directly-uploaded file. $filename = $attachment['name']; $file_content = Utils::file_get_contents( $attachment['tmp_name'] ); } else { // Used when attachment information is passed to this method. if ( ! empty( $attachment['id'] ) ) { $saved_image = $this->get_saved_image( $attachment ); if ( $saved_image ) { return $saved_image; } } // Extract the file name and extension from the url. $filename = basename( $attachment['url'] ); $request = wp_safe_remote_get( $attachment['url'] ); // Make sure the request returns a valid result. if ( is_wp_error( $request ) || ( ! empty( $request['response']['code'] ) && 200 !== (int) $request['response']['code'] ) ) { return false; } $file_content = wp_remote_retrieve_body( $request ); } if ( empty( $file_content ) ) { return false; } $filetype = wp_check_filetype( $filename ); // If the file type is not recognized by WordPress, exit here to avoid creation of an empty attachment document. if ( ! $filetype['ext'] ) { return false; } if ( 'svg' === $filetype['ext'] ) { // In case that unfiltered-files upload is not enabled, SVG images should not be imported. if ( ! Uploads_Manager::are_unfiltered_uploads_enabled() ) { return false; } $svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' ); $file_content = $svg_handler->sanitizer( $file_content ); }; $upload = wp_upload_bits( $filename, null, $file_content ); $post = [ 'post_title' => $filename, 'guid' => $upload['url'], ]; $info = wp_check_filetype( $upload['file'] ); if ( $info ) { $post['post_mime_type'] = $info['type']; } else { // For now just return the origin attachment return $attachment; // return new \WP_Error( 'attachment_processing_error', esc_html__( 'Invalid file type.', 'elementor' ) ); } $post_id = wp_insert_attachment( $post, $upload['file'], $parent_post_id ); apply_filters( 'elementor/template_library/import_images/new_attachment', $post_id ); // On REST requests. if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) { require_once ABSPATH . '/wp-admin/includes/image.php'; } if ( ! function_exists( 'wp_read_video_metadata' ) ) { require_once ABSPATH . '/wp-admin/includes/media.php'; } wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) ); update_post_meta( $post_id, '_elementor_source_image_hash', $this->get_hash_image( $attachment['url'] ) ); $new_attachment = [ 'id' => $post_id, 'url' => $upload['url'], ]; if ( ! empty( $attachment['id'] ) ) { $this->_replace_image_ids[ $attachment['id'] ] = $new_attachment; } return $new_attachment; } /** * Template library import images constructor. * * Initializing the images import class used by the template library through * the WordPress Filesystem API. * * @since 1.0.0 * @access public */ public function __construct() { if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); } } data/controller.php 0000644 00000001266 14721615050 0010357 0 ustar 00 <?php namespace Elementor\Includes\TemplateLibrary\Data; use Elementor\User; use Elementor\TemplateLibrary\Source_Local; use Elementor\Data\V2\Base\Controller as Controller_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Controller_Base { public function get_name() { return 'template-library'; } public function register_endpoints() { $this->register_endpoint( new Endpoints\Templates( $this ) ); } protected function register_index_endpoint() { // Bypass, currently does not required. } public function get_permission_callback( $request ) { return User::is_current_user_can_edit_post_type( Source_Local::CPT ); } } data/endpoints/templates.php 0000644 00000004421 14721615050 0012171 0 ustar 00 <?php namespace Elementor\Includes\TemplateLibrary\Data\Endpoints; use Elementor\Data\V2\Base\Endpoint; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Templates extends Endpoint { protected function register() { parent::register(); $document_types = Plugin::$instance->documents->get_document_types( [ 'show_in_library' => true, ] ); $this->register_route( '', \WP_REST_Server::CREATABLE, [ 'is_multi' => true, 'title' => [ 'required' => false, 'type' => 'string', 'description' => 'The title of the document', ], 'type' => [ 'required' => true, 'description' => 'The document type.', 'type' => 'string', 'enum' => array_keys( $document_types ), ], 'content' => [ 'required' => false, 'description' => 'Elementor data object', 'type' => 'object', ], ] ); } public function get_name() { return 'templates'; } public function get_format() { return 'template-library/templates'; } public function get_items( $request ) { return $this->reorder_categories( Plugin::$instance->templates_manager->get_library_data( [ 'filter_sources' => [ $request->get_param( 'source' ) ] ] ) ); } /** * Move the '404 page' category to the end of the list * * @param array $library_data * @return array */ private function reorder_categories( array $library_data ): array { $not_found_category = '404 page'; $key = array_search( $not_found_category, $library_data['config']['block']['categories'] ); if ( false === $key ) { return $library_data; } array_splice( $library_data['config']['block']['categories'], $key, 1 ); $library_data['config']['block']['categories'][] = $not_found_category; return $library_data; } public function create_items( $request ) { /** @var Source_Local $source */ $source = Plugin::$instance->templates_manager->get_source( 'local' ); $result = $source->save_item( [ 'title' => $request->get_param( 'title' ), 'type' => $request->get_param( 'type' ), 'content' => $request->get_param( 'content' ), 'page_settings' => $request->get_param( 'page_settings' ), ] ); if ( is_wp_error( $result ) ) { return $result; } return $source->get_item( $result ); } } manager.php 0000644 00000051317 14721615050 0006677 0 ustar 00 <?php namespace Elementor\TemplateLibrary; use Elementor\Api; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Isolation\Wordpress_Adapter; use Elementor\Core\Isolation\Wordpress_Adapter_Interface; use Elementor\Core\Isolation\Elementor_Adapter; use Elementor\Core\Isolation\Elementor_Adapter_Interface; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Includes\TemplateLibrary\Data\Controller; use Elementor\TemplateLibrary\Classes\Import_Images; use Elementor\Plugin; use Elementor\User; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor template library manager. * * Elementor template library manager handler class is responsible for * initializing the template library. * * @since 1.0.0 */ class Manager { /** * Registered template sources. * * Holds a list of all the supported sources with their instances. * * @access protected * * @var Source_Base[] */ protected $_registered_sources = []; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore /** * Imported template images. * * Holds an instance of `Import_Images` class. * * @access private * * @var Import_Images */ private $_import_images = null; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore /** * @var Wordpress_Adapter_Interface */ protected $wordpress_adapter = null; /** * @var Elementor_Adapter_Interface */ protected $elementor_adapter = null; /** * Template library manager constructor. * * Initializing the template library manager by registering default template * sources and initializing ajax calls. * * @since 1.0.0 * @access public */ public function __construct() { Plugin::$instance->data_manager_v2->register_controller( new Controller() ); $this->register_default_sources(); $this->add_actions(); } /** * @since 2.3.0 * @access public */ public function add_actions() { add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); add_action( 'wp_ajax_elementor_library_direct_actions', [ $this, 'handle_direct_actions' ] ); } /** * Get `Import_Images` instance. * * Retrieve the instance of the `Import_Images` class. * * @since 1.0.0 * @access public * * @return Import_Images Imported images instance. */ public function get_import_images_instance() { if ( null === $this->_import_images ) { $this->_import_images = new Import_Images(); } return $this->_import_images; } public function set_wordpress_adapter( Wordpress_Adapter_Interface $wordpress_adapter ) { $this->wordpress_adapter = $wordpress_adapter; } public function set_elementor_adapter( Elementor_Adapter_Interface $elementor_adapter ): void { $this->elementor_adapter = $elementor_adapter; } /** * Register template source. * * Used to register new template sources displayed in the template library. * * @since 1.0.0 * @access public * * @param string $source_class The name of source class. * @param array $args Optional. Class arguments. Default is an * empty array. * * @return \WP_Error|true True if the source was registered, `WP_Error` * otherwise. */ public function register_source( $source_class, $args = [] ) { if ( ! class_exists( $source_class ) ) { return new \WP_Error( 'source_class_name_not_exists' ); } $source_instance = new $source_class( $args ); if ( ! $source_instance instanceof Source_Base ) { return new \WP_Error( 'wrong_instance_source' ); } $source_id = $source_instance->get_id(); if ( isset( $this->_registered_sources[ $source_id ] ) ) { return new \WP_Error( 'source_exists' ); } $this->_registered_sources[ $source_id ] = $source_instance; return true; } /** * Unregister template source. * * Remove an existing template sources from the list of registered template * sources. * * @since 1.0.0 * @deprecated 2.7.0 * @access public * * @param string $id The source ID. * * @return bool Whether the source was unregistered. */ public function unregister_source( $id ) { return true; } /** * Get registered template sources. * * Retrieve registered template sources. * * @since 1.0.0 * @access public * * @return Source_Base[] Registered template sources. */ public function get_registered_sources() { return $this->_registered_sources; } /** * Get template source. * * Retrieve single template sources for a given template ID. * * @since 1.0.0 * @access public * * @param string $id The source ID. * * @return false|Source_Base Template sources if one exist, False otherwise. */ public function get_source( $id ) { $sources = $this->get_registered_sources(); if ( ! isset( $sources[ $id ] ) ) { return false; } return $sources[ $id ]; } /** * Get templates. * * Retrieve all the templates from all the registered sources. * * @param array $filter_sources * @param bool $force_update * @return array */ public function get_templates( array $filter_sources = [], bool $force_update = false ): array { $templates = []; foreach ( $this->get_registered_sources() as $source ) { if ( ! empty( $filter_sources ) && ! in_array( $source->get_id(), $filter_sources, true ) ) { continue; } $templates = array_merge( $templates, $source->get_items( [ 'force_update' => $force_update ] ) ); } return $templates; } /** * Get library data. * * Retrieve the library data. * * @since 1.9.0 * @access public * * @param array $args Library arguments. * * @return array Library data. */ public function get_library_data( array $args ) { $library_data = Api::get_library_data( ! empty( $args['sync'] ) ); if ( empty( $library_data ) ) { return $library_data; } // Ensure all document are registered. Plugin::$instance->documents->get_document_types(); $filter_sources = ! empty( $args['filter_sources'] ) ? $args['filter_sources'] : []; $force_update = ! empty( $args['sync'] ); return [ 'templates' => $this->get_templates( $filter_sources, $force_update ), 'config' => $library_data['types_data'], ]; } /** * Save template. * * Save new or update existing template on the database. * * @since 1.0.0 * @access public * * @param array $args Template arguments. * * @return \WP_Error|int The ID of the saved/updated template. */ public function save_template( array $args ) { $validate_args = $this->ensure_args( [ 'post_id', 'source', 'content', 'type' ], $args ); if ( is_wp_error( $validate_args ) ) { return $validate_args; } $source = $this->get_source( $args['source'] ); if ( ! $source ) { return new \WP_Error( 'template_error', 'Template source not found.' ); } $args['content'] = json_decode( $args['content'], true ); $page = SettingsManager::get_settings_managers( 'page' )->get_model( $args['post_id'] ); $args['page_settings'] = $page->get_data( 'settings' ); $template_id = $source->save_item( $args ); if ( is_wp_error( $template_id ) ) { return $template_id; } return $source->get_item( $template_id ); } /** * Update template. * * Update template on the database. * * @since 1.0.0 * @access public * * @param array $template_data New template data. * * @return \WP_Error|Source_Base Template sources instance if the templates * was updated, `WP_Error` otherwise. */ public function update_template( array $template_data ) { $validate_args = $this->ensure_args( [ 'source', 'content', 'type' ], $template_data ); if ( is_wp_error( $validate_args ) ) { return $validate_args; } $source = $this->get_source( $template_data['source'] ); if ( ! $source ) { return new \WP_Error( 'template_error', 'Template source not found.' ); } $template_data['content'] = json_decode( $template_data['content'], true ); $update = $source->update_item( $template_data ); if ( is_wp_error( $update ) ) { return $update; } return $source->get_item( $template_data['id'] ); } /** * Update templates. * * Update template on the database. * * @since 1.0.0 * @access public * * @param array $args Template arguments. * * @return \WP_Error|true True if templates updated, `WP_Error` otherwise. */ public function update_templates( array $args ) { foreach ( $args['templates'] as $template_data ) { $result = $this->update_template( $template_data ); if ( is_wp_error( $result ) ) { return $result; } } return true; } /** * Get template data. * * Retrieve the template data. * * @since 1.5.0 * @access public * * @param array $args Template arguments. * * @return \WP_Error|bool|array ?? */ public function get_template_data( array $args ) { $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args ); if ( is_wp_error( $validate_args ) ) { return $validate_args; } if ( ! $this->is_allowed_to_read_template( $args ) ) { return new \WP_Error( 'template_error', esc_html__( 'You do not have permission to access this template.', 'elementor' ) ); } if ( isset( $args['edit_mode'] ) ) { Plugin::$instance->editor->set_edit_mode( $args['edit_mode'] ); } $source = $this->get_source( $args['source'] ); if ( ! $source ) { return new \WP_Error( 'template_error', 'Template source not found.' ); } do_action( 'elementor/template-library/before_get_source_data', $args, $source ); $data = $source->get_data( $args ); do_action( 'elementor/template-library/after_get_source_data', $args, $source ); return $data; } /** * Delete template. * * Delete template from the database. * * @since 1.0.0 * @access public * * @param array $args Template arguments. * * @return \WP_Post|\WP_Error|false|null Post data on success, false or null * or 'WP_Error' on failure. */ public function delete_template( array $args ) { $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args ); if ( is_wp_error( $validate_args ) ) { return $validate_args; } $source = $this->get_source( $args['source'] ); if ( ! $source ) { return new \WP_Error( 'template_error', 'Template source not found.' ); } return $source->delete_template( $args['template_id'] ); } /** * Export template. * * Export template to a file after ensuring it is a valid Elementor template * and checking user permissions for private posts. * * @since 1.0.0 * @access public * * @param array $args Template arguments. * * @return mixed Whether the export succeeded or failed. */ public function export_template( array $args ) { $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args ); if ( is_wp_error( $validate_args ) ) { return $validate_args; } $post_id = intval( $args['template_id'] ); $post_status = get_post_status( $post_id ); if ( get_post_type( $post_id ) !== Source_Local::CPT ) { return new \WP_Error( 'template_error', esc_html__( 'Invalid template type or template does not exist.', 'elementor' ) ); } if ( 'private' === $post_status && ! current_user_can( 'read_private_posts', $post_id ) ) { return new \WP_Error( 'template_error', esc_html__( 'You do not have permission to access this template.', 'elementor' ) ); } if ( 'publish' !== $post_status && ! current_user_can( 'edit_post', $post_id ) ) { return new \WP_Error( 'template_error', esc_html__( 'You do not have permission to export this template.', 'elementor' ) ); } $source = $this->get_source( $args['source'] ); if ( ! $source ) { return new \WP_Error( 'template_error', 'Template source not found' ); } return $source->export_template( $args['template_id'] ); } /** * @since 2.3.0 * @access public */ public function direct_import_template() { /** @var Source_Local $source */ $source = $this->get_source( 'local' ); $file = Utils::get_super_global_value( $_FILES, 'file' ); return $source->import_template( $file['name'], $file['tmp_name'] ); } /** * Import template. * * Import template from a file. * * @since 1.0.0 * @access public * * @param array $data * * @return mixed Whether the export succeeded or failed. */ public function import_template( array $data ) { // If the template is a JSON file, allow uploading it. add_filter( 'elementor/files/allow-file-type/json', [ $this, 'enable_json_template_upload' ] ); add_filter( 'elementor/files/allow_unfiltered_upload', [ $this, 'enable_json_template_upload' ] ); // Imported templates can be either JSON files, or Zip files containing multiple JSON files $upload_result = Plugin::$instance->uploads_manager->handle_elementor_upload( $data, [ 'zip', 'json' ] ); remove_filter( 'elementor/files/allow-file-type/json', [ $this, 'enable_json_template_upload' ] ); remove_filter( 'elementor/files/allow_unfiltered_upload', [ $this, 'enable_json_template_upload' ] ); if ( is_wp_error( $upload_result ) ) { Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $upload_result['tmp_name'] ) ); return $upload_result; } /** @var Source_Local $source_local */ $source_local = $this->get_source( 'local' ); $import_result = $source_local->import_template( $upload_result['name'], $upload_result['tmp_name'] ); // Remove the temporary directory generated for the stream-uploaded file. Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $upload_result['tmp_name'] ) ); return $import_result; } /** * Enable JSON Template Upload * * Runs on the 'elementor/files/allow-file-type/json' Uploads Manager filter. * * @since 3.5.0 * @access public * * return bool */ public function enable_json_template_upload() { return true; } /** * Mark template as favorite. * * Add the template to the user favorite templates. * * @since 1.9.0 * @access public * * @param array $args Template arguments. * * @return mixed Whether the template marked as favorite. */ public function mark_template_as_favorite( $args ) { $validate_args = $this->ensure_args( [ 'source', 'template_id', 'favorite' ], $args ); if ( is_wp_error( $validate_args ) ) { return $validate_args; } $source = $this->get_source( $args['source'] ); return $source->mark_as_favorite( $args['template_id'], filter_var( $args['favorite'], FILTER_VALIDATE_BOOLEAN ) ); } public function import_from_json( array $args ) { $validate_args = $this->ensure_args( [ 'editor_post_id', 'elements' ], $args ); if ( is_wp_error( $validate_args ) ) { return $validate_args; } $elements = json_decode( $args['elements'], true ); $document = Plugin::$instance->documents->get( $args['editor_post_id'] ); if ( ! $document ) { return new \WP_Error( 'template_error', 'Document not found.' ); } $import_data = $document->get_import_data( [ 'content' => $elements ] ); return $import_data['content']; } /** * Register default template sources. * * Register the 'local' and 'remote' template sources that Elementor use by * default. * * @since 1.0.0 * @access private */ private function register_default_sources() { $sources = [ 'local', 'remote', ]; foreach ( $sources as $source_filename ) { $class_name = ucwords( $source_filename ); $class_name = str_replace( '-', '_', $class_name ); $this->register_source( __NAMESPACE__ . '\Source_' . $class_name ); } } /** * Handle ajax request. * * Fire authenticated ajax actions for any given ajax request. * * @since 1.0.0 * @access private * * @param string $ajax_request Ajax request. * * @param array $data * * @return mixed * @throws \Exception */ private function handle_ajax_request( $ajax_request, array $data ) { if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) { throw new \Exception( 'Access denied.' ); } if ( ! empty( $data['editor_post_id'] ) ) { $editor_post_id = absint( $data['editor_post_id'] ); if ( ! get_post( $editor_post_id ) ) { throw new \Exception( 'Post not found.' ); } Plugin::$instance->db->switch_to_post( $editor_post_id ); } $result = call_user_func( [ $this, $ajax_request ], $data ); if ( is_wp_error( $result ) ) { throw new \Exception( $result->get_error_message() ); } return $result; } /** * Init ajax calls. * * Initialize template library ajax calls for allowed ajax requests. * * @since 2.3.0 * @access public * * @param Ajax $ajax */ public function register_ajax_actions( Ajax $ajax ) { $library_ajax_requests = [ 'get_library_data', 'get_template_data', 'save_template', 'update_templates', 'delete_template', 'import_template', 'mark_template_as_favorite', 'import_from_json', ]; foreach ( $library_ajax_requests as $ajax_request ) { $ajax->register_ajax_action( $ajax_request, function( $data ) use ( $ajax_request ) { return $this->handle_ajax_request( $ajax_request, $data ); } ); } } /** * @since 2.3.0 * @access public */ public function handle_direct_actions() { if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) { return; } /** @var Ajax $ajax */ $ajax = Plugin::$instance->common->get_component( 'ajax' ); if ( ! $ajax->verify_request_nonce() ) { $this->handle_direct_action_error( 'Access Denied' ); } $action = Utils::get_super_global_value( $_REQUEST, 'library_action' ); // phpcs:ignore -- Nonce already verified. $whitelist_methods = [ 'export_template', 'direct_import_template', ]; if ( 'direct_import_template' === $action && ! User::is_current_user_can_upload_json() ) { return; } if ( in_array( $action, $whitelist_methods, true ) ) { $result = $this->$action( $_REQUEST ); // phpcs:ignore -- Nonce already verified. } else { $result = new \WP_Error( 'method_not_exists', 'Method Not exists' ); } if ( is_wp_error( $result ) ) { /** @var \WP_Error $result */ $this->handle_direct_action_error( $result->get_error_message() . '.' ); } $callback = "on_{$action}_success"; if ( method_exists( $this, $callback ) ) { $this->$callback( $result ); } die; } /** * On successful template import. * * Redirect the user to the template library after template import was * successful finished. * * @since 2.3.0 * @access private */ private function on_direct_import_template_success() { wp_safe_redirect( admin_url( Source_Local::ADMIN_MENU_SLUG ) ); } /** * @since 2.3.0 * @access private */ private function handle_direct_action_error( $message ) { _default_wp_die_handler( $message, 'Elementor Library' ); } /** * Ensure arguments exist. * * Checks whether the required arguments exist in the specified arguments. * * @since 1.0.0 * @access private * * @param array $required_args Required arguments to check whether they * exist. * @param array $specified_args The list of all the specified arguments to * check against. * * @return \WP_Error|true True on success, 'WP_Error' otherwise. */ private function ensure_args( array $required_args, array $specified_args ) { $not_specified_args = array_diff( $required_args, array_keys( $specified_args ) ); if ( $not_specified_args ) { return new \WP_Error( 'arguments_not_specified', sprintf( 'The required argument(s) "%s" not specified.', implode( ', ', $not_specified_args ) ) ); } return true; } private function is_allowed_to_read_template( array $args ): bool { if ( 'remote' === $args['source'] ) { return true; } if ( null === $this->wordpress_adapter ) { $this->set_wordpress_adapter( new WordPress_Adapter() ); } if ( ! $this->should_check_permissions( $args ) ) { return true; } $post_id = intval( $args['template_id'] ); $post_status = $this->wordpress_adapter->get_post_status( $post_id ); $is_private_or_non_published = ( 'private' === $post_status && ! $this->wordpress_adapter->current_user_can( 'read_private_posts', $post_id ) ) || ( 'publish' !== $post_status ); $can_read_template = $is_private_or_non_published || $this->wordpress_adapter->current_user_can( 'edit_post', $post_id ); return apply_filters( 'elementor/template-library/is_allowed_to_read_template', $can_read_template, $args ); } private function should_check_permissions( array $args ): bool { if ( null === $this->elementor_adapter ) { $this->set_elementor_adapter( new Elementor_Adapter() ); } // TODO: Remove $isWidgetTemplate in 3.28.0 as there is a Pro dependency $check_permissions = isset( $args['check_permissions'] ) && false === $args['check_permissions']; $is_widget_template = 'widget' === $this->elementor_adapter->get_template_type( $args['template_id'] ); if ( $check_permissions || $is_widget_template ) { return false; } return true; } } sources/base.php 0000644 00000020720 14721615050 0007654 0 ustar 00 <?php namespace Elementor\TemplateLibrary; use Elementor\Controls_Stack; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor template library source base. * * Elementor template library source base handler class is responsible for * initializing all the methods controlling the source of Elementor templates. * * @since 1.0.0 * @abstract */ abstract class Source_Base { /** * User meta. * * Holds the current user meta data. * * @access private * * @var array */ private $user_meta; /** * Get template ID. * * Retrieve the template ID. * * @since 1.0.0 * @access public * @abstract */ abstract public function get_id(); /** * Get template title. * * Retrieve the template title. * * @since 1.0.0 * @access public * @abstract */ abstract public function get_title(); /** * Register template data. * * Used to register custom template data like a post type, a taxonomy or any * other data. * * @since 1.0.0 * @access public * @abstract */ abstract public function register_data(); /** * Get templates. * * Retrieve templates from the template library. * * @since 1.0.0 * @access public * @abstract * * @param array $args Optional. Filter templates list based on a set of * arguments. Default is an empty array. */ abstract public function get_items( $args = [] ); /** * Get template. * * Retrieve a single template from the template library. * * @since 1.0.0 * @access public * @abstract * * @param int $template_id The template ID. */ abstract public function get_item( $template_id ); /** * Get template data. * * Retrieve a single template data from the template library. * * @since 1.5.0 * @access public * @abstract * * @param array $args Custom template arguments. */ abstract public function get_data( array $args ); /** * Delete template. * * Delete template from the database. * * @since 1.0.0 * @access public * @abstract * * @param int $template_id The template ID. */ abstract public function delete_template( $template_id ); /** * Save template. * * Save new or update existing template on the database. * * @since 1.0.0 * @access public * @abstract * * @param array $template_data The template data. */ abstract public function save_item( $template_data ); /** * Update template. * * Update template on the database. * * @since 1.0.0 * @access public * @abstract * * @param array $new_data New template data. */ abstract public function update_item( $new_data ); /** * Export template. * * Export template to a file. * * @since 1.0.0 * @access public * @abstract * * @param int $template_id The template ID. */ abstract public function export_template( $template_id ); /** * Template library source base constructor. * * Initializing the template library source base by registering custom * template data. * * @since 1.0.0 * @access public */ public function __construct() { $this->register_data(); } /** * Mark template as favorite. * * Update user meta containing his favorite templates. For a given template * ID, add the template to the favorite templates or remove it from the * favorites, based on the `favorite` parameter. * * @since 1.9.0 * @access public * * @param int $template_id The template ID. * @param bool $favorite Optional. Whether the template is marked as * favorite, or not. Default is true. * * @return int|bool User meta ID if the key didn't exist, true on successful * update, false on failure. */ public function mark_as_favorite( $template_id, $favorite = true ) { $favorites_templates = $this->get_user_meta( 'favorites' ); if ( ! $favorites_templates ) { $favorites_templates = []; } if ( $favorite ) { $favorites_templates[ $template_id ] = $favorite; } elseif ( isset( $favorites_templates[ $template_id ] ) ) { unset( $favorites_templates[ $template_id ] ); } return $this->update_user_meta( 'favorites', $favorites_templates ); } /** * Get current user meta. * * Retrieve Elementor meta data for the current user. * * @since 1.9.0 * @access public * * @param string $item Optional. User meta key. Default is null. * * @return null|array An array of user meta data, or null otherwise. */ public function get_user_meta( $item = null ) { if ( null === $this->user_meta ) { $this->user_meta = get_user_meta( get_current_user_id(), $this->get_user_meta_prefix(), true ); } if ( ! $this->user_meta ) { $this->user_meta = []; } if ( $item ) { if ( isset( $this->user_meta[ $item ] ) ) { return $this->user_meta[ $item ]; } return null; } return $this->user_meta; } /** * Update current user meta. * * Update user meta data based on meta key an value. * * @since 1.9.0 * @access public * * @param string $key Optional. User meta key. * @param mixed $value Optional. User meta value. * * @return int|bool User meta ID if the key didn't exist, true on successful * update, false on failure. */ public function update_user_meta( $key, $value ) { $meta = $this->get_user_meta(); $meta[ $key ] = $value; $this->user_meta = $meta; return update_user_meta( get_current_user_id(), $this->get_user_meta_prefix(), $meta ); } /** * Replace elements IDs. * * For any given Elementor content/data, replace the IDs with new randomly * generated IDs. * * @since 1.0.0 * @access protected * * @param array $content Any type of Elementor data. * * @return mixed Iterated data. */ protected function replace_elements_ids( $content ) { return Plugin::$instance->db->iterate_data( $content, function( $element ) { $element['id'] = Utils::generate_random_string(); return $element; } ); } /** * Get Elementor library user meta prefix. * * Retrieve user meta prefix used to save Elementor data. * * @since 1.9.0 * @access protected * * @return string User meta prefix. */ protected function get_user_meta_prefix() { return 'elementor_library_' . $this->get_id(); } /** * Process content for export/import. * * Process the content and all the inner elements, and prepare all the * elements data for export/import. * * @since 1.5.0 * @access protected * * @param array $content A set of elements. * @param string $method Accepts either `on_export` to export data or * `on_import` to import data. * * @return mixed Processed content data. */ protected function process_export_import_content( $content, $method ) { return Plugin::$instance->db->iterate_data( $content, function( $element_data ) use ( $method ) { $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); // If the widget/element isn't exist, like a plugin that creates a widget but deactivated if ( ! $element ) { return null; } return $this->process_element_export_import_content( $element, $method ); } ); } /** * Process single element content for export/import. * * Process any given element and prepare the element data for export/import. * * @since 1.5.0 * @access protected * * @param Controls_Stack $element * @param string $method * * @return array Processed element data. */ protected function process_element_export_import_content( Controls_Stack $element, $method ) { $element_data = $element->get_data(); if ( method_exists( $element, $method ) ) { // TODO: Use the internal element data without parameters. $element_data = $element->{$method}( $element_data ); } foreach ( $element->get_controls() as $control ) { $control_class = Plugin::$instance->controls_manager->get_control( $control['type'] ); // If the control isn't exist, like a plugin that creates the control but deactivated. if ( ! $control_class ) { return $element_data; } if ( method_exists( $control_class, $method ) ) { $element_data['settings'][ $control['name'] ] = $control_class->{$method}( $element->get_settings( $control['name'] ), $control ); } // On Export, check if the control has an argument 'export' => false. if ( 'on_export' === $method && isset( $control['export'] ) && false === $control['export'] ) { unset( $element_data['settings'][ $control['name'] ] ); } } return $element_data; } } sources/local.php 0000644 00000140576 14721615050 0010050 0 ustar 00 <?php namespace Elementor\TemplateLibrary; use Elementor\Core\Admin\Menu\Admin_Menu_Manager; use Elementor\Core\Base\Document; use Elementor\Core\Editor\Editor; use Elementor\Core\Utils\Collection; use Elementor\DB; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Core\Settings\Page\Model; use Elementor\Includes\TemplateLibrary\Sources\AdminMenuItems\Add_New_Template_Menu_Item; use Elementor\Includes\TemplateLibrary\Sources\AdminMenuItems\Saved_Templates_Menu_Item; use Elementor\Includes\TemplateLibrary\Sources\AdminMenuItems\Templates_Categories_Menu_Item; use Elementor\Modules\Library\Documents\Library_Document; use Elementor\Plugin; use Elementor\Utils; use Elementor\User; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor template library local source. * * Elementor template library local source handler class is responsible for * handling local Elementor templates saved by the user locally on his site. * * @since 1.0.0 */ class Source_Local extends Source_Base { /** * Elementor template-library post-type slug. */ const CPT = 'elementor_library'; /** * Elementor template-library taxonomy slug. */ const TAXONOMY_TYPE_SLUG = 'elementor_library_type'; /** * Elementor template-library category slug. */ const TAXONOMY_CATEGORY_SLUG = 'elementor_library_category'; /** * Elementor template-library meta key. * * @deprecated 2.3.0 Use `Elementor\Core\Base\Document::TYPE_META_KEY` const instead. */ const TYPE_META_KEY = '_elementor_template_type'; /** * Elementor template-library temporary files folder. */ const TEMP_FILES_DIR = 'elementor/tmp'; /** * Elementor template-library bulk export action name. */ const BULK_EXPORT_ACTION = 'elementor_export_multiple_templates'; const ADMIN_MENU_SLUG = 'edit.php?post_type=elementor_library'; const ADMIN_MENU_PRIORITY = 10; const ADMIN_SCREEN_ID = 'edit-elementor_library'; /** * Template types. * * Holds the list of supported template types that can be displayed. * * @access private * @static * * @var array */ private static $template_types = []; /** * Post type object. * * Holds the post type object of the current post. * * @access private * * @var \WP_Post_Type */ private $post_type_object; /** * @since 2.3.0 * @access public * @static * @return array */ public static function get_template_types() { return self::$template_types; } /** * Get local template type. * * Retrieve the template type from the post meta. * * @since 1.0.0 * @access public * @static * * @param int $template_id The template ID. * * @return mixed The value of meta data field. */ public static function get_template_type( $template_id ) { return get_post_meta( $template_id, Document::TYPE_META_KEY, true ); } /** * Is base templates screen. * * Whether the current screen base is edit and the post type is template. * * @since 1.0.0 * @access public * @static * * @return bool True on base templates screen, False otherwise. */ public static function is_base_templates_screen() { global $current_screen; if ( ! $current_screen ) { return false; } return 'edit' === $current_screen->base && self::CPT === $current_screen->post_type; } /** * Add template type. * * Register new template type to the list of supported local template types. * * @since 1.0.3 * @access public * @static * * @param string $type Template type. */ public static function add_template_type( $type ) { self::$template_types[ $type ] = $type; } /** * Remove template type. * * Remove existing template type from the list of supported local template * types. * * @since 1.8.0 * @access public * @static * * @param string $type Template type. */ public static function remove_template_type( $type ) { if ( isset( self::$template_types[ $type ] ) ) { unset( self::$template_types[ $type ] ); } } public static function get_admin_url( $relative = false ) { $base_url = self::ADMIN_MENU_SLUG; if ( ! $relative ) { $base_url = admin_url( $base_url ); } return add_query_arg( 'tabs_group', 'library', $base_url ); } /** * Get local template ID. * * Retrieve the local template ID. * * @since 1.0.0 * @access public * * @return string The local template ID. */ public function get_id() { return 'local'; } /** * Get local template title. * * Retrieve the local template title. * * @since 1.0.0 * @access public * * @return string The local template title. */ public function get_title() { return esc_html__( 'Local', 'elementor' ); } /** * Register local template data. * * Used to register custom template data like a post type, a taxonomy or any * other data. * * The local template class registers a new `elementor_library` post type * and an `elementor_library_type` taxonomy. They are used to store data for * local templates saved by the user on his site. * * @since 1.0.0 * @access public */ public function register_data() { $labels = [ 'name' => esc_html_x( 'My Templates', 'Template Library', 'elementor' ), 'singular_name' => esc_html_x( 'Template', 'Template Library', 'elementor' ), 'add_new' => esc_html__( 'Add New Template', 'elementor' ), 'add_new_item' => esc_html__( 'Add New Template', 'elementor' ), 'edit_item' => esc_html__( 'Edit Template', 'elementor' ), 'new_item' => esc_html__( 'New Template', 'elementor' ), 'all_items' => esc_html__( 'All Templates', 'elementor' ), 'view_item' => esc_html__( 'View Template', 'elementor' ), 'search_items' => esc_html__( 'Search Template', 'elementor' ), 'not_found' => esc_html__( 'No Templates found', 'elementor' ), 'not_found_in_trash' => esc_html__( 'No Templates found in Trash', 'elementor' ), 'parent_item_colon' => esc_html__( 'Parent Template:', 'elementor' ), 'menu_name' => esc_html_x( 'Templates', 'Template Library', 'elementor' ), ]; $args = [ 'labels' => $labels, 'public' => true, 'rewrite' => false, 'menu_icon' => 'dashicons-admin-page', 'show_ui' => true, 'show_in_menu' => true, 'show_in_nav_menus' => false, 'exclude_from_search' => true, 'capability_type' => 'post', 'hierarchical' => false, 'supports' => [ 'title', 'thumbnail', 'author', 'elementor' ], ]; /** * Register template library post type args. * * Filters the post type arguments when registering elementor template library post type. * * @since 1.0.0 * * @param array $args Arguments for registering a post type. */ $args = apply_filters( 'elementor/template_library/sources/local/register_post_type_args', $args ); $this->post_type_object = register_post_type( self::CPT, $args ); $args = [ 'hierarchical' => false, 'show_ui' => false, 'show_in_nav_menus' => false, 'show_admin_column' => true, 'query_var' => is_admin(), 'rewrite' => false, 'public' => false, 'label' => esc_html_x( 'Type', 'Template Library', 'elementor' ), ]; /** * Register template library taxonomy args. * * Filters the taxonomy arguments when registering elementor template library taxonomy. * * @since 1.0.0 * * @param array $args Arguments for registering a taxonomy. */ $args = apply_filters( 'elementor/template_library/sources/local/register_taxonomy_args', $args ); $cpts_to_associate = [ self::CPT ]; /** * Custom post types to associate. * * Filters the list of custom post types when registering elementor template library taxonomy. * * @since 1.0.0 * * @param array $cpts_to_associate Custom post types. Default is `elementor_library` post type. */ $cpts_to_associate = apply_filters( 'elementor/template_library/sources/local/register_taxonomy_cpts', $cpts_to_associate ); register_taxonomy( self::TAXONOMY_TYPE_SLUG, $cpts_to_associate, $args ); /** * Categories */ $args = [ 'hierarchical' => true, 'show_ui' => true, 'show_in_nav_menus' => false, 'show_admin_column' => true, 'query_var' => is_admin(), 'rewrite' => false, 'public' => false, 'labels' => [ 'name' => esc_html_x( 'Categories', 'Template Library', 'elementor' ), 'singular_name' => esc_html_x( 'Category', 'Template Library', 'elementor' ), 'all_items' => esc_html_x( 'All Categories', 'Template Library', 'elementor' ), ], ]; /** * Register template library category args. * * Filters the category arguments when registering elementor template library category. * * @since 2.4.0 * * @param array $args Arguments for registering a category. */ $args = apply_filters( 'elementor/template_library/sources/local/register_category_args', $args ); register_taxonomy( self::TAXONOMY_CATEGORY_SLUG, self::CPT, $args ); } /** * Remove Add New item from admin menu. * * Fired by `admin_menu` action. * * @since 2.4.0 * @access public */ private function admin_menu_reorder( Admin_Menu_Manager $admin_menu ) { global $submenu; if ( ! isset( $submenu[ static::ADMIN_MENU_SLUG ] ) ) { return; } remove_submenu_page( static::ADMIN_MENU_SLUG, static::ADMIN_MENU_SLUG ); $add_new_slug = 'post-new.php?post_type=' . static::CPT; $category_slug = 'edit-tags.php?taxonomy=' . static::TAXONOMY_CATEGORY_SLUG . '&post_type=' . static::CPT; $library_submenu = new Collection( $submenu[ static::ADMIN_MENU_SLUG ] ); $add_new_item = $library_submenu->find( function ( $item ) use ( $add_new_slug ) { return $add_new_slug === $item[2]; } ); $categories_item = $library_submenu->find( function ( $item ) use ( $category_slug ) { return $category_slug === $item[2]; } ); if ( $add_new_item ) { remove_submenu_page( static::ADMIN_MENU_SLUG, $add_new_slug ); $admin_menu->register( admin_url( static::ADMIN_MENU_SLUG . '#add_new' ), new Add_New_Template_Menu_Item() ); } if ( $categories_item ) { remove_submenu_page( static::ADMIN_MENU_SLUG, $category_slug ); $admin_menu->register( $category_slug, new Templates_Categories_Menu_Item() ); } } /** * Add a `current` CSS class to the `Saved Templates` submenu page when it's active. * It should work by default, but since we interfere with WordPress' builtin CPT menus it doesn't work properly. * * @return void */ private function admin_menu_set_current() { global $submenu; if ( $this->is_current_screen() ) { $library_submenu = &$submenu[ static::ADMIN_MENU_SLUG ]; $library_title = $this->get_library_title(); foreach ( $library_submenu as &$item ) { if ( $library_title === $item[0] ) { if ( ! isset( $item[4] ) ) { $item[4] = ''; } $item[4] .= ' current'; } } } } private function register_admin_menu( Admin_Menu_Manager $admin_menu ) { $admin_menu->register( static::get_admin_url( true ), new Saved_Templates_Menu_Item() ); } public function admin_title( $admin_title, $title ) { $library_title = $this->get_library_title(); if ( $library_title ) { $admin_title = str_replace( $title, $library_title, $admin_title ); } return $admin_title; } public function replace_admin_heading() { $library_title = $this->get_library_title(); if ( $library_title ) { global $post_type_object; $post_type_object->labels->name = $library_title; } } /** * Get local templates. * * Retrieve local templates saved by the user on his site. * * @since 1.0.0 * @access public * * @param array $args Optional. Filter templates based on a set of * arguments. Default is an empty array. * * @return array Local templates. */ public function get_items( $args = [] ) { $template_types = array_values( self::$template_types ); if ( ! empty( $args['type'] ) ) { $template_types = $args['type']; unset( $args['type'] ); } $defaults = [ 'post_type' => self::CPT, 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC', 'meta_query' => [ [ 'key' => Document::TYPE_META_KEY, 'value' => $template_types, ], ], ]; $query_args = wp_parse_args( $args, $defaults ); $templates_query = new \WP_Query( $query_args ); $templates = []; if ( $templates_query->have_posts() ) { foreach ( $templates_query->get_posts() as $post ) { $templates[] = $this->get_item( $post->ID ); } } return $templates; } /** * Save local template. * * Save new or update existing template on the database. * * @since 1.0.0 * @access public * * @param array $template_data Local template data. * * @return \WP_Error|int The ID of the saved/updated template, `WP_Error` otherwise. */ public function save_item( $template_data ) { if ( ! current_user_can( $this->post_type_object->cap->edit_posts ) ) { return new \WP_Error( 'save_error', esc_html__( 'Access denied.', 'elementor' ) ); } $defaults = [ 'title' => esc_html__( '(no title)', 'elementor' ), 'page_settings' => [], ]; $template_data = wp_parse_args( $template_data, $defaults ); $template_data['status'] = current_user_can( 'publish_posts' ) ? 'publish' : 'pending'; // BC: Allow importing any template type when using CLI // to support users that rely on this mechanism. $should_check_template_type = ! $this->is_wp_cli(); if ( $should_check_template_type && ! $this->is_valid_template_type( $template_data['type'] ) ) { return new \WP_Error( 'invalid_template_type', esc_html__( 'Invalid template type.', 'elementor' ) ); } $document = Plugin::$instance->documents->create( $template_data['type'], [ 'post_title' => $template_data['title'], 'post_status' => $template_data['status'], ] ); if ( is_wp_error( $document ) ) { /** * @var \WP_Error $document */ return $document; } if ( ! empty( $template_data['content'] ) ) { $template_data['content'] = $this->replace_elements_ids( $template_data['content'] ); } $document->save( [ 'elements' => $template_data['content'], 'settings' => $template_data['page_settings'], ] ); $template_id = $document->get_main_id(); /** * After template library save. * * Fires after Elementor template library was saved. * * @since 1.0.1 * * @param int $template_id The ID of the template. * @param array $template_data The template data. */ do_action( 'elementor/template-library/after_save_template', $template_id, $template_data ); /** * After template library update. * * Fires after Elementor template library was updated. * * @since 1.0.1 * * @param int $template_id The ID of the template. * @param array $template_data The template data. */ do_action( 'elementor/template-library/after_update_template', $template_id, $template_data ); return $template_id; } protected function is_valid_template_type( $type ) { $document_class = Plugin::$instance->documents->get_document_type( $type, false ); if ( ! $document_class ) { return false; } $cpt = $document_class::get_property( 'cpt' ); if ( ! $cpt || ! is_array( $cpt ) || 1 !== count( $cpt ) ) { return false; } $is_valid_template_type = in_array( static::CPT, $cpt, true ); return apply_filters( 'elementor/template_library/sources/local/is_valid_template_type', $is_valid_template_type, $cpt, ); } // For testing purposes only, in order to be able to mock the `WP_CLI` constant. protected function is_wp_cli() { return Utils::is_wp_cli(); } /** * Update local template. * * Update template on the database. * * @since 1.0.0 * @access public * * @param array $new_data New template data. * * @return \WP_Error|true True if template updated, `WP_Error` otherwise. */ public function update_item( $new_data ) { if ( ! current_user_can( $this->post_type_object->cap->edit_post, $new_data['id'] ) ) { return new \WP_Error( 'save_error', esc_html__( 'Access denied.', 'elementor' ) ); } $document = Plugin::$instance->documents->get( $new_data['id'] ); if ( ! $document ) { return new \WP_Error( 'save_error', esc_html__( 'Template not exist.', 'elementor' ) ); } $document->save( [ 'elements' => $new_data['content'], ] ); /** * After template library update. * * Fires after Elementor template library was updated. * * @since 1.0.0 * * @param int $new_data_id The ID of the new template. * @param array $new_data The new template data. */ do_action( 'elementor/template-library/after_update_template', $new_data['id'], $new_data ); return true; } /** * Get local template. * * Retrieve a single local template saved by the user on his site. * * @since 1.0.0 * @access public * * @param int $template_id The template ID. * * @return array Local template. */ public function get_item( $template_id ) { $post = get_post( $template_id ); $user = get_user_by( 'id', $post->post_author ); $page = SettingsManager::get_settings_managers( 'page' )->get_model( $template_id ); $page_settings = $page->get_data( 'settings' ); $date = strtotime( $post->post_date ); $data = [ 'template_id' => $post->ID, 'source' => $this->get_id(), 'type' => self::get_template_type( $post->ID ), 'title' => $post->post_title, 'thumbnail' => get_the_post_thumbnail_url( $post ), 'date' => $date, 'human_date' => date_i18n( get_option( 'date_format' ), $date ), 'human_modified_date' => date_i18n( get_option( 'date_format' ), strtotime( $post->post_modified ) ), 'author' => $user->display_name, 'status' => $post->post_status, 'hasPageSettings' => ! empty( $page_settings ), 'tags' => [], 'export_link' => $this->get_export_link( $template_id ), 'url' => get_permalink( $post->ID ), ]; /** * Get template library template. * * Filters the template data when retrieving a single template from the * template library. * * @since 1.0.0 * * @param array $data Template data. */ $data = apply_filters( 'elementor/template-library/get_template', $data ); return $data; } /** * Get template data. * * Retrieve the data of a single local template saved by the user on his site. * * @since 1.5.0 * @access public * * @param array $args Custom template arguments. * * @return array Local template data. */ public function get_data( array $args ) { $template_id = $args['template_id']; $document = Plugin::$instance->documents->get( $template_id ); $content = []; if ( $document ) { // TODO: Validate the data (in JS too!). if ( ! empty( $args['display'] ) ) { $content = $document->get_elements_raw_data( null, true ); } else { $content = $document->get_elements_data(); } if ( ! empty( $content ) ) { $content = $this->replace_elements_ids( $content ); } } $data = [ 'content' => $content, ]; if ( ! empty( $args['with_page_settings'] ) ) { $page = SettingsManager::get_settings_managers( 'page' )->get_model( $args['template_id'] ); $data['page_settings'] = $page->get_data( 'settings' ); } return $data; } /** * Delete local template. * * Delete template from the database. * * @since 1.0.0 * @access public * * @param int $template_id The template ID. * * @return \WP_Post|\WP_Error|false|null Post data on success, false or null * or 'WP_Error' on failure. */ public function delete_template( $template_id ) { if ( ! current_user_can( $this->post_type_object->cap->delete_post, $template_id ) ) { return new \WP_Error( 'template_error', esc_html__( 'Access denied.', 'elementor' ) ); } return wp_delete_post( $template_id, true ); } /** * Export local template. * * Export template to a file. * * @since 1.0.0 * @access public * * @param int $template_id The template ID. * * @return \WP_Error WordPress error if template export failed. */ public function export_template( $template_id ) { $file_data = $this->prepare_template_export( $template_id ); if ( is_wp_error( $file_data ) ) { return $file_data; } $this->send_file_headers( $file_data['name'], strlen( $file_data['content'] ) ); // Clear buffering just in case. @ob_end_clean(); flush(); // Output file contents. // PHPCS - Export widget json echo $file_data['content']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped die; } /** * Export multiple local templates. * * Export multiple template to a ZIP file. * * @since 1.6.0 * @access public * * @param array $template_ids An array of template IDs. * * @return \WP_Error WordPress error if export failed. */ public function export_multiple_templates( array $template_ids ) { $files = []; $wp_upload_dir = wp_upload_dir(); $temp_path = $wp_upload_dir['basedir'] . '/' . self::TEMP_FILES_DIR; // Create temp path if it doesn't exist wp_mkdir_p( $temp_path ); // Create all json files foreach ( $template_ids as $template_id ) { $file_data = $this->prepare_template_export( $template_id ); if ( is_wp_error( $file_data ) ) { continue; } $complete_path = $temp_path . '/' . $file_data['name']; $put_contents = file_put_contents( $complete_path, $file_data['content'] ); if ( ! $put_contents ) { return new \WP_Error( '404', sprintf( 'Cannot create file "%s".', $file_data['name'] ) ); } $files[] = [ 'path' => $complete_path, 'name' => $file_data['name'], ]; } if ( ! $files ) { return new \WP_Error( 'empty_files', 'There is no files to export (probably all the requested templates are empty).' ); } // Create temporary .zip file $zip_archive_filename = 'elementor-templates-' . gmdate( 'Y-m-d' ) . '.zip'; $zip_archive = new \ZipArchive(); $zip_complete_path = $temp_path . '/' . $zip_archive_filename; $zip_archive->open( $zip_complete_path, \ZipArchive::CREATE ); foreach ( $files as $file ) { $zip_archive->addFile( $file['path'], $file['name'] ); } $zip_archive->close(); foreach ( $files as $file ) { unlink( $file['path'] ); } $this->send_file_headers( $zip_archive_filename, filesize( $zip_complete_path ) ); @ob_end_flush(); @readfile( $zip_complete_path ); unlink( $zip_complete_path ); die; } /** * Import local template. * * Import template from a file. * * @since 1.0.0 * @access public * * @param string $name - The file name * @param string $path - The file path * @return \WP_Error|array An array of items on success, 'WP_Error' on failure. */ public function import_template( $name, $path ) { if ( empty( $path ) ) { return new \WP_Error( 'file_error', 'Please upload a file to import' ); } // Set the Request's state as an Elementor upload request, in order to support unfiltered file uploads. Plugin::$instance->uploads_manager->set_elementor_upload_state( true ); $items = []; // If the import file is a Zip file with potentially multiple JSON files if ( 'zip' === pathinfo( $name, PATHINFO_EXTENSION ) ) { $extracted_files = Plugin::$instance->uploads_manager->extract_and_validate_zip( $path, [ 'json' ] ); if ( is_wp_error( $extracted_files ) ) { // Delete the temporary extraction directory, since it's now not necessary. Plugin::$instance->uploads_manager->remove_file_or_dir( $extracted_files['extraction_directory'] ); return $extracted_files; } foreach ( $extracted_files['files'] as $file_path ) { $import_result = $this->import_single_template( $file_path ); if ( is_wp_error( $import_result ) ) { // Delete the temporary extraction directory, since it's now not necessary. Plugin::$instance->uploads_manager->remove_file_or_dir( $extracted_files['extraction_directory'] ); return $import_result; } $items[] = $import_result; } // Delete the temporary extraction directory, since it's now not necessary. Plugin::$instance->uploads_manager->remove_file_or_dir( $extracted_files['extraction_directory'] ); } else { // If the import file is a single JSON file $import_result = $this->import_single_template( $path ); if ( is_wp_error( $import_result ) ) { return $import_result; } $items[] = $import_result; } return $items; } /** * Post row actions. * * Add an export link to the template library action links table list. * * Fired by `post_row_actions` filter. * * @since 1.0.0 * @access public * * @param array $actions An array of row action links. * @param \WP_Post $post The post object. * * @return array An updated array of row action links. */ public function post_row_actions( $actions, \WP_Post $post ) { if ( self::is_base_templates_screen() ) { if ( $this->is_template_supports_export( $post->ID ) ) { $actions['export-template'] = sprintf( '<a href="%1$s">%2$s</a>', $this->get_export_link( $post->ID ), esc_html__( 'Export Template', 'elementor' ) ); } } return $actions; } /** * Admin import template form. * * The import form displayed in "My Library" screen in WordPress dashboard. * * The form allows the user to import template in json/zip format to the site. * * Fired by `admin_footer` action. * * @since 1.0.0 * @access public */ public function admin_import_template_form() { if ( ! self::is_base_templates_screen() || ! User::is_current_user_can_upload_json() ) { return; } /** @var \Elementor\Core\Common\Modules\Ajax\Module $ajax */ $ajax = Plugin::$instance->common->get_component( 'ajax' ); ?> <div id="elementor-hidden-area"> <a id="elementor-import-template-trigger" class="page-title-action"><?php echo esc_html__( 'Import Templates', 'elementor' ); ?></a> <div id="elementor-import-template-area"> <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> <form id="elementor-import-template-form" method="post" action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>" enctype="multipart/form-data"> <input type="hidden" name="action" value="elementor_library_direct_actions"> <input type="hidden" name="library_action" value="direct_import_template"> <input type="hidden" name="_nonce" value="<?php Utils::print_unescaped_internal_string( $ajax->create_nonce() ); ?>"> <fieldset id="elementor-import-template-form-inputs"> <input type="file" name="file" accept=".json,application/json,.zip,application/octet-stream,application/zip,application/x-zip,application/x-zip-compressed" required> <input id="e-import-template-action" type="submit" class="button" value="<?php echo esc_attr__( 'Import Now', 'elementor' ); ?>"> </fieldset> </form> </div> </div> <?php } /** * Block template frontend * * Don't display the single view of the template library post type in the * frontend, for users that don't have the proper permissions. * * Fired by `template_redirect` action. * * @since 1.0.0 * @access public */ public function block_template_frontend() { if ( is_singular( self::CPT ) && ! current_user_can( Editor::EDITING_CAPABILITY ) ) { wp_safe_redirect( site_url(), 301 ); die; } } /** * Is template library supports export. * * whether the template library supports export. * * Template saved by the user locally on his site, support export by default * but this can be changed using a filter. * * @since 1.0.0 * @access public * * @param int $template_id The template ID. * * @return bool Whether the template library supports export. */ public function is_template_supports_export( $template_id ) { $export_support = true; /** * Is template library supports export. * * Filters whether the template library supports export. * * @since 1.0.0 * * @param bool $export_support Whether the template library supports export. * Default is true. * @param int $template_id Post ID. */ $export_support = apply_filters( 'elementor/template_library/is_template_supports_export', $export_support, $template_id ); return $export_support; } /** * Remove Elementor post state. * * Remove the 'elementor' post state from the display states of the post. * * Used to remove the 'elementor' post state from the template library items. * * Fired by `display_post_states` filter. * * @since 1.8.0 * @access public * * @param array $post_states An array of post display states. * @param \WP_Post $post The current post object. * * @return array Updated array of post display states. */ public function remove_elementor_post_state_from_library( $post_states, $post ) { if ( self::CPT === $post->post_type && isset( $post_states['elementor'] ) ) { unset( $post_states['elementor'] ); } return $post_states; } /** * Get template export link. * * Retrieve the link used to export a single template based on the template * ID. * * @since 2.0.0 * @access private * * @param int $template_id The template ID. * * @return string Template export URL. */ private function get_export_link( $template_id ) { // TODO: BC since 2.3.0 - Use `$ajax->create_nonce()` /** @var \Elementor\Core\Common\Modules\Ajax\Module $ajax */ // $ajax = Plugin::$instance->common->get_component( 'ajax' ); return add_query_arg( [ 'action' => 'elementor_library_direct_actions', 'library_action' => 'export_template', 'source' => $this->get_id(), '_nonce' => wp_create_nonce( 'elementor_ajax' ), 'template_id' => $template_id, ], admin_url( 'admin-ajax.php' ) ); } /** * On template save. * * Run this method when template is being saved. * * Fired by `save_post` action. * * @since 1.0.1 * @access public * * @param int $post_id Post ID. * @param \WP_Post $post The current post object. */ public function on_save_post( $post_id, \WP_Post $post ) { if ( self::CPT !== $post->post_type ) { return; } if ( self::get_template_type( $post_id ) ) { // It's already with a type return; } // Don't save type on import, the importer will do it. if ( did_action( 'import_start' ) ) { return; } $this->save_item_type( $post_id, 'page' ); } /** * Save item type. * * When saving/updating templates, this method is used to update the post * meta data and the taxonomy. * * @since 1.0.1 * @access private * * @param int $post_id Post ID. * @param string $type Item type. */ private function save_item_type( $post_id, $type ) { update_post_meta( $post_id, Document::TYPE_META_KEY, $type ); wp_set_object_terms( $post_id, $type, self::TAXONOMY_TYPE_SLUG ); } /** * Bulk export action. * * Adds an 'Export' action to the Bulk Actions drop-down in the template * library. * * Fired by `bulk_actions-edit-elementor_library` filter. * * @since 1.6.0 * @access public * * @param array $actions An array of the available bulk actions. * * @return array An array of the available bulk actions. */ public function admin_add_bulk_export_action( $actions ) { $actions[ self::BULK_EXPORT_ACTION ] = esc_html__( 'Export', 'elementor' ); return $actions; } /** * Add bulk export action. * * Handles the template library bulk export action. * * Fired by `handle_bulk_actions-edit-elementor_library` filter. * * @since 1.6.0 * @access public * * @param string $redirect_to The redirect URL. * @param string $action The action being taken. * @param array $post_ids The items to take the action on. */ public function admin_export_multiple_templates( $redirect_to, $action, $post_ids ) { if ( self::BULK_EXPORT_ACTION === $action ) { $result = $this->export_multiple_templates( $post_ids ); // If you reach this line, the export failed // PHPCS - Not user input. wp_die( $result->get_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } /** * Print admin tabs. * * Used to output the template library tabs with their labels. * * Fired by `views_edit-elementor_library` filter. * * @since 2.0.0 * @access public * * @param array $views An array of available list table views. * * @return array An updated array of available list table views. */ public function admin_print_tabs( $views ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required to retrieve the value. $current_type = Utils::get_super_global_value( $_REQUEST, self::TAXONOMY_TYPE_SLUG ) ?? ''; $active_class = $current_type ? '' : ' nav-tab-active'; $current_tabs_group = $this->get_current_tab_group(); $url_args = [ 'post_type' => self::CPT, 'tabs_group' => $current_tabs_group, ]; $baseurl = add_query_arg( $url_args, admin_url( 'edit.php' ) ); $filter = [ 'admin_tab_group' => $current_tabs_group, ]; $operator = 'and'; if ( empty( $current_tabs_group ) ) { // Don't include 'not-supported' or other templates that don't set their `admin_tab_group`. $operator = 'NOT'; } $doc_types = Plugin::$instance->documents->get_document_types( $filter, $operator ); if ( 1 >= count( $doc_types ) ) { return $views; } ?> <div id="elementor-template-library-tabs-wrapper" class="nav-tab-wrapper"> <a class="nav-tab<?php echo esc_attr( $active_class ); ?>" href="<?php echo esc_url( $baseurl ); ?>"> <?php $all_title = $this->get_library_title(); if ( ! $all_title ) { $all_title = esc_html__( 'All', 'elementor' ); } Utils::print_unescaped_internal_string( $all_title ); ?> </a> <?php foreach ( $doc_types as $type => $class_name ) : $active_class = ''; if ( $current_type === $type ) { $active_class = ' nav-tab-active'; } $type_url = esc_url( add_query_arg( self::TAXONOMY_TYPE_SLUG, $type, $baseurl ) ); $type_label = $this->get_template_label_by_type( $type ); Utils::print_unescaped_internal_string( "<a class='nav-tab{$active_class}' href='{$type_url}'>{$type_label}</a>" ); endforeach; ?> </div> <?php return $views; } /** * Maybe render blank state. * * When the template library has no saved templates, display a blank admin page offering * to create the very first template. * * Fired by `manage_posts_extra_tablenav` action. * * @since 2.0.0 * @access public * * @param string $which The location of the extra table nav markup: 'top' or 'bottom'. * @param array $args */ public function maybe_render_blank_state( $which, array $args = [] ) { global $post_type; $args = wp_parse_args( $args, [ 'cpt' => self::CPT, 'post_type' => get_query_var( 'elementor_library_type' ), ] ); if ( $args['cpt'] !== $post_type || 'bottom' !== $which ) { return; } global $wp_list_table; $total_items = $wp_list_table->get_pagination_arg( 'total_items' ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required to retrieve the value. if ( ! empty( $total_items ) || ! empty( $_REQUEST['s'] ) ) { return; } $current_type = $args['post_type']; $document_types = Plugin::instance()->documents->get_document_types(); if ( empty( $document_types[ $current_type ] ) ) { return; } // TODO: Better way to exclude widget type. if ( 'widget' === $current_type ) { return; } // TODO: This code maybe unreachable see if above `if ( empty( $document_types[ $current_type ] ) )`. if ( empty( $current_type ) ) { $counts = (array) wp_count_posts( self::CPT ); unset( $counts['auto-draft'] ); $count = array_sum( $counts ); if ( 0 < $count ) { return; } $current_type = 'template'; $args['additional_inline_style'] = '#elementor-template-library-tabs-wrapper {display: none;}'; } $this->render_blank_state( $current_type, $args ); } private function render_blank_state( $current_type, array $args = [] ) { $current_type_label = $this->get_template_label_by_type( $current_type ); $inline_style = '#posts-filter .wp-list-table, #posts-filter .tablenav.top, .tablenav.bottom .actions, .wrap .subsubsub { display:none;}'; $args = wp_parse_args( $args, [ 'additional_inline_style' => '', 'href' => '', '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' ), ] ); $inline_style .= $args['additional_inline_style']; ?> <style type="text/css"><?php Utils::print_unescaped_internal_string( $inline_style ); ?></style> <div class="elementor-template_library-blank_state"> <?php $this->print_blank_state_template( $current_type_label, $args['href'], $args['description'] ); ?> </div> <?php } /** * Print Blank State Template * * When the an entity (CPT, Taxonomy...etc) has no saved items, print a blank admin page offering * to create the very first item. * * This method is public because it needs to be accessed from outside the Source_Local * * @since 3.1.0 * @access public * * @param string $current_type_label The Entity title * @param string $href The URL for the 'Add New' button * @param string $description The sub title describing the Entity (Post Type, Taxonomy, etc.) */ public function print_blank_state_template( $current_type_label, $href, $description ) { ?> <div class="elementor-blank_state"> <i class="eicon-folder"></i> <h3> <?php /* translators: %s: Template type label. */ printf( esc_html__( 'Create Your First %s', 'elementor' ), esc_html( $current_type_label ) ); ?> </h3> <p><?php echo wp_kses_post( $description ); ?></p> <a id="elementor-template-library-add-new" class="elementor-button e-primary" href="<?php echo esc_url( $href ); ?>"> <?php /* translators: %s: Template type label. */ printf( esc_html__( 'Add New %s', 'elementor' ), esc_html( $current_type_label ) ); ?> </a> </div> <?php } /** * Add filter by category. * * In the templates library, add a filter by Elementor library category. * * @access public * * @param string $post_type The post type slug. */ public function add_filter_by_category( $post_type ) { if ( self::CPT !== $post_type ) { return; } $all_items = get_taxonomy( self::TAXONOMY_CATEGORY_SLUG )->labels->all_items; $dropdown_options = array( 'show_option_all' => $all_items, 'show_option_none' => $all_items, 'hide_empty' => 0, 'hierarchical' => 1, 'show_count' => 0, 'orderby' => 'name', 'value_field' => 'slug', 'taxonomy' => self::TAXONOMY_CATEGORY_SLUG, 'name' => self::TAXONOMY_CATEGORY_SLUG, //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required to retrieve the value. 'selected' => Utils::get_super_global_value( $_GET, self::TAXONOMY_CATEGORY_SLUG ) ?? '', ); printf( '<label class="screen-reader-text" for="%1$s">%2$s</label>', esc_attr( self::TAXONOMY_CATEGORY_SLUG ), esc_html_x( 'Filter by category', 'Template Library', 'elementor' ) ); wp_dropdown_categories( $dropdown_options ); } /** * Import single template. * * Import template from a file to the database. * * @since 1.6.0 * @access private * * @param string $file_path File name. * * @return \WP_Error|int|array Local template array, or template ID, or * `WP_Error`. */ private function import_single_template( $file_path ) { $data = json_decode( Utils::file_get_contents( $file_path ), true ); if ( empty( $data ) ) { return new \WP_Error( 'file_error', 'Invalid File' ); } $content = $data['content']; if ( ! is_array( $content ) ) { return new \WP_Error( 'file_error', 'Invalid Content In File' ); } $content = $this->process_export_import_content( $content, 'on_import' ); $page_settings = []; if ( ! empty( $data['page_settings'] ) ) { $page = new Model( [ 'id' => 0, 'settings' => $data['page_settings'], ] ); $page_settings_data = $this->process_element_export_import_content( $page, 'on_import' ); if ( ! empty( $page_settings_data['settings'] ) ) { $page_settings = $page_settings_data['settings']; } } $template_id = $this->save_item( [ 'content' => $content, 'title' => $data['title'], 'type' => $data['type'], 'page_settings' => $page_settings, ] ); if ( is_wp_error( $template_id ) ) { return $template_id; } return $this->get_item( $template_id ); } /** * Prepare template to export. * * Retrieve the relevant template data and return them as an array. * * @since 1.6.0 * @access private * * @param int $template_id The template ID. * * @return \WP_Error|array Exported template data. */ private function prepare_template_export( $template_id ) { $document = Plugin::$instance->documents->get( $template_id ); $template_data = $document->get_export_data(); if ( empty( $template_data['content'] ) ) { return new \WP_Error( 'empty_template', 'The template is empty' ); } $export_data = [ 'content' => $template_data['content'], 'page_settings' => $template_data['settings'], 'version' => DB::DB_VERSION, 'title' => $document->get_main_post()->post_title, 'type' => self::get_template_type( $template_id ), ]; return [ 'name' => 'elementor-' . $template_id . '-' . gmdate( 'Y-m-d' ) . '.json', 'content' => wp_json_encode( $export_data ), ]; } /** * Send file headers. * * Set the file header when export template data to a file. * * @since 1.6.0 * @access private * * @param string $file_name File name. * @param int $file_size File size. */ private function send_file_headers( $file_name, $file_size ) { header( 'Content-Type: application/octet-stream' ); header( 'Content-Disposition: attachment; filename=' . $file_name ); header( 'Expires: 0' ); header( 'Cache-Control: must-revalidate' ); header( 'Pragma: public' ); header( 'Content-Length: ' . $file_size ); } /** * Get template label by type. * * Retrieve the template label for any given template type. * * @since 2.0.0 * @access private * * @param string $template_type Template type. * * @return string Template label. */ private function get_template_label_by_type( $template_type ) { $document_types = Plugin::instance()->documents->get_document_types(); if ( isset( $document_types[ $template_type ] ) ) { $template_label = call_user_func( [ $document_types[ $template_type ], 'get_title' ] ); } else { $template_label = ucwords( str_replace( [ '_', '-' ], ' ', $template_type ) ); } /** * Template label by template type. * * Filters the template label by template type in the template library . * * @since 2.0.0 * * @param string $template_label Template label. * @param string $template_type Template type. */ $template_label = apply_filters( 'elementor/template-library/get_template_label_by_type', $template_label, $template_type ); return $template_label; } /** * Filter template types in admin query. * * Update the template types in the main admin query. * * Fired by `parse_query` action. * * @since 2.4.0 * @access public * * @param \WP_Query $query The `WP_Query` instance. */ public function admin_query_filter_types( \WP_Query $query ) { if ( ! $this->is_current_screen() || ! empty( $query->query_vars['meta_key'] ) ) { return; } $current_tabs_group = $this->get_current_tab_group(); if ( isset( $query->query_vars[ self::TAXONOMY_CATEGORY_SLUG ] ) && '-1' === $query->query_vars[ self::TAXONOMY_CATEGORY_SLUG ] ) { unset( $query->query_vars[ self::TAXONOMY_CATEGORY_SLUG ] ); } if ( empty( $current_tabs_group ) ) { return; } $doc_types = Plugin::$instance->documents->get_document_types( [ 'admin_tab_group' => $current_tabs_group, ] ); $query->query_vars['meta_key'] = Document::TYPE_META_KEY; $query->query_vars['meta_value'] = array_keys( $doc_types ); } /** * Add template library actions. * * Register filters and actions for the template library. * * @since 2.0.0 * @access private */ private function add_actions() { if ( is_admin() ) { add_action( 'elementor/admin/menu/register', function ( Admin_Menu_Manager $admin_menu ) { $this->register_admin_menu( $admin_menu ); }, static::ADMIN_MENU_PRIORITY ); add_action( 'elementor/admin/menu/register', function ( Admin_Menu_Manager $admin_menu ) { $this->admin_menu_reorder( $admin_menu ); }, 800 ); add_action( 'elementor/admin/menu/after_register', function () { $this->admin_menu_set_current(); } ); add_filter( 'admin_title', [ $this, 'admin_title' ], 10, 2 ); add_action( 'all_admin_notices', [ $this, 'replace_admin_heading' ] ); add_filter( 'post_row_actions', [ $this, 'post_row_actions' ], 10, 2 ); add_action( 'admin_footer', [ $this, 'admin_import_template_form' ] ); add_action( 'save_post', [ $this, 'on_save_post' ], 10, 2 ); add_filter( 'display_post_states', [ $this, 'remove_elementor_post_state_from_library' ], 11, 2 ); add_action( 'parse_query', [ $this, 'admin_query_filter_types' ] ); // Template filter by category. add_action( 'restrict_manage_posts', [ $this, 'add_filter_by_category' ] ); // Template type column. add_action( 'manage_' . self::CPT . '_posts_columns', [ $this, 'admin_columns_headers' ] ); add_action( 'manage_' . self::CPT . '_posts_custom_column', [ $this, 'admin_columns_content' ], 10, 2 ); // Template library bulk actions. add_filter( 'bulk_actions-edit-elementor_library', [ $this, 'admin_add_bulk_export_action' ] ); add_filter( 'handle_bulk_actions-edit-elementor_library', [ $this, 'admin_export_multiple_templates' ], 10, 3 ); // Print template library tabs. add_filter( 'views_edit-' . self::CPT, [ $this, 'admin_print_tabs' ] ); // Show blank state. add_action( 'manage_posts_extra_tablenav', [ $this, 'maybe_render_blank_state' ] ); } add_action( 'template_redirect', [ $this, 'block_template_frontend' ] ); // Remove elementor library templates from WP Sitemap add_filter( 'wp_sitemaps_post_types', function( $post_types ) { return $this->remove_elementor_cpt_from_sitemap( $post_types ); } ); } /** * @since 2.0.6 * @access public */ public function admin_columns_content( $column_name, $post_id ) { if ( 'elementor_library_type' === $column_name ) { /** @var Document $document */ $document = Plugin::$instance->documents->get( $post_id ); if ( $document && $document instanceof Library_Document ) { $document->print_admin_column_type(); } } } /** * @since 2.0.6 * @access public */ public function admin_columns_headers( $posts_columns ) { // Replace original column that bind to the taxonomy - with another column. unset( $posts_columns['taxonomy-elementor_library_type'] ); $offset = 2; $posts_columns = array_slice( $posts_columns, 0, $offset, true ) + [ 'elementor_library_type' => esc_html__( 'Type', 'elementor' ), ] + array_slice( $posts_columns, $offset, null, true ); return $posts_columns; } public function get_current_tab_group( $default = '' ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. $current_tabs_group = Utils::get_super_global_value( $_REQUEST, 'tabs_group' ) ?? ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. $type_slug = Utils::get_super_global_value( $_REQUEST, self::TAXONOMY_TYPE_SLUG ); if ( $type_slug ) { $doc_type = Plugin::$instance->documents->get_document_type( $type_slug, '' ); if ( $doc_type ) { $current_tabs_group = $doc_type::get_property( 'admin_tab_group' ); } } return $current_tabs_group; } private function get_library_title() { $title = ''; if ( $this->is_current_screen() ) { $current_tab_group = $this->get_current_tab_group(); if ( $current_tab_group ) { $titles = [ 'library' => esc_html__( 'Saved Templates', 'elementor' ), 'theme' => esc_html__( 'Theme Builder', 'elementor' ), 'popup' => esc_html__( 'Popups', 'elementor' ), ]; if ( ! empty( $titles[ $current_tab_group ] ) ) { $title = $titles[ $current_tab_group ]; } } } return $title; } private function is_current_screen() { global $pagenow, $typenow; return 'edit.php' === $pagenow && self::CPT === $typenow; } /** * @param array $post_types * * @return array */ private function remove_elementor_cpt_from_sitemap( array $post_types ) { unset( $post_types[ self::CPT ] ); return $post_types; } /** * Template library local source constructor. * * Initializing the template library local source base by registering custom * template data and running custom actions. * * @since 1.0.0 * @access public */ public function __construct() { parent::__construct(); $this->add_actions(); } } sources/remote.php 0000644 00000021705 14721615050 0010241 0 ustar 00 <?php namespace Elementor\TemplateLibrary; use Elementor\Api; use Elementor\Core\Common\Modules\Connect\Module as ConnectModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor template library remote source. * * Elementor template library remote source handler class is responsible for * handling remote templates from Elementor.com servers. * * @since 1.0.0 */ class Source_Remote extends Source_Base { const API_TEMPLATES_URL = 'https://my.elementor.com/api/connect/v1/library/templates'; const TEMPLATES_DATA_TRANSIENT_KEY_PREFIX = 'elementor_remote_templates_data_'; public function __construct() { parent::__construct(); $this->add_actions(); } public function add_actions() { add_action( 'elementor/experiments/feature-state-change/container', [ $this, 'clear_cache' ], 10, 0 ); } /** * Get remote template ID. * * Retrieve the remote template ID. * * @since 1.0.0 * @access public * * @return string The remote template ID. */ public function get_id() { return 'remote'; } /** * Get remote template title. * * Retrieve the remote template title. * * @since 1.0.0 * @access public * * @return string The remote template title. */ public function get_title() { return esc_html__( 'Remote', 'elementor' ); } /** * Register remote template data. * * Used to register custom template data like a post type, a taxonomy or any * other data. * * @since 1.0.0 * @access public */ public function register_data() {} /** * Get remote templates. * * Retrieve remote templates from Elementor.com servers. * * @since 1.0.0 * @access public * * @param array $args Optional. Not used in remote source. * * @return array Remote templates. */ public function get_items( $args = [] ) { $force_update = ! empty( $args['force_update'] ) && is_bool( $args['force_update'] ); $templates_data = $this->get_templates_data( $force_update ); $templates = []; foreach ( $templates_data as $template_data ) { $templates[] = $this->prepare_template( $template_data ); } return $templates; } /** * Get remote template. * * Retrieve a single remote template from Elementor.com servers. * * @since 1.0.0 * @access public * * @param int $template_id The template ID. * * @return array Remote template. */ public function get_item( $template_id ) { $templates = $this->get_items(); return $templates[ $template_id ]; } /** * Save remote template. * * Remote template from Elementor.com servers cannot be saved on the * database as they are retrieved from remote servers. * * @since 1.0.0 * @access public * * @param array $template_data Remote template data. * * @return \WP_Error */ public function save_item( $template_data ) { return new \WP_Error( 'invalid_request', 'Cannot save template to a remote source' ); } /** * Update remote template. * * Remote template from Elementor.com servers cannot be updated on the * database as they are retrieved from remote servers. * * @since 1.0.0 * @access public * * @param array $new_data New template data. * * @return \WP_Error */ public function update_item( $new_data ) { return new \WP_Error( 'invalid_request', 'Cannot update template to a remote source' ); } /** * Delete remote template. * * Remote template from Elementor.com servers cannot be deleted from the * database as they are retrieved from remote servers. * * @since 1.0.0 * @access public * * @param int $template_id The template ID. * * @return \WP_Error */ public function delete_template( $template_id ) { return new \WP_Error( 'invalid_request', 'Cannot delete template from a remote source' ); } /** * Export remote template. * * Remote template from Elementor.com servers cannot be exported from the * database as they are retrieved from remote servers. * * @since 1.0.0 * @access public * * @param int $template_id The template ID. * * @return \WP_Error */ public function export_template( $template_id ) { return new \WP_Error( 'invalid_request', 'Cannot export template from a remote source' ); } /** * Get remote template data. * * Retrieve the data of a single remote template from Elementor.com servers. * * @since 1.5.0 * @access public * * @param array $args Custom template arguments. * @param string $context Optional. The context. Default is `display`. * * @return array|\WP_Error Remote Template data. */ public function get_data( array $args, $context = 'display' ) { $data = Api::get_template_content( $args['template_id'] ); if ( is_wp_error( $data ) ) { return $data; } // Set the Request's state as an Elementor upload request, in order to support unfiltered file uploads. Plugin::$instance->uploads_manager->set_elementor_upload_state( true ); // BC. $data = (array) $data; $data['content'] = $this->replace_elements_ids( $data['content'] ); $data['content'] = $this->process_export_import_content( $data['content'], 'on_import' ); $post_id = $args['editor_post_id']; $document = Plugin::$instance->documents->get( $post_id ); if ( $document ) { $data['content'] = $document->get_elements_raw_data( $data['content'], true ); } // After the upload complete, set the elementor upload state back to false Plugin::$instance->uploads_manager->set_elementor_upload_state( false ); return $data; } /** * Get templates data from a transient or from a remote request. * In any of the following 2 conditions, the remote request will be triggered: * 1. Force update - "$force_update = true" parameter was passed. * 2. The data saved in the transient is empty or not exist. * * @param bool $force_update * @return array */ private function get_templates_data( bool $force_update ) : array { $templates_data_cache_key = static::TEMPLATES_DATA_TRANSIENT_KEY_PREFIX . ELEMENTOR_VERSION; $experiments_manager = Plugin::$instance->experiments; $editor_layout_type = $experiments_manager->is_feature_active( 'container' ) ? 'container_flexbox' : ''; if ( $force_update ) { return $this->get_templates( $editor_layout_type ); } $templates_data = get_transient( $templates_data_cache_key ); if ( empty( $templates_data ) ) { return $this->get_templates( $editor_layout_type ); } return $templates_data; } /** * Get the templates from a remote server and set a transient. * * @param string $editor_layout_type * @return array */ private function get_templates( string $editor_layout_type ): array { $templates_data_cache_key = static::TEMPLATES_DATA_TRANSIENT_KEY_PREFIX . ELEMENTOR_VERSION; $templates_data = $this->get_templates_remotely( $editor_layout_type ); if ( empty( $templates_data ) ) { return []; } set_transient( $templates_data_cache_key, $templates_data, 12 * HOUR_IN_SECONDS ); return $templates_data; } /** * Fetch templates from the remote server. * * @param string $editor_layout_type * @return array|false */ private function get_templates_remotely( string $editor_layout_type ) { $response = wp_remote_get( static::API_TEMPLATES_URL, [ 'body' => [ 'plugin_version' => ELEMENTOR_VERSION, 'editor_layout_type' => $editor_layout_type, ], ] ); if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { return false; } $templates_data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( empty( $templates_data ) || ! is_array( $templates_data ) ) { return []; } return $templates_data; } /** * @since 2.2.0 * @access private */ private function prepare_template( array $template_data ) { $favorite_templates = $this->get_user_meta( 'favorites' ); // BC: Support legacy APIs that don't have access tiers. if ( isset( $template_data['access_tier'] ) ) { $access_tier = $template_data['access_tier']; } else { $access_tier = 0 === $template_data['access_level'] ? ConnectModule::ACCESS_TIER_FREE : ConnectModule::ACCESS_TIER_ESSENTIAL; } return [ 'template_id' => $template_data['id'], 'source' => $this->get_id(), 'type' => $template_data['type'], 'subtype' => $template_data['subtype'], 'title' => $template_data['title'], 'thumbnail' => $template_data['thumbnail'], 'date' => $template_data['tmpl_created'], 'author' => $template_data['author'], 'tags' => json_decode( $template_data['tags'] ), 'isPro' => ( '1' === $template_data['is_pro'] ), 'accessLevel' => $template_data['access_level'], 'accessTier' => $access_tier, 'popularityIndex' => (int) $template_data['popularity_index'], 'trendIndex' => (int) $template_data['trend_index'], 'hasPageSettings' => ( '1' === $template_data['has_page_settings'] ), 'url' => $template_data['url'], 'favorite' => ! empty( $favorite_templates[ $template_data['id'] ] ), ]; } public function clear_cache() { delete_transient( static::TEMPLATES_DATA_TRANSIENT_KEY_PREFIX . ELEMENTOR_VERSION ); } } sources/admin-menu-items/templates-categories-menu-item.php 0000644 00000001200 14721615050 0020122 0 ustar 00 <?php namespace Elementor\Includes\TemplateLibrary\Sources\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item; use Elementor\Core\Editor\Editor; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Templates_Categories_Menu_Item implements Admin_Menu_Item { public function is_visible() { return true; } public function get_parent_slug() { return Source_Local::ADMIN_MENU_SLUG; } public function get_label() { return esc_html__( 'Categories', 'elementor' ); } public function get_capability() { return 'manage_categories'; } } sources/admin-menu-items/saved-templates-menu-item.php 0000644 00000001207 14721615050 0017106 0 ustar 00 <?php namespace Elementor\Includes\TemplateLibrary\Sources\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item; use Elementor\Core\Editor\Editor; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Saved_Templates_Menu_Item implements Admin_Menu_Item { public function is_visible() { return true; } public function get_parent_slug() { return Source_Local::ADMIN_MENU_SLUG; } public function get_label() { return esc_html__( 'Saved Templates', 'elementor' ); } public function get_capability() { return Editor::EDITING_CAPABILITY; } } sources/admin-menu-items/add-new-template-menu-item.php 0000644 00000001200 14721615050 0017131 0 ustar 00 <?php namespace Elementor\Includes\TemplateLibrary\Sources\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item; use Elementor\Core\Editor\Editor; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Add_New_Template_Menu_Item implements Admin_Menu_Item { public function is_visible() { return true; } public function get_parent_slug() { return Source_Local::ADMIN_MENU_SLUG; } public function get_label() { return esc_html__( 'Add New', 'elementor' ); } public function get_capability() { return Editor::EDITING_CAPABILITY; } } forms/new-template-form.php 0000644 00000003132 14721615050 0011746 0 ustar 00 <?php namespace Elementor\TemplateLibrary\Forms; use Elementor\Controls_Manager; use Elementor\Controls_Stack; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class New_Template_Form extends Controls_Stack { public function get_name() { return 'add-template-form'; } /** * @throws \Exception */ public function render() { foreach ( $this->get_controls() as $control ) { switch ( $control['type'] ) { case Controls_Manager::SELECT: $this->render_select( $control ); break; default: throw new \Exception( "'{$control['type']}' control type is not supported." ); } } } private function render_select( $control_settings ) { $control_id = "elementor-new-template__form__{$control_settings['name']}"; $wrapper_class = isset( $control_settings['wrapper_class'] ) ? $control_settings['wrapper_class'] : ''; ?> <div id="<?php echo esc_attr( $control_id ); ?>__wrapper" class="elementor-form-field <?php echo esc_attr( $wrapper_class ); ?>"> <label for="<?php echo esc_attr( $control_id ); ?>" class="elementor-form-field__label"> <?php echo esc_html( $control_settings['label'] ); ?> </label> <div class="elementor-form-field__select__wrapper"> <select id="<?php echo esc_attr( $control_id ); ?>" class="elementor-form-field__select" name="meta[<?php echo esc_html( $control_settings['name'] ); ?>]"> <?php foreach ( $control_settings['options'] as $key => $value ) { echo sprintf( '<option value="%1$s">%2$s</option>', esc_html( $key ), esc_html( $value ) ); } ?> </select> </div> </div> <?php } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.3-4ubuntu2.24 | Генерация страницы: 0.01 |
proxy
|
phpinfo
|
Настройка