File manager - Edit - /home/dxgrqzvp/public_html/hairbymimmie/NewFolder/app.tar
Back
view.php 0000644 00000001324 15233532517 0006235 0 ustar 00 <?php namespace Elementor\App; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @var App $this */ $theme_class = 'dark' === $this->get_elementor_ui_theme_preference() ? 'eps-theme-dark' : ''; ?> <!DOCTYPE html> <html <?php language_attributes(); ?>> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title><?php echo esc_html__( 'Elementor', 'elementor' ) . ' ... '; ?></title> <base target="_parent"> <?php wp_print_styles(); ?> </head> <body class="<?php Utils::print_unescaped_internal_string( $theme_class ); ?>"> <div id="e-app"></div> <?php wp_print_footer_scripts(); ?> </body> </html> modules/site-editor/module.php 0000644 00000002055 15233532517 0012452 0 ustar 00 <?php namespace Elementor\App\Modules\SiteEditor; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Site Editor Module * * Responsible for initializing Elementor App functionality */ class Module extends BaseModule { /** * Get name. * * @access public * * @return string */ public function get_name() { return 'site-editor'; } public function add_menu_in_admin_bar( $admin_bar_config ) { $admin_bar_config['elementor_edit_page']['children'][] = [ 'id' => 'elementor_app_site_editor', 'title' => esc_html__( 'Theme Builder', 'elementor' ), 'sub_title' => esc_html__( 'Site', 'elementor' ), 'href' => Plugin::$instance->app->get_settings( 'menu_url' ), 'class' => 'elementor-app-link', 'parent_class' => 'elementor-second-section', ]; return $admin_bar_config; } public function __construct() { add_filter( 'elementor/frontend/admin_bar/settings', [ $this, 'add_menu_in_admin_bar' ] ); // After kit (Site settings) } } modules/import-export/wp-cli.php 0000644 00000017673 15233532517 0012775 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport; use Elementor\Core\Utils\Collection; use Elementor\Core\Utils\Plugins_Manager; use Elementor\Plugin; use Elementor\App\Modules\KitLibrary\Connect\Kit_Library; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Wp_Cli extends \WP_CLI_Command { const AVAILABLE_SETTINGS = [ 'include', 'overrideConditions', 'selectedCustomPostTypes', 'plugins' ]; /** * Export a Kit * * [--include] * Which type of content to include. Possible values are 'content', 'templates', 'site-settings'. * if this parameter won't be specified, All data types will be included. * * ## EXAMPLES * * 1. wp elementor kit export path/to/export-file-name.zip * - This will export all site data to the specified file name. * * 2. wp elementor kit export path/to/export-file-name.zip --include=kit-settings,content * - This will export only site settings and content. * * @param array $args * @param array $assoc_args */ public function export( $args, $assoc_args ) { if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please specify a file name' ); } \WP_CLI::line( 'Kit export started.' ); $export_settings = []; foreach ( $assoc_args as $key => $value ) { if ( ! in_array( $key, static::AVAILABLE_SETTINGS, true ) ) { continue; } $export_settings[ $key ] = explode( ',', $value ); } try { /** * Running the export process through the import-export module so the export property in the module will be available to use. * * @type Module $import_export_module */ $import_export_module = Plugin::$instance->app->get_component( 'import-export' ); $result = $import_export_module->export_kit( $export_settings ); rename( $result['file_name'], $args[0] ); } catch ( \Error $error ) { \WP_CLI::error( $error->getMessage() ); } \WP_CLI::success( 'Kit exported successfully.' ); } /** * Import a Kit * * [--include] * Which type of content to include. Possible values are 'content', 'templates', 'site-settings'. * if this parameter won't be specified, All data types will be included. * * [--overrideConditions] * Templates ids to override conditions for. * * [--sourceType] * Which source type is used in the current session. Available values are 'local', 'remote', 'library'. * The default value is 'local' * * ## EXAMPLES * * 1. wp elementor kit import path/to/elementor-kit.zip * - This will import the whole kit file content. * * 2. wp elementor kit import path/to/elementor-kit.zip --include=site-settings,content * - This will import only site settings and content. * * 3. wp elementor kit import path/to/elementor-kit.zip --overrideConditions=3478,4520 * - This will import all content and will override conditions for the given template ids. * * 4. wp elementor kit import path/to/elementor-kit.zip --unfilteredFilesUpload=enable * - This will allow the import process to import unfiltered files. * * @param array $args * @param array $assoc_args */ public function import( array $args, array $assoc_args ) { if ( ! current_user_can( 'manage_options' ) ) { \WP_CLI::error( 'You must run this command as an admin user' ); } if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please specify a file to import' ); } \WP_CLI::line( 'Kit import started' ); $assoc_args = wp_parse_args( $assoc_args, [ 'sourceType' => 'local', ] ); $url = null; $file_path = $args[0]; $import_settings = []; $import_settings['referrer'] = Module::REFERRER_LOCAL; switch ( $assoc_args['sourceType'] ) { case 'library': $url = $this->get_url_from_library( $file_path ); $zip_path = $this->create_temp_file_from_url( $url ); $import_settings['referrer'] = Module::REFERRER_KIT_LIBRARY; break; case 'remote': $zip_path = $this->create_temp_file_from_url( $file_path ); break; case 'local': $zip_path = $file_path; break; default: \WP_CLI::error( 'Unknown source type.' ); break; } if ( 'enable' === $assoc_args['unfilteredFilesUpload'] ) { Plugin::$instance->uploads_manager->enable_unfiltered_files_upload(); } foreach ( $assoc_args as $key => $value ) { if ( ! in_array( $key, static::AVAILABLE_SETTINGS, true ) ) { continue; } $import_settings[ $key ] = explode( ',', $value ); } try { \WP_CLI::line( 'Importing data...' ); /** * Running the import process through the import-export module so the import property in the module will be available to use. * * @type Module $import_export_module */ $import_export_module = Plugin::$instance->app->get_component( 'import-export' ); if ( ! $import_export_module ) { \WP_CLI::error( 'Import Export module is not available.' ); } $import = $import_export_module->import_kit( $zip_path, $import_settings ); $manifest_data = $import_export_module->import->get_manifest(); /** * Import Export Manifest Data * * Allows 3rd parties to read and edit the kit's manifest before it is used. * * @since 3.7.0 * * @param array $manifest_data The Kit's Manifest data */ $manifest_data = apply_filters( 'elementor/import-export/wp-cli/manifest_data', $manifest_data ); \WP_CLI::line( 'Removing temp files...' ); // The file was created from remote or library request, it also should be removed. if ( $url ) { Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $zip_path ) ); } \WP_CLI::success( 'Kit imported successfully' ); } catch ( \Error $error ) { Plugin::$instance->logger->get_logger()->error( $error->getMessage(), [ 'meta' => [ 'trace' => $error->getTraceAsString(), ], ] ); if ( $url ) { Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $zip_path ) ); } \WP_CLI::error( $error->getMessage() ); } } /** * Revert last imported kit. */ public function revert() { \WP_CLI::line( 'Kit revert started.' ); try { /** * Running the revert process through the import-export module so the revert property in the module will be available to use. * * @type Module $import_export_module */ $import_export_module = Plugin::$instance->app->get_component( 'import-export' ); $import_export_module->revert_last_imported_kit(); } catch ( \Error $error ) { \WP_CLI::error( $error->getMessage() ); } \WP_CLI::success( 'Kit reverted successfully.' ); } /** * Helper to get kit url by the kit id * TODO: Maybe extract it. * * @param $kit_id * * @return string */ private function get_url_from_library( $kit_id ) { /** @var Kit_Library $app */ $app = Plugin::$instance->common->get_component( 'connect' )->get_app( 'kit-library' ); if ( ! $app ) { \WP_CLI::error( 'Kit library app not found' ); } $response = $app->download_link( $kit_id ); if ( is_wp_error( $response ) ) { \WP_CLI::error( "Library Response: {$response->get_error_message()}" ); } return $response->download_link; } /** * Helper to get kit zip file path by the kit url * TODO: Maybe extract it. * * @param $url * * @return string */ private function create_temp_file_from_url( $url ) { \WP_CLI::line( 'Extracting zip archive...' ); $response = wp_remote_get( $url ); if ( is_wp_error( $response ) ) { \WP_CLI::error( "Download file url: {$response->get_error_message()}" ); } if ( 200 !== $response['response']['code'] ) { \WP_CLI::error( "Download file url: {$response['response']['message']}" ); } // 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 ); $file = Plugin::$instance->uploads_manager->create_temp_file( $response['body'], 'kit.zip' ); // After the upload complete, set the elementor upload state back to false. Plugin::$instance->uploads_manager->set_elementor_upload_state( false ); return $file; } } modules/import-export/compatibility/kit-library.php 0000644 00000001362 15233532517 0016670 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Compatibility; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Kit_Library extends Base_Adapter { public static function is_compatibility_needed( array $manifest_data, array $meta ) { return ! empty( $meta['referrer'] ) && 'kit-library' === $meta['referrer']; } public function adapt_manifest( array $manifest_data ) { if ( ! empty( $manifest_data['content']['page'] ) ) { foreach ( $manifest_data['content']['page'] as & $page ) { $page['thumbnail'] = false; } } if ( ! empty( $manifest_data['templates'] ) ) { foreach ( $manifest_data['templates'] as & $template ) { $template['thumbnail'] = false; } } return $manifest_data; } } modules/import-export/compatibility/base-adapter.php 0000644 00000001400 15233532517 0016760 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Compatibility; use Elementor\App\Modules\ImportExport\Import; use Elementor\Core\Base\Base_Object; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Adapter { /** * @param array $manifest_data * @param array $meta * @return false */ public static function is_compatibility_needed( array $manifest_data, array $meta ) { return false; } public function adapt_manifest( array $manifest_data ) { return $manifest_data; } public function adapt_site_settings( array $site_settings, array $manifest_data, $path ) { return $site_settings; } public function adapt_template( array $template_data, array $template_settings ) { return $template_data; } } modules/import-export/compatibility/envato.php 0000644 00000005712 15233532517 0015736 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Compatibility; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Envato extends Base_Adapter { public static function is_compatibility_needed( array $manifest_data, array $meta ) { return ! empty( $manifest_data['manifest_version'] ); } public function adapt_manifest( array $manifest_data ) { $templates = $manifest_data['templates']; $manifest_data['templates'] = []; foreach ( $templates as $template ) { // Envato store their global kit styles as a 'global.json' template file. // We need to be able to know the path to this specific 'global.json' since it functions as the site-settings.json $is_global = ! empty( $template['metadata']['template_type'] ) && 'global-styles' === $template['metadata']['template_type']; if ( $is_global ) { // Adding the path of the 'global.json' template to the manifest which will be used in the future. $manifest_data['path-to-envto-site-settings'] = $template['source']; // Getting the site-settings because Envato stores them in one of the posts. $kit = Plugin::$instance->kits_manager->get_active_kit(); $kit_tabs = $kit->get_tabs(); unset( $kit_tabs['settings-site-identity'] ); $manifest_data['site-settings'] = array_keys( $kit_tabs ); continue; } // Evanto uses "type" instead of "doc_type" $template['doc_type'] = $template['type']; // Evanto uses for "name" instead of "title" $template['title'] = $template['name']; // Envato specifying an exact path to the template rather than using its "ID" as an index. // This extracts the "file name" part out of our exact source list and we treat that as an ID. $file_name_without_extension = str_replace( '.json', '', basename( $template['source'] ) ); // Append the template to the global list: $manifest_data['templates'][ $file_name_without_extension ] = $template; } $manifest_data['name'] = $manifest_data['title']; return $manifest_data; } public function adapt_site_settings( array $site_settings, array $manifest_data, $path ) { if ( empty( $manifest_data['path-to-envto-site-settings'] ) ) { return $site_settings; } $global_file_path = $path . $manifest_data['path-to-envto-site-settings']; $global_file_data = ImportExportUtils::read_json_file( $global_file_path ); return [ 'settings' => $global_file_data['page_settings'], ]; } public function adapt_template( array $template_data, array $template_settings ) { if ( ! empty( $template_data['metadata']['elementor_pro_conditions'] ) ) { foreach ( $template_data['metadata']['elementor_pro_conditions'] as $condition ) { list ( $type, $name, $sub_name, $sub_id ) = array_pad( explode( '/', $condition ), 4, '' ); $template_data['import_settings']['conditions'][] = compact( 'type', 'name', 'sub_name', 'sub_id' ); } } return $template_data; } } modules/import-export/usage.php 0000644 00000002015 15233532517 0012666 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport; use Elementor\App\Modules\ImportExport\Processes\Revert; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Usage { /** * Register hooks. * * @return void */ public function register() { add_filter( 'elementor/tracker/send_tracking_data_params', function ( array $params ) { $params['usages']['import_export']['revert'] = $this->get_revert_usage_data(); return $params; } ); } /** * Get the Revert usage data. * * @return array */ private function get_revert_usage_data() { $revert_sessions = ( new Revert() )->get_revert_sessions(); $data = []; foreach ( $revert_sessions as $revert_session ) { $data[] = [ 'kit_name' => $revert_session['kit_name'], 'source' => $revert_session['source'], 'revert_timestamp' => (int) $revert_session['revert_timestamp'], 'total_time' => ( (int) $revert_session['revert_timestamp'] - (int) $revert_session['import_timestamp'] ), ]; } return $data; } } modules/import-export/module.php 0000644 00000100146 15233532517 0013053 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport; use Elementor\App\Modules\ImportExport\Processes\Export; use Elementor\App\Modules\ImportExport\Processes\Import; use Elementor\App\Modules\ImportExport\Processes\Revert; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Files\Uploads_Manager; use Elementor\Modules\System_Info\Reporters\Server; use Elementor\Plugin; use Elementor\Tools; use Elementor\Utils as ElementorUtils; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; use Elementor\Modules\CloudKitLibrary\Module as CloudKitLibrary; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Import Export Module * * Responsible for initializing Elementor App functionality */ class Module extends BaseModule { const FORMAT_VERSION = '2.0'; const EXPORT_TRIGGER_KEY = 'elementor_export_kit'; const UPLOAD_TRIGGER_KEY = 'elementor_upload_kit'; const IMPORT_TRIGGER_KEY = 'elementor_import_kit'; const IMPORT_RUNNER_TRIGGER_KEY = 'elementor_import_kit__runner'; const REFERRER_KIT_LIBRARY = 'kit-library'; const REFERRER_LOCAL = 'local'; const REFERRER_CLOUD = 'cloud'; const PLUGIN_PERMISSIONS_ERROR_KEY = 'plugin-installation-permissions-error'; const KIT_LIBRARY_ERROR_KEY = 'invalid-kit-library-zip-error'; const NO_WRITE_PERMISSIONS_KEY = 'no-write-permissions'; const THIRD_PARTY_ERROR = 'third-party-error'; const DOMDOCUMENT_MISSING = 'domdocument-missing'; const OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS = 'elementor_import_sessions'; const OPTION_KEY_ELEMENTOR_REVERT_SESSIONS = 'elementor_revert_sessions'; const META_KEY_ELEMENTOR_IMPORT_SESSION_ID = '_elementor_import_session_id'; const META_KEY_ELEMENTOR_EDIT_MODE = '_elementor_edit_mode'; const IMPORT_PLUGINS_ACTION = 'import-plugins'; const EXPORT_SOURCE_CLOUD = 'cloud'; const EXPORT_SOURCE_FILE = 'file'; /** * Assigning the export process to a property, so we can use the process from outside the class. * * @var Export */ public $export; /** * Assigning the import process to a property, so we can use the process from outside the class. * * @var Import */ public $import; /** * Assigning the revert process to a property, so we can use the process from outside the class. * * @var Revert */ public $revert; /** * Get name. * * @access public * * @return string */ public function get_name() { return 'import-export'; } public function __construct() { $this->register_actions(); if ( ElementorUtils::is_wp_cli() ) { \WP_CLI::add_command( 'elementor kit', WP_CLI::class ); } ( new Usage() )->register(); $this->revert = new Revert(); } public function get_init_settings() { if ( ! Plugin::$instance->app->is_current() ) { return []; } return $this->get_config_data(); } /** * Register the import/export tab in elementor tools. */ public function register_settings_tab( Tools $tools ) { $tools->add_tab( 'import-export-kit', [ 'label' => esc_html__( 'Website Templates', 'elementor' ), 'sections' => [ 'intro' => [ 'label' => esc_html__( 'Website Templates', 'elementor' ), 'callback' => function() { $this->render_import_export_tab_content(); }, 'fields' => [], ], ], ] ); } /** * Render the import/export tab content. */ private function render_import_export_tab_content() { $is_cloud_kits_available = CloudKitLibrary::get_app()->check_eligibility()['is_eligible']; $content_data = [ 'export' => [ 'title' => esc_html__( 'Export this website', 'elementor' ), 'button' => [ 'url' => Plugin::$instance->app->get_base_url() . '#/export', 'text' => esc_html__( 'Export', 'elementor' ), 'id' => 'elementor-import-export__export', ], 'description' => esc_html__( 'You can download this website as a .zip file, or upload it to the library.', 'elementor' ), ], 'import' => [ 'title' => esc_html__( 'Import website templates', 'elementor' ), 'button' => [ 'url' => Plugin::$instance->app->get_base_url() . '#/import', 'text' => esc_html__( 'Import', 'elementor' ), 'id' => 'elementor-import-export__import', ], 'description' => esc_html__( 'You can import design and settings from a .zip file or choose from the library.', 'elementor' ), ], ]; if ( $is_cloud_kits_available ) { $content_data['import']['button_secondary'] = [ 'url' => Plugin::$instance->app->get_base_url() . '#/kit-library/cloud', 'text' => esc_html__( 'Import from library', 'elementor' ), 'id' => 'elementor-import-export__import_from_library', ]; } $last_imported_kit = $this->revert->get_last_import_session(); $penultimate_imported_kit = $this->revert->get_penultimate_import_session(); $user_date_format = get_option( 'date_format' ); $user_time_format = get_option( 'time_format' ); $date_format = $user_date_format . ' ' . $user_time_format; $should_show_revert_section = $this->should_show_revert_section( $last_imported_kit ); if ( $should_show_revert_section ) { if ( ! empty( $penultimate_imported_kit ) ) { $revert_text = sprintf( /* translators: 1: Last imported kit title, 2: Last imported kit date, 3: Line break <br>, 4: Penultimate imported kit title, 5: Penultimate imported kit date. */ esc_html__( 'Remove all the content and site settings that came with "%1$s" on %2$s %3$s and revert to the site setting that came with "%4$s" on %5$s.', 'elementor' ), ! empty( $last_imported_kit['kit_title'] ) ? $last_imported_kit['kit_title'] : esc_html__( 'imported kit', 'elementor' ), gmdate( $date_format, $last_imported_kit['start_timestamp'] ), '<br>', ! empty( $penultimate_imported_kit['kit_title'] ) ? $penultimate_imported_kit['kit_title'] : esc_html__( 'imported kit', 'elementor' ), gmdate( $date_format, $penultimate_imported_kit['start_timestamp'] ) ); } else { $revert_text = sprintf( /* translators: 1: Last imported kit title, 2: Last imported kit date, 3: Line break <br>. */ esc_html__( 'Remove all the content and site settings that came with "%1$s" on %2$s.%3$s Your original site settings will be restored.', 'elementor' ), ! empty( $last_imported_kit['kit_title'] ) ? $last_imported_kit['kit_title'] : esc_html__( 'imported kit', 'elementor' ), gmdate( $date_format, $last_imported_kit['start_timestamp'] ), '<br>' ); } } ?> <div class="tab-import-export-kit__content"> <p class="tab-import-export-kit__info"> <?php printf( '%1$s <a href="https://go.elementor.com/wp-dash-import-export-general/" target="_blank">%2$s</a>', esc_html__( 'Here’s where you can export this website as a .zip file, upload it to the cloud, or start the process of applying an existing template to your site.', 'elementor' ), esc_html__( 'Learn more', 'elementor' ), ); ?> </p> <div class="tab-import-export-kit__wrapper"> <?php foreach ( $content_data as $data ) { $this->print_item_content( $data ); } ?> </div> <?php if ( $should_show_revert_section ) { $link_attributes = [ 'href' => $this->get_revert_href(), 'id' => 'elementor-import-export__revert_kit', 'class' => 'button', ]; ?> <div class="tab-import-export-kit__revert"> <h2> <?php echo esc_html__( 'Remove the most recent Website Template', 'elementor' ); ?> </h2> <p class="tab-import-export-kit__info"> <?php ElementorUtils::print_unescaped_internal_string( $revert_text ); ?> </p> <?php $this->render_last_kit_thumbnail( $last_imported_kit ); ?> <a <?php ElementorUtils::print_html_attributes( $link_attributes ); ?> > <?php echo esc_html__( 'Remove Website Template', 'elementor' ); ?> </a> </div> <?php } ?> </div> <?php } private function print_item_content( $data ) { ?> <div class="tab-import-export-kit__container"> <div class="tab-import-export-kit__box"> <h2><?php ElementorUtils::print_unescaped_internal_string( $data['title'] ); ?></h2> </div> <p class="description"><?php ElementorUtils::print_unescaped_internal_string( $data['description'] ); ?></p> <?php if ( ! empty( $data['link'] ) ) : ?> <a href="<?php ElementorUtils::print_unescaped_internal_string( $data['link']['url'] ); ?>" target="_blank"><?php ElementorUtils::print_unescaped_internal_string( $data['link']['text'] ); ?></a> <?php endif; ?> <div class="tab-import-export-kit__box action-buttons"> <?php if ( ! empty( $data['button_secondary'] ) ) : ?> <a href="<?php ElementorUtils::print_unescaped_internal_string( $data['button_secondary']['url'] ); ?>" class="elementor-button e-btn-txt e-btn-txt-border"> <?php ElementorUtils::print_unescaped_internal_string( $data['button_secondary']['text'] ); ?> </a> <?php endif; ?> <a <?php ElementorUtils::print_html_attributes( [ 'id' => $data['button']['id'] ] ); ?> href="<?php ElementorUtils::print_unescaped_internal_string( $data['button']['url'] ); ?>" class="elementor-button e-primary"> <?php ElementorUtils::print_unescaped_internal_string( $data['button']['text'] ); ?> </a> </div> </div> <?php } private function get_revert_href(): string { $admin_post_url = admin_url( 'admin-post.php?action=elementor_revert_kit' ); $nonced_admin_post_url = wp_nonce_url( $admin_post_url, 'elementor_revert_kit' ); return $this->maybe_add_referrer_param( $nonced_admin_post_url ); } /** * Checks if referred by a kit and adds the referrer ID to the href * * @param string $href * * @return string */ private function maybe_add_referrer_param( string $href ): string { $param_name = 'referrer_kit'; if ( empty( $_GET[ $param_name ] ) ) { return $href; } return add_query_arg( $param_name, sanitize_key( $_GET[ $param_name ] ), $href ); } /** * Render the last kit thumbnail if exists * * @param $last_imported_kit * * @return void */ private function render_last_kit_thumbnail( $last_imported_kit ) { if ( empty( $last_imported_kit['kit_thumbnail'] ) ) { return; } ?> <div class="tab-import-export-kit__kit-item-row"> <article class="tab-import-export-kit__kit-item"> <header> <h3> <?php echo esc_html( $last_imported_kit['kit_title'] ); ?> </h3> </header> <img src="<?php echo esc_url( $last_imported_kit['kit_thumbnail'] ); ?>" alt="<?php echo esc_attr( $last_imported_kit['kit_title'] ); ?>" loading="lazy" > </article> </div> <?php } /** * Upload a kit zip file and get the kit data. * * Assigning the Import process to the 'import' property, * so it will be available to use in different places such as: WP_Cli, Pro, etc. * * @param string $file Path to the file. * @param string $referrer Referrer of the file 'local' or 'kit-library'. * @param string $kit_id * @return array * @throws \Exception If export validation fails or processing errors occur. */ public function upload_kit( $file, $referrer, $kit_id = null ) { $this->ensure_writing_permissions(); $this->import = new Import( $file, [ 'referrer' => $referrer, 'id' => $kit_id, ] ); return [ 'session' => $this->import->get_session_id(), 'manifest' => $this->import->get_manifest(), 'conflicts' => $this->import->get_settings_conflicts(), ]; } /** * Import a kit by session_id. * Upload and import a kit by kit zip file. * * If the split_to_chunks flag is true, the process won't start * It will initialize the import process and return the session_id and the runners. * * Assigning the Import process to the 'import' property, * so it will be available to use in different places such as: WP_Cli, Pro, etc. * * @param string $path Path to the file or session_id. * @param array $settings Settings the import use to determine which content to import. * (e.g: include, selected_plugins, selected_cpt, selected_override_conditions, etc.) * @param bool $split_to_chunks Determine if the import process should be split into chunks. * @return array * @throws \Exception If export configuration is invalid or processing fails. */ public function import_kit( string $path, array $settings, bool $split_to_chunks = false ): array { $this->ensure_writing_permissions(); $this->ensure_DOMDocument_exists(); $this->import = new Import( $path, $settings ); $this->import->register_default_runners(); remove_filter( 'elementor/document/save/data', [ Plugin::$instance->modules_manager->get_modules( 'content-sanitizer' ), 'sanitize_content' ] ); do_action( 'elementor/import-export/import-kit', $this->import ); if ( $split_to_chunks ) { $this->import->init_import_session( true ); return [ 'session' => $this->import->get_session_id(), 'runners' => $this->import->get_runners_name(), ]; } return $this->import->run(); } /** * Resuming import process by re-creating the import instance and running the specific runner. * * @param string $session_id The id off the import session. * @param string $runner_name The specific runner that we want to run. * * @return array Two types of response. * 1. The status and the runner name. * 2. The imported data. (Only if the runner is the last one in the import process) * @throws \Exception If import configuration is invalid or processing fails. */ public function import_kit_by_runner( string $session_id, string $runner_name ): array { // Check session_id $this->import = Import::from_session( $session_id ); $runners = $this->import->get_runners_name(); $run = $this->import->run_runner( $runner_name ); if ( end( $runners ) === $run['runner'] ) { return $this->import->get_imported_data(); } return $run; } /** * Export a kit. * * Assigning the Export process to the 'export' property, * so it will be available to use in different places such as: WP_Cli, Pro, etc. * * @param array $settings Settings the export use to determine which content to export. * (e.g: include, kit_info, selected_plugins, selected_cpt, etc.) * @return array * @throws \Exception If import/export process fails or validation errors occur. */ public function export_kit( array $settings ) { $this->ensure_writing_permissions(); $this->export = new Export( $settings ); $this->export->register_default_runners(); do_action( 'elementor/import-export/export-kit', $this->export ); return $this->export->run(); } /** * Handle revert kit ajax request. */ public function revert_last_imported_kit() { $this->revert = new Revert(); $this->revert->register_default_runners(); do_action( 'elementor/import-export/revert-kit', $this->revert ); $this->revert->run(); } /** * Handle revert last imported kit ajax request. */ public function handle_revert_last_imported_kit() { check_admin_referer( 'elementor_revert_kit' ); $this->revert_last_imported_kit(); wp_safe_redirect( admin_url( 'admin.php?page=' . Tools::PAGE_ID . '#tab-import-export-kit' ) ); die; } /** * Register appropriate actions. */ private function register_actions() { add_action( 'admin_init', function() { if ( wp_doing_ajax() && isset( $_POST['action'] ) && wp_verify_nonce( ElementorUtils::get_super_global_value( $_POST, '_nonce' ), Ajax::NONCE_KEY ) && current_user_can( 'manage_options' ) ) { $this->maybe_handle_ajax(); } } ); add_action( 'admin_post_elementor_revert_kit', [ $this, 'handle_revert_last_imported_kit' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); if ( ! Plugin::$instance->experiments->is_feature_active( 'import-export-customization' ) ) { $page_id = Tools::PAGE_ID; add_action( "elementor/admin/after_create_settings/{$page_id}", [ $this, 'register_settings_tab' ] ); } // TODO 18/04/2023 : This needs to be moved to the runner itself after https://elementor.atlassian.net/browse/HTS-434 is done. if ( self::IMPORT_PLUGINS_ACTION === ElementorUtils::get_super_global_value( $_SERVER, 'HTTP_X_ELEMENTOR_ACTION' ) ) { add_filter( 'woocommerce_create_pages', [ $this, 'empty_pages' ], 10, 0 ); } // TODO ^^^ add_filter( 'elementor/import/kit/result', function( $result ) { if ( ! empty( $result['file_url'] ) ) { return [ 'file_name' => $this->get_remote_kit_zip( $result['file_url'] ), 'referrer' => static::REFERRER_KIT_LIBRARY, 'file_url' => $result['file_url'], ]; } return $result; } ); } /** * Prevent the creation of the default WooCommerce pages (Cart, Checkout, etc.) * * TODO 18/04/2023 : This needs to be moved to the runner itself after https://elementor.atlassian.net/browse/HTS-434 is done. * * @return array */ public function empty_pages(): array { return []; } private function ensure_writing_permissions() { $server = new Server(); $paths_to_check = [ Server::KEY_PATH_WP_CONTENT_DIR => $server->get_system_path( Server::KEY_PATH_WP_CONTENT_DIR ), Server::KEY_PATH_UPLOADS_DIR => $server->get_system_path( Server::KEY_PATH_UPLOADS_DIR ), Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR => $server->get_system_path( Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ), ]; $permissions = $server->get_paths_permissions( $paths_to_check ); // WP Content dir has to be exists and writable. if ( ! $permissions[ Server::KEY_PATH_WP_CONTENT_DIR ]['write'] ) { throw new \Error( self::NO_WRITE_PERMISSIONS_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } // WP Uploads dir has to be exists and writable. if ( ! $permissions[ Server::KEY_PATH_UPLOADS_DIR ]['write'] ) { throw new \Error( self::NO_WRITE_PERMISSIONS_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } // Elementor uploads dir permissions is divided to 2 cases: // 1. If the dir exists, it has to be writable. // 2. If the dir doesn't exist, the parent dir has to be writable (wp uploads dir), so we can create it. if ( $permissions[ Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ]['exists'] && ! $permissions[ Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ]['write'] ) { throw new \Error( self::NO_WRITE_PERMISSIONS_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } } private function ensure_DOMDocument_exists() { if ( ! class_exists( 'DOMDocument' ) ) { throw new \Error( self::DOMDOCUMENT_MISSING ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } } /** * Enqueue admin scripts */ public function enqueue_scripts() { wp_enqueue_script( 'elementor-import-export-admin', $this->get_js_assets_url( 'import-export-admin' ), [ 'elementor-common' ], ELEMENTOR_VERSION, true ); wp_localize_script( 'elementor-import-export-admin', 'elementorImportExport', [ 'lastImportedSession' => $this->revert->get_last_import_session(), 'appUrl' => Plugin::$instance->app->get_base_url() . '#/kit-library', ] ); } /** * Assign each ajax action to a method. */ private function maybe_handle_ajax() { // phpcs:ignore WordPress.Security.NonceVerification.Missing $action = ElementorUtils::get_super_global_value( $_POST, 'action' ); try { switch ( $action ) { case static::EXPORT_TRIGGER_KEY: $this->handle_export_kit(); break; case static::UPLOAD_TRIGGER_KEY: $this->handle_upload_kit(); break; case static::IMPORT_TRIGGER_KEY: $this->handle_import_kit(); break; case static::IMPORT_RUNNER_TRIGGER_KEY: $this->handle_import_kit__runner(); break; default: break; } } catch ( \Error $e ) { if ( isset( $this->import ) ) { $this->import->finalize_import_session_option(); } Plugin::$instance->logger->get_logger()->error( $e->getMessage(), [ 'meta' => [ 'trace' => $e->getTraceAsString(), ], ] ); if ( isset( $this->import ) && $this->is_third_party_class( $e->getTrace()[0]['class'] ) ) { wp_send_json_error( self::THIRD_PARTY_ERROR, 500 ); } wp_send_json_error( $e->getMessage(), 500 ); } } /** * Handle upload kit ajax request. * * @throws \Error If operation validation fails or processing errors occur. */ private function handle_upload_kit() { // PHPCS - A URL that should contain special chars (auth headers information). $file_url = isset( $_POST['e_import_file'] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ? wp_unslash( $_POST['e_import_file'] ) : ''; // PHPCS - Already validated in caller function $kit_id = ElementorUtils::get_super_global_value( $_POST, 'kit_id' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $source = ElementorUtils::get_super_global_value( $_POST, 'source' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $is_import_from_library = ! empty( $file_url ); if ( $is_import_from_library ) { if ( ! wp_verify_nonce( ElementorUtils::get_super_global_value( $_POST, 'e_kit_library_nonce' ), 'kit-library-import' ) ) { throw new \Error( 'Invalid kit library nonce.' ); } if ( ! filter_var( $file_url, FILTER_VALIDATE_URL ) || 0 !== strpos( $file_url, 'http' ) ) { throw new \Error( static::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $import_result = apply_filters( 'elementor/import/kit/result', [ 'file_url' => $file_url ] ); } elseif ( ! empty( $source ) ) { $import_result = apply_filters( 'elementor/import/kit/result/' . $source, [ 'kit_id' => $kit_id, 'source' => $source, ] ); } else { $import_result = [ 'file_name' => ElementorUtils::get_super_global_value( $_FILES, 'e_import_file' )['tmp_name'], 'referrer' => static::REFERRER_LOCAL, ]; } Plugin::$instance->logger->get_logger()->info( 'Uploading Kit: ', [ 'meta' => [ 'kit_id' => $kit_id, 'referrer' => $import_result['referrer'], ], ] ); if ( is_wp_error( $import_result ) ) { wp_send_json_error( $import_result->get_error_message() ); } $uploaded_kit = $this->upload_kit( $import_result['file_name'], $import_result['referrer'], $kit_id ); $session_dir = $uploaded_kit['session']; $manifest = $uploaded_kit['manifest']; $conflicts = $uploaded_kit['conflicts']; if ( $is_import_from_library || ! empty( $source ) ) { Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $import_result['file_name'] ) ); } if ( isset( $manifest['plugins'] ) && ! current_user_can( 'install_plugins' ) ) { throw new \Error( static::PLUGIN_PERMISSIONS_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $result = [ 'session' => $session_dir, 'manifest' => $manifest, 'file_url' => $import_result['file_url'], ]; if ( ! empty( $import_result['kit'] ) ) { $result['uploaded_kit'] = $import_result['kit']; } if ( ! empty( $conflicts ) ) { $result['conflicts'] = $conflicts; } else { // Moved into the IE process \Elementor\App\Modules\ImportExport\Processes\Import::get_default_settings_conflicts // TODO: remove in 3.10.0 $result = apply_filters( 'elementor/import/stage_1/result', $result ); } wp_send_json_success( $result ); } protected function get_remote_kit_zip( $url ) { $remote_zip_request = wp_safe_remote_get( $url ); if ( is_wp_error( $remote_zip_request ) ) { Plugin::$instance->logger->get_logger()->error( $remote_zip_request->get_error_message() ); throw new \Error( static::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } if ( 200 !== $remote_zip_request['response']['code'] ) { Plugin::$instance->logger->get_logger()->error( $remote_zip_request['response']['message'] ); throw new \Error( static::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return Plugin::$instance->uploads_manager->create_temp_file( $remote_zip_request['body'], 'kit.zip' ); } /** * Handle import kit ajax request. */ private function handle_import_kit() { // PHPCS - Already validated in caller function $settings = json_decode( ElementorUtils::get_super_global_value( $_POST, 'data' ), true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $tmp_folder_id = $settings['session']; $import = $this->import_kit( $tmp_folder_id, $settings, true ); // get_settings_config() added manually because the frontend Ajax request doesn't trigger the get_init_settings(). $import['configData'] = $this->get_config_data(); Plugin::$instance->logger->get_logger()->info( sprintf( 'Selected import runners: %1$s', implode( ', ', $import['runners'] ) ) ); wp_send_json_success( $import ); } /** * Handle ajax request for running specific runner in the import kit process. */ private function handle_import_kit__runner() { // PHPCS - Already validated in caller function $settings = json_decode( ElementorUtils::get_super_global_value( $_POST, 'data' ), true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $session_id = $settings['session']; $runner = $settings['runner']; $import = $this->import_kit_by_runner( $session_id, $runner ); // get_settings_config() added manually because the frontend Ajax request doesn't trigger the get_init_settings(). $import['configData'] = $this->get_config_data(); if ( ! empty( $import['status'] ) ) { Plugin::$instance->logger->get_logger()->info( sprintf( 'Import runner completed: %1$s %2$s', $import['runner'], ( 'success' === $import['status'] ? '✓' : '✗' ) ) ); } do_action( 'elementor/import-export/import-kit/runner/after-run', $import ); wp_send_json_success( $import ); } /** * Handle export kit ajax request. * * @throws \Error If cleanup process fails or file system errors occur. */ private function handle_export_kit() { // PHPCS - Already validated in caller function $settings = json_decode( ElementorUtils::get_super_global_value( $_POST, 'data' ), true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $source = $settings['kitInfo']['source']; $export = $this->export_kit( $settings ); $file_name = $export['file_name']; $file_size = filesize( $file_name ); $file = ElementorUtils::file_get_contents( $file_name ); if ( ! $file ) { throw new \Error( 'Could not read the exported file.' ); } Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $file_name ) ); $result = apply_filters( 'elementor/export/kit/export-result', [ 'manifest' => $export['manifest'], 'file' => base64_encode( $file ), ], $source, $export, $settings, $file, $file_size, ); if ( is_wp_error( $result ) ) { wp_send_json_error( $result ); } wp_send_json_success( $result ); } /** * Get config data that will be exposed to the frontend. */ private function get_config_data() { $export_nonce = wp_create_nonce( 'elementor_export' ); $export_url = add_query_arg( [ '_nonce' => $export_nonce ], Plugin::$instance->app->get_base_url() ); return [ 'exportURL' => $export_url, 'summaryTitles' => $this->get_summary_titles(), 'builtinWpPostTypes' => ImportExportUtils::get_builtin_wp_post_types(), 'elementorPostTypes' => ImportExportUtils::get_elementor_post_types(), 'isUnfilteredFilesEnabled' => Uploads_Manager::are_unfiltered_uploads_enabled(), 'elementorHomePageUrl' => $this->get_elementor_home_page_url(), 'recentlyEditedElementorPageUrl' => $this->get_recently_edited_elementor_page_url(), 'tools_url' => Tools::get_url(), 'importSessions' => Revert::get_import_sessions(), 'lastImportedSession' => $this->revert->get_last_import_session(), 'kitPreviewNonce' => wp_create_nonce( 'kit_thumbnail' ), ]; } /** * Get labels of Elementor document types, Elementor Post types, WordPress Post types and Custom Post types. */ private function get_summary_titles() { $summary_titles = []; $document_types = Plugin::$instance->documents->get_document_types(); foreach ( $document_types as $name => $document_type ) { $summary_titles['templates'][ $name ] = [ 'single' => $document_type::get_title(), 'plural' => $document_type::get_plural_title(), ]; } $elementor_post_types = ImportExportUtils::get_elementor_post_types(); $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types(); $post_types = array_merge( $elementor_post_types, $wp_builtin_post_types ); foreach ( $post_types as $post_type ) { $post_type_object = get_post_type_object( $post_type ); $summary_titles['content'][ $post_type ] = [ 'single' => $post_type_object->labels->singular_name ?? '', 'plural' => $post_type_object->label ?? '', ]; } $custom_post_types = ImportExportUtils::get_registered_cpt_names(); if ( ! empty( $custom_post_types ) ) { foreach ( $custom_post_types as $custom_post_type ) { $custom_post_types_object = get_post_type_object( $custom_post_type ); // CPT data appears in two arrays: // 1. content object: in order to show the export summary when completed in getLabel function $summary_titles['content'][ $custom_post_type ] = [ 'single' => $custom_post_types_object->labels->singular_name ?? '', 'plural' => $custom_post_types_object->label ?? '', ]; // 2. customPostTypes object: in order to actually export the data $summary_titles['content']['customPostTypes'][ $custom_post_type ] = [ 'single' => $custom_post_types_object->labels->singular_name ?? '', 'plural' => $custom_post_types_object->label ?? '', ]; } } $active_kit = Plugin::$instance->kits_manager->get_active_kit(); foreach ( $active_kit->get_tabs() as $key => $tab ) { $summary_titles['site-settings'][ $key ] = $tab->get_title(); } return $summary_titles; } public function should_show_revert_section( $last_imported_kit ) { if ( empty( $last_imported_kit ) ) { return false; } // TODO: BC - remove in the future // The 'templates' runner was in core and moved to the Pro plugin. (Part of it still exits in the Core for BC) // The runner that is in the core version is missing the revert functionality, // therefore we shouldn't display the revert section if the import process done with the core version. $is_import_templates_ran = isset( $last_imported_kit['runners']['templates'] ); if ( $this->has_pro() && $is_import_templates_ran ) { $has_imported_templates = ! empty( $last_imported_kit['runners']['templates'] ); return $has_imported_templates; } return true; } public function has_pro(): bool { return ElementorUtils::has_pro(); } private function get_elementor_editor_home_page_url() { if ( 'page' !== get_option( 'show_on_front' ) ) { return ''; } $frontpage_id = get_option( 'page_on_front' ); return $this->get_elementor_editor_page_url( $frontpage_id ); } private function get_elementor_home_page_url() { if ( 'page' !== get_option( 'show_on_front' ) ) { return ''; } $frontpage_id = get_option( 'page_on_front' ); return $this->get_elementor_page_url( $frontpage_id ); } private function get_recently_edited_elementor_page_url() { $query = ElementorUtils::get_recently_edited_posts_query( [ 'posts_per_page' => 1 ] ); if ( ! isset( $query->post ) ) { return ''; } return $this->get_elementor_page_url( $query->post->ID ); } private function get_recently_edited_elementor_editor_page_url() { $query = ElementorUtils::get_recently_edited_posts_query( [ 'posts_per_page' => 1 ] ); if ( ! isset( $query->post ) ) { return ''; } return $this->get_elementor_editor_page_url( $query->post->ID ); } private function get_elementor_document( $page_id ) { $document = Plugin::$instance->documents->get( $page_id ); if ( ! $document || ! $document->is_built_with_elementor() ) { return false; } return $document; } private function get_elementor_page_url( $page_id ) { $document = $this->get_elementor_document( $page_id ); return $document ? $document->get_preview_url() : ''; } private function get_elementor_editor_page_url( $page_id ) { $document = $this->get_elementor_document( $page_id ); return $document ? $document->get_edit_url() : ''; } /** * @param string $class_name * * @return bool */ public function is_third_party_class( $class_name ) { $allowed_classes = [ 'Elementor\\', 'ElementorPro\\', 'WP_', 'wp_', ]; foreach ( $allowed_classes as $allowed_class ) { if ( str_starts_with( $class_name, $allowed_class ) ) { return false; } } return true; } } modules/import-export/processes/revert.php 0000644 00000011464 15233532517 0015107 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Processes; use Elementor\App\Modules\ImportExport\Module; use Elementor\App\Modules\ImportExport\Runners\Revert\Elementor_Content; use Elementor\App\Modules\ImportExport\Runners\Revert\Revert_Runner_Base; use Elementor\App\Modules\ImportExport\Runners\Revert\Plugins; use Elementor\App\Modules\ImportExport\Runners\Revert\Site_Settings; use Elementor\App\Modules\ImportExport\Runners\Revert\Taxonomies; use Elementor\App\Modules\ImportExport\Runners\Revert\Templates; use Elementor\App\Modules\ImportExport\Runners\Revert\Wp_Content; use Elementor\App\Modules\ImportExport\Utils; class Revert { /** * @var Revert_Runner_Base[] */ protected $runners = []; private $import_sessions; private $revert_sessions; public function __construct() { $this->import_sessions = self::get_import_sessions(); $this->revert_sessions = self::get_revert_sessions(); } /** * Register a runner. * * @param Revert_Runner_Base $runner_instance */ public function register( Revert_Runner_Base $runner_instance ) { $this->runners[ $runner_instance::get_name() ] = $runner_instance; } public function register_default_runners() { $this->register( new Site_Settings() ); $this->register( new Plugins() ); $this->register( new Templates() ); $this->register( new Taxonomies() ); $this->register( new Elementor_Content() ); $this->register( new Wp_Content() ); } /** * Execute the revert process. * * @throws \Exception If no revert runners have been specified. */ public function run() { if ( empty( $this->runners ) ) { throw new \Exception( 'Couldn’t execute the revert process because no revert runners have been specified. Try again by specifying revert runners.' ); } $import_session = $this->get_last_import_session(); if ( empty( $import_session ) ) { throw new \Exception( 'Couldn’t execute the revert process because there are no import sessions to revert.' ); } // fallback if the import session failed and doesn't have the runners metadata if ( ! isset( $import_session['runners'] ) && isset( $import_session['instance_data'] ) ) { $import_session['runners'] = $import_session['instance_data']['runners_import_metadata'] ?? []; } foreach ( $this->runners as $runner ) { if ( $runner->should_revert( $import_session ) ) { $runner->revert( $import_session ); } } $this->revert_attachments( $import_session ); $this->delete_last_import_data(); } public static function get_import_sessions() { $import_sessions = Utils::get_import_sessions(); if ( ! $import_sessions ) { return []; } usort( $import_sessions, function( $a, $b ) { return strcmp( $a['start_timestamp'], $b['start_timestamp'] ); } ); return $import_sessions; } public static function get_revert_sessions() { $revert_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_REVERT_SESSIONS ); if ( ! $revert_sessions ) { return []; } return $revert_sessions; } public function get_last_import_session() { $import_sessions = $this->import_sessions; if ( empty( $import_sessions ) ) { return []; } return end( $import_sessions ); } public function get_penultimate_import_session() { $sessions_data = $this->import_sessions; $penultimate_element_value = []; if ( empty( $sessions_data ) ) { return []; } end( $sessions_data ); prev( $sessions_data ); if ( ! is_null( key( $sessions_data ) ) ) { $penultimate_element_value = current( $sessions_data ); } return $penultimate_element_value; } private function delete_last_import_data() { $import_sessions = $this->import_sessions; $revert_sessions = $this->revert_sessions; $reverted_session = array_pop( $import_sessions ); $revert_sessions[] = [ 'session_id' => $reverted_session['session_id'], 'kit_title' => $reverted_session['kit_title'], 'kit_name' => $reverted_session['kit_name'], 'kit_thumbnail' => $reverted_session['kit_thumbnail'], 'source' => $reverted_session['kit_source'], 'user_id' => get_current_user_id(), 'import_timestamp' => $reverted_session['start_timestamp'], 'revert_timestamp' => current_time( 'timestamp' ), ]; update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false ); update_option( Module::OPTION_KEY_ELEMENTOR_REVERT_SESSIONS, $revert_sessions, false ); $this->import_sessions = $import_sessions; $this->revert_sessions = $revert_sessions; } private function revert_attachments( $data ) { $query_args = [ 'post_type' => 'attachment', 'post_status' => 'any', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => Module::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ]; $query = new \WP_Query( $query_args ); foreach ( $query->posts as $post ) { wp_delete_attachment( $post->ID, true ); } } } modules/import-export/processes/import.php 0000644 00000060277 15233532517 0015120 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Processes; use Elementor\App\Modules\ImportExport\Compatibility\Base_Adapter; use Elementor\App\Modules\ImportExport\Compatibility\Envato; use Elementor\App\Modules\ImportExport\Compatibility\Kit_Library; use Elementor\App\Modules\ImportExport\Utils; use Elementor\Core\Base\Document; use Elementor\Core\Kits\Documents\Kit; use Elementor\Plugin; use Elementor\App\Modules\ImportExport\Runners\Import\Elementor_Content; use Elementor\App\Modules\ImportExport\Runners\Import\Import_Runner_Base; use Elementor\App\Modules\ImportExport\Runners\Import\Plugins; use Elementor\App\Modules\ImportExport\Runners\Import\Site_Settings; use Elementor\App\Modules\ImportExport\Runners\Import\Taxonomies; use Elementor\App\Modules\ImportExport\Runners\Import\Templates; use Elementor\App\Modules\ImportExport\Runners\Import\Wp_Content; use Elementor\App\Modules\ImportExport\Module; class Import { const MANIFEST_ERROR_KEY = 'manifest-error'; const ZIP_FILE_ERROR_KEY = 'invalid-zip-file'; const ZIP_ARCHIVE_ERROR_KEY = 'zip-archive-module-missing'; /** * @var Import_Runner_Base[] */ protected $runners = []; /** * The session ID of the import process. * This ID is uniquely generated for each import process (by the temp folder which contains the extracted kit files). * * @var string */ private $session_id; /** * The Kit ID. * * @var string */ private $kit_id; /** * Adapter for the kit compatibility. * * @var Base_Adapter[] */ private $adapters; /** * Document's data (elements and settings) that was imported during the process. * * @var array { [document_id] => { "elements": array , "settings": array } } */ private $documents_data = []; /** * Path to the extracted kit files. * * @var string */ private $extracted_directory_path; /** * Imported kit manifest. * * @var array */ private $manifest; /** * Imported kit site settings. (e.g: custom_colors, custom_typography, etc.) * * @var array */ private $site_settings; /** * Selected content types to import. * * @var array */ private $settings_include; /** * Referer of the import. (e.g: kit-library, local, etc.) * * @var string */ private $settings_referrer; /** * All the conflict between the exited templates and the kit templates. * * @var array */ private $settings_conflicts; /** * Selected elementor templates conditions to override. * * @var array */ private $settings_selected_override_conditions; /** * Selected custom post types to import. * * @var array */ private $settings_selected_custom_post_types; /** * Selected plugins to import. * * @var array */ private $settings_selected_plugins; /** * The imported data output. * * @var array */ private $imported_data = []; /** * The metadata output of the import runners. * Will be saved in the import_session and will be used to revert the import process. * * @var array */ private $runners_import_metadata = []; /** * @param string $path session_id | zip_file_path * @param array $settings Use to determine which content to import. * (e.g: include, selected_plugins, selected_cpt, selected_override_conditions, etc.) * @param array|null $old_instance An array of old instance parameters that will be used for creating new instance. * We are using it for quick creation of the instance when the import process is being split into chunks. * * @throws \Exception If the import session does not exist. */ public function __construct( string $path, array $settings = [], array $old_instance = null ) { if ( ! empty( $old_instance ) ) { $this->set_import_object( $old_instance ); } else { if ( is_file( $path ) ) { $this->extracted_directory_path = $this->extract_zip( $path ); } else { $elementor_tmp_directory = Plugin::$instance->uploads_manager->get_temp_dir(); $path = $elementor_tmp_directory . basename( $path ); if ( ! is_dir( $path ) ) { throw new \Exception( 'Couldn’t execute the import process because the import session does not exist.' ); } $this->extracted_directory_path = $path . '/'; } $this->session_id = basename( $this->extracted_directory_path ); $this->kit_id = $settings['id'] ?? ''; $this->settings_referrer = ! empty( $settings['referrer'] ) ? $settings['referrer'] : 'local'; $this->settings_include = ! empty( $settings['include'] ) ? $settings['include'] : null; // Using isset and not empty is important since empty array is valid option. $this->settings_selected_override_conditions = $settings['overrideConditions'] ?? null; $this->settings_selected_custom_post_types = $settings['selectedCustomPostTypes'] ?? null; $this->settings_selected_plugins = $settings['plugins'] ?? null; $this->manifest = $this->read_manifest_json(); $this->site_settings = $this->read_site_settings_json(); $this->set_default_settings(); } add_filter( 'wp_php_error_args', function ( $args, $error ) { return $this->filter_php_error_args( $args, $error ); }, 10, 2 ); } /** * Set the import object parameters. * * @param array $instance * @return void */ private function set_import_object( array $instance ) { $this->session_id = $instance['session_id']; $instance_data = $instance['instance_data']; $this->extracted_directory_path = $instance_data['extracted_directory_path']; $this->runners = $instance_data['runners']; $this->adapters = $instance_data['adapters']; $this->manifest = $instance_data['manifest']; $this->site_settings = $instance_data['site_settings']; $this->settings_include = $instance_data['settings_include']; $this->settings_referrer = $instance_data['settings_referrer']; $this->settings_conflicts = $instance_data['settings_conflicts']; $this->settings_selected_override_conditions = $instance_data['settings_selected_override_conditions']; $this->settings_selected_custom_post_types = $instance_data['settings_selected_custom_post_types']; $this->settings_selected_plugins = $instance_data['settings_selected_plugins']; $this->documents_data = $instance_data['documents_data']; $this->imported_data = $instance_data['imported_data']; $this->runners_import_metadata = $instance_data['runners_import_metadata']; } /** * Creating a new instance of the import process by the id of the old import session. * * @param string $session_id * * @return Import * @throws \Exception If the import session does not exist. */ public static function from_session( string $session_id ): Import { $import_sessions = Utils::get_import_sessions(); if ( ! $import_sessions || ! isset( $import_sessions[ $session_id ] ) ) { throw new \Exception( 'Couldn’t execute the import process because the import session does not exist.' ); } $import_session = $import_sessions[ $session_id ]; return new self( $session_id, [], $import_session ); } /** * Register a runner. * Be aware that the runner will be executed in the order of registration, the order is crucial for the import process. * * @param Import_Runner_Base $runner_instance */ public function register( Import_Runner_Base $runner_instance ) { $this->runners[ $runner_instance::get_name() ] = $runner_instance; } public function register_default_runners() { $this->register( new Site_Settings() ); $this->register( new Plugins() ); $this->register( new Templates() ); $this->register( new Taxonomies() ); $this->register( new Elementor_Content() ); $this->register( new Wp_Content() ); } /** * Set default settings for the import. */ private function set_default_settings() { if ( ! is_array( $this->get_settings_include() ) ) { $this->settings_include( $this->get_default_settings_include() ); } if ( ! is_array( $this->get_settings_conflicts() ) ) { $this->settings_conflicts( $this->get_default_settings_conflicts() ); } if ( ! is_array( $this->get_settings_selected_override_conditions() ) ) { $this->settings_selected_override_conditions( $this->get_default_settings_override_conditions() ); } if ( ! is_array( $this->get_settings_selected_custom_post_types() ) ) { $this->settings_selected_custom_post_types( $this->get_default_settings_custom_post_types() ); } if ( ! is_array( $this->get_settings_selected_plugins() ) ) { $this->settings_selected_plugins( $this->get_default_settings_plugins() ); } } /** * Execute the import process. * * @return array The imported data output. * * @throws \Exception If no import runners have been specified. */ public function run() { if ( empty( $this->runners ) ) { throw new \Exception( 'Couldn’t execute the import process because no import runners have been specified. Try again by specifying import runners.' ); } $data = [ 'session_id' => $this->session_id, 'include' => $this->settings_include, 'manifest' => $this->manifest, 'site_settings' => $this->site_settings, 'selected_plugins' => $this->settings_selected_plugins, 'extracted_directory_path' => $this->extracted_directory_path, 'selected_custom_post_types' => $this->settings_selected_custom_post_types, ]; $this->init_import_session(); remove_filter( 'elementor/document/save/data', [ Plugin::$instance->modules_manager->get_modules( 'content-sanitizer' ), 'sanitize_content' ] ); add_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10, 2 ); // 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 ); foreach ( $this->runners as $runner ) { if ( $runner->should_import( $data ) ) { $import = $runner->import( $data, $this->imported_data ); $this->imported_data = array_merge_recursive( $this->imported_data, $import ); $this->runners_import_metadata[ $runner::get_name() ] = $runner->get_import_session_metadata(); } } // After the upload complete, set the elementor upload state back to false. Plugin::$instance->uploads_manager->set_elementor_upload_state( false ); remove_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10 ); $this->finalize_import_session_option(); $this->save_elements_of_imported_posts(); Plugin::$instance->uploads_manager->remove_file_or_dir( $this->extracted_directory_path ); return $this->imported_data; } /** * Run specific runner by runner_name * * @param string $runner_name * * @return array * * @throws \Exception If no export runners have been specified. */ public function run_runner( string $runner_name ): array { if ( empty( $this->runners ) ) { throw new \Exception( 'Couldn’t execute the import process because no import runners have been specified. Try again by specifying import runners.' ); } $data = [ 'session_id' => $this->session_id, 'include' => $this->settings_include, 'manifest' => $this->manifest, 'site_settings' => $this->site_settings, 'selected_plugins' => $this->settings_selected_plugins, 'extracted_directory_path' => $this->extracted_directory_path, 'selected_custom_post_types' => $this->settings_selected_custom_post_types, ]; add_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10, 2 ); // 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 ); $runner = $this->runners[ $runner_name ]; if ( empty( $runner ) ) { throw new \Exception( 'Couldn’t execute the import process because the import runner was not found. Try again by specifying an import runner.' ); } if ( $runner->should_import( $data ) ) { $import = $runner->import( $data, $this->imported_data ); $this->imported_data = array_merge_recursive( $this->imported_data, $import ); $this->runners_import_metadata[ $runner::get_name() ] = $runner->get_import_session_metadata(); } // After the upload complete, set the elementor upload state back to false. Plugin::$instance->uploads_manager->set_elementor_upload_state( false ); remove_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10 ); $is_last_runner = key( array_slice( $this->runners, -1, 1, true ) ) === $runner_name; if ( $is_last_runner ) { $this->finalize_import_session_option(); $this->save_elements_of_imported_posts(); } else { $this->update_instance_data_in_import_session_option(); } return [ 'status' => 'success', 'runner' => $runner_name, ]; } /** * Create and save all the instance data to the import sessions option. * * @return void */ public function init_import_session( $save_instance_data = false ) { $import_sessions = Utils::get_import_sessions( true ); $import_sessions[ $this->session_id ] = [ 'session_id' => $this->session_id, 'kit_title' => $this->manifest['title'] ?? '', 'kit_name' => $this->manifest['name'] ?? '', 'kit_thumbnail' => $this->get_kit_thumbnail(), 'kit_source' => $this->settings_referrer, 'user_id' => get_current_user_id(), 'start_timestamp' => current_time( 'timestamp' ), ]; if ( $save_instance_data ) { $import_sessions[ $this->session_id ]['instance_data'] = [ 'extracted_directory_path' => $this->extracted_directory_path, 'runners' => $this->runners, 'adapters' => $this->adapters, 'manifest' => $this->manifest, 'site_settings' => $this->site_settings, 'settings_include' => $this->settings_include, 'settings_referrer' => $this->settings_referrer, 'settings_conflicts' => $this->settings_conflicts, 'settings_selected_override_conditions' => $this->settings_selected_override_conditions, 'settings_selected_custom_post_types' => $this->settings_selected_custom_post_types, 'settings_selected_plugins' => $this->settings_selected_plugins, 'documents_data' => $this->documents_data, 'imported_data' => $this->imported_data, 'runners_import_metadata' => $this->runners_import_metadata, ]; } update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false ); } /** * Get the Kit thumbnail, goes to the home page thumbnail if main doesn't exist * * @return string */ private function get_kit_thumbnail(): string { if ( ! empty( $this->manifest['thumbnail'] ) ) { return $this->manifest['thumbnail']; } return apply_filters( 'elementor/import/kit_thumbnail', '', $this->kit_id, $this->settings_referrer ); } public function get_runners_name(): array { return array_keys( $this->runners ); } public function get_manifest() { return $this->manifest; } public function get_extracted_directory_path() { return $this->extracted_directory_path; } public function get_session_id() { return $this->session_id; } public function get_adapters() { return $this->adapters; } public function get_imported_data() { return $this->imported_data; } /** * Get settings by key. * Used for backward compatibility. * * @param string $key The key of the setting. */ public function get_settings( $key ) { switch ( $key ) { case 'include': return $this->get_settings_include(); case 'overrideConditions': return $this->get_settings_selected_override_conditions(); case 'selectedCustomPostTypes': return $this->get_settings_selected_custom_post_types(); case 'plugins': return $this->get_settings_selected_plugins(); default: return []; } } public function settings_include( array $settings_include ) { $this->settings_include = $settings_include; return $this; } public function get_settings_include() { return $this->settings_include; } public function settings_referrer( $settings_referrer ) { $this->settings_referrer = $settings_referrer; return $this; } public function get_settings_referrer() { return $this->settings_referrer; } public function settings_conflicts( array $settings_conflicts ) { $this->settings_conflicts = $settings_conflicts; return $this; } public function get_settings_conflicts() { return $this->settings_conflicts; } public function settings_selected_override_conditions( array $settings_selected_override_conditions ) { $this->settings_selected_override_conditions = $settings_selected_override_conditions; return $this; } public function get_settings_selected_override_conditions() { return $this->settings_selected_override_conditions; } public function settings_selected_custom_post_types( array $settings_selected_custom_post_types ) { $this->settings_selected_custom_post_types = $settings_selected_custom_post_types; return $this; } public function get_settings_selected_custom_post_types() { return $this->settings_selected_custom_post_types; } public function settings_selected_plugins( array $settings_selected_plugins ) { $this->settings_selected_plugins = $settings_selected_plugins; return $this; } public function get_settings_selected_plugins() { return $this->settings_selected_plugins; } /** * Prevent saving elements on elementor post creation. * * @param array $data * @param Document $document * * @return array */ public function prevent_saving_elements_on_post_creation( array $data, Document $document ) { if ( isset( $data['elements'] ) ) { $this->documents_data[ $document->get_main_id() ] = [ 'elements' => $data['elements'] ]; $data['elements'] = []; } if ( isset( $data['settings'] ) ) { $this->documents_data[ $document->get_main_id() ]['settings'] = $data['settings']; } return $data; } /** * Extract the zip file. * * @param string $zip_path The path to the zip file. * @return string The extracted directory path. * @throws \Error If import process fails, file validation errors occur, or data corruption is detected. */ private function extract_zip( $zip_path ) { $extraction_result = Plugin::$instance->uploads_manager->extract_and_validate_zip( $zip_path, [ 'json', 'xml' ] ); if ( is_wp_error( $extraction_result ) ) { if ( isset( $extraction_result->errors['zip_error'] ) ) { throw new \Error( static::ZIP_ARCHIVE_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } throw new \Error( static::ZIP_FILE_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return $extraction_result['extraction_directory']; } /** * Get the manifest file from the extracted directory and adapt it if needed. * * @return string The manifest file content. * * @throws \Error If import validation fails or processing errors occur. */ private function read_manifest_json() { $manifest = Utils::read_json_file( $this->extracted_directory_path . 'manifest' ); if ( ! $manifest ) { Plugin::$instance->logger->get_logger()->error( static::MANIFEST_ERROR_KEY ); throw new \Error( static::ZIP_FILE_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $this->init_adapters( $manifest ); foreach ( $this->adapters as $adapter ) { $manifest = $adapter->adapt_manifest( $manifest ); } return $manifest; } /** * Init the adapters and determine which ones to use. * * @param array $manifest_data The manifest file content. */ private function init_adapters( array $manifest_data ) { $this->adapters = []; /** @var Base_Adapter[] $adapter_types */ $adapter_types = [ Envato::class, Kit_Library::class ]; foreach ( $adapter_types as $adapter_type ) { if ( $adapter_type::is_compatibility_needed( $manifest_data, [ 'referrer' => $this->get_settings_referrer() ] ) ) { $this->adapters[] = new $adapter_type( $this ); } } } /** * Get the site settings file from the extracted directory and adapt it if needed. * * @return string The site settings file content. */ private function read_site_settings_json() { $site_settings = Utils::read_json_file( $this->extracted_directory_path . 'site-settings' ); foreach ( $this->adapters as $adapter ) { $site_settings = $adapter->adapt_site_settings( $site_settings, $this->manifest, $this->extracted_directory_path ); } return $site_settings; } /** * Get all the custom post types in the kit. * * @return array Custom post types names. */ private function get_default_settings_custom_post_types() { if ( empty( $this->manifest['custom-post-type-title'] ) ) { return []; } $manifest_post_types = array_keys( $this->manifest['custom-post-type-title'] ); return array_diff( $manifest_post_types, Utils::get_builtin_wp_post_types() ); } /** * Get the default settings of elementor templates conditions to override. * * @return array */ private function get_default_settings_conflicts() { if ( empty( $this->manifest['templates'] ) ) { return []; } return apply_filters( 'elementor/import/get_default_settings_conflicts', [], $this->manifest['templates'] ); } /** * Get the default settings of elementor templates conditions to override. * * @return array */ private function get_default_settings_override_conditions() { if ( empty( $this->settings_conflicts ) ) { return []; } return array_keys( $this->settings_conflicts ); } /** * Get the default settings of the plugins that should be imported. * * @return array */ private function get_default_settings_plugins() { return ! empty( $this->manifest['plugins'] ) ? $this->manifest['plugins'] : []; } /** * Get the default settings of which content types should be imported. * * @return array */ private function get_default_settings_include() { return [ 'templates', 'plugins', 'content', 'settings' ]; } /** * Get the data that requires updating/replacement when imported. * * @return array{post_ids: array, term_ids: array} */ private function get_imported_data_replacements(): array { return [ 'post_ids' => Utils::map_old_new_post_ids( $this->imported_data ), 'term_ids' => Utils::map_old_new_term_ids( $this->imported_data ), ]; } /** * Save the prevented elements on elementor post creation elements. * Handle the replacement of all the dynamic content of the elements that probably have been changed during the import. */ private function save_elements_of_imported_posts() { $imported_data_replacements = $this->get_imported_data_replacements(); foreach ( $this->documents_data as $new_id => $data ) { $document = Plugin::$instance->documents->get( $new_id ); if ( isset( $data['elements'] ) ) { $data['elements'] = $document->on_import_update_dynamic_content( $data['elements'], $imported_data_replacements ); } if ( isset( $data['settings'] ) ) { if ( $document instanceof Kit ) { // Without post_status certain tabs in the Kit will not save properly. $data['settings']['post_status'] = get_post_status( $new_id ); } $data['settings'] = $document->on_import_update_settings( $data['settings'], $imported_data_replacements ); } $document->save( $data ); } } private function update_instance_data_in_import_session_option() { $import_sessions = Utils::get_import_sessions(); $import_sessions[ $this->session_id ]['instance_data']['documents_data'] = $this->documents_data; $import_sessions[ $this->session_id ]['instance_data']['imported_data'] = $this->imported_data; $import_sessions[ $this->session_id ]['instance_data']['runners_import_metadata'] = $this->runners_import_metadata; update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false ); } public function finalize_import_session_option() { $import_sessions = Utils::get_import_sessions(); if ( ! isset( $import_sessions[ $this->session_id ] ) ) { return; } unset( $import_sessions[ $this->session_id ]['instance_data'] ); $import_sessions[ $this->session_id ]['end_timestamp'] = current_time( 'timestamp' ); $import_sessions[ $this->session_id ]['runners'] = $this->runners_import_metadata; update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false ); } /** * Filter the php error args and return 408 status code if the error is a timeout. * * @param array $args * @param array $error * @return array */ private function filter_php_error_args( $args, $error ) { if ( strpos( $error['message'], 'Maximum execution time' ) !== false ) { $args['response'] = 408; } return $args; } } modules/import-export/processes/export.php 0000644 00000022274 15233532517 0015122 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Processes; use Elementor\App\Modules\ImportExport\Module; use Elementor\App\Modules\ImportExport\Utils; use Elementor\Core\Utils\Str; use Elementor\Plugin; use Elementor\App\Modules\ImportExport\Runners\Export\Elementor_Content; use Elementor\App\Modules\ImportExport\Runners\Export\Export_Runner_Base; use Elementor\App\Modules\ImportExport\Runners\Export\Plugins; use Elementor\App\Modules\ImportExport\Runners\Export\Site_Settings; use Elementor\App\Modules\ImportExport\Runners\Export\Taxonomies; use Elementor\App\Modules\ImportExport\Runners\Export\Templates; use Elementor\App\Modules\ImportExport\Runners\Export\Wp_Content; class Export { const ZIP_ARCHIVE_MODULE_MISSING = 'zip-archive-module-is-missing'; /** * @var Export_Runner_Base[] */ protected $runners = []; /** * Selected content types to export. * * @var array */ private $settings_include; /** * The kit information. (e.g: title, description) * * @var array $export_data */ private $settings_kit_info; /** * Selected plugins to export. * Contains the plugins essential data for export. (e.g: name, path, version, etc.) * * @var array */ private $settings_selected_plugins; /** * Selected custom post types to export. * * @var array */ private $settings_selected_custom_post_types; /** * The output data of the export process. * Will be written into the manifest.json file. * * @var array */ private $manifest_data; /** * The zip archive object. * * @var \ZipArchive */ private $zip; public function __construct( $settings = [] ) { $this->settings_include = ! empty( $settings['include'] ) ? $settings['include'] : null; $this->settings_kit_info = ! empty( $settings['kitInfo'] ) ? $settings['kitInfo'] : null; $this->settings_selected_plugins = isset( $settings['plugins'] ) ? $settings['plugins'] : null; $this->settings_selected_custom_post_types = isset( $settings['selectedCustomPostTypes'] ) ? $settings['selectedCustomPostTypes'] : null; } /** * Register a runner. * * @param Export_Runner_Base $runner_instance */ public function register( Export_Runner_Base $runner_instance ) { $this->runners[ $runner_instance::get_name() ] = $runner_instance; } public function register_default_runners() { $this->register( new Site_Settings() ); $this->register( new Plugins() ); $this->register( new Templates() ); $this->register( new Taxonomies() ); $this->register( new Elementor_Content() ); $this->register( new Wp_Content() ); } /** * Execute the export process. * * @return array The export data output. * * @throws \Exception If no export runners have been specified. */ public function run() { if ( empty( $this->runners ) ) { throw new \Exception( 'Couldn’t execute the export process because no export runners have been specified. Try again by specifying export runners.' ); } $this->set_default_settings(); $this->init_zip_archive(); $this->init_manifest_data(); $data = [ 'include' => $this->settings_include, 'selected_plugins' => $this->settings_selected_plugins, 'selected_custom_post_types' => $this->settings_selected_custom_post_types, ]; foreach ( $this->runners as $runner ) { if ( $runner->should_export( $data ) ) { $export_result = $runner->export( $data ); $this->handle_export_result( $export_result ); } } $this->add_json_file( 'manifest', $this->manifest_data ); $zip_file_name = $this->zip->filename; $this->zip->close(); return [ 'manifest' => $this->manifest_data, 'file_name' => $zip_file_name, ]; } /** * Set default settings for the export. */ private function set_default_settings() { if ( ! is_array( $this->get_settings_include() ) ) { $this->settings_include( $this->get_default_settings_include() ); } if ( ! is_array( $this->get_settings_kit_info() ) ) { $this->settings_kit_info( $this->get_default_settings_kit_info() ); } if ( ! is_array( $this->get_settings_selected_custom_post_types() ) && in_array( 'content', $this->settings_include, true ) ) { $this->settings_selected_custom_post_types( $this->get_default_settings_custom_post_types() ); } if ( ! is_array( $this->get_settings_selected_plugins() ) && in_array( 'plugins', $this->settings_include, true ) ) { $this->settings_selected_plugins( $this->get_default_settings_selected_plugins() ); } } public function settings_include( $included_settings ) { $this->settings_include = $included_settings; } public function get_settings_include() { return $this->settings_include; } private function settings_kit_info( $kit_info ) { $this->settings_kit_info = $kit_info; } private function get_settings_kit_info() { return $this->settings_kit_info; } public function settings_selected_custom_post_types( $selected_custom_post_types ) { $this->settings_selected_custom_post_types = $selected_custom_post_types; } public function get_settings_selected_custom_post_types() { return $this->settings_selected_custom_post_types; } public function settings_selected_plugins( $plugins ) { $this->settings_selected_plugins = $plugins; } public function get_settings_selected_plugins() { return $this->settings_selected_plugins; } /** * Get the default settings of which content types should be exported. * * @return array */ private function get_default_settings_include() { return [ 'templates', 'content', 'settings', 'plugins' ]; } /** * Get the default settings of the kit info. * * @return array */ private function get_default_settings_kit_info() { return [ 'title' => 'kit', 'description' => '', ]; } /** * Get the default settings of the plugins that should be exported. * * @return array{name: string, plugin:string, pluginUri: string, version: string} */ private function get_default_settings_selected_plugins() { $installed_plugins = Plugin::$instance->wp->get_plugins(); return $installed_plugins->map( function ( $item, $key ) { return [ 'name' => $item['Name'], 'plugin' => $key, 'pluginUri' => $item['PluginURI'], 'version' => $item['Version'], ]; } )->all(); } /** * Get the default settings of all the custom post types that should be exported. * Should be all the custom post types that are not built in to WordPress and not part of Elementor. * * @return array */ private function get_default_settings_custom_post_types() { return Utils::get_registered_cpt_names(); } /** * Init the zip archive. * * @throws \Error If export process fails, file creation errors occur, or data serialization fails. */ private function init_zip_archive() { if ( ! class_exists( '\ZipArchive' ) ) { throw new \Error( static::ZIP_ARCHIVE_MODULE_MISSING ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $zip = new \ZipArchive(); $temp_dir = Plugin::$instance->uploads_manager->create_unique_dir(); $zip_file_name = $temp_dir . sanitize_title( $this->settings_kit_info['title'] ) . '.zip'; $zip->open( $zip_file_name, \ZipArchive::CREATE | \ZipArchive::OVERWRITE ); $this->zip = $zip; } /** * Init the manifest data and add some basic info to it. */ private function init_manifest_data() { $kit_post = Plugin::$instance->kits_manager->get_active_kit()->get_post(); $manifest_data = [ 'name' => sanitize_title( $this->settings_kit_info['title'] ), 'title' => $this->settings_kit_info['title'], 'description' => $this->settings_kit_info['description'], 'author' => get_the_author_meta( 'display_name', $kit_post->post_author ), 'version' => Module::FORMAT_VERSION, 'elementor_version' => ELEMENTOR_VERSION, 'created' => gmdate( 'Y-m-d H:i:s' ), 'thumbnail' => get_the_post_thumbnail_url( $kit_post ), 'site' => get_site_url(), ]; $this->manifest_data = $manifest_data; } /** * Handle the export process output. * Add the manifest data from the runner to the manifest.json file. * Create files according to the files array that should be exported by the runner. * * @param array $export_result */ private function handle_export_result( $export_result ) { foreach ( $export_result['manifest'] as $data ) { $this->manifest_data += $data; } if ( isset( $export_result['files']['path'] ) ) { $export_result['files'] = [ $export_result['files'] ]; } foreach ( $export_result['files'] as $file ) { $file_extension = pathinfo( $file['path'], PATHINFO_EXTENSION ); if ( empty( $file_extension ) ) { $this->add_json_file( $file['path'], $file['data'] ); } else { $this->add_file( $file['path'], $file['data'] ); } } } /** * Add json file to the zip archive. * * @param string $path The relative path to the file. * @param array $content The content of the file. * @param int $json_flags */ private function add_json_file( $path, array $content, $json_flags = 0 ) { if ( ! Str::ends_with( $path, '.json' ) ) { $path .= '.json'; } $this->add_file( $path, wp_json_encode( $content, $json_flags ) ); } /** * Add file to the zip archive. * * @param string $file * @param string $content The content of the file. */ private function add_file( $file, $content ) { $this->zip->addFromString( $file, $content ); } } modules/import-export/utils.php 0000644 00000007236 15233532517 0012734 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport; use Elementor\Core\Utils\Str; use Elementor\Modules\LandingPages\Module as Landing_Pages_Module; use Elementor\Modules\FloatingButtons\Module as Floating_Buttons_Module; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils as ElementorUtils; class Utils { public static function read_json_file( $path ) { if ( ! Str::ends_with( $path, '.json' ) ) { $path .= '.json'; } $file_content = ElementorUtils::file_get_contents( $path, true ); return $file_content ? json_decode( $file_content, true ) : []; } public static function map_old_new_post_ids( array $imported_data ) { $result = []; $result += $imported_data['templates']['succeed'] ?? []; if ( isset( $imported_data['content'] ) ) { foreach ( $imported_data['content'] as $post_type ) { $result += $post_type['succeed'] ?? []; } } if ( isset( $imported_data['wp-content'] ) ) { foreach ( $imported_data['wp-content'] as $post_type ) { $result += $post_type['succeed'] ?? []; } } return $result; } public static function map_old_new_term_ids( array $imported_data ) { $result = []; if ( ! isset( $imported_data['taxonomies'] ) ) { return $result; } foreach ( $imported_data['taxonomies'] as $post_type_taxonomies ) { foreach ( $post_type_taxonomies as $taxonomy ) { foreach ( $taxonomy as $term ) { $result[ $term['old_id'] ] = $term['new_id']; } } } return $result; } public static function get_elementor_post_types() { $elementor_post_types = get_post_types_by_support( 'elementor' ); return array_filter( $elementor_post_types, function ( $value ) { // Templates are handled in a separate process. return 'elementor_library' !== $value; } ); } public static function get_builtin_wp_post_types() { return [ 'post', 'page', 'nav_menu_item' ]; } public static function get_registered_cpt_names() { $post_types = get_post_types( [ 'public' => true, 'can_export' => true, '_builtin' => false, ] ); unset( $post_types[ Landing_Pages_Module::CPT ], $post_types[ Source_Local::CPT ], $post_types[ Floating_Buttons_Module::CPT_FLOATING_BUTTONS ] ); return array_keys( $post_types ); } /** * Transform a string name to title format. * * @param $name * * @return string */ public static function transform_name_to_title( $name ): string { if ( empty( $name ) ) { return ''; } $title = str_replace( [ '-', '_' ], ' ', $name ); return ucwords( $title ); } public static function get_import_sessions( $should_run_cleanup = false ) { $import_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, [] ); if ( $should_run_cleanup ) { foreach ( $import_sessions as $session_id => $import_session ) { if ( ! isset( $import_session['runners'] ) && isset( $import_session['instance_data'] ) ) { $import_sessions[ $session_id ]['runners'] = $import_session['instance_data']['runners_import_metadata'] ?? []; unset( $import_sessions[ $session_id ]['instance_data'] ); } } update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions ); } return $import_sessions; } public static function update_space_between_widgets_values( $space_between_widgets ) { $setting_exist = isset( $space_between_widgets['size'] ); $already_processed = isset( $space_between_widgets['column'] ); if ( ! $setting_exist || $already_processed ) { return $space_between_widgets; } $size = strval( $space_between_widgets['size'] ); $space_between_widgets['column'] = $size; $space_between_widgets['row'] = $size; $space_between_widgets['isLinked'] = true; return $space_between_widgets; } } modules/import-export/runners/runner-interface.php 0000644 00000001017 15233532517 0016526 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners; use Elementor\App\Modules\ImportExport\Module; interface Runner_Interface { const META_KEY_ELEMENTOR_IMPORT_SESSION_ID = Module::META_KEY_ELEMENTOR_IMPORT_SESSION_ID; const META_KEY_ELEMENTOR_EDIT_MODE = Module::META_KEY_ELEMENTOR_EDIT_MODE; /** * Get the name of the runners, used to identify the runner. * The name should be unique, unless you want to run over existing runner. * * @return string */ public static function get_name(): string; } modules/import-export/runners/import/elementor-content.php 0000644 00000010530 15233532517 0020233 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Import; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; use Elementor\Plugin; class Elementor_Content extends Import_Runner_Base { private $show_page_on_front; private $page_on_front_id; private $import_session_id; public function __construct() { $this->init_page_on_front_data(); } public static function get_name(): string { return 'elementor-content'; } public function should_import( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) && ! empty( $data['manifest']['content'] ) && ! empty( $data['extracted_directory_path'] ) ); } public function import( array $data, array $imported_data ) { $result['content'] = []; $this->import_session_id = $data['session_id']; $elementor_post_types = ImportExportUtils::get_elementor_post_types(); foreach ( $elementor_post_types as $post_type ) { if ( empty( $data['manifest']['content'][ $post_type ] ) ) { continue; } $posts_settings = $data['manifest']['content'][ $post_type ]; $path = $data['extracted_directory_path'] . 'content/' . $post_type . '/'; $imported_terms = ! empty( $imported_data['taxonomies'] ) ? ImportExportUtils::map_old_new_term_ids( $imported_data ) : []; $result['content'][ $post_type ] = $this->import_elementor_post_type( $posts_settings, $path, $post_type, $imported_terms ); } return $result; } private function import_elementor_post_type( array $posts_settings, $path, $post_type, array $imported_terms ) { $result = [ 'succeed' => [], 'failed' => [], ]; foreach ( $posts_settings as $id => $post_settings ) { try { $post_data = ImportExportUtils::read_json_file( $path . $id ); $import = $this->import_post( $post_settings, $post_data, $post_type, $imported_terms ); if ( is_wp_error( $import ) ) { $result['failed'][ $id ] = $import->get_error_message(); continue; } $result['succeed'][ $id ] = $import; } catch ( \Exception $error ) { $result['failed'][ $id ] = $error->getMessage(); } } return $result; } private function import_post( array $post_settings, array $post_data, $post_type, array $imported_terms ) { $post_attributes = [ 'post_title' => $post_settings['title'], 'post_type' => $post_type, 'post_status' => 'publish', ]; if ( ! empty( $post_settings['excerpt'] ) ) { $post_attributes['post_excerpt'] = $post_settings['excerpt']; } $new_document = Plugin::$instance->documents->create( $post_settings['doc_type'], $post_attributes ); if ( is_wp_error( $new_document ) ) { throw new \Exception( esc_html( $new_document->get_error_message() ) ); } $post_data['import_settings'] = $post_settings; $new_attachment_callback = function( $attachment_id ) { $this->set_session_post_meta( $attachment_id, $this->import_session_id ); }; add_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback ); $new_document->import( $post_data ); remove_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback ); $new_post_id = $new_document->get_main_id(); if ( ! empty( $post_settings['terms'] ) ) { $this->set_post_terms( $new_post_id, $post_settings['terms'], $imported_terms ); } if ( ! empty( $post_settings['show_on_front'] ) ) { $this->set_page_on_front( $new_post_id ); } $this->set_session_post_meta( $new_post_id, $this->import_session_id ); return $new_post_id; } private function set_post_terms( $post_id, array $terms, array $imported_terms ) { foreach ( $terms as $term ) { if ( ! isset( $imported_terms[ $term['term_id'] ] ) ) { continue; } wp_set_post_terms( $post_id, [ $imported_terms[ $term['term_id'] ] ], $term['taxonomy'], false ); } } private function init_page_on_front_data() { $this->show_page_on_front = 'page' === get_option( 'show_on_front' ); if ( $this->show_page_on_front ) { $this->page_on_front_id = (int) get_option( 'page_on_front' ); } } private function set_page_on_front( $page_id ) { update_option( 'page_on_front', $page_id ); if ( ! $this->show_page_on_front ) { update_option( 'show_on_front', 'page' ); } } public function get_import_session_metadata(): array { return [ 'page_on_front' => $this->page_on_front_id ?? 0, ]; } } modules/import-export/runners/import/wp-content.php 0000644 00000006510 15233532517 0016672 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Import; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; use Elementor\Core\Utils\ImportExport\WP_Import; class Wp_Content extends Import_Runner_Base { private $import_session_id; /** * @var array */ private $selected_custom_post_types = []; public static function get_name(): string { return 'wp-content'; } public function should_import( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) && ! empty( $data['extracted_directory_path'] ) && ! empty( $data['manifest']['wp-content'] ) ); } public function import( array $data, array $imported_data ) { $this->import_session_id = $data['session_id']; $path = $data['extracted_directory_path'] . 'wp-content/'; $post_types = $this->filter_post_types( $data['selected_custom_post_types'] ); $taxonomies = $imported_data['taxonomies'] ?? []; $imported_terms = ImportExportUtils::map_old_new_term_ids( $imported_data ); $result['wp-content'] = []; foreach ( $post_types as $post_type ) { $import = $this->import_wp_post_type( $path, $post_type, $imported_data, $taxonomies, $imported_terms ); if ( empty( $import ) ) { continue; } $result['wp-content'][ $post_type ] = $import; $imported_data = array_merge( $imported_data, $result ); } return $result; } private function import_wp_post_type( $path, $post_type, array $imported_data, array $taxonomies, array $imported_terms ) { $args = [ 'fetch_attachments' => true, 'posts' => ImportExportUtils::map_old_new_post_ids( $imported_data ), 'terms' => $imported_terms, 'taxonomies' => ! empty( $taxonomies[ $post_type ] ) ? $taxonomies[ $post_type ] : [], 'posts_meta' => [ static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID => $this->import_session_id, ], 'terms_meta' => [ static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID => $this->import_session_id, ], ]; $file = $path . $post_type . '/' . $post_type . '.xml'; if ( ! file_exists( $file ) ) { return []; } $wp_importer = new WP_Import( $file, $args ); $result = $wp_importer->run(); return $result['summary']['posts']; } private function filter_post_types( $selected_custom_post_types = [] ) { $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types(); foreach ( $selected_custom_post_types as $custom_post_type ) { if ( post_type_exists( $custom_post_type ) ) { $this->selected_custom_post_types[] = $custom_post_type; } } $post_types = array_merge( $wp_builtin_post_types, $this->selected_custom_post_types ); $post_types = $this->force_element_to_be_last_by_value( $post_types, 'nav_menu_item' ); return $post_types; } public function get_import_session_metadata(): array { return [ 'custom_post_types' => $this->selected_custom_post_types, ]; } /** * @param array $base_array The array we want to relocate his element. * @param mixed $element The value of the element in the array we want to shift to end of the array. * @return mixed */ private function force_element_to_be_last_by_value( array $base_array, $element ) { $index = array_search( $element, $base_array, true ); if ( false !== $index ) { unset( $base_array[ $index ] ); $base_array[] = $element; } return $base_array; } } modules/import-export/runners/import/taxonomies.php 0000644 00000007364 15233532517 0016772 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Import; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; class Taxonomies extends Import_Runner_Base { private $import_session_id; public static function get_name(): string { return 'taxonomies'; } public function should_import( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) && ! empty( $data['extracted_directory_path'] ) && ! empty( $data['manifest']['taxonomies'] ) ); } public function import( array $data, array $imported_data ) { $path = $data['extracted_directory_path'] . 'taxonomies/'; $this->import_session_id = $data['session_id']; $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types(); $selected_custom_post_types = isset( $data['selected_custom_post_types'] ) ? $data['selected_custom_post_types'] : []; $post_types = array_merge( $wp_builtin_post_types, $selected_custom_post_types ); $result = []; foreach ( $post_types as $post_type ) { if ( empty( $data['manifest']['taxonomies'][ $post_type ] ) ) { continue; } $result['taxonomies'][ $post_type ] = $this->import_taxonomies( $data['manifest']['taxonomies'][ $post_type ], $path ); } return $result; } private function import_taxonomies( array $taxonomies, $path ) { $result = []; $imported_taxonomies = []; foreach ( $taxonomies as $taxonomy ) { if ( ! taxonomy_exists( $taxonomy ) ) { continue; } if ( ! empty( $imported_taxonomies[ $taxonomy ] ) ) { $result[ $taxonomy ] = $imported_taxonomies[ $taxonomy ]; continue; } $taxonomy_data = ImportExportUtils::read_json_file( $path . $taxonomy ); if ( empty( $taxonomy_data ) ) { continue; } $import = $this->import_taxonomy( $taxonomy_data ); $result[ $taxonomy ] = $import; $imported_taxonomies[ $taxonomy ] = $import; } return $result; } private function import_taxonomy( array $taxonomy_data ) { $terms = []; foreach ( $taxonomy_data as $term ) { $old_slug = $term['slug']; $existing_term = term_exists( $term['slug'], $term['taxonomy'] ); if ( $existing_term ) { if ( 'nav_menu' === $term['taxonomy'] ) { $term = $this->handle_duplicated_nav_menu_term( $term ); } else { $terms[] = [ 'old_id' => (int) $term['term_id'], 'new_id' => (int) $existing_term['term_id'], 'old_slug' => $old_slug, 'new_slug' => $term['slug'], ]; continue; } } $parent = $this->get_term_parent( $term, $terms ); $args = [ 'slug' => $term['slug'], 'description' => wp_slash( $term['description'] ), 'parent' => (int) $parent, ]; $new_term = wp_insert_term( wp_slash( $term['name'] ), $term['taxonomy'], $args ); if ( ! is_wp_error( $new_term ) ) { $this->set_session_term_meta( (int) $new_term['term_id'], $this->import_session_id ); $terms[] = [ 'old_id' => $term['term_id'], 'new_id' => (int) $new_term['term_id'], 'old_slug' => $old_slug, 'new_slug' => $term['slug'], ]; } } return $terms; } private function handle_duplicated_nav_menu_term( $term ) { do { $term['slug'] = $term['slug'] . '-duplicate'; $term['name'] = $term['name'] . ' duplicate'; } while ( term_exists( $term['slug'], 'nav_menu' ) ); return $term; } private function get_term_parent( $term, array $imported_terms ) { $parent = $term['parent']; if ( 0 !== $parent && ! empty( $imported_terms ) ) { foreach ( $imported_terms as $imported_term ) { if ( $parent === $imported_term['old_id'] ) { $parent_term = term_exists( $imported_term['new_id'], $term['taxonomy'] ); break; } } if ( isset( $parent_term['term_id'] ) ) { return $parent_term['term_id']; } } return 0; } } modules/import-export/runners/import/import-runner-base.php 0000644 00000002170 15233532517 0020323 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Import; use Elementor\App\Modules\ImportExport\Runners\Runner_Interface; abstract class Import_Runner_Base implements Runner_Interface { /** * By the passed data we should decide if we want to run the import function of the runner or not. * * @param array $data * * @return bool */ abstract public function should_import( array $data ); /** * Main function of the runner import process. * * @param array $data Necessary data for the import process. * @param array $imported_data Data that already imported by previously runners. * * @return array The result of the import process */ abstract public function import( array $data, array $imported_data ); public function get_import_session_metadata(): array { return []; } public function set_session_post_meta( $post_id, $meta_value ) { update_post_meta( $post_id, static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, $meta_value ); } public function set_session_term_meta( $term_id, $meta_value ) { update_term_meta( $term_id, static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, $meta_value ); } } modules/import-export/runners/import/templates.php 0000644 00000004604 15233532517 0016574 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Import; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; class Templates extends Import_Runner_Base { private $import_session_id; public static function get_name(): string { return 'templates'; } public function should_import( array $data ) { return ( Utils::has_pro() && isset( $data['include'] ) && in_array( 'templates', $data['include'], true ) && ! empty( $data['extracted_directory_path'] ) && ! empty( $data['manifest']['templates'] ) ); } public function import( array $data, array $imported_data ) { $this->import_session_id = $data['session_id']; $path = $data['extracted_directory_path'] . 'templates/'; $templates = $data['manifest']['templates']; $result['templates'] = [ 'succeed' => [], 'failed' => [], ]; foreach ( $templates as $id => $template_settings ) { try { $template_data = ImportExportUtils::read_json_file( $path . $id ); $import = $this->import_template( $id, $template_settings, $template_data ); $result['templates']['succeed'][ $id ] = $import; } catch ( \Exception $error ) { $result['templates']['failed'][ $id ] = $error->getMessage(); } } return $result; } private function import_template( $id, array $template_settings, array $template_data ) { $doc_type = $template_settings['doc_type']; $new_document = Plugin::$instance->documents->create( $doc_type, [ 'post_title' => $template_settings['title'], 'post_type' => Source_Local::CPT, 'post_status' => 'publish', ] ); if ( is_wp_error( $new_document ) ) { throw new \Exception( esc_html( $new_document->get_error_message() ) ); } $template_data['import_settings'] = $template_settings; $template_data['id'] = $id; $new_attachment_callback = function( $attachment_id ) { $this->set_session_post_meta( $attachment_id, $this->import_session_id ); }; add_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback ); $new_document->import( $template_data ); remove_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback ); $document_id = $new_document->get_main_id(); $this->set_session_post_meta( $document_id, $this->import_session_id ); return $document_id; } } modules/import-export/runners/import/site-settings.php 0000644 00000016615 15233532517 0017405 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Import; use Elementor\Plugin; use Elementor\Core\Settings\Page\Manager as PageManager; use Elementor\App\Modules\ImportExport\Utils; use Elementor\Core\Experiments\Manager as ExperimentsManager; class Site_Settings extends Import_Runner_Base { /** * @var int */ private $previous_kit_id; /** * @var int */ private $active_kit_id; /** * @var int */ private $imported_kit_id; /** * @var string|null */ private ?string $installed_theme = null; /** * @var string|null */ private ?string $activated_theme = null; /** * @var array|null */ private ?array $previous_active_theme = null; /** * @var array */ private $previous_experiments = []; /** * @var array */ private $imported_experiments = []; public function get_theme_upgrader(): \Theme_Upgrader { if ( ! class_exists( '\Theme_Upgrader' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } if ( ! class_exists( '\WP_Ajax_Upgrader_Skin' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php'; } return new \Theme_Upgrader( new \WP_Ajax_Upgrader_Skin() ); } public static function get_name(): string { return 'site-settings'; } public function should_import( array $data ) { return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) && ! empty( $data['site_settings']['settings'] ) ); } public function import( array $data, array $imported_data ) { $new_site_settings = $data['site_settings']['settings']; $title = $data['manifest']['title'] ?? 'Imported Kit'; $active_kit = Plugin::$instance->kits_manager->get_active_kit(); $this->active_kit_id = (int) $active_kit->get_id(); $this->previous_kit_id = (int) Plugin::$instance->kits_manager->get_previous_id(); $result = []; $old_settings = $active_kit->get_meta( PageManager::META_KEY ); if ( ! $old_settings ) { $old_settings = []; } if ( ! empty( $old_settings['custom_colors'] ) ) { $new_site_settings['custom_colors'] = array_merge( $old_settings['custom_colors'], $new_site_settings['custom_colors'] ); } if ( ! empty( $old_settings['custom_typography'] ) ) { $new_site_settings['custom_typography'] = array_merge( $old_settings['custom_typography'], $new_site_settings['custom_typography'] ); } if ( ! empty( $new_site_settings['space_between_widgets'] ) ) { $new_site_settings['space_between_widgets'] = Utils::update_space_between_widgets_values( $new_site_settings['space_between_widgets'] ); } $new_site_settings = array_replace_recursive( $old_settings, $new_site_settings ); $new_kit = Plugin::$instance->kits_manager->create_new_kit( $title, $new_site_settings ); $this->imported_kit_id = (int) $new_kit; $result['site-settings'] = (bool) $new_kit; $import_theme_result = $this->import_theme( $data ); if ( ! empty( $import_theme_result ) ) { $result['theme'] = $import_theme_result; } $this->import_experiments( $data ); if ( ! empty( $this->imported_experiments ) ) { $result['experiments'] = $this->imported_experiments; } return $result; } protected function install_theme( $slug, $version ) { $download_url = "https://downloads.wordpress.org/theme/{$slug}.{$version}.zip"; return $this->get_theme_upgrader()->install( $download_url ); } protected function activate_theme( $slug ) { switch_theme( $slug ); } public function import_theme( array $data ) { if ( empty( $data['site_settings']['theme'] ) ) { return null; } $theme = $data['site_settings']['theme']; $theme_slug = $theme['slug']; $theme_name = $theme['name']; $current_theme = wp_get_theme(); $this->previous_active_theme = []; $this->previous_active_theme['slug'] = $current_theme->get_stylesheet(); $this->previous_active_theme['version'] = $current_theme->get( 'Version' ); if ( $current_theme->get_stylesheet() === $theme_slug ) { $result['succeed'][ $theme_slug ] = sprintf( /* translators: %s: Theme name. */ __( 'Theme: %1$s is already used', 'elementor' ), $theme_name ); return $result; } try { if ( wp_get_theme( $theme_slug )->exists() ) { $this->activate_theme( $theme_slug ); $this->activated_theme = $theme_slug; $result['succeed'][ $theme_slug ] = sprintf( /* translators: %s: Theme name. */ __( 'Theme: %1$s has already been installed and activated', 'elementor' ), $theme_name ); return $result; } $import = $this->install_theme( $theme_slug, $theme['version'] ); if ( is_wp_error( $import ) ) { $result['failed'][ $theme_slug ] = sprintf( /* translators: %s: Theme name. */ __( 'Failed to install theme: %1$s', 'elementor' ), $theme_name ); return $result; } $result['succeed'][ $theme_slug ] = sprintf( /* translators: %s: Theme name. */ __( 'Theme: %1$s has been successfully installed', 'elementor' ), $theme_name ); $this->installed_theme = $theme_slug; $this->activate_theme( $theme_slug ); } catch ( \Exception $error ) { $result['failed'][ $theme_slug ] = $error->getMessage(); } return $result; } private function import_experiments( array $data ) { if ( empty( $data['site_settings']['experiments'] ) ) { return null; } $experiments_data = $data['site_settings']['experiments']; $experiments_manager = Plugin::$instance->experiments; $current_features = $experiments_manager->get_features(); $this->save_previous_experiments_state( $current_features ); foreach ( $experiments_data as $feature_name => $feature_data ) { if ( ! isset( $current_features[ $feature_name ] ) ) { continue; } $current_feature = $current_features[ $feature_name ]; $current_feature_state = $current_feature['state']; $new_state = $feature_data['state']; if ( $current_feature_state === $new_state ) { continue; } if ( ! in_array( $new_state, [ ExperimentsManager::STATE_DEFAULT, ExperimentsManager::STATE_ACTIVE, ExperimentsManager::STATE_ACTIVE ], true ) ) { continue; } $option_key = $experiments_manager->get_feature_option_key( $feature_name ); if ( 'default' === $new_state ) { delete_option( $option_key ); } else { update_option( $option_key, $new_state ); } $this->imported_experiments[ $feature_name ] = $feature_data; } } private function save_previous_experiments_state( array $current_features ) { $experiments_manager = Plugin::$instance->experiments; foreach ( $current_features as $feature_name => $feature ) { if ( ! $feature['mutable'] ) { continue; } $option_key = $experiments_manager->get_feature_option_key( $feature_name ); $saved_state = get_option( $option_key ); $this->previous_experiments[ $feature_name ] = [ 'name' => $feature_name, 'title' => $feature['title'], 'state' => empty( $saved_state ) ? 'default' : $saved_state, 'default' => $feature['default'], 'release_status' => $feature['release_status'], ]; } } public function get_import_session_metadata(): array { return [ 'previous_kit_id' => $this->previous_kit_id, 'active_kit_id' => $this->active_kit_id, 'imported_kit_id' => $this->imported_kit_id, 'installed_theme' => $this->installed_theme, 'activated_theme' => $this->activated_theme, 'previous_active_theme' => $this->previous_active_theme, 'previous_experiments' => $this->previous_experiments, 'imported_experiments' => $this->imported_experiments, ]; } } modules/import-export/runners/import/plugins.php 0000644 00000003226 15233532517 0016256 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Import; use Elementor\Core\Utils\Collection; use Elementor\Core\Utils\Plugins_Manager; use Elementor\Core\Utils\Str; class Plugins extends Import_Runner_Base { /** * @var Plugins_Manager */ private $plugins_manager; public function __construct( $plugins_manager = null ) { if ( $plugins_manager ) { $this->plugins_manager = $plugins_manager; } else { $this->plugins_manager = new Plugins_Manager(); } } public static function get_name(): string { return 'plugins'; } public function should_import( array $data ) { return ( isset( $data['include'] ) && in_array( 'plugins', $data['include'], true ) && ! empty( $data['manifest']['plugins'] ) && ! empty( $data['selected_plugins'] ) ); } public function import( array $data, array $imported_data ) { $plugins = $data['selected_plugins']; $plugins_collection = ( new Collection( $plugins ) ) ->map( function ( $item ) { if ( ! Str::ends_with( $item['plugin'], '.php' ) ) { $item['plugin'] .= '.php'; } return $item; } ); $slugs = $plugins_collection ->map( function ( $item ) { return $item['plugin']; } ) ->all(); $installed = $this->plugins_manager->install( $slugs ); $activated = $this->plugins_manager->activate( $installed['succeeded'] ); $ordered_activated_plugins = $plugins_collection ->filter( function ( $item ) use ( $activated ) { return in_array( $item['plugin'], $activated['succeeded'], true ); } ) ->map( function ( $item ) { return $item['name']; } ) ->all(); $result['plugins'] = $ordered_activated_plugins; return $result; } } modules/import-export/runners/export/elementor-content.php 0000644 00000006262 15233532517 0020251 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Export; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; use Elementor\Plugin; class Elementor_Content extends Export_Runner_Base { private $page_on_front_id; public function __construct() { $this->init_page_on_front_data(); } public static function get_name(): string { return 'elementor-content'; } public function should_export( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) ); } public function export( array $data ) { $elementor_post_types = ImportExportUtils::get_elementor_post_types(); $files = []; $manifest = []; foreach ( $elementor_post_types as $post_type ) { $export = $this->export_elementor_post_type( $post_type ); $files = array_merge( $files, $export['files'] ); $manifest[ $post_type ] = $export['manifest_data']; } $manifest_data['content'] = $manifest; return [ 'files' => $files, 'manifest' => [ $manifest_data, ], ]; } private function export_elementor_post_type( $post_type ) { $query_args = [ 'post_type' => $post_type, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE, 'compare' => 'EXISTS', ], [ 'key' => '_elementor_data', 'compare' => 'EXISTS', ], [ 'key' => '_elementor_data', 'compare' => '!=', 'value' => '[]', ], ], ]; $query = new \WP_Query( $query_args ); if ( empty( $query ) ) { return [ 'files' => [], 'manifest_data' => [], ]; } $post_type_taxonomies = $this->get_post_type_taxonomies( $post_type ); $manifest_data = []; $files = []; foreach ( $query->posts as $post ) { $document = Plugin::$instance->documents->get( $post->ID ); $terms = ! empty( $post_type_taxonomies ) ? $this->get_post_terms( $post->ID, $post_type_taxonomies ) : []; $post_manifest_data = [ 'title' => $post->post_title, 'excerpt' => $post->post_excerpt, 'doc_type' => $document->get_name(), 'thumbnail' => get_the_post_thumbnail_url( $post ), 'url' => get_permalink( $post ), 'terms' => $terms, ]; if ( $post->ID === $this->page_on_front_id ) { $post_manifest_data['show_on_front'] = true; } $manifest_data[ $post->ID ] = $post_manifest_data; $files[] = [ 'path' => 'content/' . $post_type . '/' . $post->ID, 'data' => $document->get_export_data(), ]; } return [ 'files' => $files, 'manifest_data' => $manifest_data, ]; } private function get_post_type_taxonomies( $post_type ) { return get_object_taxonomies( $post_type ); } private function get_post_terms( $post_id, array $taxonomies ) { $terms = wp_get_object_terms( $post_id, $taxonomies ); $result = []; foreach ( $terms as $term ) { $result[] = [ 'term_id' => $term->term_id, 'taxonomy' => $term->taxonomy, 'slug' => $term->slug, ]; } return $result; } private function init_page_on_front_data() { $show_page_on_front = 'page' === get_option( 'show_on_front' ); if ( $show_page_on_front ) { $this->page_on_front_id = (int) get_option( 'page_on_front' ); } } } modules/import-export/runners/export/wp-content.php 0000644 00000003737 15233532517 0016711 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Export; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; use Elementor\Core\Utils\ImportExport\WP_Exporter; class Wp_Content extends Export_Runner_Base { public static function get_name(): string { return 'wp-content'; } public function should_export( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) ); } public function export( array $data ) { $post_types = ImportExportUtils::get_builtin_wp_post_types(); $custom_post_types = isset( $data['selected_custom_post_types'] ) ? $data['selected_custom_post_types'] : []; $files = []; $manifest_data = []; foreach ( $post_types as $post_type ) { $export = $this->export_wp_post_type( $post_type ); $files[] = $export['file']; $manifest_data['wp-content'][ $post_type ] = $export['manifest_data']; } foreach ( $custom_post_types as $post_type ) { $export = $this->export_wp_post_type( $post_type ); $files[] = $export['file']; $manifest_data['wp-content'][ $post_type ] = $export['manifest_data']; $post_type_object = get_post_type_object( $post_type ); $manifest_data['custom-post-type-title'][ $post_type ] = [ 'name' => $post_type_object->name, 'label' => $post_type_object->label, ]; } return [ 'files' => $files, 'manifest' => [ $manifest_data, ], ]; } private function export_wp_post_type( $post_type ) { $wp_exporter = new WP_Exporter( [ 'content' => $post_type, 'status' => 'publish', 'limit' => 20, 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE, 'compare' => 'NOT EXISTS', ], ], 'include_post_featured_image_as_attachment' => true, ] ); $export_result = $wp_exporter->run(); return [ 'file' => [ 'path' => 'wp-content/' . $post_type . '/' . $post_type . '.xml', 'data' => $export_result['xml'], ], 'manifest_data' => $export_result['ids'], ]; } } modules/import-export/runners/export/taxonomies.php 0000644 00000005177 15233532517 0017001 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Export; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; class Taxonomies extends Export_Runner_Base { public static function get_name(): string { return 'taxonomies'; } public function should_export( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) ); } public function export( array $data ) { $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types(); $selected_custom_post_types = isset( $data['selected_custom_post_types'] ) ? $data['selected_custom_post_types'] : []; $post_types = array_merge( $wp_builtin_post_types, $selected_custom_post_types ); $export = $this->export_taxonomies( $post_types ); $manifest_data['taxonomies'] = $export['manifest']; return [ 'files' => $export['files'], 'manifest' => [ $manifest_data, ], ]; } private function export_taxonomies( array $post_types ) { $files = []; $manifest = []; $taxonomies = get_taxonomies(); foreach ( $taxonomies as $taxonomy ) { $taxonomy_post_types = get_taxonomy( $taxonomy )->object_type; $intersected_post_types = array_intersect( $taxonomy_post_types, $post_types ); if ( empty( $intersected_post_types ) ) { continue; } $data = $this->export_terms( $taxonomy ); if ( empty( $data ) ) { continue; } foreach ( $intersected_post_types as $post_type ) { $manifest[ $post_type ][] = $taxonomy; } $files[] = [ 'path' => 'taxonomies/' . $taxonomy, 'data' => $data, ]; } return [ 'files' => $files, 'manifest' => $manifest, ]; } private function export_terms( $taxonomy ) { $terms = get_terms( [ 'taxonomy' => (array) $taxonomy, 'hide_empty' => true, 'get' => 'all', ] ); $ordered_terms = $this->order_terms( $terms ); if ( empty( $ordered_terms ) ) { return []; } $data = []; foreach ( $ordered_terms as $term ) { $data[] = [ 'term_id' => $term->term_id, 'name' => $term->name, 'slug' => $term->slug, 'taxonomy' => $term->taxonomy, 'description' => $term->description, 'parent' => $term->parent, ]; } return $data; } /** * Put terms in order with no child going before its parent. */ private function order_terms( array $terms ) { $ordered_terms = []; while ( $term = array_shift( $terms ) ) { $is_top_level = 0 === $term->parent; $is_parent_exits = isset( $ordered_terms[ $term->parent ] ); if ( $is_top_level || $is_parent_exits ) { $ordered_terms[ $term->term_id ] = $term; } else { $terms[] = $term; } } return $ordered_terms; } } modules/import-export/runners/export/export-runner-base.php 0000644 00000001320 15233532517 0020335 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Export; use Elementor\App\Modules\ImportExport\Runners\Runner_Interface; abstract class Export_Runner_Base implements Runner_Interface { /** * By the passed data we should decide if we want to run the export function of the runner or not. * * @param array $data * * @return bool */ abstract public function should_export( array $data ); /** * Main function of the runner export process. * * @param array $data Necessary data for the export process. * * @return array{files: array, manifest: array} * The files that should be part of the kit and the relevant manifest data. */ abstract public function export( array $data ); } modules/import-export/runners/export/templates.php 0000644 00000002664 15233532517 0016607 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Export; use Elementor\Core\Base\Document; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; class Templates extends Export_Runner_Base { public static function get_name(): string { return 'templates'; } public function should_export( array $data ) { return ( Utils::has_pro() && isset( $data['include'] ) && in_array( 'templates', $data['include'], true ) ); } public function export( array $data ) { $template_types = array_values( Source_Local::get_template_types() ); $query_args = [ 'post_type' => Source_Local::CPT, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => Document::TYPE_META_KEY, 'value' => $template_types, ], ], ]; $templates_query = new \WP_Query( $query_args ); $templates_manifest_data = []; $files = []; foreach ( $templates_query->posts as $template_post ) { $template_id = $template_post->ID; $template_document = Plugin::$instance->documents->get( $template_id ); $templates_manifest_data[ $template_id ] = $template_document->get_export_summary(); $files[] = [ 'path' => 'templates/' . $template_id, 'data' => $template_document->get_export_data(), ]; } $manifest_data['templates'] = $templates_manifest_data; return [ 'files' => $files, 'manifest' => [ $manifest_data, ], ]; } } modules/import-export/runners/export/site-settings.php 0000644 00000004426 15233532517 0017411 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Export; use Elementor\Plugin; class Site_Settings extends Export_Runner_Base { public static function get_name(): string { return 'site-settings'; } public function should_export( array $data ) { return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) ); } public function export( array $data ) { $kit = Plugin::$instance->kits_manager->get_active_kit(); $kit_data = $kit->get_export_data(); $kit_tabs = $kit->get_tabs(); $excluded_kit_settings_keys = [ 'site_name', 'site_description', 'site_logo', 'site_favicon', ]; foreach ( $excluded_kit_settings_keys as $setting_key ) { unset( $kit_data['settings'][ $setting_key ] ); } unset( $kit_tabs['settings-site-identity'] ); $kit_tabs = array_keys( $kit_tabs ); $theme_data = $this->export_theme(); if ( $theme_data ) { $kit_data['theme'] = $theme_data; $manifest_data['theme'] = $theme_data; } $experiments_data = $this->export_experiments(); if ( $experiments_data ) { $kit_data['experiments'] = $experiments_data; $manifest_data['experiments'] = array_keys( $experiments_data ); } $manifest_data['site-settings'] = $kit_tabs; return [ 'files' => [ 'path' => 'site-settings', 'data' => $kit_data, ], 'manifest' => [ $manifest_data, ], ]; } public function export_theme() { $theme = wp_get_theme(); if ( empty( $theme ) || empty( $theme->get( 'ThemeURI' ) ) ) { return null; } $theme_data['name'] = $theme->get( 'Name' ); $theme_data['theme_uri'] = $theme->get( 'ThemeURI' ); $theme_data['version'] = $theme->get( 'Version' ); $theme_data['slug'] = $theme->get_stylesheet(); return $theme_data; } private function export_experiments() { $features = Plugin::$instance->experiments->get_features(); if ( empty( $features ) ) { return null; } $experiments_data = []; foreach ( $features as $feature_name => $feature ) { $experiments_data[ $feature_name ] = [ 'name' => $feature_name, 'title' => $feature['title'], 'state' => $feature['state'], 'default' => $feature['default'], 'release_status' => $feature['release_status'], ]; } return empty( $experiments_data ) ? null : $experiments_data; } } modules/import-export/runners/export/plugins.php 0000644 00000001056 15233532517 0016264 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Export; class Plugins extends Export_Runner_Base { public static function get_name(): string { return 'plugins'; } public function should_export( array $data ) { return ( isset( $data['include'] ) && in_array( 'plugins', $data['include'], true ) && is_array( $data['selected_plugins'] ) ); } public function export( array $data ) { $manifest_data['plugins'] = $data['selected_plugins']; return [ 'manifest' => [ $manifest_data, ], 'files' => [], ]; } } modules/import-export/runners/revert/elementor-content.php 0000644 00000004427 15233532517 0020240 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Revert; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; use Elementor\Plugin; class Elementor_Content extends Revert_Runner_Base { private $show_page_on_front; private $page_on_front_id; public function __construct() { $this->init_page_on_front_data(); } public static function get_name(): string { return 'elementor-content'; } public function should_revert( array $data ): bool { return ( isset( $data['runners'] ) && array_key_exists( static::get_name(), $data['runners'] ) ); } public function revert( array $data ) { $elementor_post_types = ImportExportUtils::get_elementor_post_types(); $query_args = [ 'post_type' => $elementor_post_types, 'post_status' => 'any', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE, 'compare' => 'EXISTS', ], [ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ]; $query = new \WP_Query( $query_args ); foreach ( $query->posts as $post ) { $post_type_document = Plugin::$instance->documents->get( $post->ID ); $post_type_document->delete(); // Deleting the post will reset the show_on_front option. We need to set it to false, // so we can set it back to what it was. if ( $post->ID === $this->page_on_front_id ) { $this->show_page_on_front = false; } } $this->restore_page_on_front( $data ); } private function init_page_on_front_data() { $this->show_page_on_front = 'page' === get_option( 'show_on_front' ); if ( $this->show_page_on_front ) { $this->page_on_front_id = (int) get_option( 'page_on_front' ); } } private function restore_page_on_front( $data ) { if ( empty( $data['runners'][ static::get_name() ]['page_on_front'] ) ) { return; } $page_on_front = $data['runners'][ static::get_name() ]['page_on_front']; $document = Plugin::$instance->documents->get( $page_on_front ); if ( ! $document ) { return; } $this->set_page_on_front( $document->get_main_id() ); } private function set_page_on_front( $page_id ) { update_option( 'page_on_front', $page_id ); if ( ! $this->show_page_on_front ) { update_option( 'show_on_front', 'page' ); } } } modules/import-export/runners/revert/revert-runner-base.php 0000644 00000001124 15233532517 0020313 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Revert; use Elementor\App\Modules\ImportExport\Runners\Runner_Interface; abstract class Revert_Runner_Base implements Runner_Interface { /** * By the passed data we should decide if we want to run the revert function of the runner or not. * * @param array $data * * @return bool */ abstract public function should_revert( array $data ): bool; /** * Main function of the runner revert process. * * @param array $data Necessary data for the revert process. */ abstract public function revert( array $data ); } modules/import-export/runners/revert/wp-content.php 0000644 00000003413 15233532517 0016666 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Revert; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; class Wp_Content extends Revert_Runner_Base { public static function get_name(): string { return 'wp-content'; } public function should_revert( array $data ): bool { return ( isset( $data['runners'] ) && array_key_exists( static::get_name(), $data['runners'] ) ); } public function revert( array $data ) { $builtin_post_types = ImportExportUtils::get_builtin_wp_post_types(); $custom_post_types = $data['runners']['wp-content']['custom_post_types'] ?? []; $post_types = array_merge( $builtin_post_types, $custom_post_types ); $query_args = [ 'post_type' => $post_types, 'post_status' => 'any', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE, 'compare' => 'NOT EXISTS', ], [ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ]; $query = new \WP_Query( $query_args ); foreach ( $query->posts as $post ) { wp_delete_post( $post->ID, true ); } /** * Revert the nav menu terms. * BC: The nav menu in new kits will be imported as part of the taxonomies, but old kits * importing the nav menu terms as part from the wp-content import. */ $this->revert_nav_menus( $data ); } private function revert_nav_menus( array $data ) { $terms = get_terms( [ 'taxonomy' => 'nav_menu', 'hide_empty' => false, 'get' => 'all', 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ] ); foreach ( $terms as $term ) { wp_delete_term( $term->term_id, $term->taxonomy ); } } } modules/import-export/runners/revert/taxonomies.php 0000644 00000001413 15233532517 0016754 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Revert; class Taxonomies extends Revert_Runner_Base { public static function get_name(): string { return 'taxonomies'; } public function should_revert( array $data ): bool { return ( isset( $data['runners'] ) && array_key_exists( static::get_name(), $data['runners'] ) ); } public function revert( array $data ) { $taxonomies = get_taxonomies(); $terms = get_terms( [ 'taxonomy' => $taxonomies, 'hide_empty' => false, 'get' => 'all', 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ] ); foreach ( $terms as $term ) { wp_delete_term( $term->term_id, $term->taxonomy ); } } } modules/import-export/runners/revert/templates.php 0000644 00000000566 15233532517 0016574 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Revert; class Templates extends Revert_Runner_Base { /** * The implement of this runner is part of the Pro plugin. */ public static function get_name(): string { return 'templates'; } public function should_revert( array $data ): bool { return false; } public function revert( array $data ) { } } modules/import-export/runners/revert/site-settings.php 0000644 00000007351 15233532517 0017377 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Revert; use Elementor\Plugin; use Elementor\Core\Experiments\Manager as ExperimentsManager; class Site_Settings extends Revert_Runner_Base { public static function get_name(): string { return 'site-settings'; } public function should_revert( array $data ): bool { return ( isset( $data['runners'] ) && array_key_exists( static::get_name(), $data['runners'] ) ); } public function revert( array $data ) { Plugin::$instance->kits_manager->revert( $data['runners'][ static::get_name() ]['imported_kit_id'], $data['runners'][ static::get_name() ]['active_kit_id'], $data['runners'][ static::get_name() ]['previous_kit_id'] ); $this->revert_theme( $data ); $this->revert_experiments( $data ); } public function get_theme_upgrader(): \Theme_Upgrader { if ( ! class_exists( '\Theme_Upgrader' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } if ( ! class_exists( '\WP_Ajax_Upgrader_Skin' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php'; } return new \Theme_Upgrader( new \WP_Ajax_Upgrader_Skin() ); } protected function revert_theme( $data ) { $installed_theme = $data['runners'][ static::get_name() ]['installed_theme']; $activated_theme = $data['runners'][ static::get_name() ]['activated_theme']; $previous_active_theme = $data['runners'][ static::get_name() ]['previous_active_theme']; if ( empty( $installed_theme ) && empty( $activated_theme ) ) { // no need to remove a theme as it was used before import return; } if ( ! empty( $activated_theme ) ) { $previous_theme = wp_get_theme( $previous_active_theme['slug'] ); // no need to remove imported theme as it existed before import $this->activate_previous_theme( $previous_active_theme ); return; } if ( ! empty( $installed_theme ) ) { $this->activate_previous_theme( $previous_active_theme ); $this->delete_theme( $installed_theme ); } } protected function should_delete_theme( $theme_slug ): bool { $current_theme = wp_get_theme(); return $theme_slug !== $current_theme->get_stylesheet() && wp_get_theme( $theme_slug )->exists(); } protected function delete_theme( $theme_slug ): bool { return delete_theme( $theme_slug ); } protected function activate_previous_theme( $previous_active_theme ) { if ( ! $previous_active_theme ) { return; } $theme = wp_get_theme( $previous_active_theme['slug'] ); if ( $theme->exists() ) { switch_theme( $theme->get_stylesheet() ); return; } $download_url = "https://downloads.wordpress.org/theme/{$previous_active_theme['slug']}.{$previous_active_theme['version']}.zip"; $install = $this->get_theme_upgrader()->install( $download_url ); if ( ! $install || is_wp_error( $install ) ) { return; } switch_theme( $previous_active_theme['slug'] ); } protected function revert_experiments( array $data ) { $runner_data = $data['runners'][ static::get_name() ]; $previous_experiments = $runner_data['previous_experiments'] ?? []; if ( empty( $previous_experiments ) ) { return; } $experiments_manager = Plugin::$instance->experiments; $current_features = $experiments_manager->get_features(); foreach ( $previous_experiments as $feature_name => $feature_data ) { if ( ! isset( $current_features[ $feature_name ] ) ) { continue; } if ( ! array_key_exists( $feature_name, $previous_experiments ) ) { continue; } $option_key = $experiments_manager->get_feature_option_key( $feature_name ); $previous_state = $feature_data['state']; if ( ExperimentsManager::STATE_DEFAULT === $previous_state ) { delete_option( $option_key ); } else { update_option( $option_key, $previous_state ); } } } } modules/import-export/runners/revert/plugins.php 0000644 00000000454 15233532517 0016253 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport\Runners\Revert; class Plugins extends Revert_Runner_Base { public static function get_name(): string { return 'plugins'; } public function should_revert( array $data ): bool { return false; } public function revert( array $data ) {} } modules/kit-library/data/taxonomies/controller.php 0000644 00000001454 15233532517 0016452 0 ustar 00 <?php namespace Elementor\App\Modules\KitLibrary\Data\Taxonomies; use Elementor\App\Modules\KitLibrary\Data\Base_Controller; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Base_Controller { public function get_name() { return 'kit-taxonomies'; } public function get_collection_params() { return [ 'force' => [ 'description' => 'Force an API request and skip the cache.', 'required' => false, 'default' => false, 'type' => 'boolean', ], ]; } public function get_items( $request ) { $data = $this->get_repository()->get_taxonomies( $request->get_param( 'force' ) ); return [ 'data' => $data->values(), ]; } public function get_permission_callback( $request ) { return current_user_can( 'manage_options' ); } } modules/kit-library/data/base-controller.php 0000644 00000001710 15233532517 0015167 0 ustar 00 <?php namespace Elementor\App\Modules\KitLibrary\Data; use Elementor\Plugin; use Elementor\Data\V2\Base\Controller; use Elementor\Core\Utils\Collection; use Elementor\Modules\Library\User_Favorites; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Controller extends Controller { /** * @var Repository */ private $repository; /** * @return Repository */ public function get_repository() { if ( ! $this->repository ) { /** @var \Elementor\Core\Common\Modules\Connect\Module $connect */ $connect = Plugin::$instance->common->get_component( 'connect' ); $subscription_plans = ( new Collection( $connect->get_subscription_plans() ) ) ->map( function ( $value ) { return $value['label']; } ); $this->repository = new Repository( $connect->get_app( 'kit-library' ), new User_Favorites( get_current_user_id() ), $subscription_plans ); } return $this->repository; } } modules/kit-library/data/repository.php 0000644 00000022106 15233532517 0014315 0 ustar 00 <?php namespace Elementor\App\Modules\KitLibrary\Data; use Elementor\Core\Common\Modules\Connect\Module as ConnectModule; use Elementor\Core\Utils\Collection; use Elementor\Data\V2\Base\Exceptions\Error_404; use Elementor\Data\V2\Base\Exceptions\WP_Error_Exception; use Elementor\Modules\Library\User_Favorites; use Elementor\App\Modules\KitLibrary\Connect\Kit_Library; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Repository { /** * There is no label for subscription plan with access_level=0 + it should not * be translated. */ const SUBSCRIPTION_PLAN_FREE_TAG = 'Free'; const TAXONOMIES_KEYS = [ 'tags', 'categories', 'main_category', 'third_category', 'features', 'types' ]; const KITS_CACHE_KEY = 'elementor_remote_kits'; const KITS_TAXONOMIES_CACHE_KEY = 'elementor_remote_kits_taxonomies'; const KITS_CACHE_TTL_HOURS = 12; const KITS_TAXONOMIES_CACHE_TTL_HOURS = 12; /** * @var Kit_Library */ protected $api; /** * @var User_Favorites */ protected $user_favorites; /** * @var Collection */ protected $subscription_plans; /** * Get all kits. * * @param false $force_api_request * * @return Collection */ public function get_all( $force_api_request = false ) { return $this->get_kits_data( $force_api_request ) ->map( function ( $kit ) { return $this->transform_kit_api_response( $kit ); } ); } /** * Get specific kit. * * @param $id * @param array $options * * @return array|null * * @throws WP_Error_Exception If kit is not found. */ public function find( $id, $options = [] ) { $options = wp_parse_args( $options, [ 'manifest_included' => true, ] ); $item = $this->get_kits_data() ->find( function ( $kit ) use ( $id ) { return $kit->_id === $id; } ); if ( ! $item ) { return null; } $manifest = null; if ( $options['manifest_included'] ) { $manifest = $this->api->get_manifest( $id ); if ( is_wp_error( $manifest ) ) { throw new WP_Error_Exception( esc_html( $manifest ) ); } } return $this->transform_kit_api_response( $item, $manifest ); } /** * @param false $force_api_request * * @return Collection */ public function get_taxonomies( $force_api_request = false ) { return $this->get_taxonomies_data( $force_api_request ) ->only( static::TAXONOMIES_KEYS ) ->reduce( function ( Collection $carry, $taxonomies, $type ) { return $carry->merge( array_map( function ( $taxonomy ) use ( $type ) { return [ 'text' => $taxonomy->name, 'type' => $type, ]; }, $taxonomies ) ); }, new Collection( [] ) ) ->merge( $this->subscription_plans->map( function ( $label ) { return [ 'text' => $label ? $label : self::SUBSCRIPTION_PLAN_FREE_TAG, 'type' => 'subscription_plans', ]; } ) ) ->unique( [ 'text', 'type' ] ); } /** * @param $id * * @return array * * @throws WP_Error_Exception If download link retrieval fails or API errors occur. */ public function get_download_link( $id ) { $response = $this->api->download_link( $id ); if ( is_wp_error( $response ) ) { throw new WP_Error_Exception( esc_html( $response ) ); } return [ 'download_link' => $response->download_link ]; } /** * @param $id * * @return array * * @throws Error_404 If kit is not found. */ public function add_to_favorites( $id ) { $kit = $this->find( $id, [ 'manifest_included' => false ] ); if ( ! $kit ) { throw new Error_404( esc_html__( 'Kit not found', 'elementor' ), 'kit_not_found' ); } $this->user_favorites->add( 'elementor', 'kits', $kit['id'] ); $kit['is_favorite'] = true; return $kit; } /** * @param $id * * @return array * * @throws Error_404 If kit is not found. */ public function remove_from_favorites( $id ) { $kit = $this->find( $id, [ 'manifest_included' => false ] ); if ( ! $kit ) { throw new Error_404( esc_html__( 'Kit not found', 'elementor' ), 'kit_not_found' ); } $this->user_favorites->remove( 'elementor', 'kits', $kit['id'] ); $kit['is_favorite'] = false; return $kit; } /** * @param bool $force_api_request * * @return Collection * * @throws WP_Error_Exception If kits data retrieval fails. */ private function get_kits_data( $force_api_request = false ) { $data = get_transient( static::KITS_CACHE_KEY ); $experiments_manager = Plugin::$instance->experiments; $kits_editor_layout_type = $experiments_manager->is_feature_active( 'container' ) ? 'container_flexbox' : ''; if ( ! $data || $force_api_request ) { $args = [ 'body' => [ 'editor_layout_type' => $kits_editor_layout_type, ], ]; /** * Filters arguments for the request to the Kits API. * * @since 3.11.0 * * @param array[] $args Array of http arguments. */ $args = apply_filters( 'elementor/kit-library/get-kits-data/args', $args ); $data = $this->api->get_all( $args ); if ( is_wp_error( $data ) ) { throw new WP_Error_Exception( esc_html( $data ) ); } set_transient( static::KITS_CACHE_KEY, $data, static::KITS_CACHE_TTL_HOURS * HOUR_IN_SECONDS ); } return new Collection( $data ); } /** * @param bool $force_api_request * * @return Collection * * @throws WP_Error_Exception If taxonomies data retrieval fails. */ private function get_taxonomies_data( $force_api_request = false ) { $data = get_transient( static::KITS_TAXONOMIES_CACHE_KEY ); if ( ! $data || $force_api_request ) { $data = $this->api->get_taxonomies(); if ( is_wp_error( $data ) ) { throw new WP_Error_Exception( esc_html( $data ) ); } set_transient( static::KITS_TAXONOMIES_CACHE_KEY, $data, static::KITS_TAXONOMIES_CACHE_TTL_HOURS * HOUR_IN_SECONDS ); } return new Collection( (array) $data ); } /** * @param $kit * @param null $manifest * * @return array */ private function transform_kit_api_response( $kit, $manifest = null ) { // BC: Support legacy APIs that don't have access tiers. if ( isset( $kit->access_tier ) ) { $access_tier = $kit->access_tier; } else { $access_tier = 0 === $kit->access_level ? ConnectModule::ACCESS_TIER_FREE : ConnectModule::ACCESS_TIER_ESSENTIAL; } $subscription_plan_tag = $this->subscription_plans->get( $access_tier ); $taxonomies = ( new Collection( ( (array) $kit )['taxonomies'] ) ) ->filter( function ( $taxonomy ) { return in_array( $taxonomy->type, self::TAXONOMIES_KEYS ); } ) ->flatten() ->pluck( 'name' ) ->push( $subscription_plan_tag ? $subscription_plan_tag : self::SUBSCRIPTION_PLAN_FREE_TAG ); return array_merge( [ 'id' => $kit->_id, 'title' => $kit->title, 'thumbnail_url' => $kit->thumbnail, 'access_level' => $kit->access_level, 'access_tier' => $access_tier, 'keywords' => $kit->keywords, 'taxonomies' => $taxonomies->values(), 'is_favorite' => $this->user_favorites->exists( 'elementor', 'kits', $kit->_id ), // TODO: Remove all the isset when the API stable. 'trend_index' => isset( $kit->trend_index ) ? $kit->trend_index : 0, 'featured_index' => isset( $kit->featured_index ) ? $kit->featured_index : 0, 'popularity_index' => isset( $kit->popularity_index ) ? $kit->popularity_index : 0, 'created_at' => isset( $kit->created_at ) ? $kit->created_at : null, 'updated_at' => isset( $kit->updated_at ) ? $kit->updated_at : null, ], $manifest ? $this->transform_manifest_api_response( $manifest ) : [] ); } /** * @param $manifest * * @return array */ private function transform_manifest_api_response( $manifest ) { $manifest_content = ( new Collection( (array) $manifest->content ) ) ->reduce( function ( $carry, $content, $type ) { $mapped_documents = array_map( function ( $document ) use ( $type ) { // TODO: Fix it! // Hack to override a bug when a document with type of 'wp-page' is declared as 'wp-post'. if ( 'page' === $type ) { $document->doc_type = 'wp-page'; } return $document; }, (array) $content ); return $carry + $mapped_documents; }, [] ); $content = ( new Collection( (array) $manifest->templates ) ) ->union( $manifest_content ) ->map( function ( $manifest_item, $key ) { return [ 'id' => isset( $manifest_item->id ) ? $manifest_item->id : $key, 'title' => $manifest_item->title, 'doc_type' => $manifest_item->doc_type, 'thumbnail_url' => $manifest_item->thumbnail, 'preview_url' => isset( $manifest_item->url ) ? $manifest_item->url : null, ]; } ); return [ 'description' => $manifest->description, 'preview_url' => isset( $manifest->site ) ? $manifest->site : '', 'documents' => $content->values(), ]; } /** * @param Kit_Library $kit_library * @param User_Favorites $user_favorites * @param Collection $subscription_plans */ public function __construct( Kit_Library $kit_library, User_Favorites $user_favorites, Collection $subscription_plans ) { $this->api = $kit_library; $this->user_favorites = $user_favorites; $this->subscription_plans = $subscription_plans; } public static function clear_cache() { delete_transient( static::KITS_CACHE_KEY ); } } modules/kit-library/data/kits/controller.php 0000644 00000002765 15233532517 0015244 0 ustar 00 <?php namespace Elementor\App\Modules\KitLibrary\Data\Kits; use Elementor\App\Modules\KitLibrary\Data\Base_Controller; use Elementor\Data\V2\Base\Exceptions\Error_404; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Base_Controller { public function get_name() { return 'kits'; } public function get_items( $request ) { $data = $this->get_repository()->get_all( $request->get_param( 'force' ) ); return [ 'data' => $data->values(), ]; } public function get_item( $request ) { $data = $this->get_repository()->find( $request->get_param( 'id' ) ); if ( ! $data ) { return new Error_404( esc_html__( 'Kit not exists.', 'elementor' ), 'kit_not_exists' ); } return [ 'data' => $data, ]; } public function get_collection_params() { return [ 'force' => [ 'description' => 'Force an API request and skip the cache.', 'required' => false, 'default' => false, 'type' => 'boolean', ], ]; } public function register_endpoints() { $this->index_endpoint->register_item_route( \WP_REST_Server::READABLE, [ 'id' => [ 'description' => 'Unique identifier for the object.', 'type' => 'string', 'required' => true, ], 'id_arg_type_regex' => '[\w]+', ] ); $this->register_endpoint( new Endpoints\Download_Link( $this ) ); $this->register_endpoint( new Endpoints\Favorites( $this ) ); } public function get_permission_callback( $request ) { return current_user_can( 'manage_options' ); } } modules/kit-library/data/kits/endpoints/favorites.php 0000644 00000002032 15233532517 0017051 0 ustar 00 <?php namespace Elementor\App\Modules\KitLibrary\Data\Kits\Endpoints; use Elementor\App\Modules\KitLibrary\Data\Kits\Controller; use Elementor\Data\V2\Base\Endpoint; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @property Controller $controller */ class Favorites extends Endpoint { public function get_name() { return 'favorites'; } public function get_format() { return 'kits/favorites/{id}'; } protected function register() { $args = [ 'id_arg_type_regex' => '[\w]+', ]; $this->register_item_route( \WP_REST_Server::CREATABLE, $args ); $this->register_item_route( \WP_REST_Server::DELETABLE, $args ); } public function create_item( $id, $request ) { $repository = $this->controller->get_repository(); $kit = $repository->add_to_favorites( $id ); return [ 'data' => $kit, ]; } public function delete_item( $id, $request ) { $repository = $this->controller->get_repository(); $kit = $repository->remove_from_favorites( $id ); return [ 'data' => $kit, ]; } } modules/kit-library/data/kits/endpoints/download-link.php 0000644 00000001533 15233532517 0017616 0 ustar 00 <?php namespace Elementor\App\Modules\KitLibrary\Data\Kits\Endpoints; use Elementor\Data\V2\Base\Endpoint; use Elementor\App\Modules\KitLibrary\Data\Kits\Controller; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @property Controller $controller */ class Download_Link extends Endpoint { public function get_name() { return 'download-link'; } public function get_format() { return 'kits/download-link/{id}'; } protected function register() { $this->register_item_route( \WP_REST_Server::READABLE, [ 'id_arg_type_regex' => '[\w]+', ] ); } public function get_item( $id, $request ) { $repository = $this->controller->get_repository(); $data = $repository->get_download_link( $id ); return [ 'data' => $data, 'meta' => [ 'nonce' => wp_create_nonce( 'kit-library-import' ), ], ]; } } modules/kit-library/module.php 0000644 00000012730 15233532517 0012454 0 ustar 00 <?php namespace Elementor\App\Modules\KitLibrary; use Elementor\App\Modules\KitLibrary\Data\Repository; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Core\Base\Module as BaseModule; use Elementor\App\Modules\KitLibrary\Connect\Kit_Library; use Elementor\Core\Common\Modules\Connect\Module as ConnectModule; use Elementor\App\Modules\KitLibrary\Data\Kits\Controller as Kits_Controller; use Elementor\App\Modules\KitLibrary\Data\Taxonomies\Controller as Taxonomies_Controller; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Utils as ElementorUtils; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\App\Modules\KitLibrary\AdminMenuItems\Editor_One_Website_Templates_Menu; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { /** * Get name. * * @access public * * @return string */ public function get_name() { return 'kit-library'; } private function register_editor_one_menu( Menu_Data_Provider $menu_data_provider ) { $menu_data_provider->register_menu( new Editor_One_Website_Templates_Menu() ); } private function set_kit_library_settings() { if ( ! Plugin::$instance->common ) { return; } /** @var ConnectModule $connect */ $connect = Plugin::$instance->common->get_component( 'connect' ); /** @var Kit_Library $kit_library */ $kit_library = $connect->get_app( 'kit-library' ); Plugin::$instance->app->set_settings( 'kit-library', [ 'has_access_to_module' => current_user_can( 'manage_options' ), 'subscription_plans' => $this->apply_filter_subscription_plans( $connect->get_subscription_plans( 'kit-library' ) ), 'is_pro' => false, 'is_library_connected' => $kit_library->is_connected(), 'library_connect_url' => $kit_library->get_admin_url( 'authorize', [ 'utm_source' => 'kit-library', 'utm_medium' => 'wp-dash', 'utm_campaign' => 'library-connect', 'utm_term' => '%%page%%', // Will be replaced in the frontend. ] ), 'access_level' => ConnectModule::ACCESS_LEVEL_CORE, 'access_tier' => ConnectModule::ACCESS_TIER_FREE, 'plan_type' => ConnectModule::ACCESS_TIER_FREE, 'app_url' => Plugin::$instance->app->get_base_url() . '#/' . $this->get_name(), 'urls' => [ 'createNewPage' => Plugin::$instance->documents->get_create_new_post_url(), ], ] ); } private function apply_filter_subscription_plans( array $subscription_plans ): array { foreach ( $subscription_plans as $key => $plan ) { if ( null === $plan['promotion_url'] ) { continue; } $subscription_plans[ $key ] = Filtered_Promotions_Manager::get_filtered_promotion_data( $plan, 'elementor/kit_library/' . $key . '/promotion', 'promotion_url' ); } return $subscription_plans; } /** * Module constructor. */ public function __construct() { Plugin::$instance->data_manager_v2->register_controller( new Kits_Controller() ); Plugin::$instance->data_manager_v2->register_controller( new Taxonomies_Controller() ); $this->register_actions(); do_action( 'elementor/kit_library/registered', $this ); } public function register_actions() { // Assigning this action here since the repository is being loaded by demand. add_action( 'elementor/experiments/feature-state-change/container', [ Repository::class, 'clear_cache' ], 10, 0 ); add_action( 'elementor/editor-one/menu/register', function ( Menu_Data_Provider $menu_data_provider ) { $this->register_editor_one_menu( $menu_data_provider ); } ); add_filter( 'elementor/editor-one/menu/protected_templates_submenu_slugs', function ( array $protected_slugs ): array { $protected_slugs[] = 'edit-tags.php?taxonomy=elementor_library_category&post_type=elementor_library'; return $protected_slugs; } ); add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) { $connect_module->register_app( 'kit-library', Kit_Library::get_class_name() ); } ); add_action( 'elementor/init', function () { $this->set_kit_library_settings(); }, 12 /** After the initiation of the connect kit library */ ); add_action( 'template_redirect', [ $this, 'handle_kit_screenshot_generation' ] ); } public function handle_kit_screenshot_generation() { $is_kit_preview = ElementorUtils::get_super_global_value( $_GET, 'kit_thumbnail' ); $nonce = ElementorUtils::get_super_global_value( $_GET, 'nonce' ); if ( $is_kit_preview ) { if ( ! wp_verify_nonce( $nonce, 'kit_thumbnail' ) ) { wp_die( esc_html__( 'Not Authorized', 'elementor' ), esc_html__( 'Error', 'elementor' ), 403 ); } $suffix = ( ElementorUtils::is_script_debug() || ElementorUtils::is_elementor_tests() ) ? '' : '.min'; show_admin_bar( false ); wp_enqueue_script( 'dom-to-image', ELEMENTOR_ASSETS_URL . "/lib/dom-to-image/js/dom-to-image{$suffix}.js", [], '2.6.0', true ); wp_enqueue_script( 'html2canvas', ELEMENTOR_ASSETS_URL . "/lib/html2canvas/js/html2canvas{$suffix}.js", [], '1.4.1', true ); wp_enqueue_script( 'cloud-library-screenshot', ELEMENTOR_ASSETS_URL . "/js/cloud-library-screenshot{$suffix}.js", [ 'dom-to-image', 'html2canvas', 'elementor-common', 'elementor-common-modules' ], ELEMENTOR_VERSION, true ); $config = [ 'home_url' => home_url(), 'kit_id' => uniqid(), 'selector' => 'body', ]; wp_add_inline_script( 'cloud-library-screenshot', 'var ElementorScreenshotConfig = ' . wp_json_encode( $config ) . ';' ); } } } modules/kit-library/connect/kit-library.php 0000644 00000004031 15233532517 0015044 0 ustar 00 <?php namespace Elementor\App\Modules\KitLibrary\Connect; use Elementor\Core\Common\Modules\Connect\Apps\Base_App; use Elementor\Core\Common\Modules\Connect\Apps\Library; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Kit_Library extends Library { const DEFAULT_BASE_ENDPOINT = 'https://my.elementor.com/api/v1/kits-library'; const FALLBACK_BASE_ENDPOINT = 'https://ms-8874.elementor.com/api/v1/kits-library'; public function get_title() { return esc_html__( 'Kit Library', 'elementor' ); } public function get_all( $args = [] ) { return $this->http_request( 'GET', 'kits/plugin-version/' . ELEMENTOR_VERSION, $args ); } public function get_by_id( $id ) { return $this->http_request( 'GET', 'kits/' . $id ); } public function get_taxonomies() { return $this->http_request( 'GET', 'taxonomies' ); } public function get_manifest( $id ) { return $this->http_request( 'GET', "kits/{$id}/manifest" ); } public function download_link( $id ) { return $this->http_request( 'GET', "kits/{$id}/download-link" ); } protected function get_api_url() { return [ static::DEFAULT_BASE_ENDPOINT, static::FALLBACK_BASE_ENDPOINT, ]; } /** * Get all the connect information * * @return array */ protected function get_connect_info() { $connect_info = $this->get_base_connect_info(); $additional_info = []; // BC Support. $old_kit_library = new \Elementor\Core\App\Modules\KitLibrary\Connect\Kit_Library(); /** * Additional connect info. * * Filters the connection information when connecting to Elementor servers. * This hook can be used to add more information or add more data. * * @param array $additional_info Additional connecting information array. * @param Base_App $this The base app instance. */ $additional_info = apply_filters( 'elementor/connect/additional-connect-info', $additional_info, $old_kit_library ); return array_merge( $connect_info, $additional_info ); } protected function init() { // Remove parent init actions. } } modules/kit-library/admin-menu-items/editor-one-website-templates-menu.php 0000644 00000002223 15233532517 0022777 0 ustar 00 <?php namespace Elementor\App\Modules\KitLibrary\AdminMenuItems; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Website_Templates_Menu implements Menu_Item_Interface { public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'Website Templates', 'elementor' ); } public function get_position(): int { return 30; } public function get_slug(): string { $app = Plugin::$instance->app; if ( $app ) { $return_to = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) ); return add_query_arg( [ 'return_to' => $return_to, 'source' => 'wp_db_templates_menu', ], $app->get_base_url() ) . '#/kit-library'; } return 'elementor-app#/kit-library'; } public function get_group_id(): string { return Menu_Config::TEMPLATES_GROUP_ID; } } modules/onboarding/data/controller.php 0000644 00000003120 15233532517 0014145 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Data; use Elementor\App\Modules\Onboarding\Data\Endpoints\Install_Pro; use Elementor\App\Modules\Onboarding\Data\Endpoints\Install_Theme; use Elementor\App\Modules\Onboarding\Data\Endpoints\Pro_Install_Screen; use Elementor\App\Modules\Onboarding\Data\Endpoints\User_Choices; use Elementor\App\Modules\Onboarding\Data\Endpoints\User_Progress; use Elementor\Data\V2\Base\Controller as Base_Controller; if ( ! defined( 'ABSPATH' ) ) { exit; } class Controller extends Base_Controller { public function get_name(): string { return 'onboarding'; } public function register_endpoints(): void { $this->register_endpoint( new User_Progress( $this ) ); $this->register_endpoint( new User_Choices( $this ) ); $this->register_endpoint( new Pro_Install_Screen( $this ) ); $this->register_endpoint( new Install_Pro( $this ) ); $this->register_endpoint( new Install_Theme( $this ) ); } public function get_items_permissions_check( $request ) { return current_user_can( 'manage_options' ); } public function get_item_permissions_check( $request ) { return current_user_can( 'manage_options' ); } public function create_items_permissions_check( $request ) { return current_user_can( 'manage_options' ); } public function create_item_permissions_check( $request ) { return current_user_can( 'manage_options' ); } public function update_items_permissions_check( $request ) { return current_user_can( 'manage_options' ); } public function update_item_permissions_check( $request ) { return current_user_can( 'manage_options' ); } } modules/onboarding/data/endpoints/install-pro.php 0000644 00000003631 15233532517 0016240 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Data\Endpoints; use Elementor\Data\V2\Base\Endpoint as Endpoint_Base; use Elementor\Modules\ProInstall\Plugin_Installer; use Elementor\Plugin; use Elementor\Utils; use WP_REST_Server; if ( ! defined( 'ABSPATH' ) ) { exit; } class Install_Pro extends Endpoint_Base { public function get_name(): string { return 'install-pro'; } public function get_format(): string { return 'onboarding'; } protected function register(): void { parent::register(); $this->register_items_route( WP_REST_Server::CREATABLE ); } public function create_items( $request ) { if ( Utils::has_pro() || Utils::is_pro_installed_and_not_active() ) { return [ 'data' => [ 'success' => true, 'message' => 'already_installed', ], ]; } $connect = Plugin::$instance->common->get_component( 'connect' ); if ( ! $connect ) { return new \WP_Error( 'connect_unavailable', __( 'Connect module is not available.', 'elementor' ), [ 'status' => 500 ] ); } $pro_install_app = $connect->get_app( 'pro-install' ); if ( ! $pro_install_app || ! $pro_install_app->is_connected() ) { return new \WP_Error( 'not_connected', __( 'You must be connected to install Elementor Pro.', 'elementor' ), [ 'status' => 400 ] ); } $download_link = $pro_install_app->get_download_link(); if ( empty( $download_link ) ) { return new \WP_Error( 'no_subscription', __( 'There are no available subscriptions at the moment.', 'elementor' ), [ 'status' => 400 ] ); } $plugin_installer = new Plugin_Installer( 'elementor-pro', $download_link ); $result = $plugin_installer->install(); if ( is_wp_error( $result ) ) { return new \WP_Error( 'install_failed', $result->get_error_message(), [ 'status' => 500 ] ); } return [ 'data' => [ 'success' => true, 'message' => 'installed', ], ]; } } modules/onboarding/data/endpoints/install-theme.php 0000644 00000004747 15233532517 0016553 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Data\Endpoints; use Elementor\Data\V2\Base\Endpoint as Endpoint_Base; use WP_REST_Server; if ( ! defined( 'ABSPATH' ) ) { exit; } class Install_Theme extends Endpoint_Base { const ALLOWED_THEMES = [ 'hello-elementor', 'hello-biz' ]; public function get_name(): string { return 'install-theme'; } public function get_format(): string { return 'onboarding'; } protected function register(): void { parent::register(); $this->register_items_route( WP_REST_Server::CREATABLE ); } public function create_items( $request ) { $permission = $this->check_permission(); if ( is_wp_error( $permission ) ) { return $permission; } $params = $request->get_json_params(); $theme_slug = $params['theme_slug'] ?? ''; if ( empty( $theme_slug ) || ! in_array( $theme_slug, self::ALLOWED_THEMES, true ) ) { return new \WP_Error( 'invalid_theme', __( 'Invalid or unsupported theme.', 'elementor' ), [ 'status' => 400 ] ); } if ( ! current_user_can( 'install_themes' ) || ! current_user_can( 'switch_themes' ) ) { return new \WP_Error( 'insufficient_permissions', __( 'You do not have permission to install themes.', 'elementor' ), [ 'status' => 403 ] ); } $theme = wp_get_theme( $theme_slug ); if ( ! $theme->exists() ) { if ( ! function_exists( 'request_filesystem_credentials' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } if ( ! class_exists( '\Theme_Upgrader' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } if ( ! class_exists( '\WP_Ajax_Upgrader_Skin' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php'; } $skin = new \WP_Ajax_Upgrader_Skin(); $upgrader = new \Theme_Upgrader( $skin ); $result = $upgrader->install( "https://downloads.wordpress.org/theme/{$theme_slug}.latest-stable.zip" ); if ( is_wp_error( $result ) || ! $result ) { return new \WP_Error( 'theme_install_failed', __( 'Failed to install the theme.', 'elementor' ), [ 'status' => 500 ] ); } } switch_theme( $theme_slug ); return [ 'data' => [ 'success' => true, 'message' => 'theme_installed', ], ]; } private function check_permission() { if ( ! current_user_can( 'manage_options' ) ) { return new \WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to access onboarding data.', 'elementor' ), [ 'status' => 403 ] ); } return true; } } modules/onboarding/data/endpoints/pro-install-screen.php 0000644 00000001223 15233532517 0017510 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Data\Endpoints; use Elementor\App\Modules\Onboarding\Module; use Elementor\Data\V2\Base\Endpoint as Endpoint_Base; if ( ! defined( 'ABSPATH' ) ) { exit; } class Pro_Install_Screen extends Endpoint_Base { public function get_name(): string { return 'pro-install-screen'; } public function get_format(): string { return 'onboarding'; } protected function register(): void { parent::register(); $this->register_items_route(); } public function get_items( $request ) { return [ 'data' => [ 'shouldShowProInstallScreen' => Module::should_show_pro_install_screen(), ], ]; } } modules/onboarding/data/endpoints/user-choices.php 0000644 00000003326 15233532517 0016366 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Data\Endpoints; use Elementor\App\Modules\Onboarding\Storage\Onboarding_Progress_Manager; use Elementor\App\Modules\Onboarding\Validation\User_Choices_Validator; use Elementor\Data\V2\Base\Endpoint as Endpoint_Base; use WP_REST_Server; if ( ! defined( 'ABSPATH' ) ) { exit; } class User_Choices extends Endpoint_Base { public function get_name(): string { return 'user-choices'; } public function get_format(): string { return 'onboarding'; } protected function register(): void { parent::register(); $this->register_items_route( WP_REST_Server::EDITABLE ); } public function get_items( $request ) { $permission = $this->check_permission(); if ( is_wp_error( $permission ) ) { return $permission; } $manager = Onboarding_Progress_Manager::instance(); $choices = $manager->get_choices(); return [ 'data' => $choices->to_array(), ]; } public function update_items( $request ) { $permission = $this->check_permission(); if ( is_wp_error( $permission ) ) { return $permission; } $params = $request->get_json_params(); $validator = new User_Choices_Validator(); $validated = $validator->validate( $params ?? [] ); if ( is_wp_error( $validated ) ) { return $validated; } $manager = Onboarding_Progress_Manager::instance(); $choices = $manager->update_choices( $validated ); return [ 'data' => 'success', 'choices' => $choices->to_array(), ]; } private function check_permission() { if ( ! current_user_can( 'manage_options' ) ) { return new \WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to access onboarding data.', 'elementor' ), [ 'status' => 403 ] ); } return true; } } modules/onboarding/data/endpoints/user-progress.php 0000644 00000003612 15233532517 0016613 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Data\Endpoints; use Elementor\App\Modules\Onboarding\Module; use Elementor\App\Modules\Onboarding\Storage\Onboarding_Progress_Manager; use Elementor\App\Modules\Onboarding\Validation\User_Progress_Validator; use Elementor\Data\V2\Base\Endpoint as Endpoint_Base; use WP_REST_Server; if ( ! defined( 'ABSPATH' ) ) { exit; } class User_Progress extends Endpoint_Base { public function get_name(): string { return 'user-progress'; } public function get_format(): string { return 'onboarding'; } protected function register(): void { parent::register(); $this->register_items_route( WP_REST_Server::EDITABLE ); } public function get_items( $request ) { $permission = $this->check_permission(); if ( is_wp_error( $permission ) ) { return $permission; } $manager = Onboarding_Progress_Manager::instance(); $progress = $manager->get_progress(); return [ 'data' => $progress->to_array(), 'meta' => [ 'had_unexpected_exit' => $progress->had_unexpected_exit( Module::has_user_finished_onboarding() ), ], ]; } public function update_items( $request ) { $permission = $this->check_permission(); if ( is_wp_error( $permission ) ) { return $permission; } $params = $request->get_json_params(); $validator = new User_Progress_Validator(); $validated = $validator->validate( $params ?? [] ); if ( is_wp_error( $validated ) ) { return $validated; } $manager = Onboarding_Progress_Manager::instance(); $progress = $manager->update_progress( $validated ); return [ 'data' => 'success', 'progress' => $progress->to_array(), ]; } private function check_permission() { if ( ! current_user_can( 'manage_options' ) ) { return new \WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to access onboarding data.', 'elementor' ), [ 'status' => 403 ] ); } return true; } } modules/onboarding/storage/onboarding-progress-manager.php 0000644 00000010154 15233532517 0020116 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Storage; use Elementor\App\Modules\Onboarding\Module; use Elementor\App\Modules\Onboarding\Storage\Entities\User_Choices; use Elementor\App\Modules\Onboarding\Storage\Entities\User_Progress; if ( ! defined( 'ABSPATH' ) ) { exit; } class Onboarding_Progress_Manager { const PROGRESS_OPTION_KEY = 'elementor_onboarding_progress'; const CHOICES_OPTION_KEY = 'elementor_onboarding_choices'; const DEFAULT_TOTAL_STEPS = 5; private static ?Onboarding_Progress_Manager $instance = null; public static function instance(): Onboarding_Progress_Manager { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } public function get_progress(): User_Progress { $data = get_option( self::PROGRESS_OPTION_KEY, [] ); return User_Progress::from_array( $data ); } public function save_progress( User_Progress $progress ): User_Progress { update_option( self::PROGRESS_OPTION_KEY, $progress->to_array() ); return $progress; } public function update_progress( array $params ): User_Progress { $progress = $this->get_progress(); if ( isset( $params['current_step'] ) ) { $progress->set_current_step( (int) $params['current_step'] ); } if ( isset( $params['completed_steps'] ) ) { $progress->set_completed_steps( (array) $params['completed_steps'] ); } if ( isset( $params['exit_type'] ) ) { $progress->set_exit_type( $params['exit_type'] ); } if ( isset( $params['complete_step'] ) ) { $step = $params['complete_step']; $progress->add_completed_step( $step ); $step_index = $params['step_index'] ?? $progress->get_current_step_index(); $total_steps = $params['total_steps'] ?? self::DEFAULT_TOTAL_STEPS; $next_index = $step_index + 1; if ( $next_index < $total_steps ) { $progress->set_current_step_index( $next_index ); $progress->set_current_step_id( null ); } } if ( ! empty( $params['skip_step'] ) ) { $step_index = $params['step_index'] ?? $progress->get_current_step_index(); $total_steps = $params['total_steps'] ?? self::DEFAULT_TOTAL_STEPS; $next_index = $step_index + 1; if ( $next_index < $total_steps ) { $progress->set_current_step_index( $next_index ); $progress->set_current_step_id( null ); } } if ( isset( $params['start'] ) && $params['start'] ) { $progress->set_started_at( current_time( 'timestamp' ) ); $progress->set_exit_type( null ); } if ( isset( $params['complete'] ) && $params['complete'] ) { $progress->set_exit_type( 'user_exit' ); update_option( Module::ONBOARDING_OPTION, Module::VERSION ); } if ( isset( $params['user_exit'] ) && $params['user_exit'] ) { $progress->set_exit_type( 'user_exit' ); } if ( isset( $params['starter_dismissed'] ) && $params['starter_dismissed'] ) { $progress->set_starter_dismissed( true ); } $progress->set_last_active_timestamp( current_time( 'timestamp' ) ); return $this->save_progress( $progress ); } public function get_choices(): User_Choices { $data = get_option( self::CHOICES_OPTION_KEY, [] ); return User_Choices::from_array( $data ); } public function save_choices( User_Choices $choices ): User_Choices { update_option( self::CHOICES_OPTION_KEY, $choices->to_array() ); return $choices; } public function update_choices( array $params ): User_Choices { $choices = $this->get_choices(); if ( isset( $params['building_for'] ) ) { $choices->set_building_for( $params['building_for'] ); } if ( isset( $params['site_about'] ) ) { $choices->set_site_about( (array) $params['site_about'] ); } if ( isset( $params['experience_level'] ) ) { $choices->set_experience_level( $params['experience_level'] ); } if ( isset( $params['theme_selection'] ) ) { $choices->set_theme_selection( $params['theme_selection'] ); } if ( isset( $params['site_features'] ) ) { $choices->set_site_features( (array) $params['site_features'] ); } return $this->save_choices( $choices ); } public function reset(): void { delete_option( self::PROGRESS_OPTION_KEY ); delete_option( self::CHOICES_OPTION_KEY ); } private function __construct() {} } modules/onboarding/storage/entities/user-choices.php 0000644 00000003601 15233532517 0016736 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Storage\Entities; if ( ! defined( 'ABSPATH' ) ) { exit; } class User_Choices { private ?string $building_for = null; private array $site_about = []; private ?string $experience_level = null; private ?string $theme_selection = null; private array $site_features = []; public static function from_array( array $data ): self { $instance = new self(); $instance->building_for = $data['building_for'] ?? null; $instance->site_about = $data['site_about'] ?? []; $instance->experience_level = $data['experience_level'] ?? null; $instance->theme_selection = $data['theme_selection'] ?? null; $instance->site_features = $data['site_features'] ?? []; return $instance; } public function to_array(): array { return [ 'building_for' => $this->building_for, 'site_about' => $this->site_about, 'experience_level' => $this->experience_level, 'theme_selection' => $this->theme_selection, 'site_features' => $this->site_features, ]; } public function get_building_for(): ?string { return $this->building_for; } public function set_building_for( ?string $value ): void { $this->building_for = $value; } public function get_site_about(): array { return $this->site_about; } public function set_site_about( array $value ): void { $this->site_about = $value; } public function get_experience_level(): ?string { return $this->experience_level; } public function set_experience_level( ?string $value ): void { $this->experience_level = $value; } public function get_theme_selection(): ?string { return $this->theme_selection; } public function set_theme_selection( ?string $value ): void { $this->theme_selection = $value; } public function get_site_features(): array { return $this->site_features; } public function set_site_features( array $value ): void { $this->site_features = $value; } } modules/onboarding/storage/entities/user-progress.php 0000644 00000006620 15233532517 0017171 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Storage\Entities; if ( ! defined( 'ABSPATH' ) ) { exit; } class User_Progress { private int $current_step_index = 0; private ?string $current_step_id = null; private array $completed_steps = []; private ?string $exit_type = null; private ?int $last_active_timestamp = null; private ?int $started_at = null; private bool $starter_dismissed = false; public static function from_array( array $data ): self { $instance = new self(); $instance->current_step_index = $data['current_step_index'] ?? $data['current_step'] ?? 0; $instance->current_step_id = $data['current_step_id'] ?? null; $instance->completed_steps = $data['completed_steps'] ?? []; $instance->exit_type = $data['exit_type'] ?? null; $instance->last_active_timestamp = $data['last_active_timestamp'] ?? null; $instance->started_at = $data['started_at'] ?? null; $instance->starter_dismissed = ! empty( $data['starter_dismissed'] ); return $instance; } public function to_array(): array { return [ 'current_step' => $this->current_step_index, 'current_step_index' => $this->current_step_index, 'current_step_id' => $this->current_step_id, 'completed_steps' => $this->completed_steps, 'exit_type' => $this->exit_type, 'last_active_timestamp' => $this->last_active_timestamp, 'started_at' => $this->started_at, 'starter_dismissed' => $this->starter_dismissed, ]; } public function get_current_step(): int { return $this->current_step_index; } public function get_current_step_index(): int { return $this->current_step_index; } public function set_current_step_index( int $index ): void { $this->current_step_index = $index; } public function get_current_step_id(): ?string { return $this->current_step_id; } public function set_current_step_id( ?string $step_id ): void { $this->current_step_id = $step_id; } public function set_current_step( int $step, ?string $step_id = null ): void { $this->current_step_index = $step; if ( null !== $step_id ) { $this->current_step_id = $step_id; } } public function get_completed_steps(): array { return $this->completed_steps; } public function set_completed_steps( array $steps ): void { $this->completed_steps = $steps; } public function add_completed_step( $step ): void { if ( ! in_array( $step, $this->completed_steps, true ) ) { $this->completed_steps[] = $step; } } public function is_step_completed( $step ): bool { return in_array( $step, $this->completed_steps, true ); } public function get_exit_type(): ?string { return $this->exit_type; } public function set_exit_type( ?string $type ): void { $this->exit_type = $type; } public function get_last_active_timestamp(): ?int { return $this->last_active_timestamp; } public function set_last_active_timestamp( ?int $timestamp ): void { $this->last_active_timestamp = $timestamp; } public function get_started_at(): ?int { return $this->started_at; } public function set_started_at( ?int $timestamp ): void { $this->started_at = $timestamp; } public function is_starter_dismissed(): bool { return $this->starter_dismissed; } public function set_starter_dismissed( bool $dismissed ): void { $this->starter_dismissed = $dismissed; } public function had_unexpected_exit( bool $is_completed ): bool { return null === $this->exit_type && $this->current_step_index > 0 && ! $is_completed; } } modules/onboarding/module.php 0000644 00000024706 15233532517 0012353 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding; use Elementor\App\Modules\Onboarding\Data\Controller; use Elementor\App\Modules\Onboarding\Data\Endpoints\Install_Theme; use Elementor\App\Modules\Onboarding\Storage\Entities\User_Choices; use Elementor\App\Modules\Onboarding\Storage\Entities\User_Progress; use Elementor\App\Modules\Onboarding\Storage\Onboarding_Progress_Manager; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Includes\EditorAssetsAPI; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Module extends BaseModule { const VERSION = '2.0.0'; const ASSETS_BASE_URL = 'https://assets.elementor.com/onboarding/v1/strings/'; const ONBOARDING_OPTION = 'elementor_onboarded'; const SUPPORTED_LOCALES = [ 'de_DE' => 'de', 'es_ES' => 'es', 'fr_FR' => 'fr', 'he_IL' => 'he', 'id_ID' => 'id', 'it_IT' => 'it', 'nl_NL' => 'nl', 'pl_PL' => 'pl', 'pt_BR' => 'pt', 'tr_TR' => 'tr', ]; private Onboarding_Progress_Manager $progress_manager; public function get_name(): string { return 'onboarding'; } public static function has_user_finished_onboarding(): bool { return (bool) get_option( self::ONBOARDING_OPTION ); } public function __construct() { $this->progress_manager = Onboarding_Progress_Manager::instance(); Plugin::instance()->data_manager_v2->register_controller( new Controller() ); add_action( 'elementor/init', [ $this, 'on_elementor_init' ], 12 ); if ( $this->should_show_starter() ) { add_filter( 'elementor/editor/localize_settings', [ $this, 'add_starter_settings' ] ); add_filter( 'elementor/editor/v2/packages', [ $this, 'add_starter_packages' ] ); add_action( 'elementor/editor/v2/styles/enqueue', [ $this, 'enqueue_fonts' ] ); add_action( 'elementor/preview/enqueue_styles', [ $this, 'enqueue_starter_preview_css' ] ); } } public function on_elementor_init(): void { if ( ! Plugin::instance()->app->is_current() ) { return; } $this->set_onboarding_settings(); $this->enqueue_fonts(); } public function enqueue_fonts(): void { wp_enqueue_style( 'elementor-onboarding-fonts', 'https://fonts.googleapis.com/css2?family=Poppins:wght@500&display=swap', [], ELEMENTOR_VERSION ); } public function enqueue_starter_preview_css(): void { $css = ' #site-header, .page-header { display: var(--e-starter-header-display, none); } '; wp_register_style( 'elementor-starter-preview', false ); wp_enqueue_style( 'elementor-starter-preview' ); wp_add_inline_style( 'elementor-starter-preview', $css ); } public function progress_manager(): Onboarding_Progress_Manager { return $this->progress_manager; } private function set_onboarding_settings(): void { if ( ! Plugin::instance()->common ) { return; } $progress = $this->progress_manager->get_progress(); $choices = $this->progress_manager->get_choices(); $steps = $this->get_steps_config(); // If the user previously selected a theme but it's no longer the active theme, // clear the theme selection so the user can re-select. $this->maybe_invalidate_theme_selection( $progress, $choices ); $is_connected = $this->is_user_connected(); Plugin::$instance->app->set_settings( 'onboarding', [ 'version' => self::VERSION, 'restUrl' => rest_url( 'elementor/v1/onboarding/' ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'progress' => $this->validate_progress_for_steps( $progress, $steps ), 'choices' => $choices->to_array(), 'hadUnexpectedExit' => $progress->had_unexpected_exit( self::has_user_finished_onboarding() ), 'isConnected' => $is_connected, 'userName' => $this->get_user_display_name(), 'steps' => $steps, 'uiTheme' => $this->get_ui_theme_preference(), 'translations' => $this->get_translated_strings(), 'shouldShowProInstallScreen' => $is_connected ? $this->should_show_pro_install_screen() : false, 'urls' => [ 'dashboard' => admin_url(), 'editor' => admin_url( 'edit.php?post_type=elementor_library' ), 'connect' => $this->get_connect_url(), 'signUp' => $this->get_connect_url( 'signup' ), 'comparePlans' => 'https://go.elementor.com/go-pro-onboarding-editor-features-step-upgrade/', 'createNewPage' => Plugin::$instance->documents->get_create_new_post_url(), 'upgradeUrl' => 'https://go.elementor.com/go-pro-onboarding-editor-header-upgrade/', ], ] ); } private function validate_progress_for_steps( User_Progress $progress, array $steps ): array { $progress_data = $progress->to_array(); $step_count = count( $steps ); $current_step_index = $progress->get_current_step_index() ?? 0; $current_step_id = $progress->get_current_step_id() ?? $steps[0]['id'] ?? 'building_for'; $is_invalid_step_index = $current_step_index < 0 || $current_step_index >= $step_count; if ( $is_invalid_step_index ) { $current_step_id = $steps[0]['id']; $current_step_index = 0; } $progress_data['current_step_id'] = $current_step_id; $progress_data['current_step_index'] = $current_step_index; return $progress_data; } private function is_user_connected(): bool { $library = $this->get_library_app(); return $library ? $library->is_connected() : false; } private function get_connect_url( string $screen_hint = '' ): string { $library = $this->get_library_app(); if ( ! $library ) { return ''; } return $library->get_admin_url( 'authorize', [ 'utm_source' => 'onboarding-wizard', 'utm_campaign' => 'connect-account', 'utm_medium' => 'wp-dash', 'utm_term' => self::VERSION, 'source' => 'generic', 'screen_hint' => $screen_hint, ] ) ?? ''; } private function get_library_app() { $connect = Plugin::instance()->common->get_component( 'connect' ); if ( ! $connect ) { return null; } return $connect->get_app( 'library' ); } public static function should_show_pro_install_screen(): bool { if ( self::is_elementor_pro_installed() ) { return false; } $connect = Plugin::$instance->common->get_component( 'connect' ); if ( ! $connect ) { return false; } $pro_install_app = $connect->get_app( 'pro-install' ); if ( ! $pro_install_app || ! $pro_install_app->is_connected() ) { return false; } $download_link = $pro_install_app->get_download_link(); return ! empty( $download_link ); } private function get_ui_theme_preference(): string { $editor_preferences = SettingsManager::get_settings_managers( 'editorPreferences' ); $ui_theme = $editor_preferences->get_model()->get_settings( 'ui_theme' ); return $ui_theme ? $ui_theme : 'auto'; } private function get_user_display_name(): string { $library = $this->get_library_app(); if ( ! $library || ! $library->is_connected() ) { return ''; } $user = $library->get( 'user' ); return $user->first_name ?? ''; } public function should_show_starter(): bool { $progress = $this->progress_manager->get_progress(); return self::VERSION === get_option( self::ONBOARDING_OPTION ) && ! $progress->is_starter_dismissed(); } public function add_starter_packages( array $packages ): array { $packages[] = 'editor-starter'; return $packages; } public function add_starter_settings( array $settings ): array { $settings['starter'] = [ 'restPath' => 'elementor/v1/onboarding/user-progress', 'aiPlannerUrl' => 'https://planner.elementor.com/home.html', 'kitLibraryUrl' => Plugin::$instance->app->get_base_url() . '#/kit-library', ]; return $settings; } private function maybe_invalidate_theme_selection( User_Progress $progress, User_Choices $choices ): void { $selected_theme = $choices->get_theme_selection(); if ( empty( $selected_theme ) ) { return; } $active_theme = get_stylesheet(); if ( $active_theme !== $selected_theme ) { $completed = $this->filter_out_theme_selection_step( $progress->get_completed_steps() ); $progress->set_completed_steps( $completed ); $this->progress_manager->save_progress( $progress ); $choices->set_theme_selection( null ); $this->progress_manager->save_choices( $choices ); } } private function filter_out_theme_selection_step( array $steps ): array { return array_values( array_filter( $steps, function ( $step ) { return 'theme_selection' !== $step; } ) ); } private function get_translated_strings(): array { $locale = $this->get_onboarding_locale(); $api = new EditorAssetsAPI( [ EditorAssetsAPI::ASSETS_DATA_URL => self::ASSETS_BASE_URL . $locale . '.json', EditorAssetsAPI::ASSETS_DATA_TRANSIENT_KEY => '_elementor_onboarding_strings_' . $locale, EditorAssetsAPI::ASSETS_DATA_KEY => 'translations', ] ); return $api->get_assets_data(); } private function get_onboarding_locale(): string { static $flipped_locales = null; if ( null === $flipped_locales ) { $flipped_locales = array_flip( self::SUPPORTED_LOCALES ); } $user_locale = get_user_locale(); if ( isset( self::SUPPORTED_LOCALES[ $user_locale ] ) ) { return $user_locale; } $locale = substr( $user_locale, 0, 2 ); if ( isset( $flipped_locales[ $locale ] ) ) { return $flipped_locales[ $locale ]; } return 'en'; } private function get_steps_config(): array { $steps = [ [ 'id' => 'building_for', 'label' => __( 'Who are you building for?', 'elementor' ), 'type' => 'single', ], [ 'id' => 'site_about', 'label' => __( 'What is your site about?', 'elementor' ), 'type' => 'multiple', ], [ 'id' => 'experience_level', 'label' => __( 'How much experience do you have with Elementor?', 'elementor' ), 'type' => 'single', ], ]; if ( ! $this->is_elementor_theme_active() ) { $steps[] = [ 'id' => 'theme_selection', 'label' => __( 'Start with a theme that fits your needs', 'elementor' ), 'type' => 'single', ]; } if ( ! self::is_elementor_pro_installed() ) { $steps[] = [ 'id' => 'site_features', 'label' => __( 'What do you want to include in your site?', 'elementor' ), 'type' => 'multiple', ]; } return apply_filters( 'elementor/onboarding/steps', $steps ); } private static function is_elementor_pro_installed(): bool { $is_pro_installed = Utils::has_pro() || Utils::is_pro_installed_and_not_active(); return (bool) apply_filters( 'elementor/onboarding/is_elementor_pro_installed', $is_pro_installed ); } private function is_elementor_theme_active(): bool { $active_theme = get_stylesheet(); $is_active = in_array( $active_theme, Install_Theme::ALLOWED_THEMES, true ); return (bool) apply_filters( 'elementor/onboarding/is_elementor_theme_active', $is_active ); } } modules/onboarding/validation/user-progress-validator.php 0000644 00000004523 15233532517 0020016 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Validation; if ( ! defined( 'ABSPATH' ) ) { exit; } class User_Progress_Validator extends Base_Validator { private const ALLOWED_EXIT_TYPES = [ 'user_exit', 'unexpected', null, '' ]; protected function get_rules(): array { return [ 'current_step' => [ 'type' => 'int', ], 'completed_steps' => [ 'type' => 'mixed_array', ], 'exit_type' => [ 'type' => 'exit_type', 'nullable' => true, ], 'complete_step' => [ 'type' => 'string_or_int', ], 'skip_step' => [ 'type' => 'bool', ], 'step_index' => [ 'type' => 'int', ], 'total_steps' => [ 'type' => 'int', ], 'start' => [ 'type' => 'bool', ], 'complete' => [ 'type' => 'bool', ], 'user_exit' => [ 'type' => 'bool', ], 'starter_dismissed' => [ 'type' => 'bool', ], ]; } protected function validate_field( string $field, $value, array $rule ) { $type = $rule['type'] ?? 'string'; switch ( $type ) { case 'exit_type': return $this->validate_exit_type( $value ); case 'string_or_int': return $this->validate_string_or_int( $field, $value ); case 'mixed_array': return $this->validate_mixed_array( $field, $value ); default: return parent::validate_field( $field, $value, $rule ); } } private function validate_exit_type( $value ) { if ( ! in_array( $value, self::ALLOWED_EXIT_TYPES, true ) ) { return $this->error( 'exit_type', 'Exit type is invalid.' ); } return '' === $value ? null : $value; } private function validate_string_or_int( string $field, $value ) { if ( is_numeric( $value ) ) { return (int) $value; } if ( is_string( $value ) ) { return sanitize_text_field( $value ); } return $this->error( $field, "{$field} must be a number or string." ); } private function validate_mixed_array( string $field, $value ) { if ( ! is_array( $value ) ) { return $this->error( $field, "{$field} must be an array." ); } return array_values( array_filter( array_map( static function ( $item ) { if ( is_numeric( $item ) ) { return (int) $item; } if ( is_string( $item ) ) { return sanitize_text_field( $item ); } return null; }, $value ), static function ( $item ) { return null !== $item; } ) ); } } modules/onboarding/validation/base-validator.php 0000644 00000007505 15233532517 0016113 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Validation; use WP_Error; if ( ! defined( 'ABSPATH' ) ) { exit; } abstract class Base_Validator { protected array $errors = []; abstract protected function get_rules(): array; public function validate( array $params ) { if ( ! is_array( $params ) ) { return new WP_Error( 'invalid_params', 'Parameters must be an array.', [ 'status' => 400 ] ); } $this->errors = []; $validated = []; foreach ( $this->get_rules() as $field => $rule ) { if ( ! array_key_exists( $field, $params ) ) { continue; } $result = $this->validate_field( $field, $params[ $field ], $rule ); if ( is_wp_error( $result ) ) { return $result; } $validated[ $field ] = $result; } return $validated; } protected function validate_field( string $field, $value, array $rule ) { $type = $rule['type'] ?? 'string'; $nullable = $rule['nullable'] ?? false; if ( null === $value ) { if ( $nullable ) { return null; } return $this->error( $field, "{$field} cannot be null." ); } switch ( $type ) { case 'string': return $this->validate_string( $field, $value ); case 'int': return $this->validate_int( $field, $value ); case 'bool': return $this->validate_bool( $field, $value ); case 'array': return $this->validate_array( $field, $value, $rule ); case 'string_array': return $this->validate_string_array( $field, $value ); case 'custom_data': return $this->validate_custom_data( $field, $value ); default: return $value; } } protected function validate_string( string $field, $value ) { if ( ! is_string( $value ) ) { return $this->error( $field, "{$field} must be a string." ); } return sanitize_text_field( $value ); } protected function validate_int( string $field, $value ) { if ( ! is_numeric( $value ) ) { return $this->error( $field, "{$field} must be a number." ); } return (int) $value; } protected function validate_bool( string $field, $value ) { if ( ! is_bool( $value ) ) { return $this->error( $field, "{$field} must be a boolean." ); } return $value; } protected function validate_array( string $field, $value, array $rule ) { if ( ! is_array( $value ) ) { return $this->error( $field, "{$field} must be an array." ); } $allowed = $rule['allowed'] ?? null; if ( $allowed && ! in_array( $value, $allowed, true ) ) { return $this->error( $field, "{$field} contains invalid value." ); } return $value; } protected function validate_string_array( string $field, $value ) { if ( ! is_array( $value ) ) { return $this->error( $field, "{$field} must be an array." ); } return array_values( array_filter( array_map( static function ( $item ) { return is_string( $item ) ? sanitize_text_field( $item ) : null; }, $value ), static function ( $item ) { return null !== $item; } ) ); } protected function validate_custom_data( string $field, $value ) { if ( ! is_array( $value ) ) { return $this->error( $field, "{$field} must be an array." ); } return $this->sanitize_recursive( $value ); } protected function sanitize_recursive( array $data ): array { $sanitized = []; foreach ( $data as $key => $value ) { $safe_key = sanitize_key( $key ); if ( is_string( $value ) ) { $sanitized[ $safe_key ] = sanitize_text_field( $value ); } elseif ( is_array( $value ) ) { $sanitized[ $safe_key ] = $this->sanitize_recursive( $value ); } elseif ( is_numeric( $value ) || is_bool( $value ) || null === $value ) { $sanitized[ $safe_key ] = $value; } else { $sanitized[ $safe_key ] = null; } } return $sanitized; } protected function error( string $field, string $message ): WP_Error { return new WP_Error( 'invalid_' . $field, $message, [ 'status' => 400 ] ); } } modules/onboarding/validation/user-choices-validator.php 0000644 00000001105 15233532517 0017560 0 ustar 00 <?php namespace Elementor\App\Modules\Onboarding\Validation; if ( ! defined( 'ABSPATH' ) ) { exit; } class User_Choices_Validator extends Base_Validator { protected function get_rules(): array { return [ 'building_for' => [ 'type' => 'string', 'nullable' => true, ], 'site_about' => [ 'type' => 'string_array', ], 'experience_level' => [ 'type' => 'string', 'nullable' => true, ], 'theme_selection' => [ 'type' => 'string', 'nullable' => true, ], 'site_features' => [ 'type' => 'string_array', ], ]; } } modules/import-export-customization/data/response.php 0000644 00000001635 15233532517 0017246 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data; class Response { private array $data; private array $meta; public function __construct( array $data, array $meta = [] ) { $this->data = $data; $this->meta = $meta; } public static function success( array $data, array $meta = [] ): \WP_REST_Response { $response = new self( $data, $meta ); return $response->to_wp_rest_response( 200 ); } public static function error( string $code, $message, array $meta = [] ): \WP_REST_Response { $response = new self([ 'code' => $code, 'message' => $message, ], $meta); return $response->to_wp_rest_response( 500 ); } private function to_array(): array { return [ 'data' => $this->data, 'meta' => $this->meta, ]; } private function to_wp_rest_response( int $status_code = 200 ): \WP_REST_Response { return new \WP_REST_Response( $this->to_array(), $status_code ); } } modules/import-export-customization/data/routes/upload.php 0000644 00000012600 15233532517 0020207 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data\Routes; use Elementor\App\Modules\ImportExportCustomization\Module as ImportExportCustomizationModule; use Elementor\Plugin; use Elementor\App\Modules\ImportExportCustomization\Data\Response; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Upload extends Base_Route { protected function get_route(): string { return 'upload'; } protected function get_method(): string { return \WP_REST_Server::CREATABLE; } private function format_url( string $url ): string { return wp_unslash( urldecode( $url ) ); } /** * @param $request \WP_REST_Request * @return \WP_REST_Response */ protected function callback( $request ): \WP_REST_Response { /** * @var $module ImportExportCustomizationModule */ $module = Plugin::$instance->app->get_component( 'import-export-customization' ); try { $file_url = $request->get_param( 'file_url' ); $kit_id = $request->get_param( 'kit_id' ); $source = $request->get_param( 'source' ); $module = Plugin::$instance->app->get_component( 'import-export-customization' ); $is_import_from_library = ! empty( $file_url ); if ( $is_import_from_library ) { $file_url = $this->format_url( $file_url ); } if ( $is_import_from_library ) { if ( ! filter_var( $file_url, FILTER_VALIDATE_URL ) || 0 !== strpos( $file_url, 'http' ) ) { return Response::error( ImportExportCustomizationModule::KIT_LIBRARY_ERROR_KEY, 'Invalid kit library URL.' ); } $import_result = apply_filters( 'elementor/import/kit/result', [ 'file_url' => $file_url ] ); } elseif ( ! empty( $source ) ) { $import_result = apply_filters( 'elementor/import/kit/result/' . $source, [ 'kit_id' => $kit_id, 'source' => $source, ] ); } else { $files = $request->get_file_params(); $file = $files['e_import_file'] ?? null; if ( empty( $file ) || empty( $file['tmp_name'] ) ) { return Response::error( 'no_file_uploaded', 'No file uploaded or upload error occurred.' ); } $import_result = [ 'file_name' => $file['tmp_name'], 'referrer' => $module::REFERRER_LOCAL, ]; } Plugin::$instance->logger->get_logger()->info( 'Uploading Kit via REST API: ', [ 'meta' => [ 'kit_id' => $kit_id, 'referrer' => $import_result['referrer'] ?? 'unknown', ], ] ); if ( is_wp_error( $import_result ) ) { return Response::error( $import_result->get_error_message(), 'upload_error' ); } if ( ! empty( $import_result['media_file_name'] ) ) { $this->setup_media_mapping( $import_result['media_file_name'] ); } $uploaded_kit = $module->upload_kit( $import_result['file_name'], $import_result['referrer'], $kit_id ); $result = [ 'session' => $uploaded_kit['session'], 'manifest' => $uploaded_kit['manifest'], ]; if ( ! empty( $import_result['file_url'] ) ) { $result['file_url'] = $import_result['file_url']; } if ( ! empty( $import_result['kit'] ) ) { $result['uploaded_kit'] = $import_result['kit']; } if ( ! empty( $uploaded_kit['conflicts'] ) ) { $result['conflicts'] = $uploaded_kit['conflicts']; } // Clean up temporary files if ( $is_import_from_library || ! empty( $source ) ) { Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $import_result['file_name'] ) ); } return Response::success( $result ); } catch ( \Error | \Exception $e ) { Plugin::$instance->logger->get_logger()->error( $e->getMessage(), [ 'meta' => [ 'trace' => $e->getTraceAsString(), ], ] ); if ( $module->is_third_party_class( $e->getTrace()[0]['class'] ) ) { return Response::error( ImportExportCustomizationModule::THIRD_PARTY_ERROR, $e->getMessage() ); } return Response::error( $e->getMessage(), 'upload_error' ); } } private function setup_media_mapping( $media_zip_path ) { \Elementor\TemplateLibrary\Classes\Media_Mapper::clear_mapping(); $media_dir = null; if ( file_exists( $media_zip_path ) ) { $media_dir = $this->extract_media_zip( $media_zip_path ); } if ( $media_dir && file_exists( $media_dir . '/media-mapping.json' ) ) { $media_mapping = json_decode( file_get_contents( $media_dir . '/media-mapping.json' ), true ); \Elementor\TemplateLibrary\Classes\Media_Mapper::set_mapping( $media_mapping, $media_dir ); } Plugin::$instance->uploads_manager->remove_file_or_dir( $media_zip_path ); return $media_dir; } private function extract_media_zip( $zip_path ) { if ( ! class_exists( '\ZipArchive' ) ) { return null; } $zip = new \ZipArchive(); if ( $zip->open( $zip_path ) !== true ) { return null; } $media_dir = dirname( $zip_path ) . '/media'; if ( ! $zip->extractTo( $media_dir ) ) { $zip->close(); return null; } $zip->close(); return $media_dir; } protected function get_args(): array { return [ 'file_url' => [ 'type' => 'string', 'description' => 'File URL for upload action', 'required' => false, 'validate_callback' => function ( $value ) { if ( empty( $value ) ) { return true; } return filter_var( $this->format_url( $value ), FILTER_VALIDATE_URL ); }, ], 'kit_id' => [ 'type' => 'string', 'description' => 'Kit ID for upload action', 'required' => false, ], 'source' => [ 'type' => 'string', 'description' => 'Source for upload action', 'required' => false, ], ]; } } modules/import-export-customization/data/routes/revert.php 0000644 00000002701 15233532517 0020233 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data\Routes; use Elementor\App\Modules\ImportExportCustomization\Module as ImportExportCustomizationModule; use Elementor\Plugin; use Elementor\App\Modules\ImportExportCustomization\Data\Response; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Revert extends Base_Route { protected function get_route(): string { return 'revert'; } protected function get_method(): string { return \WP_REST_Server::CREATABLE; } protected function callback( $request ): \WP_REST_Response { /** * @var $module ImportExportCustomizationModule */ $module = Plugin::$instance->app->get_component( 'import-export-customization' ); try { $revert_result = $module->revert_last_imported_kit(); Plugin::$instance->logger->get_logger()->info( 'Kit revert completed via REST API' ); return Response::success( $revert_result ); } catch ( \Error | \Exception $e ) { Plugin::$instance->logger->get_logger()->error( $e->getMessage(), [ 'meta' => [ 'trace' => $e->getTraceAsString(), ], ] ); $frame = $e->getTrace()[0] ?? []; $class = $frame['class'] ?? ''; if ( $module->is_third_party_class( $class ) ) { return Response::error( ImportExportCustomizationModule::THIRD_PARTY_ERROR, $e->getMessage() ); } return Response::error( 'revert_error', $e->getMessage() ); } } protected function get_args(): array { return []; } } modules/import-export-customization/data/routes/import-runner.php 0000644 00000004543 15233532517 0021553 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data\Routes; use Elementor\App\Modules\ImportExportCustomization\Module as ImportExportCustomizationModule; use Elementor\Plugin; use Elementor\App\Modules\ImportExportCustomization\Data\Response; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Import_Runner extends Base_Route { protected function get_route(): string { return 'import-runner'; } protected function get_method(): string { return \WP_REST_Server::CREATABLE; } protected function callback( $request ): \WP_REST_Response { /** * @var $module ImportExportCustomizationModule */ $module = Plugin::$instance->app->get_component( 'import-export-customization' ); try { $session_id = $request->get_param( 'session' ); $runner = $request->get_param( 'runner' ); $module = Plugin::$instance->app->get_component( 'import-export-customization' ); if ( empty( $session_id ) ) { return Response::error( 'Session ID is required.', 'missing_session_id' ); } if ( empty( $runner ) ) { return Response::error( 'Runner name is required.', 'missing_runner_name' ); } $import = $module->import_kit_by_runner( $session_id, $runner ); if ( ! empty( $import['status'] ) ) { Plugin::$instance->logger->get_logger()->info( sprintf( 'Import runner completed via REST API: %1$s %2$s', $import['runner'] ?? $runner, ( 'success' === $import['status'] ? '✓' : '✗' ) ) ); } do_action( 'elementor/import-export-customization/import-kit/runner/after-run', $import ); return Response::success( $import ); } catch ( \Error | \Exception $e ) { Plugin::$instance->logger->get_logger()->error( $e->getMessage(), [ 'meta' => [ 'trace' => $e->getTraceAsString(), ], ] ); if ( $module->is_third_party_class( $e->getTrace()[0]['class'] ) ) { return Response::error( ImportExportCustomizationModule::THIRD_PARTY_ERROR, $e->getMessage() ); } return Response::error( 'import-runner-error', $e->getMessage() ); } } protected function get_args(): array { return [ 'session' => [ 'type' => 'string', 'description' => 'Session ID for import operations', 'required' => true, ], 'runner' => [ 'type' => 'string', 'description' => 'Runner name for import_runner action', 'required' => true, ], ]; } } modules/import-export-customization/data/routes/import.php 0000644 00000004405 15233532517 0020241 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data\Routes; use Elementor\App\Modules\ImportExportCustomization\Module as ImportExportCustomizationModule; use Elementor\Plugin; use Elementor\App\Modules\ImportExportCustomization\Data\Response; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Import extends Base_Route { protected function get_route(): string { return 'import'; } protected function get_method(): string { return \WP_REST_Server::CREATABLE; } protected function callback( $request ): \WP_REST_Response { /** * @var $module ImportExportCustomizationModule */ $module = Plugin::$instance->app->get_component( 'import-export-customization' ); try { $session = $request->get_param( 'session' ); if ( empty( $session ) ) { return Response::error( 'missing_session_id', 'Session ID is required.' ); } $settings = [ 'include' => $request->get_param( 'include' ), 'customization' => $request->get_param( 'customization' ), ]; $import = $module->import_kit( $session, $settings, true ); Plugin::$instance->logger->get_logger()->info( sprintf( 'Selected import runners via REST API: %1$s', implode( ', ', $import['runners'] ?? [] ) ) ); return Response::success( $import ); } catch ( \Error | \Exception $e ) { Plugin::$instance->logger->get_logger()->error( $e->getMessage(), [ 'meta' => [ 'trace' => $e->getTraceAsString(), ], ] ); if ( $module->is_third_party_class( $e->getTrace()[0]['class'] ) ) { return Response::error( ImportExportCustomizationModule::THIRD_PARTY_ERROR, $e->getMessage() ); } return Response::error( $e->getMessage(), 'import_error' ); } } protected function get_args(): array { return [ 'session' => [ 'type' => 'string', 'description' => 'Session ID for import operations', 'required' => true, ], 'include' => [ 'type' => 'array', 'description' => 'Content types to import (e.g. templates, content, plugins, settings, design-system)', 'required' => false, 'default' => [], ], 'customization' => [ 'type' => 'object', 'description' => 'Fine-grained import customization per content type', 'required' => false, 'default' => [], ], ]; } } modules/import-export-customization/data/routes/traits/handles-quota-errors.php 0000644 00000002427 15233532517 0024316 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data\Routes\Traits; use Elementor\App\Modules\ImportExportCustomization\Data\Response; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } trait Handles_Quota_Errors { protected function get_cloud_kit_library_app() { try { return \Elementor\Modules\CloudKitLibrary\Module::get_app(); } catch ( \Exception | \Error $e ) { return null; } } private function is_quota_error( $error_message ) { return \Elementor\Modules\CloudKitLibrary\Connect\Cloud_Kits::INSUFFICIENT_STORAGE_QUOTA === $error_message; } private function get_quota_error_response( $quota, $kit_data ) { $max_size_gb = 0; if ( ! empty( $quota['storage']['threshold'] ) ) { $max_size_gb = round( $quota['storage']['threshold'] / ( 1024 * 1024 * 1024 ), 2 ); } $filename = __( 'This file', 'elementor' ); if ( ! empty( $kit_data['title'] ) ) { $filename = '"' . $kit_data['title'] . '"'; } elseif ( ! empty( $kit_data['fileName'] ) ) { $filename = '"' . $kit_data['fileName'] . '"'; } return Response::error( \Elementor\Modules\CloudKitLibrary\Connect\Cloud_Kits::INSUFFICIENT_STORAGE_QUOTA, [ 'replacements' => [ 'filename' => $filename, 'maxSize' => $max_size_gb, ], ] ); } } modules/import-export-customization/data/routes/process-media.php 0000644 00000006433 15233532517 0021465 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data\Routes; use Elementor\Plugin; use Elementor\App\Modules\ImportExportCustomization\Data\Response; use Elementor\App\Modules\ImportExportCustomization\Module as ImportExportCustomizationModule; use Elementor\Modules\CloudKitLibrary\Module as CloudKitLibrary; use Elementor\Utils as ElementorUtils; use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Traits\Handles_Quota_Errors; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Process_Media extends Base_Route { use Handles_Quota_Errors; protected function get_route(): string { return 'process-media'; } protected function get_method(): string { return \WP_REST_Server::CREATABLE; } protected function callback( $request ): \WP_REST_Response { /** * @var $module ImportExportCustomizationModule */ $module = Plugin::$instance->app->get_component( 'import-export-customization' ); $cloud_kit_library_app = $this->get_cloud_kit_library_app(); if ( $cloud_kit_library_app && ! $cloud_kit_library_app->is_connected() ) { return Response::error( ImportExportCustomizationModule::MEDIA_PROCESSING_ERROR, 'Cloud Library is not connected' ); } $media_urls = $request->get_param( 'media_urls' ); $kit = $request->get_param( 'kit' ); $quota = null; try { if ( empty( $media_urls ) || ! is_array( $media_urls ) ) { throw new \Error( 'Invalid media URLs provided' ); } $media_collector = new \Elementor\TemplateLibrary\Classes\Media_Collector(); $zip_path = $media_collector->process_media_collection( $media_urls ); if ( $cloud_kit_library_app ) { $quota = $cloud_kit_library_app->get_quota(); $cloud_kit_library_app->validate_storage_quota( filesize( $zip_path ), $quota ); } if ( ! $zip_path ) { throw new \Error( 'Failed to process media' ); } $zip_file = ElementorUtils::file_get_contents( $zip_path ); $upload_success = false; if ( $cloud_kit_library_app ) { $upload_success = $cloud_kit_library_app->upload_content_file( $kit['mediaUploadUrl'], $zip_file ); $cloud_kit_library_app->update_kit( $kit['id'], [ 'mediaFileId' => $upload_success ? $kit['mediaFileId'] : null ] ); } $media_collector->cleanup(); return Response::success( [ 'success' => true, 'message' => 'Media processed and uploaded successfully', ] ); } catch ( \Error | \Exception $e ) { Plugin::$instance->logger->get_logger()->error( $e->getMessage(), [ 'meta' => [ 'trace' => $e->getTraceAsString(), ], ] ); if ( $cloud_kit_library_app ) { $cloud_kit_library_app->update_kit( $kit['id'], [ 'mediaFileId' => null ] ); } if ( $module->is_third_party_class( $e->getTrace()[0]['class'] ) ) { return Response::error( ImportExportCustomizationModule::THIRD_PARTY_ERROR, $e->getMessage() ); } if ( $this->is_quota_error( $e->getMessage() ) ) { return $this->get_quota_error_response( $quota, $kit ); } return Response::error( ImportExportCustomizationModule::MEDIA_PROCESSING_ERROR, $e->getMessage() ); } } protected function get_args(): array { return [ 'media_urls' => [ 'type' => 'array', 'description' => 'Array of media URLs to process', 'required' => true, 'items' => [ 'type' => 'string', ], ], ]; } } modules/import-export-customization/data/routes/manager-url.php 0000644 00000003551 15233532517 0021142 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data\Routes; use Elementor\Plugin; use Elementor\App\Modules\ImportExportCustomization\Data\Response; use Elementor\Core\Base\Document; if ( ! defined( 'ABSPATH' ) ) { exit; } class Manager_Url extends Base_Route { private const ALLOWED_PANELS = [ 'global-classes-manager', 'variables-manager', ]; protected function get_route(): string { return 'manager-url'; } protected function get_method(): string { return \WP_REST_Server::READABLE; } protected function callback( $request ): \WP_REST_Response { $panel = $request->get_param( 'panel' ); if ( ! in_array( $panel, self::ALLOWED_PANELS, true ) ) { return Response::error( 'Invalid panel parameter', 'invalid_panel' ); } $url = $this->get_editor_url_with_panel( $panel ); return Response::success( [ 'url' => $url, ] ); } private function get_editor_url_with_panel( string $panel ): string { $elementor_page = $this->get_elementor_page(); if ( $elementor_page ) { $document = Plugin::$instance->documents->get( $elementor_page->ID ); if ( $document && $document->is_built_with_elementor() ) { return add_query_arg( 'active-panel', $panel, $document->get_edit_url() ); } } return add_query_arg( 'active-panel', $panel, Plugin::$instance->documents->get_create_new_post_url( 'page' ) ); } private function get_elementor_page() { $pages = get_pages( [ 'post_status' => [ 'publish', 'draft' ], 'meta_key' => Document::BUILT_WITH_ELEMENTOR_META_KEY, 'sort_order' => 'desc', 'sort_column' => 'post_modified', 'number' => 1, ] ); return $pages[0] ?? null; } protected function get_args(): array { return [ 'panel' => [ 'type' => 'string', 'description' => 'Panel ID to open in the editor', 'required' => true, 'enum' => self::ALLOWED_PANELS, ], ]; } } modules/import-export-customization/data/routes/base-route.php 0000644 00000001520 15233532517 0020770 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data\Routes; abstract class Base_Route { public function __construct() {} public function register_route( $name_space, $base_route ): void { register_rest_route( $name_space, '/' . $base_route . '/' . $this->get_route(), [ [ 'methods' => $this->get_method(), 'callback' => fn( $request ) => $this->callback( $request ), 'permission_callback' => $this->permission_callback(), 'args' => $this->get_args(), ], ] ); } abstract protected function get_route(): string; abstract protected function get_method(): string; abstract protected function callback( $request ): \WP_REST_Response; protected function permission_callback(): callable { return fn() => current_user_can( 'manage_options' ); } abstract protected function get_args(): array; } modules/import-export-customization/data/routes/export.php 0000644 00000011320 15233532517 0020242 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data\Routes; use Elementor\Plugin; use Elementor\App\Modules\ImportExportCustomization\Data\Response; use Elementor\Utils as ElementorUtils; use Elementor\App\Modules\ImportExportCustomization\Module as ImportExportCustomizationModule; use Elementor\App\Modules\ImportExportCustomization\Processes\Import; use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Traits\Handles_Quota_Errors; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Export extends Base_Route { use Handles_Quota_Errors; protected function get_route(): string { return 'export'; } protected function get_method(): string { return \WP_REST_Server::CREATABLE; } protected function callback( $request ): \WP_REST_Response { /** * @var $module ImportExportCustomizationModule */ $module = Plugin::$instance->app->get_component( 'import-export-customization' ); try { $settings = [ 'include' => $request->get_param( 'include' ), 'kitInfo' => $request->get_param( 'kitInfo' ), 'screenShotBlob' => $request->get_param( 'screenShotBlob' ), 'customization' => $request->get_param( 'customization' ), 'plugins' => $request->get_param( 'plugins' ), 'selectedCustomPostTypes' => $request->get_param( 'selectedCustomPostTypes' ), ]; $settings = array_filter( $settings ); $source = $settings['kitInfo']['source']; $export = $module->export_kit( $settings ); $file_name = $export['file_name']; $file_size = filesize( $file_name ); $file = ElementorUtils::file_get_contents( $file_name ); if ( ! $file ) { throw new \Error( Import::ZIP_FILE_ERROR_KEY ); } Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $file_name ) ); $result = apply_filters( 'elementor/export/kit/export-result', [ 'manifest' => $export['manifest'], 'file' => base64_encode( $file ), 'media_urls' => $export['media_urls'], ], $source, $export, $settings, $file, $file_size, ); if ( is_wp_error( $result ) ) { throw new \Error( $result->get_error_message() ); } return Response::success( $result ); } catch ( \Error | \Exception $e ) { Plugin::$instance->logger->get_logger()->error( $e->getMessage(), [ 'meta' => [ 'trace' => $e->getTraceAsString(), ], ] ); if ( $module->is_third_party_class( $e->getTrace()[0]['class'] ) ) { return Response::error( ImportExportCustomizationModule::THIRD_PARTY_ERROR, $e->getMessage() ); } if ( $this->is_quota_error( $e->getMessage() ) ) { $quota = null; $cloud_kit_library_app = $this->get_cloud_kit_library_app(); if ( $cloud_kit_library_app ) { try { $quota = $cloud_kit_library_app->get_quota(); } catch ( \Exception | \Error $quota_error ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch // Quota fetch failed, error message will use default value. } } return $this->get_quota_error_response( $quota, $settings['kitInfo'] ?? [] ); } return Response::error( $e->getMessage(), 'export_error' ); } } protected function get_args(): array { return [ 'include' => [ 'type' => 'array', 'description' => 'Content types to include in export', 'required' => false, 'default' => [ 'templates', 'content', 'settings', 'plugins' ], ], 'kitInfo' => [ 'type' => 'object', 'description' => 'Kit information', 'required' => false, 'default' => [ 'title' => 'Elementor Website Template', 'description' => '', 'source' => 'local', ], ], 'screenShotBlob' => [ 'type' => [ 'string', 'null' ], 'description' => 'Base64 encoded screenshot for cloud exports', 'required' => false, 'default' => null, ], 'customization' => [ 'type' => 'object', 'description' => 'Customization settings for selective export', 'required' => false, 'default' => null, 'properties' => [ 'settings' => [ 'type' => [ 'object', 'null' ], 'description' => 'Site settings customization', ], 'templates' => [ 'type' => [ 'object', 'null' ], 'description' => 'Templates customization', ], 'content' => [ 'type' => [ 'object', 'null' ], 'description' => 'Content customization', ], 'plugins' => [ 'type' => [ 'object', 'null' ], 'description' => 'Plugins customization', ], ], ], 'plugins' => [ 'type' => 'array', 'description' => 'Selected plugins to export', 'required' => false, 'default' => [], ], 'selectedCustomPostTypes' => [ 'type' => 'array', 'description' => 'Selected custom post types', 'required' => false, 'default' => [], ], ]; } } modules/import-export-customization/data/controller.php 0000644 00000003076 15233532517 0017574 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Data; use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Export; use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Import; use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Import_Runner; use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Manager_Url; use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Process_Media; use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Revert; use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Upload; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller { const API_NAMESPACE = 'elementor/v1'; const API_BASE = 'import-export-customization'; public static function register_hooks() { add_action( 'rest_api_init', fn() => self::register_routes() ); } public static function get_base_url() { return get_rest_url() . self::API_NAMESPACE . '/' . self::API_BASE; } private static function register_routes() { ( new Export() )->register_route( self::API_NAMESPACE, self::API_BASE ); ( new Import() )->register_route( self::API_NAMESPACE, self::API_BASE ); ( new Import_Runner() )->register_route( self::API_NAMESPACE, self::API_BASE ); ( new Manager_Url() )->register_route( self::API_NAMESPACE, self::API_BASE ); ( new Process_Media() )->register_route( self::API_NAMESPACE, self::API_BASE ); ( new Revert() )->register_route( self::API_NAMESPACE, self::API_BASE ); ( new Upload() )->register_route( self::API_NAMESPACE, self::API_BASE ); } } modules/import-export-customization/wp-cli.php 0000644 00000017757 15233532517 0015706 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization; use Elementor\Core\Utils\Collection; use Elementor\Core\Utils\Plugins_Manager; use Elementor\Plugin; use Elementor\App\Modules\KitLibrary\Connect\Kit_Library; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Wp_Cli extends \WP_CLI_Command { const AVAILABLE_SETTINGS = [ 'include', 'overrideConditions', 'selectedCustomPostTypes', 'plugins' ]; /** * Export a Kit * * [--include] * Which type of content to include. Possible values are 'content', 'templates', 'site-settings'. * if this parameter won't be specified, All data types will be included. * * ## EXAMPLES * * 1. wp elementor kit export path/to/export-file-name.zip * - This will export all site data to the specified file name. * * 2. wp elementor kit export path/to/export-file-name.zip --include=kit-settings,content * - This will export only site settings and content. * * @param array $args * @param array $assoc_args */ public function export( $args, $assoc_args ) { if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please specify a file name' ); } \WP_CLI::line( 'Kit export started.' ); $export_settings = []; foreach ( $assoc_args as $key => $value ) { if ( ! in_array( $key, static::AVAILABLE_SETTINGS, true ) ) { continue; } $export_settings[ $key ] = explode( ',', $value ); } try { /** * Running the export process through the import-export module so the export property in the module will be available to use. * * @type Module $import_export_module */ $import_export_module = Plugin::$instance->app->get_component( 'import-export' ); $result = $import_export_module->export_kit( $export_settings ); rename( $result['file_name'], $args[0] ); } catch ( \Error | \Exception $error ) { \WP_CLI::error( $error->getMessage() ); } \WP_CLI::success( 'Kit exported successfully.' ); } /** * Import a Kit * * [--include] * Which type of content to include. Possible values are 'content', 'templates', 'site-settings'. * if this parameter won't be specified, All data types will be included. * * [--overrideConditions] * Templates ids to override conditions for. * * [--sourceType] * Which source type is used in the current session. Available values are 'local', 'remote', 'library'. * The default value is 'local' * * ## EXAMPLES * * 1. wp elementor kit import path/to/elementor-kit.zip * - This will import the whole kit file content. * * 2. wp elementor kit import path/to/elementor-kit.zip --include=site-settings,content * - This will import only site settings and content. * * 3. wp elementor kit import path/to/elementor-kit.zip --overrideConditions=3478,4520 * - This will import all content and will override conditions for the given template ids. * * 4. wp elementor kit import path/to/elementor-kit.zip --unfilteredFilesUpload=enable * - This will allow the import process to import unfiltered files. * * @param array $args * @param array $assoc_args */ public function import( array $args, array $assoc_args ) { if ( ! current_user_can( 'manage_options' ) ) { \WP_CLI::error( 'You must run this command as an admin user' ); } if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please specify a file to import' ); } \WP_CLI::line( 'Kit import started' ); $assoc_args = wp_parse_args( $assoc_args, [ 'sourceType' => 'local', ] ); $url = null; $file_path = $args[0]; $import_settings = []; $import_settings['referrer'] = Module::REFERRER_LOCAL; switch ( $assoc_args['sourceType'] ) { case 'library': $url = $this->get_url_from_library( $file_path ); $zip_path = $this->create_temp_file_from_url( $url ); $import_settings['referrer'] = Module::REFERRER_KIT_LIBRARY; break; case 'remote': $zip_path = $this->create_temp_file_from_url( $file_path ); break; case 'local': $zip_path = $file_path; break; default: \WP_CLI::error( 'Unknown source type.' ); break; } if ( 'enable' === $assoc_args['unfilteredFilesUpload'] ) { Plugin::$instance->uploads_manager->enable_unfiltered_files_upload(); } foreach ( $assoc_args as $key => $value ) { if ( ! in_array( $key, static::AVAILABLE_SETTINGS, true ) ) { continue; } $import_settings[ $key ] = explode( ',', $value ); } try { \WP_CLI::line( 'Importing data...' ); /** * Running the import process through the import-export module so the import property in the module will be available to use. * * @type Module $import_export_module */ $import_export_module = Plugin::$instance->app->get_component( 'import-export' ); if ( ! $import_export_module ) { \WP_CLI::error( 'Import Export module is not available.' ); } $import = $import_export_module->import_kit( $zip_path, $import_settings ); $manifest_data = $import_export_module->import->get_manifest(); /** * Import Export Manifest Data * * Allows 3rd parties to read and edit the kit's manifest before it is used. * * @since 3.7.0 * * @param array $manifest_data The Kit's Manifest data */ $manifest_data = apply_filters( 'elementor/import-export/wp-cli/manifest_data', $manifest_data ); \WP_CLI::line( 'Removing temp files...' ); // The file was created from remote or library request, it also should be removed. if ( $url ) { Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $zip_path ) ); } \WP_CLI::success( 'Kit imported successfully' ); } catch ( \Error | \Exception $error ) { Plugin::$instance->logger->get_logger()->error( $error->getMessage(), [ 'meta' => [ 'trace' => $error->getTraceAsString(), ], ] ); if ( $url ) { Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $zip_path ) ); } \WP_CLI::error( $error->getMessage() ); } } /** * Revert last imported kit. */ public function revert() { \WP_CLI::line( 'Kit revert started.' ); try { /** * Running the revert process through the import-export module so the revert property in the module will be available to use. * * @type Module $import_export_module */ $import_export_module = Plugin::$instance->app->get_component( 'import-export' ); $import_export_module->revert_last_imported_kit(); } catch ( \Error | \Exception $error ) { \WP_CLI::error( $error->getMessage() ); } \WP_CLI::success( 'Kit reverted successfully.' ); } /** * Helper to get kit url by the kit id * TODO: Maybe extract it. * * @param $kit_id * * @return string */ private function get_url_from_library( $kit_id ) { /** @var Kit_Library $app */ $app = Plugin::$instance->common->get_component( 'connect' )->get_app( 'kit-library' ); if ( ! $app ) { \WP_CLI::error( 'Kit library app not found' ); } $response = $app->download_link( $kit_id ); if ( is_wp_error( $response ) ) { \WP_CLI::error( "Library Response: {$response->get_error_message()}" ); } return $response->download_link; } /** * Helper to get kit zip file path by the kit url * TODO: Maybe extract it. * * @param $url * * @return string */ private function create_temp_file_from_url( $url ) { \WP_CLI::line( 'Extracting zip archive...' ); $response = wp_remote_get( $url ); if ( is_wp_error( $response ) ) { \WP_CLI::error( "Download file url: {$response->get_error_message()}" ); } if ( 200 !== $response['response']['code'] ) { \WP_CLI::error( "Download file url: {$response['response']['message']}" ); } // 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 ); $file = Plugin::$instance->uploads_manager->create_temp_file( $response['body'], 'kit.zip' ); // After the upload complete, set the elementor upload state back to false. Plugin::$instance->uploads_manager->set_elementor_upload_state( false ); return $file; } } modules/import-export-customization/compatibility/kit-library.php 0000644 00000001377 15233532517 0021604 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Compatibility; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Kit_Library extends Base_Adapter { public static function is_compatibility_needed( array $manifest_data, array $meta ) { return ! empty( $meta['referrer'] ) && 'kit-library' === $meta['referrer']; } public function adapt_manifest( array $manifest_data ) { if ( ! empty( $manifest_data['content']['page'] ) ) { foreach ( $manifest_data['content']['page'] as & $page ) { $page['thumbnail'] = false; } } if ( ! empty( $manifest_data['templates'] ) ) { foreach ( $manifest_data['templates'] as & $template ) { $template['thumbnail'] = false; } } return $manifest_data; } } modules/import-export-customization/compatibility/base-adapter.php 0000644 00000001432 15233532517 0021673 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Compatibility; use Elementor\App\Modules\ImportExportCustomization\Import; use Elementor\Core\Base\Base_Object; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Adapter { /** * @param array $manifest_data * @param array $meta * @return false */ public static function is_compatibility_needed( array $manifest_data, array $meta ) { return false; } public function adapt_manifest( array $manifest_data ) { return $manifest_data; } public function adapt_site_settings( array $site_settings, array $manifest_data, $path ) { return $site_settings; } public function adapt_template( array $template_data, array $template_settings ) { return $template_data; } } modules/import-export-customization/compatibility/customization.php 0000644 00000004403 15233532517 0022254 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Compatibility; use Elementor\App\Modules\ImportExportCustomization\Module; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Handles conversion from manifest format v2.0 to v3.0 * Main change: site-settings changed from array of tab keys to object with boolean values */ class Customization extends Base_Adapter { /** * Check if compatibility is needed based on manifest version * * @param array $manifest_data * @param array $meta * @return bool */ public static function is_compatibility_needed( array $manifest_data, array $meta ) { // Check if we have an old version (2.0 or lower) $version = $manifest_data['version'] ?? '1.0'; return version_compare( $version, '3.0', '<' ); } /** * Adapt the manifest from old format to new format * * @param array $manifest_data * @return array */ public function adapt_manifest( array $manifest_data ) { // Check if site-settings needs adaptation if ( isset( $manifest_data['site-settings'] ) && is_array( $manifest_data['site-settings'] ) ) { // Old format: array of tab keys // New format: object with boolean values for each setting type $old_site_settings = $manifest_data['site-settings']; // Initialize new format with all settings as false $new_site_settings = [ 'theme' => false, 'globalColors' => false, 'globalFonts' => false, 'themeStyleSettings' => false, 'generalSettings' => false, 'experiments' => false, ]; // Map old tab keys to new setting types $tab_mapping = [ 'settings-global-colors' => 'globalColors', 'settings-global-typography' => 'globalFonts', 'theme-style-typography' => 'themeStyleSettings', 'settings-general' => 'generalSettings', ]; // If we have tab keys, assume all were exported (true) if ( ! empty( $old_site_settings ) ) { // In the old format, if site-settings was included, all settings were exported $new_site_settings = [ 'theme' => true, 'globalColors' => true, 'globalFonts' => true, 'themeStyleSettings' => true, 'generalSettings' => true, 'experiments' => true, ]; } $manifest_data['site-settings'] = $new_site_settings; } return $manifest_data; } } modules/import-export-customization/compatibility/envato.php 0000644 00000005744 15233532517 0020651 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Compatibility; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Envato extends Base_Adapter { public static function is_compatibility_needed( array $manifest_data, array $meta ) { return ! empty( $manifest_data['manifest_version'] ); } public function adapt_manifest( array $manifest_data ) { $templates = $manifest_data['templates']; $manifest_data['templates'] = []; foreach ( $templates as $template ) { // Envato store their global kit styles as a 'global.json' template file. // We need to be able to know the path to this specific 'global.json' since it functions as the site-settings.json $is_global = ! empty( $template['metadata']['template_type'] ) && 'global-styles' === $template['metadata']['template_type']; if ( $is_global ) { // Adding the path of the 'global.json' template to the manifest which will be used in the future. $manifest_data['path-to-envto-site-settings'] = $template['source']; // Getting the site-settings because Envato stores them in one of the posts. $kit = Plugin::$instance->kits_manager->get_active_kit(); $kit_tabs = $kit->get_tabs(); unset( $kit_tabs['settings-site-identity'] ); $manifest_data['site-settings'] = array_keys( $kit_tabs ); continue; } // Evanto uses "type" instead of "doc_type" $template['doc_type'] = $template['type']; // Evanto uses for "name" instead of "title" $template['title'] = $template['name']; // Envato specifying an exact path to the template rather than using its "ID" as an index. // This extracts the "file name" part out of our exact source list and we treat that as an ID. $file_name_without_extension = str_replace( '.json', '', basename( $template['source'] ) ); // Append the template to the global list: $manifest_data['templates'][ $file_name_without_extension ] = $template; } $manifest_data['name'] = $manifest_data['title']; return $manifest_data; } public function adapt_site_settings( array $site_settings, array $manifest_data, $path ) { if ( empty( $manifest_data['path-to-envto-site-settings'] ) ) { return $site_settings; } $global_file_path = $path . $manifest_data['path-to-envto-site-settings']; $global_file_data = ImportExportUtils::read_json_file( $global_file_path ); return [ 'settings' => $global_file_data['page_settings'], ]; } public function adapt_template( array $template_data, array $template_settings ) { if ( ! empty( $template_data['metadata']['elementor_pro_conditions'] ) ) { foreach ( $template_data['metadata']['elementor_pro_conditions'] as $condition ) { list ( $type, $name, $sub_name, $sub_id ) = array_pad( explode( '/', $condition ), 4, '' ); $template_data['import_settings']['conditions'][] = compact( 'type', 'name', 'sub_name', 'sub_id' ); } } return $template_data; } } modules/import-export-customization/usage.php 0000644 00000002047 15233532517 0015601 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization; use Elementor\App\Modules\ImportExportCustomization\Processes\Revert; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Usage { /** * Register hooks. * * @return void */ public function register() { add_filter( 'elementor/tracker/send_tracking_data_params', function ( array $params ) { $params['usages']['import_export']['revert'] = $this->get_revert_usage_data(); return $params; } ); } /** * Get the Revert usage data. * * @return array */ private function get_revert_usage_data() { $revert_sessions = ( new Revert() )->get_revert_sessions(); $data = []; foreach ( $revert_sessions as $revert_session ) { $data[] = [ 'kit_name' => $revert_session['kit_name'], 'source' => $revert_session['source'], 'revert_timestamp' => (int) $revert_session['revert_timestamp'], 'total_time' => ( (int) $revert_session['revert_timestamp'] - (int) $revert_session['import_timestamp'] ), ]; } return $data; } } modules/import-export-customization/module.php 0000644 00000064500 15233532517 0015764 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization; use Elementor\App\Modules\ImportExportCustomization\Processes\Export; use Elementor\App\Modules\ImportExportCustomization\Processes\Import; use Elementor\App\Modules\ImportExportCustomization\Processes\Revert; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Files\Uploads_Manager; use Elementor\Modules\CloudKitLibrary\Module as CloudKitLibrary; use Elementor\Modules\GlobalClasses\Global_Classes_REST_API; use Elementor\Modules\System_Info\Reporters\Server; use Elementor\Modules\Variables\Storage\Constants; use Elementor\Modules\Variables\Storage\Variables_Collection; use Elementor\Plugin; use Elementor\Tools; use Elementor\Utils as ElementorUtils; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\App\Modules\ImportExportCustomization\Data\Controller; use Elementor\Core\Settings\Manager as SettingsManager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Import Export Module * * Responsible for initializing Elementor App functionality */ class Module extends BaseModule { const FORMAT_VERSION = '3.0'; const REFERRER_KIT_LIBRARY = 'kit-library'; const REFERRER_LOCAL = 'local'; const REFERRER_CLOUD = 'cloud'; const PLUGIN_PERMISSIONS_ERROR_KEY = 'plugin-installation-permissions-error'; const KIT_LIBRARY_ERROR_KEY = 'invalid-kit-library-zip-error'; const CLOUD_KIT_LIBRARY_ERROR_LOADING_RESOURCE = 'error-loading-resource'; const NO_WRITE_PERMISSIONS_KEY = 'no-write-permissions'; const THIRD_PARTY_ERROR = 'third-party-error'; const DOMDOCUMENT_MISSING = 'domdocument-missing'; const MEDIA_PROCESSING_ERROR = 'media-processing-error'; const OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS = 'elementor_import_sessions'; const OPTION_KEY_ELEMENTOR_REVERT_SESSIONS = 'elementor_revert_sessions'; const META_KEY_ELEMENTOR_IMPORT_SESSION_ID = '_elementor_import_session_id'; const META_KEY_ELEMENTOR_EDIT_MODE = '_elementor_edit_mode'; const IMPORT_PLUGINS_ACTION = 'import-plugins'; const EXPORT_SOURCE_CLOUD = 'cloud'; const EXPORT_SOURCE_FILE = 'file'; /** * Assigning the export process to a property, so we can use the process from outside the class. * * @var Export */ public $export; /** * Assigning the import process to a property, so we can use the process from outside the class. * * @var Import */ public $import; /** * Assigning the revert process to a property, so we can use the process from outside the class. * * @var Revert */ public $revert; /** * Get name. * * @access public * * @return string */ public function get_name() { return 'import-export-customization'; } public function __construct() { $this->register_actions(); Controller::register_hooks(); if ( ElementorUtils::is_wp_cli() ) { \WP_CLI::add_command( 'elementor kit', WP_CLI::class ); } ( new Usage() )->register(); $this->revert = new Revert(); } public function get_init_settings() { if ( ! Plugin::$instance->app->is_current() ) { return []; } return $this->get_config_data(); } /** * Register the import/export tab in elementor tools. */ public function register_settings_tab( Tools $tools ) { $tools->add_tab( 'import-export-kit', [ 'label' => esc_html__( 'Website Templates', 'elementor' ), 'sections' => [ 'intro' => [ 'label' => esc_html__( 'Website Templates', 'elementor' ), 'callback' => function() { $this->render_import_export_tab_content(); }, 'fields' => [], ], ], ] ); } /** * Render the import/export tab content. */ private function render_import_export_tab_content() { $is_cloud_kits_available = CloudKitLibrary::get_app()->check_eligibility()['is_eligible']; $content_data = [ 'export' => [ 'title' => esc_html__( 'Export this website', 'elementor' ), 'button' => [ 'url' => Plugin::$instance->app->get_base_url() . '#/export-customization', 'text' => esc_html__( 'Export', 'elementor' ), 'id' => 'elementor-import-export__export', ], 'description' => esc_html__( 'You can download this website as a .zip file, or upload it to the library.', 'elementor' ), ], 'import' => [ 'title' => esc_html__( 'Apply a Website Template', 'elementor' ), 'button' => [ 'url' => Plugin::$instance->app->get_base_url() . '#/import-customization', 'text' => $is_cloud_kits_available ? esc_html__( 'Upload .zip file', 'elementor' ) : esc_html__( 'Import', 'elementor' ), 'id' => 'elementor-import-export__import', ], 'description' => esc_html__( 'You can import design and settings from a .zip file or choose from the library.', 'elementor' ), ], ]; if ( $is_cloud_kits_available ) { $return_to_url = Tools::get_url() . '#tab-import-export-kit'; $kit_library_url = add_query_arg( [ 'return_to' => rawurlencode( $return_to_url ) ], Plugin::$instance->app->get_base_url() . '#/kit-library/cloud' ); $content_data['import']['button_secondary'] = [ 'url' => $kit_library_url, 'text' => esc_html__( 'Import from library', 'elementor' ), 'id' => 'elementor-import-export__import_from_library', ]; } $last_imported_kit = $this->revert->get_last_import_session(); $penultimate_imported_kit = $this->revert->get_penultimate_import_session(); $user_date_format = get_option( 'date_format' ); $user_time_format = get_option( 'time_format' ); $date_format = $user_date_format . ' ' . $user_time_format; $should_show_revert_section = ! empty( $last_imported_kit ); if ( $should_show_revert_section ) { if ( ! empty( $penultimate_imported_kit ) ) { $revert_text = sprintf( /* translators: 1: kit title, 2: date, 3: line break, 4: kit title, 5: date. */ esc_html__( 'Remove all the content and site settings that came with "%1$s" on %2$s %3$s and revert to the site setting that came with "%4$s" on %5$s.', 'elementor' ), ! empty( $last_imported_kit['kit_title'] ) ? $last_imported_kit['kit_title'] : esc_html__( 'imported kit', 'elementor' ), gmdate( $date_format, $last_imported_kit['start_timestamp'] ), '<br>', ! empty( $penultimate_imported_kit['kit_title'] ) ? $penultimate_imported_kit['kit_title'] : esc_html__( 'imported kit', 'elementor' ), gmdate( $date_format, $penultimate_imported_kit['start_timestamp'] ) ); } else { $revert_text = sprintf( /* translators: 1: kit title, 2: date, 3: line break */ esc_html__( 'Remove all the content and site settings that came with "%1$s" on %2$s.%3$s Your original site settings will be restored.', 'elementor' ), ! empty( $last_imported_kit['kit_title'] ) ? $last_imported_kit['kit_title'] : esc_html__( 'imported kit', 'elementor' ), gmdate( $date_format, $last_imported_kit['start_timestamp'] ), '<br>' ); } } ?> <div class="tab-import-export-kit__content"> <p class="tab-import-export-kit__info"> <?php printf( '%1$s <a href="https://go.elementor.com/wp-dash-import-export-general/" target="_blank">%2$s</a>', esc_html__( 'Here’s where you can export this website as a .zip file, upload it to the cloud, or start the process of applying an existing template to your site.', 'elementor' ), esc_html__( 'Learn more', 'elementor' ), ); ?> </p> <div class="tab-import-export-kit__wrapper"> <?php foreach ( $content_data as $data ) { $this->print_item_content( $data ); } ?> </div> <?php if ( $should_show_revert_section ) { $link_attributes = [ 'href' => $this->get_revert_href(), 'id' => 'elementor-import-export__revert_kit', 'class' => 'button', ]; ?> <div class="tab-import-export-kit__revert"> <h2> <?php echo esc_html__( 'Remove the most recent Website Template', 'elementor' ); ?> </h2> <p class="tab-import-export-kit__info"> <?php ElementorUtils::print_unescaped_internal_string( $revert_text ); ?> </p> <?php $this->render_last_kit_thumbnail( $last_imported_kit ); ?> <a <?php ElementorUtils::print_html_attributes( $link_attributes ); ?> > <?php echo esc_html__( 'Remove Website Template', 'elementor' ); ?> </a> </div> <?php } ?> </div> <?php } private function print_item_content( $data ) { $container_classes = 'tab-import-export-kit__container e-editor-one'; ?> <div class="<?php echo esc_attr( $container_classes ); ?>"> <div class="tab-import-export-kit__box"> <h2><?php ElementorUtils::print_unescaped_internal_string( $data['title'] ); ?></h2> </div> <p class="description"><?php ElementorUtils::print_unescaped_internal_string( $data['description'] ); ?></p> <?php if ( ! empty( $data['link'] ) ) : ?> <a href="<?php ElementorUtils::print_unescaped_internal_string( $data['link']['url'] ); ?>" target="_blank"><?php ElementorUtils::print_unescaped_internal_string( $data['link']['text'] ); ?></a> <?php endif; ?> <div class="tab-import-export-kit__box action-buttons"> <?php if ( ! empty( $data['button_secondary'] ) ) : ?> <a id="<?php ElementorUtils::print_unescaped_internal_string( $data['button_secondary']['id'] ); ?>" href="<?php ElementorUtils::print_unescaped_internal_string( $data['button_secondary']['url'] ); ?>" class="elementor-button e-btn-txt e-btn-txt-border"> <?php ElementorUtils::print_unescaped_internal_string( $data['button_secondary']['text'] ); ?> </a> <?php endif; ?> <a <?php ElementorUtils::print_html_attributes( [ 'id' => $data['button']['id'] ] ); ?> href="<?php ElementorUtils::print_unescaped_internal_string( $data['button']['url'] ); ?>" class="elementor-button e-primary"> <?php ElementorUtils::print_unescaped_internal_string( $data['button']['text'] ); ?> </a> </div> </div> <?php } private function get_revert_href(): string { $current_url = add_query_arg( null, null ); return $this->maybe_add_referrer_param( $current_url ); } /** * Checks if referred by a kit and adds the referrer ID to the href * * @param string $href * * @return string */ private function maybe_add_referrer_param( string $href ): string { $param_name = 'referrer_kit'; if ( empty( $_GET[ $param_name ] ) ) { return $href; } return add_query_arg( $param_name, sanitize_key( $_GET[ $param_name ] ), $href ); } /** * Get referrer kit ID from current request * * @return string */ private function get_referrer_kit_id_from_request(): string { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Called via REST API with its own authentication return sanitize_key( $_GET['referrer_kit'] ?? '' ); } /** * Render the last kit thumbnail if exists * * @param $last_imported_kit * * @return void */ private function render_last_kit_thumbnail( $last_imported_kit ) { if ( empty( $last_imported_kit['kit_thumbnail'] ) ) { return; } ?> <div class="tab-import-export-kit__kit-item-row"> <article class="tab-import-export-kit__kit-item"> <header> <h3> <?php echo esc_html( $last_imported_kit['kit_title'] ); ?> </h3> </header> <img src="<?php echo esc_url( $last_imported_kit['kit_thumbnail'] ); ?>" alt="<?php echo esc_attr( $last_imported_kit['kit_title'] ); ?>" loading="lazy" > </article> </div> <?php } /** * Upload a kit zip file and get the kit data. * * Assigning the Import process to the 'import' property, * so it will be available to use in different places such as: WP_Cli, Pro, etc. * * @param string $file Path to the file. * @param string $referrer Referrer of the file 'local' or 'kit-library'. * @param string $kit_id * @return array * @throws \Exception If customization validation fails or processing errors occur. */ public function upload_kit( $file, $referrer, $kit_id = null ) { $this->ensure_writing_permissions(); $this->import = new Import( $file, [ 'referrer' => $referrer, 'id' => $kit_id, ] ); $this->save_upload_session_data(); return [ 'session' => $this->import->get_session_id(), 'manifest' => $this->import->get_manifest(), 'conflicts' => $this->import->get_settings_conflicts(), ]; } /** * Import a kit by session_id. * Upload and import a kit by kit zip file. * * If the split_to_chunks flag is true, the process won't start * It will initialize the import process and return the session_id and the runners. * * Assigning the Import process to the 'import' property, * so it will be available to use in different places such as: WP_Cli, Pro, etc. * * @param string $path Path to the file or session_id. * @param array $settings Settings the import use to determine which content to import. * (e.g: include, selected_plugins, selected_cpt, selected_override_conditions, etc.) * @param bool $split_to_chunks Determine if the import process should be split into chunks. * @return array * @throws \Exception If export configuration is invalid or processing fails. */ public function import_kit( string $path, array $settings, bool $split_to_chunks = false ): array { $this->ensure_writing_permissions(); $this->ensure_DOMDocument_exists(); $this->import = new Import( $path, $settings ); $this->import->register_default_runners(); remove_filter( 'elementor/document/save/data', [ Plugin::$instance->modules_manager->get_modules( 'content-sanitizer' ), 'sanitize_content' ] ); do_action( 'elementor/import-export-customization/import-kit', $this->import ); if ( $split_to_chunks ) { $this->import->init_import_session( true ); return [ 'session' => $this->import->get_session_id(), 'runners' => $this->import->get_runners_name(), ]; } return $this->import->run(); } private function save_upload_session_data(): void { $this->import->init_import_session(); } /** * Resuming import process by re-creating the import instance and running the specific runner. * * @param string $session_id The id off the import session. * @param string $runner_name The specific runner that we want to run. * * @return array Two types of response. * 1. The status and the runner name. * 2. The imported data. (Only if the runner is the last one in the import process) * @throws \Exception If export configuration is invalid or processing fails. */ public function import_kit_by_runner( string $session_id, string $runner_name ): array { // Check session_id $this->import = Import::from_session( $session_id ); $runners = $this->import->get_runners_name(); $run = $this->import->run_runner( $runner_name ); if ( end( $runners ) === $run['runner'] ) { return $this->import->get_imported_data(); } return $run; } /** * Export a kit. * * Assigning the Export process to the 'export' property, * so it will be available to use in different places such as: WP_Cli, Pro, etc. * * @param array $settings Settings the export use to determine which content to export. * (e.g: include, kit_info, selected_plugins, selected_cpt, etc.) * @return array * @throws \Exception If export configuration is invalid or processing fails. */ public function export_kit( array $settings ) { $this->ensure_writing_permissions(); $this->export = new Export( $settings ); $this->export->register_default_runners(); do_action( 'elementor/import-export-customization/export-kit', $this->export ); return $this->export->run(); } /** * Handle revert kit request. */ public function revert_last_imported_kit(): array { $this->revert = new Revert(); $this->revert->register_default_runners(); $import_sessions = Revert::get_import_sessions(); if ( empty( $import_sessions ) ) { return [ 'revert_completed' => false, 'message' => __( 'No import sessions available to revert.', 'elementor' ), 'referrer_kit_id' => $this->get_referrer_kit_id_from_request(), 'show_referrer_dialog' => false, ]; } do_action( 'elementor/import-export-customization/revert-kit', $this->revert ); $this->revert->run(); $referrer_kit_id = $this->get_referrer_kit_id_from_request(); return [ 'revert_completed' => true, 'referrer_kit_id' => $referrer_kit_id, 'show_referrer_dialog' => ! empty( $referrer_kit_id ), ]; } /** * Register appropriate actions. */ private function register_actions() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); $page_id = Tools::PAGE_ID; add_action( "elementor/admin/after_create_settings/{$page_id}", [ $this, 'register_settings_tab' ] ); // TODO 18/04/2023 : This needs to be moved to the runner itself after https://elementor.atlassian.net/browse/HTS-434 is done. if ( self::IMPORT_PLUGINS_ACTION === ElementorUtils::get_super_global_value( $_SERVER, 'HTTP_X_ELEMENTOR_ACTION' ) ) { add_filter( 'woocommerce_create_pages', [ $this, 'empty_pages' ], 10, 0 ); } // TODO ^^^ add_filter( 'elementor/import/kit/result', function( $result ) { if ( ! empty( $result['file_url'] ) ) { return [ 'file_name' => $this->get_remote_kit_zip( $result['file_url'] ), 'referrer' => static::REFERRER_KIT_LIBRARY, 'file_url' => $result['file_url'], ]; } return $result; } ); } /** * Prevent the creation of the default WooCommerce pages (Cart, Checkout, etc.) * * TODO 18/04/2023 : This needs to be moved to the runner itself after https://elementor.atlassian.net/browse/HTS-434 is done. * * @return array */ public function empty_pages(): array { return []; } private function ensure_writing_permissions() { $server = new Server(); $paths_to_check = [ Server::KEY_PATH_WP_CONTENT_DIR => $server->get_system_path( Server::KEY_PATH_WP_CONTENT_DIR ), Server::KEY_PATH_UPLOADS_DIR => $server->get_system_path( Server::KEY_PATH_UPLOADS_DIR ), Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR => $server->get_system_path( Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ), ]; $permissions = $server->get_paths_permissions( $paths_to_check ); // WP Content dir has to be exists and writable. if ( ! $permissions[ Server::KEY_PATH_WP_CONTENT_DIR ]['write'] ) { throw new \Error( self::NO_WRITE_PERMISSIONS_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } // WP Uploads dir has to be exists and writable. if ( ! $permissions[ Server::KEY_PATH_UPLOADS_DIR ]['write'] ) { throw new \Error( self::NO_WRITE_PERMISSIONS_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } // Elementor uploads dir permissions is divided to 2 cases: // 1. If the dir exists, it has to be writable. // 2. If the dir doesn't exist, the parent dir has to be writable (wp uploads dir), so we can create it. if ( $permissions[ Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ]['exists'] && ! $permissions[ Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ]['write'] ) { throw new \Error( self::NO_WRITE_PERMISSIONS_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } } private function ensure_DOMDocument_exists() { if ( ! class_exists( 'DOMDocument' ) ) { throw new \Error( self::DOMDOCUMENT_MISSING ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } } /** * Enqueue admin scripts */ public function enqueue_scripts() { wp_enqueue_script( 'elementor-import-export-admin', $this->get_js_assets_url( 'import-export-admin' ), [ 'elementor-common' ], ELEMENTOR_VERSION, true ); wp_localize_script( 'elementor-import-export-admin', 'elementorImportExport', [ 'lastImportedSession' => $this->revert->get_last_import_session(), 'appUrl' => Plugin::$instance->app->get_base_url() . '#/kit-library', ] ); wp_enqueue_script( 'import-export-customization-admin', $this->get_js_assets_url( 'import-export-customization-admin' ), [ 'elementor-common', 'wp-api-fetch' ], ELEMENTOR_VERSION, true ); } protected function get_remote_kit_zip( $url ) { $remote_zip_request = wp_safe_remote_get( $url ); if ( is_wp_error( $remote_zip_request ) ) { Plugin::$instance->logger->get_logger()->error( $remote_zip_request->get_error_message() ); throw new \Error( static::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } if ( 200 !== $remote_zip_request['response']['code'] ) { Plugin::$instance->logger->get_logger()->error( $remote_zip_request['response']['message'] ); throw new \Error( static::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return Plugin::$instance->uploads_manager->create_temp_file( $remote_zip_request['body'], 'kit.zip' ); } /** * Get config data that will be exposed to the frontend. */ private function get_config_data() { $export_nonce = wp_create_nonce( 'elementor_export' ); $export_url = add_query_arg( [ '_nonce' => $export_nonce ], Plugin::$instance->app->get_base_url() ); return [ 'exportURL' => $export_url, 'summaryTitles' => $this->get_summary_titles(), 'builtinWpPostTypes' => ImportExportUtils::get_builtin_wp_post_types(), 'elementorPostTypes' => ImportExportUtils::get_elementor_post_types(), 'isUnfilteredFilesEnabled' => Uploads_Manager::are_unfiltered_uploads_enabled(), 'elementorHomePageUrl' => $this->get_elementor_home_page_url(), 'recentlyEditedElementorPageUrl' => $this->get_recently_edited_elementor_page_url(), 'tools_url' => Tools::get_url(), 'importSessions' => Revert::get_import_sessions(), 'lastImportedSession' => $this->revert->get_last_import_session(), 'kitPreviewNonce' => wp_create_nonce( 'kit_thumbnail' ), 'restApiBaseUrl' => Controller::get_base_url(), 'restNonce' => wp_create_nonce( 'wp_rest' ), 'restUrl' => rest_url(), 'uiTheme' => $this->get_elementor_ui_theme_preference(), 'exportGroups' => $this->get_export_groups(), 'manifestVersion' => self::FORMAT_VERSION, 'elementorVersion' => ELEMENTOR_VERSION, 'upgradeVersionUrl' => admin_url( 'plugins.php' ), 'limits' => $this->get_limits(), ]; } private function get_elementor_ui_theme_preference() { $editor_preferences = SettingsManager::get_settings_managers( 'editorPreferences' ); return $editor_preferences->get_model()->get_settings( 'ui_theme' ); } private function get_export_groups() { $export_groups = []; $document_types = Plugin::$instance->documents->get_document_types(); foreach ( $document_types as $name => $document_type ) { $export_groups[ $name ] = defined( $document_type . '::EXPORT_GROUP' ) ? $document_type::EXPORT_GROUP : ''; } return $export_groups; } private function get_limits() { $classes_limit = class_exists( Global_Classes_REST_API::class ) ? Global_Classes_REST_API::MAX_ITEMS : 100; $variables_limit = class_exists( Constants::class ) ? Constants::TOTAL_VARIABLES_COUNT : 100; return [ 'classes' => $classes_limit, 'variables' => $variables_limit, ]; } /** * Get labels of Elementor document types, Elementor Post types, WordPress Post types and Custom Post types. */ private function get_summary_titles() { $summary_titles = []; $document_types = Plugin::$instance->documents->get_document_types(); foreach ( $document_types as $name => $document_type ) { $summary_titles['templates'][ $name ] = [ 'single' => $document_type::get_title(), 'plural' => $document_type::get_plural_title(), ]; } $elementor_post_types = ImportExportUtils::get_elementor_post_types(); $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types(); $post_types = array_merge( $elementor_post_types, $wp_builtin_post_types ); foreach ( $post_types as $post_type ) { $post_type_object = get_post_type_object( $post_type ); $summary_titles['content'][ $post_type ] = [ 'single' => $post_type_object->labels->singular_name ?? '', 'plural' => $post_type_object->label ?? '', ]; } $custom_post_types = ImportExportUtils::get_registered_cpt_names(); if ( ! empty( $custom_post_types ) ) { foreach ( $custom_post_types as $custom_post_type ) { $custom_post_types_object = get_post_type_object( $custom_post_type ); // CPT data appears in two arrays: // 1. content object: in order to show the export summary when completed in getLabel function $summary_titles['content'][ $custom_post_type ] = [ 'single' => $custom_post_types_object->labels->singular_name ?? '', 'plural' => $custom_post_types_object->label ?? '', ]; // 2. customPostTypes object: in order to actually export the data $summary_titles['content']['customPostTypes'][ $custom_post_type ] = [ 'single' => $custom_post_types_object->labels->singular_name ?? '', 'plural' => $custom_post_types_object->label ?? '', ]; } } return $summary_titles; } private function get_elementor_editor_home_page_url() { if ( 'page' !== get_option( 'show_on_front' ) ) { return ''; } $frontpage_id = get_option( 'page_on_front' ); return $this->get_elementor_editor_page_url( $frontpage_id ); } private function get_elementor_home_page_url() { if ( 'page' !== get_option( 'show_on_front' ) ) { return ''; } $frontpage_id = get_option( 'page_on_front' ); return $this->get_elementor_page_url( $frontpage_id ); } private function get_recently_edited_elementor_page_url() { $query = ElementorUtils::get_recently_edited_posts_query( [ 'posts_per_page' => 1 ] ); if ( ! isset( $query->post ) ) { return ''; } return $this->get_elementor_page_url( $query->post->ID ); } private function get_recently_edited_elementor_editor_page_url() { $query = ElementorUtils::get_recently_edited_posts_query( [ 'posts_per_page' => 1 ] ); if ( ! isset( $query->post ) ) { return ''; } return $this->get_elementor_editor_page_url( $query->post->ID ); } private function get_elementor_document( $page_id ) { $document = Plugin::$instance->documents->get( $page_id ); if ( ! $document || ! $document->is_built_with_elementor() ) { return false; } return $document; } private function get_elementor_page_url( $page_id ) { $document = $this->get_elementor_document( $page_id ); return $document ? $document->get_preview_url() : ''; } private function get_elementor_editor_page_url( $page_id ) { $document = $this->get_elementor_document( $page_id ); return $document ? $document->get_edit_url() : ''; } /** * @param string $class_name * * @return bool */ public function is_third_party_class( $class_name ) { $allowed_classes = [ 'Elementor\\', 'ElementorPro\\', 'WP_', 'wp_', ]; foreach ( $allowed_classes as $allowed_class ) { if ( str_starts_with( $class_name, $allowed_class ) ) { return false; } } return true; } } modules/import-export-customization/design-system-import-context.php 0000644 00000003111 15233532517 0022253 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Shared import context for design-system entities (global classes, global variables). * Resolves the import flow ('settings' vs 'design-system') and its conflict resolution strategy. */ class Design_System_Import_Context { /** * Either 'settings', 'design-system' or null. * * @var string|null */ private ?string $include_key; private function __construct( ?string $include_key ) { $this->include_key = $include_key; } public static function from_data( array $data ): self { $include = $data['include'] ?? []; if ( in_array( 'settings', $include, true ) ) { return new self( 'settings' ); } if ( in_array( 'design-system', $include, true ) ) { return new self( 'design-system' ); } return new self( null ); } public function is_included(): bool { return null !== $this->include_key; } public function is_settings(): bool { return 'settings' === $this->include_key; } public function is_design_system(): bool { return 'design-system' === $this->include_key; } public function resolve_conflict_resolution( array $data, string $override_all_key ): string { if ( $this->is_settings() ) { return ! empty( $data['customization']['settings'][ $override_all_key ] ) ? 'override-all' : 'merge'; } if ( $this->is_design_system() ) { return $data['customization']['design-system']['conflict_resolution'] ?? 'skip'; } throw new \Exception( 'Cannot resolve conflict resolution without a valid include include_key.' ); } } modules/import-export-customization/processes/revert.php 0000644 00000011666 15233532517 0020021 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Processes; use Elementor\App\Modules\ImportExportCustomization\Module; use Elementor\App\Modules\ImportExportCustomization\Runners\Revert\Elementor_Content; use Elementor\App\Modules\ImportExportCustomization\Runners\Revert\Revert_Runner_Base; use Elementor\App\Modules\ImportExportCustomization\Runners\Revert\Plugins; use Elementor\App\Modules\ImportExportCustomization\Runners\Revert\Site_Settings; use Elementor\App\Modules\ImportExportCustomization\Runners\Revert\Taxonomies; use Elementor\App\Modules\ImportExportCustomization\Runners\Revert\Templates; use Elementor\App\Modules\ImportExportCustomization\Runners\Revert\Wp_Content; use Elementor\App\Modules\ImportExportCustomization\Utils; class Revert { /** * @var Revert_Runner_Base[] */ protected $runners = []; private $import_sessions; private $revert_sessions; public function __construct() { $this->import_sessions = self::get_import_sessions(); $this->revert_sessions = self::get_revert_sessions(); } /** * Register a runner. * * @param Revert_Runner_Base $runner_instance */ public function register( Revert_Runner_Base $runner_instance ) { $this->runners[ $runner_instance::get_name() ] = $runner_instance; } public function register_default_runners() { $this->register( new Site_Settings() ); $this->register( new Plugins() ); $this->register( new Templates() ); $this->register( new Taxonomies() ); $this->register( new Elementor_Content() ); $this->register( new Wp_Content() ); } /** * Execute the revert process. * * @throws \Exception If no revert runners have been specified. */ public function run() { if ( empty( $this->runners ) ) { throw new \Exception( 'Couldn’t execute the revert process because no revert runners have been specified. Try again by specifying revert runners.' ); } $import_session = $this->get_last_import_session(); if ( empty( $import_session ) ) { throw new \Exception( 'Couldn’t execute the revert process because there are no import sessions to revert.' ); } // fallback if the import session failed and doesn't have the runners metadata if ( ! isset( $import_session['runners'] ) && isset( $import_session['instance_data'] ) ) { $import_session['runners'] = $import_session['instance_data']['runners_import_metadata'] ?? []; } foreach ( $this->runners as $runner ) { if ( $runner->should_revert( $import_session ) ) { $runner->revert( $import_session ); } } $this->revert_attachments( $import_session ); $this->delete_last_import_data(); } public static function get_import_sessions() { $import_sessions = Utils::get_import_sessions(); if ( ! $import_sessions ) { return []; } usort( $import_sessions, function( $a, $b ) { return strcmp( $a['start_timestamp'], $b['start_timestamp'] ); } ); return $import_sessions; } public static function get_revert_sessions() { $revert_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_REVERT_SESSIONS ); if ( ! $revert_sessions ) { return []; } return $revert_sessions; } public function get_last_import_session() { $import_sessions = $this->import_sessions; if ( empty( $import_sessions ) ) { return []; } return end( $import_sessions ); } public function get_penultimate_import_session() { $sessions_data = $this->import_sessions; $penultimate_element_value = []; if ( empty( $sessions_data ) ) { return []; } end( $sessions_data ); prev( $sessions_data ); if ( ! is_null( key( $sessions_data ) ) ) { $penultimate_element_value = current( $sessions_data ); } return $penultimate_element_value; } private function delete_last_import_data() { $import_sessions = $this->import_sessions; $revert_sessions = $this->revert_sessions; $reverted_session = array_pop( $import_sessions ); $revert_sessions[] = [ 'session_id' => $reverted_session['session_id'], 'kit_title' => $reverted_session['kit_title'], 'kit_name' => $reverted_session['kit_name'], 'kit_thumbnail' => $reverted_session['kit_thumbnail'], 'source' => $reverted_session['kit_source'], 'user_id' => get_current_user_id(), 'import_timestamp' => $reverted_session['start_timestamp'], 'revert_timestamp' => current_time( 'timestamp' ), ]; update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false ); update_option( Module::OPTION_KEY_ELEMENTOR_REVERT_SESSIONS, $revert_sessions, false ); $this->import_sessions = $import_sessions; $this->revert_sessions = $revert_sessions; } private function revert_attachments( $data ) { $query_args = [ 'post_type' => 'attachment', 'post_status' => 'any', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => Module::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ]; $query = new \WP_Query( $query_args ); foreach ( $query->posts as $post ) { wp_delete_attachment( $post->ID, true ); } } } modules/import-export-customization/processes/import.php 0000644 00000063571 15233532517 0020026 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Processes; use Elementor\App\Modules\ImportExportCustomization\Compatibility\Base_Adapter; use Elementor\App\Modules\ImportExportCustomization\Compatibility\Envato; use Elementor\App\Modules\ImportExportCustomization\Compatibility\Kit_Library; use Elementor\App\Modules\ImportExportCustomization\Compatibility\Customization; use Elementor\App\Modules\ImportExportCustomization\Utils; use Elementor\Core\Base\Document; use Elementor\Core\Kits\Documents\Kit; use Elementor\Plugin; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Elementor_Content; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Import_Runner_Base; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Plugins; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Site_Settings; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Taxonomies; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Templates; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Wp_Content; use Elementor\App\Modules\ImportExportCustomization\Module; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Floating_Elements; class Import { const MANIFEST_ERROR_KEY = 'manifest-error'; const ZIP_FILE_ERROR_KEY = 'invalid-zip-file'; const ZIP_ARCHIVE_ERROR_KEY = 'zip-archive-module-missing'; /** * @var Import_Runner_Base[] */ protected $runners = []; /** * The session ID of the import process. * This ID is uniquely generated for each import process (by the temp folder which contains the extracted kit files). * * @var string */ private $session_id; /** * The Kit ID. * * @var string */ private $kit_id; /** * Adapter for the kit compatibility. * * @var Base_Adapter[] */ private $adapters; /** * Document's data (elements and settings) that was imported during the process. * * @var array { [document_id] => { "elements": array , "settings": array } } */ private $documents_data = []; /** * Path to the extracted kit files. * * @var string */ private $extracted_directory_path; /** * Imported kit manifest. * * @var array */ private $manifest; /** * Imported kit site settings. (e.g: custom_colors, custom_typography, etc.) * * @var array */ private $site_settings; /** * Selected content types to import. * * @var array */ private $settings_include; /** * Referer of the import. (e.g: kit-library, local, etc.) * * @var string */ private $settings_referrer; /** * All the conflict between the exited templates and the kit templates. * * @var array */ private $settings_conflicts; /** * Selected elementor templates conditions to override. * * @var array */ private $settings_selected_override_conditions; /** * Selected custom post types to import. * * @var array */ private $settings_selected_custom_post_types; /** * Selected plugins to import. * * @var array */ private $settings_selected_plugins; /** * Customization settings for selective import. * * @var array */ private $settings_customization; /** * The imported data output. * * @var array */ private $imported_data = []; /** * The metadata output of the import runners. * Will be saved in the import_session and will be used to revert the import process. * * @var array */ private $runners_import_metadata = []; /** * @param string $path session_id | zip_file_path * @param array $settings Use to determine which content to import. * (e.g: include, selected_plugins, selected_cpt, selected_override_conditions, etc.) * @param array|null $old_instance An array of old instance parameters that will be used for creating new instance. * We are using it for quick creation of the instance when the import process is being split into chunks. * @throws \Exception If the import session does not exist. */ public function __construct( string $path, array $settings = [], array $old_instance = null ) { if ( ! empty( $old_instance ) ) { $this->set_import_object( $old_instance ); } else { if ( is_file( $path ) ) { $this->extracted_directory_path = $this->extract_zip( $path ); } else { $elementor_tmp_directory = Plugin::$instance->uploads_manager->get_temp_dir(); $path = $elementor_tmp_directory . basename( $path ); if ( ! is_dir( $path ) ) { throw new \Exception( 'Couldn’t execute the import process because the import session does not exist.' ); } $this->extracted_directory_path = $path . '/'; } $this->session_id = basename( $this->extracted_directory_path ); $this->kit_id = $settings['id'] ?? ''; $this->settings_referrer = ! empty( $settings['referrer'] ) ? $settings['referrer'] : 'local'; $this->settings_include = ! empty( $settings['include'] ) ? $settings['include'] : null; // Using isset and not empty is important since empty array is valid option. $this->settings_selected_override_conditions = $settings['customization']['templates']['themeBuilder']['overrideConditions'] ?? null; $this->settings_selected_custom_post_types = $settings['customization']['content']['customPostTypes'] ?? null; $this->settings_selected_plugins = $settings['plugins'] ?? null; $this->settings_customization = $settings['customization'] ?? null; $this->manifest = $this->read_manifest_json(); $this->site_settings = $this->read_site_settings_json(); $this->set_default_settings(); } add_filter( 'wp_php_error_args', function ( $args, $error ) { return $this->filter_php_error_args( $args, $error ); }, 10, 2 ); } /** * Set the import object parameters. * * @param array $instance * @return void */ private function set_import_object( array $instance ) { $this->session_id = $instance['session_id']; $instance_data = $instance['instance_data']; $this->extracted_directory_path = $instance_data['extracted_directory_path']; $this->runners = $instance_data['runners']; $this->adapters = $instance_data['adapters']; $this->manifest = $instance_data['manifest']; $this->site_settings = $instance_data['site_settings']; $this->kit_id = $instance_data['kit_id'] ?? ''; $this->settings_include = $instance_data['settings_include']; $this->settings_referrer = $instance_data['settings_referrer']; $this->settings_conflicts = $instance_data['settings_conflicts']; $this->settings_selected_override_conditions = $instance_data['settings_selected_override_conditions']; $this->settings_selected_custom_post_types = $instance_data['settings_selected_custom_post_types']; $this->settings_selected_plugins = $instance_data['settings_selected_plugins']; $this->settings_customization = $instance_data['settings_customization']; $this->documents_data = $instance_data['documents_data']; $this->imported_data = $instance_data['imported_data']; $this->runners_import_metadata = $instance_data['runners_import_metadata']; } /** * Creating a new instance of the import process by the id of the old import session. * * @param string $session_id * * @return Import * @throws \Exception If the import session does not exist. */ public static function from_session( string $session_id ): Import { $import_sessions = Utils::get_import_sessions(); if ( ! $import_sessions || ! isset( $import_sessions[ $session_id ] ) ) { throw new \Exception( 'Couldn’t execute the import process because the import session does not exist.' ); } $import_session = $import_sessions[ $session_id ]; return new self( $session_id, [], $import_session ); } /** * Register a runner. * Be aware that the runner will be executed in the order of registration, the order is crucial for the import process. * * @param Import_Runner_Base $runner_instance */ public function register( Import_Runner_Base $runner_instance ) { $this->runners[ $runner_instance::get_name() ] = $runner_instance; } public function register_default_runners() { $this->register( new Site_Settings() ); $this->register( new Plugins() ); $this->register( new Templates() ); $this->register( new Taxonomies() ); $this->register( new Elementor_Content() ); $this->register( new Wp_Content() ); $this->register( new Floating_Elements() ); } /** * Set default settings for the import. */ private function set_default_settings() { if ( ! is_array( $this->get_settings_include() ) ) { $this->settings_include( $this->get_default_settings_include() ); } if ( ! is_array( $this->get_settings_conflicts() ) ) { $this->settings_conflicts( $this->get_default_settings_conflicts() ); } if ( ! is_array( $this->get_settings_selected_override_conditions() ) ) { $this->settings_selected_override_conditions( $this->get_default_settings_override_conditions() ); } if ( ! is_array( $this->get_settings_selected_custom_post_types() ) ) { $this->settings_selected_custom_post_types( $this->get_default_settings_custom_post_types() ); } if ( ! is_array( $this->get_settings_selected_plugins() ) ) { $this->settings_selected_plugins( $this->get_default_settings_plugins() ); } if ( ! is_array( $this->get_settings_customization() ) ) { $this->settings_customization( $this->get_default_settings_customization() ); } } /** * Execute the import process. * * @return array The imported data output. * * @throws \Exception If no import runners have been specified. */ public function run() { if ( empty( $this->runners ) ) { throw new \Exception( 'Couldn’t execute the import process because no import runners have been specified. Try again by specifying import runners.' ); } $data = $this->build_runner_data(); $this->init_import_session(); remove_filter( 'elementor/document/save/data', [ Plugin::$instance->modules_manager->get_modules( 'content-sanitizer' ), 'sanitize_content' ] ); add_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10, 2 ); // 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 ); foreach ( $this->runners as $runner ) { if ( $runner->should_import( $data ) ) { $import = $runner->import( $data, $this->imported_data ); $this->imported_data = array_merge_recursive( $this->imported_data, $import ); $this->runners_import_metadata[ $runner::get_name() ] = $runner->get_import_session_metadata(); } } // After the upload complete, set the elementor upload state back to false. Plugin::$instance->uploads_manager->set_elementor_upload_state( false ); remove_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10 ); $this->finalize_import_session_option(); $this->save_elements_of_imported_posts(); Plugin::$instance->uploads_manager->remove_file_or_dir( $this->extracted_directory_path ); return $this->imported_data; } /** * Run specific runner by runner_name * * @param string $runner_name * * @return array * * @throws \Exception If no export runners have been specified. */ public function run_runner( string $runner_name ): array { if ( empty( $this->runners ) ) { throw new \Exception( 'Couldn’t execute the import process because no import runners have been specified. Try again by specifying import runners.' ); } $data = $this->build_runner_data(); add_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10, 2 ); // 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 ); $runner = $this->runners[ $runner_name ]; if ( empty( $runner ) ) { throw new \Exception( 'Couldn’t execute the import process because the import runner was not found. Try again by specifying an import runner.' ); } if ( $runner->should_import( $data ) ) { $import = $runner->import( $data, $this->imported_data ); $this->imported_data = array_merge_recursive( $this->imported_data, $import ); $this->runners_import_metadata[ $runner::get_name() ] = $runner->get_import_session_metadata(); } // After the upload complete, set the elementor upload state back to false. Plugin::$instance->uploads_manager->set_elementor_upload_state( false ); remove_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10 ); $is_last_runner = key( array_slice( $this->runners, -1, 1, true ) ) === $runner_name; if ( $is_last_runner ) { $this->finalize_import_session_option(); $this->save_elements_of_imported_posts(); } else { $this->update_instance_data_in_import_session_option(); } return [ 'status' => 'success', 'runner' => $runner_name, 'imported_data' => $this->imported_data, ]; } /** * Create and save all the instance data to the import sessions option. * * @return void */ public function init_import_session( $save_instance_data = false ) { $import_sessions = Utils::get_import_sessions( true ); $existing_session = $import_sessions[ $this->session_id ] ?? []; $import_sessions[ $this->session_id ] = [ 'session_id' => $this->session_id, 'kit_title' => $this->manifest['title'] ?? '', 'kit_name' => $this->manifest['name'] ?? '', 'kit_thumbnail' => $existing_session['kit_thumbnail'] ?? $this->get_kit_thumbnail(), 'kit_source' => $existing_session['kit_source'] ?? $this->settings_referrer, 'user_id' => get_current_user_id(), 'start_timestamp' => $existing_session['start_timestamp'] ?? current_time( 'timestamp' ), ]; if ( $save_instance_data ) { $import_sessions[ $this->session_id ]['instance_data'] = [ 'extracted_directory_path' => $this->extracted_directory_path, 'runners' => $this->runners, 'adapters' => $this->adapters, 'manifest' => $this->manifest, 'site_settings' => $this->site_settings, 'kit_id' => $this->kit_id, 'settings_include' => $this->settings_include, 'settings_referrer' => $this->settings_referrer, 'settings_conflicts' => $this->settings_conflicts, 'settings_selected_override_conditions' => $this->settings_selected_override_conditions, 'settings_selected_custom_post_types' => $this->settings_selected_custom_post_types, 'settings_selected_plugins' => $this->settings_selected_plugins, 'settings_customization' => $this->settings_customization, 'documents_data' => $this->documents_data, 'imported_data' => $this->imported_data, 'runners_import_metadata' => $this->runners_import_metadata, ]; } update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false ); } /** * Get the Kit thumbnail, goes to the home page thumbnail if main doesn't exist * * @return string */ private function get_kit_thumbnail(): string { if ( ! empty( $this->manifest['thumbnail'] ) ) { return $this->manifest['thumbnail']; } return apply_filters( 'elementor/import/kit_thumbnail', '', $this->kit_id, $this->settings_referrer ); } public function get_runners_name(): array { $data = $this->build_runner_data(); return array_keys( array_filter( $this->runners, fn( $runner ) => $runner->should_import( $data ) ) ); } private function build_runner_data(): array { return [ 'session_id' => $this->session_id, 'include' => $this->settings_include, 'manifest' => $this->manifest, 'site_settings' => $this->site_settings, 'selected_plugins' => $this->settings_selected_plugins, 'customization' => $this->settings_customization, 'extracted_directory_path' => $this->extracted_directory_path, 'selected_custom_post_types' => $this->settings_selected_custom_post_types, ]; } public function get_manifest() { return $this->manifest; } public function get_extracted_directory_path() { return $this->extracted_directory_path; } public function get_session_id() { return $this->session_id; } public function get_adapters() { return $this->adapters; } public function get_imported_data() { return $this->imported_data; } /** * Get settings by key. * Used for backward compatibility. * * @param string $key The key of the setting. */ public function get_settings( $key ) { switch ( $key ) { case 'include': return $this->get_settings_include(); case 'overrideConditions': return $this->get_settings_selected_override_conditions(); case 'selectedCustomPostTypes': return $this->get_settings_selected_custom_post_types(); case 'plugins': return $this->get_settings_selected_plugins(); case 'customization': return $this->get_settings_customization(); default: return []; } } public function settings_include( array $settings_include ) { $this->settings_include = $settings_include; return $this; } public function get_settings_include() { return $this->settings_include; } public function settings_referrer( $settings_referrer ) { $this->settings_referrer = $settings_referrer; return $this; } public function get_settings_referrer() { return $this->settings_referrer; } public function settings_conflicts( array $settings_conflicts ) { $this->settings_conflicts = $settings_conflicts; return $this; } public function get_settings_conflicts() { return $this->settings_conflicts; } public function settings_selected_override_conditions( array $settings_selected_override_conditions ) { $this->settings_selected_override_conditions = $settings_selected_override_conditions; return $this; } public function get_settings_selected_override_conditions() { return $this->settings_selected_override_conditions; } public function settings_selected_custom_post_types( array $settings_selected_custom_post_types ) { $this->settings_selected_custom_post_types = $settings_selected_custom_post_types; return $this; } public function get_settings_selected_custom_post_types() { return $this->settings_selected_custom_post_types; } public function settings_selected_plugins( array $settings_selected_plugins ) { $this->settings_selected_plugins = $settings_selected_plugins; return $this; } public function get_settings_selected_plugins() { return $this->settings_selected_plugins; } /** * Prevent saving elements on elementor post creation. * * @param array $data * @param Document $document * * @return array */ public function prevent_saving_elements_on_post_creation( array $data, Document $document ) { if ( isset( $data['elements'] ) ) { $this->documents_data[ $document->get_main_id() ] = [ 'elements' => $data['elements'] ]; $data['elements'] = []; } if ( isset( $data['settings'] ) ) { $this->documents_data[ $document->get_main_id() ]['settings'] = $data['settings']; } return $data; } /** * Extract the zip file. * * @param string $zip_path The path to the zip file. * @return string The extracted directory path. * @throws \Error If import process fails, file validation errors occur, or data corruption is detected. */ private function extract_zip( $zip_path ) { $extraction_result = Plugin::$instance->uploads_manager->extract_and_validate_zip( $zip_path, [ 'json', 'xml' ] ); if ( is_wp_error( $extraction_result ) ) { if ( isset( $extraction_result->errors['zip_error'] ) ) { throw new \Error( static::ZIP_ARCHIVE_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } throw new \Error( static::ZIP_FILE_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return $extraction_result['extraction_directory']; } /** * Get the manifest file from the extracted directory and adapt it if needed. * * @return string The manifest file content. * @throws \Error If import validation fails or processing errors occur. */ private function read_manifest_json() { $manifest = Utils::read_json_file( $this->extracted_directory_path . 'manifest' ); if ( ! $manifest ) { Plugin::$instance->logger->get_logger()->error( static::MANIFEST_ERROR_KEY ); throw new \Error( static::ZIP_FILE_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $this->init_adapters( $manifest ); foreach ( $this->adapters as $adapter ) { $manifest = $adapter->adapt_manifest( $manifest ); } return $manifest; } /** * Init the adapters and determine which ones to use. * * @param array $manifest_data The manifest file content. */ private function init_adapters( array $manifest_data ) { $this->adapters = []; /** @var Base_Adapter[] $adapter_types */ $adapter_types = [ Customization::class, Envato::class, Kit_Library::class ]; foreach ( $adapter_types as $adapter_type ) { if ( $adapter_type::is_compatibility_needed( $manifest_data, [ 'referrer' => $this->get_settings_referrer() ] ) ) { $this->adapters[] = new $adapter_type( $this ); } } } /** * Get the site settings file from the extracted directory and adapt it if needed. * * @return string The site settings file content. */ private function read_site_settings_json() { $site_settings = Utils::read_json_file( $this->extracted_directory_path . 'site-settings' ); foreach ( $this->adapters as $adapter ) { $site_settings = $adapter->adapt_site_settings( $site_settings, $this->manifest, $this->extracted_directory_path ); } return $site_settings; } /** * Get all the custom post types in the kit. * * @return array Custom post types names. */ private function get_default_settings_custom_post_types() { $excluded = [ 'page', 'nav_menu_item' ]; if ( empty( $this->manifest['content']['post'] ?? [] ) && empty( $this->manifest['wp-content']['post'] ?? [] ) ) { $excluded[] = 'post'; } $manifest_post_types = array_keys( $this->manifest['custom-post-type-title'] ?? [] ); return array_merge( $manifest_post_types, Utils::get_builtin_wp_post_types( $excluded ) ); } /** * Get the default settings of elementor templates conditions to override. * * @return array */ private function get_default_settings_conflicts() { if ( empty( $this->manifest['templates'] ) ) { return []; } return apply_filters( 'elementor/import/get_default_settings_conflicts', [], $this->manifest['templates'] ); } /** * Get the default settings of elementor templates conditions to override. * * @return array */ private function get_default_settings_override_conditions() { if ( empty( $this->settings_conflicts ) ) { return []; } return array_keys( $this->settings_conflicts ); } /** * Get the default settings of the plugins that should be imported. * * @return array */ private function get_default_settings_plugins() { return ! empty( $this->manifest['plugins'] ) ? $this->manifest['plugins'] : []; } /** * Get the default settings of which content types should be imported. * * @return array */ private function get_default_settings_include() { return [ 'templates', 'plugins', 'content', 'settings' ]; } public function settings_customization( $customization ) { $this->settings_customization = $customization; return $this; } public function get_settings_customization() { return $this->settings_customization; } private function get_default_settings_customization() { return [ 'settings' => null, 'templates' => null, 'content' => null, 'plugins' => null, ]; } /** * Get the data that requires updating/replacement when imported. * * @return array{post_ids: array, term_ids: array} */ private function get_imported_data_replacements(): array { return [ 'post_ids' => Utils::map_old_new_post_ids( $this->imported_data ), 'term_ids' => Utils::map_old_new_term_ids( $this->imported_data ), ]; } /** * Save the prevented elements on elementor post creation elements. * Handle the replacement of all the dynamic content of the elements that probably have been changed during the import. */ private function save_elements_of_imported_posts() { $imported_data_replacements = $this->get_imported_data_replacements(); foreach ( $this->documents_data as $new_id => $data ) { $document = Plugin::$instance->documents->get( $new_id ); if ( isset( $data['elements'] ) ) { $data['elements'] = $document->on_import_update_dynamic_content( $data['elements'], $imported_data_replacements ); } if ( isset( $data['settings'] ) ) { if ( $document instanceof Kit ) { // Without post_status certain tabs in the Kit will not save properly. $data['settings']['post_status'] = get_post_status( $new_id ); } $data['settings'] = $document->on_import_update_settings( $data['settings'], $imported_data_replacements ); } $document->save( $data ); } } private function update_instance_data_in_import_session_option() { $import_sessions = Utils::get_import_sessions(); $import_sessions[ $this->session_id ]['instance_data']['documents_data'] = $this->documents_data; $import_sessions[ $this->session_id ]['instance_data']['imported_data'] = $this->imported_data; $import_sessions[ $this->session_id ]['instance_data']['runners_import_metadata'] = $this->runners_import_metadata; update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false ); } public function finalize_import_session_option() { $import_sessions = Utils::get_import_sessions(); if ( ! isset( $import_sessions[ $this->session_id ] ) ) { return; } unset( $import_sessions[ $this->session_id ]['instance_data'] ); $import_sessions[ $this->session_id ]['end_timestamp'] = current_time( 'timestamp' ); $import_sessions[ $this->session_id ]['runners'] = $this->runners_import_metadata; update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false ); } /** * Filter the php error args and return 408 status code if the error is a timeout. * * @param array $args * @param array $error * @return array */ private function filter_php_error_args( $args, $error ) { if ( strpos( $error['message'], 'Maximum execution time' ) !== false ) { $args['response'] = 408; } return $args; } } modules/import-export-customization/processes/export.php 0000644 00000025543 15233532517 0020032 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Processes; use Elementor\App\Modules\ImportExportCustomization\Module; use Elementor\App\Modules\ImportExportCustomization\Utils; use Elementor\Core\Utils\Str; use Elementor\Plugin; use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Elementor_Content; use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Export_Runner_Base; use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Plugins; use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Site_Settings; use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Taxonomies; use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Templates; use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Wp_Content; class Export { const ZIP_ARCHIVE_MODULE_MISSING = 'zip-archive-module-is-missing'; /** * @var Export_Runner_Base[] */ protected $runners = []; /** * Selected content types to export. * * @var array */ private $settings_include; /** * The kit information. (e.g: title, description) * * @var array $export_data */ private $settings_kit_info; /** * Customization settings for selective export. * * @var array */ private $settings_customization; /** * Selected plugins to export. * Contains the plugins essential data for export. (e.g: name, path, version, etc.) * * @var array */ private $settings_selected_plugins; /** * Selected custom post types to export. * * @var array */ private $settings_selected_custom_post_types; /** * The output data of the export process. * Will be written into the manifest.json file. * * @var array */ private $manifest_data; /** * The zip archive object. * * @var \ZipArchive */ private $zip; public function __construct( $settings = [] ) { $this->settings_include = ! empty( $settings['include'] ) ? $settings['include'] : null; $this->settings_kit_info = ! empty( $settings['kitInfo'] ) ? $settings['kitInfo'] : null; $this->settings_customization = isset( $settings['customization'] ) ? $settings['customization'] : null; $this->settings_selected_plugins = isset( $settings['plugins'] ) ? $settings['plugins'] : null; $this->settings_selected_custom_post_types = isset( $settings['customization']['content']['customPostTypes'] ) ? $settings['customization']['content']['customPostTypes'] : null; } /** * Register a runner. * * @param Export_Runner_Base $runner_instance */ public function register( Export_Runner_Base $runner_instance ) { $this->runners[ $runner_instance::get_name() ] = $runner_instance; } public function register_default_runners() { $this->register( new Site_Settings() ); $this->register( new Plugins() ); $this->register( new Templates() ); $this->register( new Taxonomies() ); $this->register( new Elementor_Content() ); $this->register( new Wp_Content() ); } /** * Execute the export process. * * @return array The export data output. * * @throws \Exception If no export runners have been specified. */ public function run() { if ( empty( $this->runners ) ) { throw new \Exception( 'Couldn’t execute the export process because no export runners have been specified. Try again by specifying export runners.' ); } $this->set_default_settings(); $this->init_zip_archive(); $this->init_manifest_data(); $data = [ 'include' => $this->settings_include, 'customization' => $this->settings_customization, 'selected_plugins' => $this->settings_selected_plugins, 'selected_custom_post_types' => $this->settings_selected_custom_post_types, ]; $media_collector = null; if ( $this->should_collect_media( $data ) ) { $media_collector = new \Elementor\TemplateLibrary\Classes\Media_Collector(); $media_collector->start_collection(); } foreach ( $this->runners as $runner ) { if ( $runner->should_export( $data ) ) { $export_result = $runner->export( $data ); $this->handle_export_result( $export_result ); } } $media_urls = null; if ( $media_collector ) { $media_urls = $media_collector->get_collected_urls(); } $this->add_json_file( 'manifest', $this->manifest_data ); $zip_file_name = $this->zip->filename; $this->zip->close(); return [ 'manifest' => $this->manifest_data, 'file_name' => $zip_file_name, 'media_urls' => $media_urls, ]; } /** * Set default settings for the export. */ private function set_default_settings() { if ( ! is_array( $this->get_settings_include() ) ) { $this->settings_include( $this->get_default_settings_include() ); } if ( ! is_array( $this->get_settings_kit_info() ) ) { $this->settings_kit_info( $this->get_default_settings_kit_info() ); } if ( ! is_array( $this->get_settings_selected_custom_post_types() ) && in_array( 'content', $this->settings_include, true ) ) { $this->settings_selected_custom_post_types( $this->get_default_settings_custom_post_types() ); } if ( ! is_array( $this->get_settings_selected_plugins() ) && in_array( 'plugins', $this->settings_include, true ) ) { $this->settings_selected_plugins( $this->get_default_settings_selected_plugins() ); } if ( ! is_array( $this->get_settings_customization() ) ) { $this->settings_customization( $this->get_default_settings_customization() ); } } public function settings_include( $settings_include ) { $this->settings_include = $settings_include; } public function get_settings_include() { return $this->settings_include; } private function settings_kit_info( $kit_info ) { $this->settings_kit_info = $kit_info; } private function get_settings_kit_info() { return $this->settings_kit_info; } public function settings_customization( $customization ) { $this->settings_customization = $customization; } public function get_settings_customization() { return $this->settings_customization; } public function settings_selected_custom_post_types( $selected_custom_post_types ) { $this->settings_selected_custom_post_types = $selected_custom_post_types; } public function get_settings_selected_custom_post_types() { return $this->settings_selected_custom_post_types; } public function settings_selected_plugins( $plugins ) { $this->settings_selected_plugins = $plugins; } public function get_settings_selected_plugins() { return $this->settings_selected_plugins; } /** * Get the default settings of which content types should be exported. * * @return array */ private function get_default_settings_include() { return [ 'templates', 'content', 'settings', 'plugins' ]; } /** * Get the default settings of the kit info. * * @return array */ private function get_default_settings_kit_info() { return [ 'title' => 'kit', 'description' => '', ]; } /** * Get the default settings of the plugins that should be exported. * * @return array{name: string, plugin:string, pluginUri: string, version: string} */ private function get_default_settings_selected_plugins() { $installed_plugins = Plugin::$instance->wp->get_plugins(); $result = []; foreach ( $installed_plugins->all() as $key => $item ) { $plugin_key = str_replace( '.php', '', $key ); // Inconsistency between get_plugins() and WP Rest API's key format. $result[ $plugin_key ] = [ 'name' => $item['Name'], 'plugin' => $plugin_key, 'pluginUri' => $item['PluginURI'], 'version' => $item['Version'], ]; } return $result; } private function get_default_settings_customization() { return [ 'settings' => null, 'templates' => null, 'content' => [ 'mediaFormat' => 'link', ], 'plugins' => null, ]; } /** * Get the default settings of all the custom post types that should be exported. * Should be all the custom post types that are not built in to WordPress and not part of Elementor. * * @return array */ private function get_default_settings_custom_post_types() { return Utils::get_registered_cpt_names(); } /** * Init the zip archive. * * @throws \Error If export process fails, file creation errors occur, or data serialization fails. */ private function init_zip_archive() { if ( ! class_exists( '\ZipArchive' ) ) { throw new \Error( static::ZIP_ARCHIVE_MODULE_MISSING ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $zip = new \ZipArchive(); $temp_dir = Plugin::$instance->uploads_manager->create_unique_dir(); $zip_file_name = $temp_dir . sanitize_title( $this->settings_kit_info['title'] ) . '.zip'; $zip->open( $zip_file_name, \ZipArchive::CREATE | \ZipArchive::OVERWRITE ); $this->zip = $zip; } /** * Init the manifest data and add some basic info to it. */ private function init_manifest_data() { $kit_post = Plugin::$instance->kits_manager->get_active_kit()->get_post(); $manifest_data = [ 'name' => sanitize_title( $this->settings_kit_info['title'] ), 'title' => $this->settings_kit_info['title'], 'description' => $this->settings_kit_info['description'], 'author' => get_the_author_meta( 'display_name', $kit_post->post_author ), 'version' => Module::FORMAT_VERSION, 'elementor_version' => ELEMENTOR_VERSION, 'created' => gmdate( 'Y-m-d H:i:s' ), 'thumbnail' => get_the_post_thumbnail_url( $kit_post ), 'site' => get_site_url(), ]; $this->manifest_data = $manifest_data; } /** * Handle the export process output. * Add the manifest data from the runner to the manifest.json file. * Create files according to the files array that should be exported by the runner. * * @param array $export_result */ private function handle_export_result( $export_result ) { foreach ( $export_result['manifest'] as $data ) { $this->manifest_data += $data; } if ( isset( $export_result['files']['path'] ) ) { $export_result['files'] = [ $export_result['files'] ]; } foreach ( $export_result['files'] as $file ) { $file_extension = pathinfo( $file['path'], PATHINFO_EXTENSION ); if ( empty( $file_extension ) ) { $this->add_json_file( $file['path'], $file['data'] ); } else { $this->add_file( $file['path'], $file['data'] ); } } } /** * Add json file to the zip archive. * * @param string $path The relative path to the file. * @param array $content The content of the file. * @param int $json_flags */ private function add_json_file( $path, array $content, $json_flags = 0 ) { if ( ! Str::ends_with( $path, '.json' ) ) { $path .= '.json'; } $this->add_file( $path, wp_json_encode( $content, $json_flags ) ); } /** * Add file to the zip archive. * * @param string $file * @param string $content The content of the file. */ private function add_file( $file, $content ) { $this->zip->addFromString( $file, $content ); } private function should_collect_media( $data ) { return ( isset( $data['customization']['content']['mediaFormat'] ) && 'cloud' === $data['customization']['content']['mediaFormat'] ); } } modules/import-export-customization/utils.php 0000644 00000011176 15233532517 0015640 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization; use Elementor\Core\Utils\Str; use Elementor\Modules\LandingPages\Module as Landing_Pages_Module; use Elementor\Modules\FloatingButtons\Module as Floating_Buttons_Module; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils as ElementorUtils; class Utils { public static function read_json_file( $path ) { if ( ! Str::ends_with( $path, '.json' ) ) { $path .= '.json'; } $file_content = ElementorUtils::file_get_contents( $path, true ); return $file_content ? json_decode( $file_content, true ) : []; } public static function map_old_new_post_ids( array $imported_data ) { $result = []; $result += $imported_data['templates']['succeed'] ?? []; if ( isset( $imported_data['content'] ) ) { foreach ( $imported_data['content'] as $post_type ) { $result += $post_type['succeed'] ?? []; } } if ( isset( $imported_data['wp-content'] ) ) { foreach ( $imported_data['wp-content'] as $post_type ) { $result += $post_type['succeed'] ?? []; } } return $result; } public static function map_old_new_term_ids( array $imported_data ) { $result = []; if ( ! isset( $imported_data['taxonomies'] ) ) { return $result; } foreach ( $imported_data['taxonomies'] as $post_type_taxonomies ) { foreach ( $post_type_taxonomies as $taxonomy ) { foreach ( $taxonomy as $term ) { $result[ $term['old_id'] ] = $term['new_id']; } } } return $result; } public static function get_elementor_post_types( $exclude = [] ) { $elementor_post_types = get_post_types_by_support( 'elementor' ); return array_filter( $elementor_post_types, function ( $value ) { // Templates are handled in a separate process. if ( 'elementor_library' === $value ) { return false; } if ( ! empty( $exclude ) && in_array( $value, $exclude, true ) ) { return false; } return 'elementor_library' !== $value; } ); } public static function get_builtin_wp_post_types( $exclude = [] ) { $builtin_wp_post_types = [ 'post', 'page', 'nav_menu_item' ]; if ( ! empty( $exclude ) ) { return array_diff( $builtin_wp_post_types, $exclude ); } return $builtin_wp_post_types; } public static function get_registered_cpt_names() { $post_types = get_post_types( [ 'public' => true, 'can_export' => true, '_builtin' => false, ] ); unset( $post_types[ Landing_Pages_Module::CPT ], $post_types[ Source_Local::CPT ], $post_types[ Floating_Buttons_Module::CPT_FLOATING_BUTTONS ] ); return array_keys( $post_types ); } /** * Transform a string name to title format. * * @param $name * * @return string */ public static function transform_name_to_title( $name ): string { if ( empty( $name ) ) { return ''; } $title = str_replace( [ '-', '_' ], ' ', $name ); return ucwords( $title ); } public static function get_import_sessions( $should_run_cleanup = false ) { $import_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, [] ); if ( $should_run_cleanup ) { foreach ( $import_sessions as $session_id => $import_session ) { if ( ! isset( $import_session['runners'] ) && isset( $import_session['instance_data'] ) ) { $import_sessions[ $session_id ]['runners'] = $import_session['instance_data']['runners_import_metadata'] ?? []; unset( $import_sessions[ $session_id ]['instance_data'] ); } } update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions ); } return $import_sessions; } public static function update_space_between_widgets_values( $space_between_widgets ) { $setting_exist = isset( $space_between_widgets['size'] ); $already_processed = isset( $space_between_widgets['column'] ); if ( ! $setting_exist || $already_processed ) { return $space_between_widgets; } $size = strval( $space_between_widgets['size'] ); $space_between_widgets['column'] = $size; $space_between_widgets['row'] = $size; $space_between_widgets['isLinked'] = true; return $space_between_widgets; } public static function resolve_label_conflict( string $label, array $existing_labels, int $max_length = 50 ): string { $lower_label = strtolower( $label ); if ( ! in_array( $lower_label, $existing_labels, true ) ) { return $label; } $suffix = 1; $max_suffix_attempts = 1000; do { $suffix_str = '_' . $suffix; $max_base_length = $max_length - strlen( $suffix_str ); $base_label = mb_substr( $label, 0, $max_base_length ); $new_label = $base_label . $suffix_str; ++$suffix; } while ( in_array( strtolower( $new_label ), $existing_labels, true ) && $suffix < $max_suffix_attempts ); return $new_label; } } modules/import-export-customization/runners/runner-interface.php 0000644 00000001051 15233532517 0021432 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners; use Elementor\App\Modules\ImportExportCustomization\Module; interface Runner_Interface { const META_KEY_ELEMENTOR_IMPORT_SESSION_ID = Module::META_KEY_ELEMENTOR_IMPORT_SESSION_ID; const META_KEY_ELEMENTOR_EDIT_MODE = Module::META_KEY_ELEMENTOR_EDIT_MODE; /** * Get the name of the runners, used to identify the runner. * The name should be unique, unless you want to run over existing runner. * * @return string */ public static function get_name(): string; } modules/import-export-customization/runners/import/elementor-content.php 0000644 00000017644 15233532517 0023156 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Import; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Plugin; class Elementor_Content extends Import_Runner_Base { const IMPORT_STATUS_SUCCEEDED = 'succeed'; const IMPORT_STATUS_FAILED = 'failed'; private $show_page_on_front; private $page_on_front_id; private $import_session_id; private $imported_data; private $processed_posts = []; private $post_orphans = []; public function __construct() { $this->init_page_on_front_data(); } public static function get_name(): string { return 'elementor-content'; } public function should_import( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) && ! empty( $data['manifest']['content'] ) && ! empty( $data['extracted_directory_path'] ) ); } public function import( array $data, array $imported_data ) { if ( ! function_exists( 'wp_set_post_terms' ) ) { require_once ABSPATH . 'wp-admin/includes/taxonomy.php'; } $result['content'] = []; $this->import_session_id = $data['session_id']; $this->imported_data = $imported_data; $customization = $data['customization']['content'] ?? null; $selected_custom_post_types = $data['selected_custom_post_types'] ?? null; $excluded_post_types = []; if ( $selected_custom_post_types && ! in_array( 'post', $selected_custom_post_types, true ) ) { $excluded_post_types[] = 'post'; } $post_types = ImportExportUtils::get_elementor_post_types( $excluded_post_types ); $post_types = apply_filters( 'elementor/import-export-customization/elementor-content/post-types/customization', $post_types, $customization ); foreach ( $post_types as $post_type ) { if ( empty( $data['manifest']['content'][ $post_type ] ) ) { continue; } $posts_settings = $data['manifest']['content'][ $post_type ]; $path = $data['extracted_directory_path'] . 'content/' . $post_type . '/'; $imported_terms = ! empty( $imported_data['taxonomies'] ) ? ImportExportUtils::map_old_new_term_ids( $imported_data ) : []; $result['content'][ $post_type ] = $this->import_elementor_post_type( $posts_settings, $path, $post_type, $imported_terms, $data['customization']['content'] ?? null, ); } return $result; } private function import_elementor_post_type( array $posts_settings, $path, $post_type, array $imported_terms, $customization ) { $result = [ 'succeed' => [], 'failed' => [], ]; foreach ( $posts_settings as $id => $post_settings ) { try { if ( 'page' === $post_type ) { $data = [ 'path' => $path, 'id' => $id, 'post_settings' => $post_settings, 'post_type' => $post_type, 'imported_terms' => $imported_terms, ]; $import_result = apply_filters( 'elementor/import-export-customization/import/elementor-content/customization', null, $data, [], $customization ?? [], $this ); if ( is_array( $import_result ) ) { $result[ $import_result['status'] ][ $id ] = $import_result['result']; $this->map_imported_post_id( $id, $import_result ); continue; } } $import_result = $this->read_and_import_post( $path, $id, $post_settings, $post_type, $imported_terms ); $this->map_imported_post_id( $id, $import_result ); $result[ $import_result['status'] ][ $id ] = $import_result['result']; } catch ( \Exception $error ) { $result['failed'][ $id ] = $error->getMessage(); } } $this->backfill_parents(); return $result; } public function read_and_import_post( $path, $id, $post_settings, $post_type, $imported_terms ) { try { $post_data = ImportExportUtils::read_json_file( $path . $id ); $import = $this->import_post( $post_settings, $post_data, $post_type, $imported_terms, $id ); if ( is_wp_error( $import ) ) { $result = [ 'status' => static::IMPORT_STATUS_FAILED, 'result' => $import->get_error_message(), ]; } else { $result = [ 'status' => static::IMPORT_STATUS_SUCCEEDED, 'result' => $import, ]; } } catch ( \Exception $error ) { $result = [ 'status' => static::IMPORT_STATUS_FAILED, 'result' => $error->getMessage(), ]; } return $result; } private function import_post( array $post_settings, array $post_data, $post_type, array $imported_terms, int $original_post_id ) { $post_attributes = [ 'post_title' => $post_settings['title'], 'post_type' => $post_type, 'post_status' => 'publish', ]; if ( ! empty( $post_settings['excerpt'] ) ) { $post_attributes['post_excerpt'] = $post_settings['excerpt']; } $post_parent_id = $this->get_imported_parent_id( $post_settings, $original_post_id ); if ( $post_parent_id ) { $post_attributes['post_parent'] = $post_parent_id; } $new_document = Plugin::$instance->documents->create( $post_settings['doc_type'], $post_attributes ); if ( is_wp_error( $new_document ) ) { throw new \Exception( esc_html( $new_document->get_error_message() ) ); } $post_data['import_settings'] = $post_settings; $new_attachment_callback = function( $attachment_id ) { $this->set_session_post_meta( $attachment_id, $this->import_session_id ); }; add_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback ); $new_document->import( $post_data ); remove_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback ); $new_post_id = $new_document->get_main_id(); if ( ! empty( $post_settings['terms'] ) ) { $this->set_post_terms( $new_post_id, $post_settings['terms'], $imported_terms ); } if ( ! empty( $post_settings['show_on_front'] ) ) { $this->set_page_on_front( $new_post_id ); } $this->set_session_post_meta( $new_post_id, $this->import_session_id ); return $new_post_id; } private function get_imported_parent_id( array $post_settings, int $original_post_id ): int { $post_parent_id = (int) ( $post_settings['post_parent'] ?? 0 ); if ( ! $post_parent_id ) { return 0; } if ( isset( $this->processed_posts[ $post_parent_id ] ) ) { return $this->processed_posts[ $post_parent_id ]; } $this->post_orphans[ $original_post_id ] = $post_parent_id; return 0; } private function set_post_terms( $post_id, array $terms, array $imported_terms ) { foreach ( $terms as $term ) { if ( ! isset( $imported_terms[ $term['term_id'] ] ) ) { continue; } wp_set_post_terms( $post_id, [ $imported_terms[ $term['term_id'] ] ], $term['taxonomy'], false ); } } private function init_page_on_front_data() { $this->show_page_on_front = 'page' === get_option( 'show_on_front' ); if ( $this->show_page_on_front ) { $this->page_on_front_id = (int) get_option( 'page_on_front' ); } } private function set_page_on_front( $page_id ) { update_option( 'page_on_front', $page_id ); if ( ! $this->show_page_on_front ) { update_option( 'show_on_front', 'page' ); } } public function get_import_session_metadata(): array { return [ 'page_on_front' => $this->page_on_front_id ?? 0, ]; } private function map_imported_post_id( $original_id, $import_result ) { if ( static::IMPORT_STATUS_SUCCEEDED !== $import_result['status'] ) { return; } $this->processed_posts[ $original_id ] = $import_result['result']; } private function backfill_parents() { global $wpdb; // Find parents for post orphans. foreach ( $this->post_orphans as $child_id => $parent_id ) { $local_child_id = false; $local_parent_id = false; if ( isset( $this->processed_posts[ $child_id ] ) ) { $local_child_id = $this->processed_posts[ $child_id ]; } if ( isset( $this->processed_posts[ $parent_id ] ) ) { $local_parent_id = $this->processed_posts[ $parent_id ]; } if ( $local_child_id && $local_parent_id ) { $wpdb->update( $wpdb->posts, [ 'post_parent' => $local_parent_id ], [ 'ID' => $local_child_id ], '%d', '%d' ); clean_post_cache( $local_child_id ); } } } } modules/import-export-customization/runners/import/wp-content.php 0000644 00000007577 15233532517 0021616 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Import; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Core\Utils\ImportExport\WP_Import; class Wp_Content extends Import_Runner_Base { private $import_session_id; /** * @var array */ private $selected_custom_post_types = []; public static function get_name(): string { return 'wp-content'; } public function should_import( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) && ! empty( $data['extracted_directory_path'] ) && ! empty( $data['manifest']['wp-content'] ) ); } public function import( array $data, array $imported_data ) { $this->import_session_id = $data['session_id']; $path = $data['extracted_directory_path'] . 'wp-content/'; $post_types = $this->filter_post_types( $data ); $taxonomies = $imported_data['taxonomies'] ?? []; $imported_terms = ImportExportUtils::map_old_new_term_ids( $imported_data ); $result['wp-content'] = []; foreach ( $post_types as $post_type ) { $import = $this->import_wp_post_type( $path, $post_type, $imported_data, $taxonomies, $imported_terms, $data['customization']['content'] ?? null ); if ( empty( $import ) ) { continue; } $result['wp-content'][ $post_type ] = $import; $imported_data = array_merge( $imported_data, $result ); } return $result; } private function import_wp_post_type( $path, $post_type, array $imported_data, array $taxonomies, array $imported_terms, $customization ) { $args = [ 'fetch_attachments' => true, 'posts' => ImportExportUtils::map_old_new_post_ids( $imported_data ), 'terms' => $imported_terms, 'taxonomies' => ! empty( $taxonomies[ $post_type ] ) ? $taxonomies[ $post_type ] : [], 'posts_meta' => [ static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID => $this->import_session_id, ], 'terms_meta' => [ static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID => $this->import_session_id, ], 'include' => 'page' === $post_type ? $customization['pages'] ?? null : null, ]; $args = apply_filters( 'elementor/import-export-customization/import/wp-content/query-args/customization', $args, $post_type, $customization ); $file = $path . $post_type . '/' . $post_type . '.xml'; if ( ! file_exists( $file ) ) { return []; } $wp_importer = new WP_Import( $file, $args ); $result = $wp_importer->run(); return $result['summary']['posts']; } private function filter_post_types( $data ) { $selected_custom_post_types = $data['selected_custom_post_types']; $customization = $data['customization']['content'] ?? null; $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types( [ 'post' ] ); foreach ( $selected_custom_post_types as $custom_post_type ) { if ( post_type_exists( $custom_post_type ) ) { $this->selected_custom_post_types[] = $custom_post_type; } } $post_types = array_merge( $wp_builtin_post_types, $this->selected_custom_post_types ); $post_types = apply_filters( 'elementor/import-export-customization/wp-content/post-types/customization', $post_types, $data, $customization ); $post_types = array_unique( $this->force_element_to_be_last_by_value( $post_types, 'nav_menu_item' ) ); return $post_types; } public function get_import_session_metadata(): array { return [ 'custom_post_types' => $this->selected_custom_post_types, ]; } /** * @param array $base_array The array we want to relocate his element. * @param mixed $element The value of the element in the array we want to shift to end of the array. * @return mixed */ private function force_element_to_be_last_by_value( array $base_array, $element ) { $index = array_search( $element, $base_array, true ); if ( false !== $index ) { unset( $base_array[ $index ] ); $base_array[] = $element; } return $base_array; } } modules/import-export-customization/runners/import/taxonomies.php 0000644 00000011204 15233532517 0021664 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Import; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; class Taxonomies extends Import_Runner_Base { private $import_session_id; public static function get_name(): string { return 'taxonomies'; } public function should_import( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) && ! empty( $data['extracted_directory_path'] ) && ! empty( $data['manifest']['taxonomies'] ) ); } public function import( array $data, array $imported_data ) { if ( ! function_exists( 'wp_insert_term' ) ) { require_once ABSPATH . 'wp-admin/includes/taxonomy.php'; } $customization = $data['customization']['content'] ?? null; if ( $customization ) { return $this->import_with_customization( $data, $imported_data, $customization ); } return $this->import_all( $data, $imported_data ); } public function import_all( array $data, array $imported_data ) { $path = $data['extracted_directory_path'] . 'taxonomies/'; $this->import_session_id = $data['session_id']; $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types(); $selected_custom_post_types = isset( $data['selected_custom_post_types'] ) ? $data['selected_custom_post_types'] : []; $post_types = array_merge( $wp_builtin_post_types, $selected_custom_post_types ); $result = []; foreach ( $post_types as $post_type ) { if ( empty( $data['manifest']['taxonomies'][ $post_type ] ) ) { continue; } $result['taxonomies'][ $post_type ] = $this->import_taxonomies( $data['manifest']['taxonomies'][ $post_type ], $path ); } return $result; } public function import_with_customization( array $data, array $imported_data, array $customization ) { $result = apply_filters( 'elementor/import-export-customization/import/taxonomies/customization', null, $data, $imported_data, $customization, $this ); if ( is_array( $result ) ) { return $result; } return $this->import_all( $data, $imported_data ); } public function import_taxonomies( array $taxonomies, $path ) { $result = []; $imported_taxonomies = []; foreach ( $taxonomies as $taxonomy_object ) { $taxonomy = is_array( $taxonomy_object ) ? $taxonomy_object['name'] : $taxonomy_object; if ( ! taxonomy_exists( $taxonomy ) ) { continue; } if ( ! empty( $imported_taxonomies[ $taxonomy ] ) ) { $result[ $taxonomy ] = $imported_taxonomies[ $taxonomy ]; continue; } $taxonomy_data = ImportExportUtils::read_json_file( $path . $taxonomy ); if ( empty( $taxonomy_data ) ) { continue; } $import = $this->import_taxonomy( $taxonomy_data ); $result[ $taxonomy ] = $import; $imported_taxonomies[ $taxonomy ] = $import; } return $result; } private function import_taxonomy( array $taxonomy_data ) { $terms = []; foreach ( $taxonomy_data as $term ) { $old_slug = $term['slug']; $existing_term = term_exists( $term['slug'], $term['taxonomy'] ); if ( $existing_term ) { if ( 'nav_menu' === $term['taxonomy'] ) { $term = $this->handle_duplicated_nav_menu_term( $term ); } else { $terms[] = [ 'old_id' => (int) $term['term_id'], 'new_id' => (int) $existing_term['term_id'], 'old_slug' => $old_slug, 'new_slug' => $term['slug'], ]; continue; } } $parent = $this->get_term_parent( $term, $terms ); $args = [ 'slug' => $term['slug'], 'description' => wp_slash( $term['description'] ), 'parent' => (int) $parent, ]; $new_term = wp_insert_term( wp_slash( $term['name'] ), $term['taxonomy'], $args ); if ( ! is_wp_error( $new_term ) ) { $this->set_session_term_meta( (int) $new_term['term_id'], $this->import_session_id ); $terms[] = [ 'old_id' => $term['term_id'], 'new_id' => (int) $new_term['term_id'], 'old_slug' => $old_slug, 'new_slug' => $term['slug'], ]; } } return $terms; } private function handle_duplicated_nav_menu_term( $term ) { do { $term['slug'] = $term['slug'] . '-duplicate'; $term['name'] = $term['name'] . ' duplicate'; } while ( term_exists( $term['slug'], 'nav_menu' ) ); return $term; } private function get_term_parent( $term, array $imported_terms ) { $parent = $term['parent']; if ( 0 !== $parent && ! empty( $imported_terms ) ) { foreach ( $imported_terms as $imported_term ) { if ( $parent === $imported_term['old_id'] ) { $parent_term = term_exists( $imported_term['new_id'], $term['taxonomy'] ); break; } } if ( isset( $parent_term['term_id'] ) ) { return $parent_term['term_id']; } } return 0; } } modules/import-export-customization/runners/import/import-runner-base.php 0000644 00000002222 15233532517 0023227 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Import; use Elementor\App\Modules\ImportExportCustomization\Runners\Runner_Interface; abstract class Import_Runner_Base implements Runner_Interface { /** * By the passed data we should decide if we want to run the import function of the runner or not. * * @param array $data * * @return bool */ abstract public function should_import( array $data ); /** * Main function of the runner import process. * * @param array $data Necessary data for the import process. * @param array $imported_data Data that already imported by previously runners. * * @return array The result of the import process */ abstract public function import( array $data, array $imported_data ); public function get_import_session_metadata(): array { return []; } public function set_session_post_meta( $post_id, $meta_value ) { update_post_meta( $post_id, static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, $meta_value ); } public function set_session_term_meta( $term_id, $meta_value ) { update_term_meta( $term_id, static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, $meta_value ); } } modules/import-export-customization/runners/import/templates.php 0000644 00000011243 15233532517 0021477 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Import; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; use Elementor\Modules\Library\Documents\Library_Document; class Templates extends Import_Runner_Base { private $import_session_id; private $import_session_metadata = []; public static function get_name(): string { return 'templates'; } public function should_import( array $data ) { return ( Utils::has_pro() && isset( $data['include'] ) && in_array( 'templates', $data['include'], true ) && ! empty( $data['extracted_directory_path'] ) && ! empty( $data['manifest']['templates'] ) ); } public function import( array $data, array $imported_data ) { $customization = $data['customization']['templates'] ?? null; if ( $customization ) { return $this->import_with_customization( $data, $imported_data, $customization ); } return $this->import_all( $data, $imported_data ); } private function import_with_customization( array $data, array $imported_data, array $customization ) { $result = apply_filters( 'elementor/import-export-customization/import/templates/customization', null, $data, $imported_data, $customization, $this ); if ( is_array( $result ) ) { return $result; } return $this->import_all( $data, $imported_data ); } private function import_all( array $data, array $imported_data ) { $template_types = array_keys( Plugin::$instance->documents->get_document_types( [ 'is_editable' => true, 'show_in_library' => true, 'export_group' => Library_Document::EXPORT_GROUP, ] ) ); $result = $this->process_templates_import( $data, $template_types ); /** * Filter the templates import result to allow 3rd parties to add their own imported templates. * * @param array $result The import result structure with 'templates' key containing succeed/failed/succeed_summary. * @param array $data The full import data. * @param array|null $customization The customization settings for templates. * @param object $runner The runner instance. */ $customization = $data['customization']['templates'] ?? null; $result = apply_filters( 'elementor/import-export-customization/import/templates_result', $result, $data, $customization, $this ); return $result; } public function process_templates_import( array $data, array $template_types ) { $this->import_session_id = $data['session_id']; $path = $data['extracted_directory_path'] . 'templates/'; $templates = $data['manifest']['templates']; $result['templates'] = [ 'succeed' => [], 'failed' => [], 'succeed_summary' => [], ]; foreach ( $templates as $id => $template_settings ) { if ( ! empty( $template_types ) && ! in_array( $template_settings['doc_type'], $template_types, true ) ) { continue; } try { $template_data = ImportExportUtils::read_json_file( $path . $id ); $import = $this->import_template( $id, $template_settings, $template_data ); $result['templates']['succeed'][ $id ] = $import; $result['templates']['succeed_summary'][ $template_settings['doc_type'] ] = ( $result['templates']['succeed_summary'][ $template_settings['doc_type'] ] ?? 0 ) + 1; } catch ( \Exception $error ) { $result['templates']['failed'][ $id ] = $error->getMessage(); } } return $result; } public function import_template( $id, array $template_settings, array $template_data ) { $doc_type = $template_settings['doc_type']; $new_document = Plugin::$instance->documents->create( $doc_type, [ 'post_title' => $template_settings['title'], 'post_type' => Source_Local::CPT, 'post_status' => 'publish', ] ); if ( is_wp_error( $new_document ) ) { throw new \Exception( esc_html( $new_document->get_error_message() ) ); } $template_data['import_settings'] = $template_settings; $template_data['id'] = $id; $new_attachment_callback = function( $attachment_id ) { $this->set_session_post_meta( $attachment_id, $this->import_session_id ); }; add_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback ); $new_document->import( $template_data ); remove_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback ); $document_id = $new_document->get_main_id(); $this->set_session_post_meta( $document_id, $this->import_session_id ); return $document_id; } public function get_import_session_metadata(): array { return $this->import_session_metadata; } public function add_import_session_metadata( $key, $metadata ) { $this->import_session_metadata[ $key ] = $metadata; } } modules/import-export-customization/runners/import/floating-elements.php 0000644 00000007003 15233532517 0023115 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Import; use Elementor\Modules\FloatingButtons\Documents\Floating_Buttons; use Elementor\Modules\FloatingButtons\Module as FloatingButtonsModule; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Floating_Elements extends Import_Runner_Base { const CONDITIONS_CACHE_META_KEY = 'elementor_pro_theme_builder_conditions'; private $posts_cache = []; public static function get_name(): string { return 'floating-elements'; } public function should_import( array $data ): bool { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) && ! empty( $data['manifest']['content']['e-floating-buttons'] ) && ! empty( $data['extracted_directory_path'] ) ); } public function import( array $data, array $imported_data ) { $post_type = 'e-floating-buttons'; $posts_settings = $data['manifest']['content'][ $post_type ]; $path = $data['extracted_directory_path'] . 'content/' . $post_type . '/'; $imported_floating_elements = $imported_data['content']['e-floating-buttons']['succeed'] ?? []; $imported_post_ids = []; foreach ( $posts_settings as $id => $post_settings ) { try { $imported_post_ids[] = $this->import_floating_element_metadata( $id, $path, $imported_floating_elements ); } catch ( \Exception $e ) { continue; } } $this->set_display_conditions_cache( $imported_post_ids ); return []; } private function set_display_conditions_cache( array $imported_post_ids ) { $conditions = get_option( self::CONDITIONS_CACHE_META_KEY, [] ); $conditions['floating_buttons'] = []; foreach ( $imported_post_ids as $imported_post_id ) { $conditions['floating_buttons'][ $imported_post_id ] = [ 'include/general' ]; } update_option( self::CONDITIONS_CACHE_META_KEY, $conditions ); } private function import_floating_element_metadata( $id, $path, $imported_floating_elements ) { $post_data = ImportExportUtils::read_json_file( $path . $id ); $widget_type = $post_data['content'][0]['elements'][0]['widgetType'] ?? ''; $floating_element_type = 'floating-buttons'; $imported_post_id = $imported_floating_elements[ $id ] ?? null; if ( ! $imported_post_id ) { throw new \Exception( sprintf( /* translators: %s: Floating element ID */ esc_html__( 'Imported post ID not found for floating element: %s', 'elementor' ), esc_html( $id ) ) ); } if ( str_starts_with( $widget_type, 'floating-bars' ) ) { $floating_element_type = 'floating-bars'; update_post_meta( $imported_post_id, FloatingButtonsModule::FLOATING_ELEMENTS_TYPE_META_KEY, $floating_element_type ); } if ( ! isset( $this->posts_cache[ $floating_element_type ] ) ) { $this->posts_cache[ $floating_element_type ] = get_posts( [ 'post_type' => FloatingButtonsModule::CPT_FLOATING_BUTTONS, 'posts_per_page' => -1, 'post_status' => 'publish', 'fields' => 'ids', 'no_found_rows' => true, 'update_post_term_cache' => false, 'update_post_meta_cache' => false, 'meta_query' => Floating_Buttons::get_meta_query_for_floating_buttons( $floating_element_type ), ] ); } $posts = $this->posts_cache[ $floating_element_type ]; foreach ( $posts as $post_id ) { delete_post_meta( $post_id, '_elementor_conditions' ); } update_post_meta( $imported_post_id, '_elementor_conditions', [ 'include/general' ] ); return $imported_post_id; } } modules/import-export-customization/runners/import/site-settings.php 0000644 00000026727 15233532517 0022320 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Import; use Elementor\Plugin; use Elementor\Core\Settings\Page\Manager as PageManager; use Elementor\App\Modules\ImportExportCustomization\Utils; use Elementor\Core\Experiments\Manager as ExperimentsManager; class Site_Settings extends Import_Runner_Base { const ALLOWED_SETTINGS = [ 'theme', 'globalColors', 'globalFonts', 'themeStyleSettings', 'generalSettings', 'experiments', 'customCode', 'customIcons', 'customFonts', 'classes', 'variables', ]; /** * @var int */ private $previous_kit_id; /** * @var int */ private $active_kit_id; /** * @var int */ private $imported_kit_id; /** * @var string|null */ private ?string $installed_theme = null; /** * @var string|null */ private ?string $activated_theme = null; /** * @var array|null */ private ?array $previous_active_theme = null; /** * @var array */ private $previous_experiments = []; /** * @var array */ private $imported_experiments = []; public function get_theme_upgrader(): \Theme_Upgrader { if ( ! function_exists( 'request_filesystem_credentials' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } if ( ! class_exists( '\Theme_Upgrader' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } if ( ! class_exists( '\WP_Ajax_Upgrader_Skin' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php'; } return new \Theme_Upgrader( new \WP_Ajax_Upgrader_Skin() ); } public static function get_name(): string { return 'site-settings'; } public function should_import( array $data ) { return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) && ! empty( $data['site_settings']['settings'] ) ); } public function import( array $data, array $imported_data ) { $customization = $data['customization']['settings'] ?? null; if ( $customization ) { return $this->import_with_customization( $data, $imported_data, $customization ); } return $this->import_with_manifest( $data, $imported_data ); } private function import_with_customization( array $data, array $imported_data, array $customization ) { $result = apply_filters( 'elementor/import-export-customization/import/site-settings/customization', null, $data, $imported_data, $customization, $this ); if ( is_array( $result ) ) { return $result; } return $this->import_with_manifest( $data, $imported_data, ! empty( $customization['theme'] ) ); } private function import_with_manifest( array $data, array $imported_data, $include_theme = true ) { $new_site_settings = $data['site_settings']['settings']; $title = $data['manifest']['title'] ?? 'Imported Kit'; $manifest_settings = $data['manifest']['site-settings'] ?? []; $active_kit = Plugin::$instance->kits_manager->get_active_kit(); $this->active_kit_id = (int) $active_kit->get_id(); $this->previous_kit_id = (int) Plugin::$instance->kits_manager->get_previous_id(); $result = []; $old_settings = $active_kit->get_meta( PageManager::META_KEY ); if ( ! $old_settings ) { $old_settings = []; } $new_site_settings = $this->filter_settings_by_manifest( $new_site_settings, $manifest_settings ); if ( ( $manifest_settings['globalColors'] ?? false ) && ! empty( $old_settings['custom_colors'] ) && ! empty( $new_site_settings['custom_colors'] ) ) { $new_site_settings['custom_colors'] = array_merge( $old_settings['custom_colors'], $new_site_settings['custom_colors'] ); } if ( ( $manifest_settings['globalFonts'] ?? false ) && ! empty( $old_settings['custom_typography'] ) && ! empty( $new_site_settings['custom_typography'] ) ) { $new_site_settings['custom_typography'] = array_merge( $old_settings['custom_typography'], $new_site_settings['custom_typography'] ); } if ( ( $manifest_settings['generalSettings'] ?? false ) && ! empty( $new_site_settings['space_between_widgets'] ) ) { $new_site_settings['space_between_widgets'] = Utils::update_space_between_widgets_values( $new_site_settings['space_between_widgets'] ); } $new_site_settings = array_replace_recursive( $old_settings, $new_site_settings ); $new_kit = Plugin::$instance->kits_manager->create_new_kit( $title, $new_site_settings ); $this->imported_kit_id = (int) $new_kit; $result['site-settings']['imported_kit_id'] = $this->imported_kit_id; foreach ( $new_site_settings as $key => $value ) { $result['site-settings'][ $key ] = $value; } if ( ( $manifest_settings['theme'] ?? false ) && $include_theme ) { $import_theme_result = $this->import_theme( $data ); if ( ! empty( $import_theme_result ) ) { $result['theme'] = $import_theme_result; } } if ( $manifest_settings['experiments'] ?? false ) { $this->import_experiments( $data ); if ( ! empty( $this->imported_experiments ) ) { $result['experiments'] = $this->imported_experiments; } } return $result; } private function filter_settings_by_manifest( array $settings, array $manifest_settings ): array { foreach ( self::ALLOWED_SETTINGS as $setting_key ) { if ( ! ( $manifest_settings[ $setting_key ] ?? false ) ) { $settings = $this->remove_setting_by_key( $settings, $setting_key ); } } return $settings; } private function remove_setting_by_key( array $settings, string $setting_key ): array { switch ( $setting_key ) { case 'globalColors': $settings = $this->remove_global_colors( $settings ); break; case 'globalFonts': $settings = $this->remove_global_fonts( $settings ); break; case 'themeStyleSettings': $settings = $this->remove_theme_style( $settings ); break; case 'generalSettings': $settings = $this->remove_other_settings( $settings ); break; } return $settings; } private function remove_global_colors( array $settings ): array { $color_keys = [ 'system_colors', 'custom_colors' ]; foreach ( $color_keys as $key ) { if ( isset( $settings[ $key ] ) ) { unset( $settings[ $key ] ); } } return $settings; } private function remove_global_fonts( array $settings ): array { $typography_keys = [ 'system_typography', 'custom_typography', 'default_generic_fonts' ]; foreach ( $typography_keys as $key ) { if ( isset( $settings[ $key ] ) ) { unset( $settings[ $key ] ); } } return $settings; } private function remove_theme_style( array $settings ): array { $theme_style_patterns = [ '/^body_/', '/^h[1-6]_/', '/^button_/', '/^link_/', '/^form_field_/', ]; foreach ( $settings as $key => $value ) { foreach ( $theme_style_patterns as $pattern ) { if ( preg_match( $pattern, $key ) ) { unset( $settings[ $key ] ); break; } } } return $settings; } private function remove_other_settings( array $settings ): array { $settings_keys = [ 'template', 'container_width', 'container_padding', 'space_between_widgets', 'viewport_md', 'viewport_lg', 'page_title_selector', 'activeItemIndex', ]; foreach ( $settings_keys as $key ) { if ( isset( $settings[ $key ] ) ) { unset( $settings[ $key ] ); } } return $settings; } protected function install_theme( $slug, $version ) { $download_url = "https://downloads.wordpress.org/theme/{$slug}.{$version}.zip"; return $this->get_theme_upgrader()->install( $download_url ); } protected function activate_theme( $slug ) { switch_theme( $slug ); } public function import_theme( array $data ) { if ( empty( $data['site_settings']['theme'] ) ) { return null; } if ( ! function_exists( 'wp_get_theme' ) ) { require_once ABSPATH . 'wp-admin/includes/theme.php'; } $theme = $data['site_settings']['theme']; $theme_slug = $theme['slug']; $theme_name = $theme['name']; $current_theme = wp_get_theme(); $this->previous_active_theme = []; $this->previous_active_theme['slug'] = $current_theme->get_stylesheet(); $this->previous_active_theme['version'] = $current_theme->get( 'Version' ); if ( $current_theme->get_stylesheet() === $theme_slug ) { $result['succeed'][ $theme_slug ] = sprintf( /* translators: %s: Theme name. */ __( 'Theme: %s is already used', 'elementor' ), $theme_name ); return $result; } try { if ( wp_get_theme( $theme_slug )->exists() ) { $this->activate_theme( $theme_slug ); $this->activated_theme = $theme_slug; $result['succeed'][ $theme_slug ] = sprintf( /* translators: %s: Theme name. */ __( 'Theme: %s has already been installed and activated', 'elementor' ), $theme_name ); return $result; } $import = $this->install_theme( $theme_slug, $theme['version'] ); if ( is_wp_error( $import ) ) { $result['failed'][ $theme_slug ] = sprintf( /* translators: %s: Theme name. */ __( 'Failed to install theme: %s', 'elementor' ), $theme_name ); return $result; } $result['succeed'][ $theme_slug ] = sprintf( /* translators: %s: Theme name. */ __( 'Theme: %s has been successfully installed', 'elementor' ), $theme_name ); $this->installed_theme = $theme_slug; $this->activate_theme( $theme_slug ); } catch ( \Exception $error ) { $result['failed'][ $theme_slug ] = $error->getMessage(); } return $result; } public function import_experiments( array $data ) { if ( empty( $data['site_settings']['experiments'] ) ) { return null; } $experiments_data = $data['site_settings']['experiments']; $experiments_manager = Plugin::$instance->experiments; $current_features = $experiments_manager->get_features(); $this->save_previous_experiments_state( $current_features ); foreach ( $experiments_data as $feature_name => $feature_data ) { if ( ! isset( $current_features[ $feature_name ] ) ) { continue; } $current_feature = $current_features[ $feature_name ]; $current_feature_state = $current_feature['state']; $new_state = $feature_data['state']; if ( $current_feature_state === $new_state ) { continue; } if ( ! in_array( $new_state, [ ExperimentsManager::STATE_DEFAULT, ExperimentsManager::STATE_ACTIVE, ExperimentsManager::STATE_ACTIVE ], true ) ) { continue; } $option_key = $experiments_manager->get_feature_option_key( $feature_name ); if ( 'default' === $new_state ) { delete_option( $option_key ); } else { update_option( $option_key, $new_state ); } $this->imported_experiments[ $feature_name ] = $feature_data; } } private function save_previous_experiments_state( array $current_features ) { $experiments_manager = Plugin::$instance->experiments; foreach ( $current_features as $feature_name => $feature ) { if ( ! $feature['mutable'] ) { continue; } $option_key = $experiments_manager->get_feature_option_key( $feature_name ); $saved_state = get_option( $option_key ); $this->previous_experiments[ $feature_name ] = [ 'name' => $feature_name, 'title' => $feature['title'], 'state' => empty( $saved_state ) ? 'default' : $saved_state, 'default' => $feature['default'], 'release_status' => $feature['release_status'], ]; } } public function get_import_session_metadata(): array { return [ 'previous_kit_id' => $this->previous_kit_id, 'active_kit_id' => $this->active_kit_id, 'imported_kit_id' => $this->imported_kit_id, 'installed_theme' => $this->installed_theme, 'activated_theme' => $this->activated_theme, 'previous_active_theme' => $this->previous_active_theme, 'previous_experiments' => $this->previous_experiments, 'imported_experiments' => $this->imported_experiments, ]; } } modules/import-export-customization/runners/import/plugins.php 0000644 00000004517 15233532517 0021170 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Import; use Elementor\Core\Utils\Collection; use Elementor\Core\Utils\Plugins_Manager; use Elementor\Core\Utils\Str; class Plugins extends Import_Runner_Base { /** * @var Plugins_Manager */ private $plugins_manager; public function __construct( $plugins_manager = null ) { if ( $plugins_manager ) { $this->plugins_manager = $plugins_manager; } else { $this->plugins_manager = new Plugins_Manager(); } } public static function get_name(): string { return 'plugins'; } public function should_import( array $data ) { return ( isset( $data['include'] ) && in_array( 'plugins', $data['include'], true ) && ! empty( $data['manifest']['plugins'] ) && ! empty( $data['selected_plugins'] ) ); } public function import( array $data, array $imported_data ) { $customization = $data['customization']['plugins'] ?? null; if ( $customization ) { $enabled_plugin_keys = Collection::make( $customization )->filter()->keys(); $plugins = Collection::make( $data['selected_plugins'] ) ->filter( function( $plugin_data, $plugin_key ) use ( $enabled_plugin_keys ) { return $enabled_plugin_keys->contains( $plugin_data['plugin'] ); } ) ->values(); } else { $plugins = $data['selected_plugins']; } $plugins_collection = ( new Collection( $plugins ) ) ->map( function ( $item ) { if ( ! Str::ends_with( $item['plugin'], '.php' ) ) { $item['plugin'] .= '.php'; } return $item; } ); $slugs = $plugins_collection ->map( function ( $item ) { return $item['plugin']; } ) ->all(); if ( ! function_exists( 'request_filesystem_credentials' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } $installed = $this->plugins_manager->install( $slugs ); $activated = $this->plugins_manager->activate( $installed['succeeded'] ); $ordered_activated_plugins = $plugins_collection ->filter( function ( $item ) use ( $activated ) { return in_array( $item['plugin'], $activated['succeeded'], true ); } ) ->map( function ( $item ) { return $item['name']; } ) ->all(); $result['plugins'] = $ordered_activated_plugins; return $result; } } modules/import-export-customization/runners/export/elementor-content.php 0000644 00000010010 15233532517 0023141 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Export; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Plugin; class Elementor_Content extends Export_Runner_Base { private $page_on_front_id; public function __construct() { $this->init_page_on_front_data(); } public static function get_name(): string { return 'elementor-content'; } public function should_export( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) ); } public function export( array $data ) { $customization = $data['customization']['content'] ?? null; $selected_custom_post_types = $data['selected_custom_post_types'] ?? null; $excluded_post_types = []; if ( $selected_custom_post_types && ! in_array( 'post', $selected_custom_post_types, true ) ) { $excluded_post_types[] = 'post'; } $elementor_post_types = ImportExportUtils::get_elementor_post_types( $excluded_post_types ); $elementor_post_types = apply_filters( 'elementor/import-export-customization/elementor-content/post-types/customization', $elementor_post_types, $customization ); $files = []; $manifest = []; foreach ( $elementor_post_types as $post_type ) { $export = $this->export_elementor_post_type( $post_type, $customization ); $files = array_merge( $files, $export['files'] ); $manifest[ $post_type ] = $export['manifest_data']; } $manifest_data['content'] = $manifest; return [ 'files' => $files, 'manifest' => [ $manifest_data, ], ]; } private function export_elementor_post_type( $post_type, $customization ) { $manifest_data = []; $files = []; $query_args = [ 'post_type' => $post_type, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE, 'compare' => 'EXISTS', ], [ 'key' => '_elementor_data', 'compare' => 'EXISTS', ], [ 'key' => '_elementor_data', 'compare' => '!=', 'value' => '[]', ], ], ]; $query_args = apply_filters( 'elementor/import-export-customization/export/elementor-content/query-args/customization', $query_args, $post_type, $customization ); $query = new \WP_Query( $query_args ); if ( empty( $query ) ) { return [ 'files' => [], 'manifest_data' => [], ]; } $post_type_taxonomies = $this->get_post_type_taxonomies( $post_type ); foreach ( $query->posts as $post ) { $document = Plugin::$instance->documents->get( $post->ID ); $terms = ! empty( $post_type_taxonomies ) ? $this->get_post_terms( $post->ID, $post_type_taxonomies ) : []; $post_manifest_data = [ 'title' => $post->post_title, 'excerpt' => $post->post_excerpt, 'doc_type' => $document->get_name(), 'thumbnail' => get_the_post_thumbnail_url( $post ), 'url' => get_permalink( $post ), 'terms' => $terms, ]; if ( isset( $post->post_parent ) && $post->post_parent > 0 ) { $post_manifest_data['post_parent'] = $post->post_parent; } if ( $post->ID === $this->page_on_front_id ) { $post_manifest_data['show_on_front'] = true; } $manifest_data[ $post->ID ] = $post_manifest_data; $files[] = [ 'path' => 'content/' . $post_type . '/' . $post->ID, 'data' => $document->get_export_data(), ]; } return [ 'files' => $files, 'manifest_data' => $manifest_data, ]; } private function get_post_type_taxonomies( $post_type ) { return get_object_taxonomies( $post_type ); } private function get_post_terms( $post_id, array $taxonomies ) { $terms = wp_get_object_terms( $post_id, $taxonomies ); $result = []; foreach ( $terms as $term ) { $result[] = [ 'term_id' => $term->term_id, 'taxonomy' => $term->taxonomy, 'slug' => $term->slug, ]; } return $result; } private function init_page_on_front_data() { $show_page_on_front = 'page' === get_option( 'show_on_front' ); if ( $show_page_on_front ) { $this->page_on_front_id = (int) get_option( 'page_on_front' ); } } } modules/import-export-customization/runners/export/wp-content.php 0000644 00000005724 15233532517 0021615 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Export; use Elementor\App\Modules\ImportExportCustomization\Compatibility\Customization; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Core\Utils\ImportExport\WP_Exporter; class Wp_Content extends Export_Runner_Base { public static function get_name(): string { return 'wp-content'; } public function should_export( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) ); } public function export( array $data ) { $customization = $data['customization']['content'] ?? null; $exclude_post_types = []; if ( isset( $customization['customPostTypes'] ) && ! in_array( 'post', $customization['customPostTypes'], true ) ) { $exclude_post_types[] = 'post'; } $post_types = ImportExportUtils::get_builtin_wp_post_types( $exclude_post_types ); $post_types = apply_filters( 'elementor/import-export-customization/wp-content/post-types/customization', $post_types, $data, $customization ); $custom_post_types = isset( $data['selected_custom_post_types'] ) ? $data['selected_custom_post_types'] : []; $files = []; $manifest_data = []; foreach ( $post_types as $post_type ) { $export = $this->export_wp_post_type( $post_type, $customization ); if ( ! empty( $export['file'] ) ) { $files[] = $export['file']; } $manifest_data['wp-content'][ $post_type ] = $export['manifest_data']; } foreach ( $custom_post_types as $post_type ) { $post_type_object = get_post_type_object( $post_type ); $manifest_data['custom-post-type-title'][ $post_type ] = [ 'name' => $post_type_object->name, 'label' => $post_type_object->label, ]; // handled in the previous loop if ( 'post' === $post_type ) { continue; } $export = $this->export_wp_post_type( $post_type, $customization ); if ( ! empty( $export['file'] ) ) { $files[] = $export['file']; } $manifest_data['wp-content'][ $post_type ] = $export['manifest_data']; } return [ 'files' => $files, 'manifest' => [ $manifest_data, ], ]; } private function export_wp_post_type( $post_type, $customization ) { $exporter_args = [ 'content' => $post_type, 'status' => 'publish', 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE, 'compare' => 'NOT EXISTS', ], ], 'include_post_featured_image_as_attachment' => true, ]; if ( 'pages' !== $post_type ) { $exporter_args['limit'] = 20; } $exporter_args = apply_filters( 'elementor/import-export-customization/export/wp-content/query-args/customization', $exporter_args, $post_type, $customization ); $wp_exporter = new WP_Exporter( $exporter_args ); $export_result = $wp_exporter->run(); return [ 'file' => [ 'path' => 'wp-content/' . $post_type . '/' . $post_type . '.xml', 'data' => $export_result['xml'], ], 'manifest_data' => $export_result['posts'], ]; } } modules/import-export-customization/runners/export/taxonomies.php 0000644 00000007034 15233532517 0021701 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Export; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; class Taxonomies extends Export_Runner_Base { public static function get_name(): string { return 'taxonomies'; } public function should_export( array $data ) { return ( isset( $data['include'] ) && in_array( 'content', $data['include'], true ) ); } public function export( array $data ) { $customization = $data['customization']['content'] ?? null; if ( $customization ) { return $this->export_customization( $data, $customization ); } return $this->export_all( $data ); } public function export_customization( array $data, array $customization ) { $result = apply_filters( 'elementor/import-export-customization/export/taxonomies/customization', null, $data, $customization, $this ); if ( is_array( $result ) ) { return $result; } return $this->export_all( $data ); } public function export_all( array $data ) { $selected_custom_post_types = $data['customization']['content']['customPostTypes'] ?? null; $exclude_post_types = []; if ( is_array( $selected_custom_post_types ) && ! in_array( 'post', $selected_custom_post_types, true ) ) { $exclude_post_types[] = 'post'; } $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types( $exclude_post_types ); $post_types = is_array( $selected_custom_post_types ) ? array_merge( $wp_builtin_post_types, $selected_custom_post_types ) : $wp_builtin_post_types; $export = $this->export_taxonomies( $post_types ); $manifest_data['taxonomies'] = $export['manifest']; return [ 'files' => $export['files'], 'manifest' => [ $manifest_data, ], ]; } private function export_taxonomies( array $post_types ) { $files = []; $manifest = []; $taxonomies = get_taxonomies(); foreach ( $taxonomies as $taxonomy ) { $taxonomy_obj = get_taxonomy( $taxonomy ); $taxonomy_post_types = $taxonomy_obj->object_type; $intersected_post_types = array_intersect( $taxonomy_post_types, $post_types ); if ( empty( $intersected_post_types ) ) { continue; } $data = $this->export_terms( $taxonomy ); if ( empty( $data ) ) { continue; } foreach ( $intersected_post_types as $post_type ) { $manifest[ $post_type ][] = [ 'name' => $taxonomy, 'label' => $taxonomy_obj->label, ]; } $files[] = [ 'path' => 'taxonomies/' . $taxonomy, 'data' => $data, ]; } return [ 'files' => $files, 'manifest' => $manifest, ]; } public function export_terms( $taxonomy ) { $terms = get_terms( [ 'taxonomy' => (array) $taxonomy, 'hide_empty' => false, 'get' => 'all', ] ); $ordered_terms = $this->order_terms( $terms ); if ( empty( $ordered_terms ) ) { return []; } $data = []; foreach ( $ordered_terms as $term ) { $data[] = [ 'term_id' => $term->term_id, 'name' => $term->name, 'slug' => $term->slug, 'taxonomy' => $term->taxonomy, 'description' => $term->description, 'parent' => $term->parent, ]; } return $data; } /** * Put terms in order with no child going before its parent. */ private function order_terms( array $terms ) { $ordered_terms = []; while ( $term = array_shift( $terms ) ) { $is_top_level = 0 === $term->parent; $is_parent_exits = isset( $ordered_terms[ $term->parent ] ); if ( $is_top_level || $is_parent_exits ) { $ordered_terms[ $term->term_id ] = $term; } else { $terms[] = $term; } } return $ordered_terms; } } modules/import-export-customization/runners/export/export-runner-base.php 0000644 00000001352 15233532517 0023250 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Export; use Elementor\App\Modules\ImportExportCustomization\Runners\Runner_Interface; abstract class Export_Runner_Base implements Runner_Interface { /** * By the passed data we should decide if we want to run the export function of the runner or not. * * @param array $data * * @return bool */ abstract public function should_export( array $data ); /** * Main function of the runner export process. * * @param array $data Necessary data for the export process. * * @return array{files: array, manifest: array} * The files that should be part of the kit and the relevant manifest data. */ abstract public function export( array $data ); } modules/import-export-customization/runners/export/templates.php 0000644 00000005447 15233532517 0021517 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Export; use Elementor\Core\Base\Document; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; use Elementor\Modules\Library\Documents\Library_Document; class Templates extends Export_Runner_Base { public static function get_name(): string { return 'templates'; } public function should_export( array $data ) { return ( Utils::has_pro() && isset( $data['include'] ) && in_array( 'templates', $data['include'], true ) ); } public function export( array $data ) { $customization = $data['customization']['templates'] ?? null; if ( $customization ) { return $this->export_with_customization( $data, $customization ); } return $this->export_all( $data ); } private function export_with_customization( array $data, array $customization ) { $result = apply_filters( 'elementor/import-export-customization/export/templates/customization', null, $data, $customization, $this ); if ( is_array( $result ) ) { return $result; } return $this->export_all( $data ); } private function export_all( array $data ) { $template_types = array_values( Source_Local::get_template_types() ); return $this->export_templates_by_types( $template_types, $data ); } public function export_templates_by_types( array $template_types, array $data ) { $templates_manifest_data = []; $files = []; if ( ! empty( $template_types ) ) { $query_args = [ 'post_type' => Source_Local::CPT, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => Document::TYPE_META_KEY, 'value' => $template_types, ], ], ]; $templates_query = new \WP_Query( $query_args ); foreach ( $templates_query->posts as $template_post ) { $template_id = $template_post->ID; $template_document = Plugin::$instance->documents->get( $template_id ); $templates_manifest_data[ $template_id ] = $template_document->get_export_summary(); $files[] = [ 'path' => 'templates/' . $template_id, 'data' => $template_document->get_export_data(), ]; } } $manifest_data['templates'] = $templates_manifest_data; $export_data = [ 'files' => $files, 'manifest' => [ $manifest_data, ], ]; /** * Filter the templates export data to allow adding additional data. * * @param array $export_data The export data structure with 'files' and 'manifest' keys. * @param array $data The full export data. * @param array|null $customization The customization settings for templates. */ $customization = $data['customization']['templates'] ?? null; $export_data = apply_filters( 'elementor/import-export-customization/export/templates_data', $export_data, $data, $customization ); return $export_data; } } modules/import-export-customization/runners/export/site-settings.php 0000644 00000012771 15233532517 0022321 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Export; use Elementor\Modules\AtomicWidgets\Module as Atomic_Widgets_Module; use Elementor\Modules\GlobalClasses\Module as Global_Classes_Module; use Elementor\Modules\Variables\Module as Variables_Module; use Elementor\Plugin; class Site_Settings extends Export_Runner_Base { const ALLOWED_SETTINGS = [ 'theme', 'globalColors', 'globalFonts', 'themeStyleSettings', 'generalSettings', 'experiments', 'customCode', 'customIcons', 'customFonts', 'classes', 'variables', ]; public static function get_name(): string { return 'site-settings'; } public function should_export( array $data ) { return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) ); } public function export( array $data ) { $customization = $data['customization']['settings'] ?? null; if ( $customization ) { return $this->export_customization( $data, $customization ); } return $this->export_all( $data ); } private function export_all( $data, $include_theme = true ) { $kit = Plugin::$instance->kits_manager->get_active_kit(); $kit_data = $kit->get_export_data(); $excluded_kit_settings_keys = [ 'site_name', 'site_description', 'site_logo', 'site_favicon', ]; foreach ( $excluded_kit_settings_keys as $setting_key ) { unset( $kit_data['settings'][ $setting_key ] ); } if ( $include_theme ) { $theme_data = $this->export_theme(); if ( $theme_data ) { $kit_data['theme'] = $theme_data; $manifest_data['theme'] = $theme_data; } } $experiments_data = $this->export_experiments(); if ( $experiments_data ) { $kit_data['experiments'] = $experiments_data; $manifest_data['experiments'] = array_keys( $experiments_data ); } $manifest_data['site-settings'] = array_fill_keys( self::ALLOWED_SETTINGS, true ); if ( ! $include_theme ) { $manifest_data['site-settings']['theme'] = false; } if ( $this->is_classes_feature_active() ) { $manifest_data['site-settings']['classesCount'] = $this->get_classes_count(); } else { unset( $manifest_data['site-settings']['classes'] ); } if ( $this->is_variables_feature_active() ) { $manifest_data['site-settings']['variablesCount'] = $this->get_variables_count(); } else { unset( $manifest_data['site-settings']['variables'] ); } return [ 'files' => [ 'path' => 'site-settings', 'data' => $kit_data, ], 'manifest' => [ $manifest_data, ], ]; } public function get_classes_count(): int { $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return 0; } return count( \Elementor\Modules\GlobalClasses\Global_Classes_Order::make( $kit )->set_preview( false )->get_order() ); } public function get_variables_count(): int { $kit = Plugin::$instance->kits_manager->get_active_kit(); $variables_repository = new \Elementor\Modules\Variables\Storage\Variables_Repository( $kit ); $collection = $variables_repository->load(); $count = 0; foreach ( $collection->all() as $variable ) { if ( ! $variable->is_deleted() ) { ++$count; } } return $count; } public function is_classes_feature_active(): bool { return Plugin::$instance->experiments->is_feature_active( Global_Classes_Module::NAME ) && Plugin::$instance->experiments->is_feature_active( Atomic_Widgets_Module::EXPERIMENT_NAME ); } public function is_variables_feature_active(): bool { return Plugin::$instance->experiments->is_feature_active( Variables_Module::EXPERIMENT_NAME ) && Plugin::$instance->experiments->is_feature_active( Atomic_Widgets_Module::EXPERIMENT_NAME ); } private function export_customization( $data, $customization ) { $result = apply_filters( 'elementor/import-export-customization/export/site-settings/customization', null, $data, $customization, $this ); if ( is_array( $result ) ) { return $result; } $export_result = $this->export_all( $data, ! empty( $customization['theme'] ) ); if ( $this->is_classes_feature_active() ) { $include_classes = $customization['classes'] ?? false; $export_result['manifest'][0]['site-settings']['classes'] = (bool) $include_classes; if ( ! $include_classes ) { $export_result['manifest'][0]['site-settings']['classesCount'] = 0; } } if ( $this->is_variables_feature_active() ) { $include_variables = $customization['variables'] ?? false; $export_result['manifest'][0]['site-settings']['variables'] = (bool) $include_variables; if ( ! $include_variables ) { $export_result['manifest'][0]['site-settings']['variablesCount'] = 0; } } return $export_result; } public function export_theme() { $theme = wp_get_theme(); if ( empty( $theme ) || empty( $theme->get( 'ThemeURI' ) ) ) { return null; } $theme_data['name'] = $theme->get( 'Name' ); $theme_data['theme_uri'] = $theme->get( 'ThemeURI' ); $theme_data['version'] = $theme->get( 'Version' ); $theme_data['slug'] = $theme->get_stylesheet(); return $theme_data; } public function export_experiments() { $features = Plugin::$instance->experiments->get_features(); if ( empty( $features ) ) { return null; } $experiments_data = []; foreach ( $features as $feature_name => $feature ) { $experiments_data[ $feature_name ] = [ 'name' => $feature_name, 'title' => $feature['title'], 'state' => $feature['state'], 'default' => $feature['default'], 'release_status' => $feature['release_status'], ]; } return empty( $experiments_data ) ? null : $experiments_data; } } modules/import-export-customization/runners/export/plugins.php 0000644 00000002004 15233532517 0021164 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Export; use Elementor\Core\Utils\Collection; class Plugins extends Export_Runner_Base { public static function get_name(): string { return 'plugins'; } public function should_export( array $data ) { return ( isset( $data['include'] ) && in_array( 'plugins', $data['include'], true ) && is_array( $data['selected_plugins'] ) ); } public function export( array $data ) { $customization = $data['customization']['plugins'] ?? null; if ( $customization ) { $enabled_plugin_keys = Collection::make( $customization )->filter()->keys(); $plugins = Collection::make( $data['selected_plugins'] ) ->filter( function( $plugin_data, $plugin_key ) use ( $enabled_plugin_keys ) { return $enabled_plugin_keys->contains( $plugin_key ); } ) ->all(); } else { $plugins = $data['selected_plugins']; } return [ 'manifest' => [ [ 'plugins' => array_values( $plugins ) ], ], 'files' => [], ]; } } modules/import-export-customization/runners/revert/elementor-content.php 0000644 00000004461 15233532517 0023144 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Revert; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Plugin; class Elementor_Content extends Revert_Runner_Base { private $show_page_on_front; private $page_on_front_id; public function __construct() { $this->init_page_on_front_data(); } public static function get_name(): string { return 'elementor-content'; } public function should_revert( array $data ): bool { return ( isset( $data['runners'] ) && array_key_exists( static::get_name(), $data['runners'] ) ); } public function revert( array $data ) { $elementor_post_types = ImportExportUtils::get_elementor_post_types(); $query_args = [ 'post_type' => $elementor_post_types, 'post_status' => 'any', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE, 'compare' => 'EXISTS', ], [ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ]; $query = new \WP_Query( $query_args ); foreach ( $query->posts as $post ) { $post_type_document = Plugin::$instance->documents->get( $post->ID ); $post_type_document->delete(); // Deleting the post will reset the show_on_front option. We need to set it to false, // so we can set it back to what it was. if ( $post->ID === $this->page_on_front_id ) { $this->show_page_on_front = false; } } $this->restore_page_on_front( $data ); } private function init_page_on_front_data() { $this->show_page_on_front = 'page' === get_option( 'show_on_front' ); if ( $this->show_page_on_front ) { $this->page_on_front_id = (int) get_option( 'page_on_front' ); } } private function restore_page_on_front( $data ) { if ( empty( $data['runners'][ static::get_name() ]['page_on_front'] ) ) { return; } $page_on_front = $data['runners'][ static::get_name() ]['page_on_front']; $document = Plugin::$instance->documents->get( $page_on_front ); if ( ! $document ) { return; } $this->set_page_on_front( $document->get_main_id() ); } private function set_page_on_front( $page_id ) { update_option( 'page_on_front', $page_id ); if ( ! $this->show_page_on_front ) { update_option( 'show_on_front', 'page' ); } } } modules/import-export-customization/runners/revert/revert-runner-base.php 0000644 00000001156 15233532517 0023226 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Revert; use Elementor\App\Modules\ImportExportCustomization\Runners\Runner_Interface; abstract class Revert_Runner_Base implements Runner_Interface { /** * By the passed data we should decide if we want to run the revert function of the runner or not. * * @param array $data * * @return bool */ abstract public function should_revert( array $data ): bool; /** * Main function of the runner revert process. * * @param array $data Necessary data for the revert process. */ abstract public function revert( array $data ); } modules/import-export-customization/runners/revert/wp-content.php 0000644 00000003445 15233532517 0021601 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Revert; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; class Wp_Content extends Revert_Runner_Base { public static function get_name(): string { return 'wp-content'; } public function should_revert( array $data ): bool { return ( isset( $data['runners'] ) && array_key_exists( static::get_name(), $data['runners'] ) ); } public function revert( array $data ) { $builtin_post_types = ImportExportUtils::get_builtin_wp_post_types(); $custom_post_types = $data['runners']['wp-content']['custom_post_types'] ?? []; $post_types = array_merge( $builtin_post_types, $custom_post_types ); $query_args = [ 'post_type' => $post_types, 'post_status' => 'any', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE, 'compare' => 'NOT EXISTS', ], [ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ]; $query = new \WP_Query( $query_args ); foreach ( $query->posts as $post ) { wp_delete_post( $post->ID, true ); } /** * Revert the nav menu terms. * BC: The nav menu in new kits will be imported as part of the taxonomies, but old kits * importing the nav menu terms as part from the wp-content import. */ $this->revert_nav_menus( $data ); } private function revert_nav_menus( array $data ) { $terms = get_terms( [ 'taxonomy' => 'nav_menu', 'hide_empty' => false, 'get' => 'all', 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ] ); foreach ( $terms as $term ) { wp_delete_term( $term->term_id, $term->taxonomy ); } } } modules/import-export-customization/runners/revert/taxonomies.php 0000644 00000001430 15233532517 0021661 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Revert; class Taxonomies extends Revert_Runner_Base { public static function get_name(): string { return 'taxonomies'; } public function should_revert( array $data ): bool { return ( isset( $data['runners'] ) && array_key_exists( static::get_name(), $data['runners'] ) ); } public function revert( array $data ) { $taxonomies = get_taxonomies(); $terms = get_terms( [ 'taxonomy' => $taxonomies, 'hide_empty' => false, 'get' => 'all', 'meta_query' => [ [ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ] ); foreach ( $terms as $term ) { wp_delete_term( $term->term_id, $term->taxonomy ); } } } modules/import-export-customization/runners/revert/templates.php 0000644 00000002316 15233532517 0021475 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Revert; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Core\Base\Document; class Templates extends Revert_Runner_Base { public static function get_name(): string { return 'templates'; } public function should_revert( array $data ): bool { return ( isset( $data['runners'] ) && array_key_exists( static::get_name(), $data['runners'] ) ); } public function revert( array $data ) { $template_types = array_values( Source_Local::get_template_types() ); $query_args = [ 'post_type' => Source_Local::CPT, 'post_status' => 'any', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => Document::TYPE_META_KEY, 'value' => $template_types, ], [ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, 'value' => $data['session_id'], ], ], ]; $templates_query = new \WP_Query( $query_args ); foreach ( $templates_query->posts as $template_post ) { $template_document = Plugin::$instance->documents->get( $template_post->ID ); $template_document->delete(); } do_action( 'elementor/import-export-customization/revert/templates', $data ); } } modules/import-export-customization/runners/revert/site-settings.php 0000644 00000007543 15233532517 0022310 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Revert; use Elementor\Plugin; use Elementor\Core\Experiments\Manager as ExperimentsManager; class Site_Settings extends Revert_Runner_Base { public static function get_name(): string { return 'site-settings'; } public function should_revert( array $data ): bool { return ( isset( $data['runners'] ) && array_key_exists( static::get_name(), $data['runners'] ) ); } public function revert( array $data ) { Plugin::$instance->kits_manager->revert( $data['runners'][ static::get_name() ]['imported_kit_id'], $data['runners'][ static::get_name() ]['active_kit_id'], $data['runners'][ static::get_name() ]['previous_kit_id'] ); $this->revert_theme( $data ); $this->revert_experiments( $data ); } public function get_theme_upgrader(): \Theme_Upgrader { if ( ! class_exists( '\Theme_Upgrader' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } if ( ! class_exists( '\WP_Ajax_Upgrader_Skin' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php'; } return new \Theme_Upgrader( new \WP_Ajax_Upgrader_Skin() ); } protected function revert_theme( $data ) { $installed_theme = $data['runners'][ static::get_name() ]['installed_theme']; $activated_theme = $data['runners'][ static::get_name() ]['activated_theme']; $previous_active_theme = $data['runners'][ static::get_name() ]['previous_active_theme']; if ( empty( $installed_theme ) && empty( $activated_theme ) ) { // no need to remove a theme as it was used before import return; } if ( ! empty( $activated_theme ) ) { $previous_theme = wp_get_theme( $previous_active_theme['slug'] ); // no need to remove imported theme as it existed before import $this->activate_previous_theme( $previous_active_theme ); return; } if ( ! empty( $installed_theme ) ) { $this->activate_previous_theme( $previous_active_theme ); $this->delete_theme( $installed_theme ); } } protected function should_delete_theme( $theme_slug ): bool { $current_theme = wp_get_theme(); return $theme_slug !== $current_theme->get_stylesheet() && wp_get_theme( $theme_slug )->exists(); } protected function delete_theme( $theme_slug ): bool { if ( ! function_exists( 'delete_theme' ) ) { require_once ABSPATH . 'wp-admin/includes/theme.php'; } return delete_theme( $theme_slug ); } protected function activate_previous_theme( $previous_active_theme ) { if ( ! $previous_active_theme ) { return; } $theme = wp_get_theme( $previous_active_theme['slug'] ); if ( $theme->exists() ) { switch_theme( $theme->get_stylesheet() ); return; } $download_url = "https://downloads.wordpress.org/theme/{$previous_active_theme['slug']}.{$previous_active_theme['version']}.zip"; $install = $this->get_theme_upgrader()->install( $download_url ); if ( ! $install || is_wp_error( $install ) ) { return; } switch_theme( $previous_active_theme['slug'] ); } protected function revert_experiments( array $data ) { $runner_data = $data['runners'][ static::get_name() ]; $previous_experiments = $runner_data['previous_experiments'] ?? []; if ( empty( $previous_experiments ) ) { return; } $experiments_manager = Plugin::$instance->experiments; $current_features = $experiments_manager->get_features(); foreach ( $previous_experiments as $feature_name => $feature_data ) { if ( ! isset( $current_features[ $feature_name ] ) ) { continue; } if ( ! array_key_exists( $feature_name, $previous_experiments ) ) { continue; } $option_key = $experiments_manager->get_feature_option_key( $feature_name ); $previous_state = $feature_data['state']; if ( ExperimentsManager::STATE_DEFAULT === $previous_state ) { delete_option( $option_key ); } else { update_option( $option_key, $previous_state ); } } } } modules/import-export-customization/runners/revert/plugins.php 0000644 00000000471 15233532517 0021160 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExportCustomization\Runners\Revert; class Plugins extends Revert_Runner_Base { public static function get_name(): string { return 'plugins'; } public function should_revert( array $data ): bool { return false; } public function revert( array $data ) {} } modules/site-builder/module.php 0000644 00000007170 15233532517 0012615 0 ustar 00 <?php namespace Elementor\App\Modules\SiteBuilder; use Elementor\App\Modules\SiteBuilder\Connect\App; use Elementor\App\Modules\SiteBuilder\Rest\Rest_Api; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { public function get_name() { return 'site-builder'; } public function __construct() { parent::__construct(); $this->register_experiment(); if ( ! $this->is_experiment_active() ) { return; } add_action( 'elementor/init', [ $this, 'on_elementor_init' ], 12 ); add_action( 'elementor/connect/apps/register', function ( $connect_module ) { $connect_module->register_app( 'site-builder', App::get_class_name() ); } ); add_action( 'rest_api_init', function () { ( new Rest_Api() )->register_routes(); } ); } private function register_experiment() { Plugin::instance()->experiments->add_feature([ 'name' => 'site-builder', 'title' => esc_html__( 'Site Builder', 'elementor' ), 'description' => esc_html__( 'Enable Site Builder.', 'elementor' ), 'release_status' => Plugin::$instance->experiments::RELEASE_STATUS_DEV, 'hidden' => true, ]); } private function is_experiment_active(): bool { return Plugin::$instance->experiments->is_feature_active( 'site-builder' ); } public function on_elementor_init() { if ( ! Plugin::instance()->app->is_current() ) { return; } $settings = [ 'iframeUrl' => $this->get_iframe_url(), 'isAdmin' => current_user_can( 'manage_options' ), 'exitTo' => admin_url( 'admin.php?page=elementor' ), 'elementorAiCurrentContext' => $this->get_elementor_ai_current_context(), ]; $connect_auth = $this->get_connect_auth(); if ( $connect_auth ) { $settings['connectAuth'] = $connect_auth; } Plugin::$instance->app->set_settings( 'site-builder', $settings ); } private function get_elementor_ai_current_context(): array { $choices = get_option( 'elementor_onboarding_choices', [] ); $site_about = $choices['site_about'] ?? []; return [ 'siteTitle' => (string) get_bloginfo( 'name' ), 'siteAbout' => $site_about, ]; } private function get_iframe_url(): string { if ( defined( 'ELEMENTOR_SITE_BUILDER_IFRAME_URL' ) ) { return ELEMENTOR_SITE_BUILDER_IFRAME_URL; } return 'https://planner.elementor.com/chat.html'; } public function get_config(): ?array { if ( ! $this->is_experiment_active() ) { return null; } $connect_auth = $this->get_connect_auth(); if ( ! $connect_auth ) { return null; } return [ 'siteKey' => $connect_auth['siteKey'], ]; } private function get_connect_auth(): ?array { if ( ! Plugin::instance()->common ) { return null; } $connect = Plugin::instance()->common->get_component( 'connect' ); if ( ! $connect ) { return null; } $app = $connect->get_app( 'library' ); if ( ! $app || ! $app->is_connected() ) { return null; } $access_token = $app->get( 'access_token' ); $client_id = $app->get( 'client_id' ); $home_url = trailingslashit( home_url() ); $site_key = $app->get_site_key(); $access_token_secret = $app->get( 'access_token_secret' ); $connect_data = [ 'access-token' => $access_token, 'app' => 'library', 'client-id' => $client_id, 'home-url' => $home_url, 'site-key' => $site_key, ]; ksort( $connect_data ); $signature = hash_hmac( 'sha256', wp_json_encode( $connect_data, JSON_NUMERIC_CHECK ), $access_token_secret ); return [ 'signature' => $signature, 'accessToken' => $access_token, 'clientId' => $client_id, 'homeUrl' => $home_url, 'siteKey' => $site_key, ]; } } modules/site-builder/connect/app.php 0000644 00000001526 15233532517 0013540 0 ustar 00 <?php namespace Elementor\App\Modules\SiteBuilder\Connect; use Elementor\Core\Common\Modules\Connect\Apps\Library; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class App extends Library { const API_URL = 'https://my.elementor.com/api/v2/builder/'; public function get_title() { return esc_html__( 'Site Builder', 'elementor' ); } protected function get_slug() { return 'site-builder'; } protected function get_api_url() { return static::API_URL; } public function get_home_screen() { return $this->http_request( 'GET', 'website-planner/session/home-screen', [ 'headers' => [ 'x-host-site-title' => (string) get_bloginfo( 'name' ), 'x-host-site-context' => (string) get_bloginfo( 'description' ), ], ], [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); } } modules/site-builder/rest/rest-api.php 0000644 00000005607 15233532517 0014034 0 ustar 00 <?php namespace Elementor\App\Modules\SiteBuilder\Rest; use Elementor\Plugin; use WP_Error; use WP_REST_Response; use WP_REST_Server; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Rest_Api { const API_NAMESPACE = 'elementor/v1'; const API_BASE = 'site-builder'; const SNAPSHOT_MAX_KEYS = 20; public function register_routes(): void { register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/home-screen', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_home_screen' ], 'permission_callback' => fn() => current_user_can( 'manage_options' ), ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/snapshot', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_snapshot' ], 'permission_callback' => fn() => current_user_can( 'manage_options' ), ], [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [ $this, 'update_snapshot' ], 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'value' => [ 'required' => true, 'type' => 'object', 'validate_callback' => fn( $value ) => is_array( $value ), 'sanitize_callback' => fn( $value ) => is_array( $value ) ? $value : [], ], ], ], ] ); } public function get_home_screen() { $app = $this->get_connect_app(); if ( ! $app || ! $app->is_connected() ) { return new WP_Error( 'site_builder_unavailable', 'Site builder is not connected.', [ 'status' => 503 ] ); } $data = $app->get_home_screen(); if ( is_wp_error( $data ) ) { return $data; } return new WP_REST_Response( $data, 200 ); } public function get_snapshot() { $snapshot = get_option( 'elementor_site_builder_snapshot', [] ); return new WP_REST_Response( [ 'success' => true, 'data' => [ 'value' => $snapshot ], ], 200 ); } public function update_snapshot( $request ) { $value = $request->get_param( 'value' ); $sanitized = is_array( $value ) ? $value : []; if ( count( $sanitized ) > self::SNAPSHOT_MAX_KEYS ) { return new WP_REST_Response( [ 'success' => false, 'data' => [ 'message' => 'Snapshot exceeds maximum allowed size.' ], ], 400 ); } $success = update_option( 'elementor_site_builder_snapshot', $sanitized, false ); if ( $success || get_option( 'elementor_site_builder_snapshot' ) === $sanitized ) { return new WP_REST_Response( [ 'success' => true, 'data' => [ 'message' => 'Snapshot updated successfully.' ], ], 200 ); } return new WP_REST_Response( [ 'success' => false, 'data' => [ 'message' => 'Failed to update snapshot.' ], ], 500 ); } protected function get_connect_app() { if ( ! Plugin::$instance->common ) { return null; } $connect = Plugin::$instance->common->get_component( 'connect' ); if ( ! $connect ) { return null; } return $connect->get_app( 'site-builder' ); } } modules/site-builder/services/design-system-service.php 0000644 00000004200 15233532517 0017373 0 ustar 00 <?php namespace Elementor\App\Modules\SiteBuilder\Services; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Modules\Variables\Storage\Constants as Variables_Constants; use Elementor\Modules\Variables\Storage\Variables_Collection; use Elementor\Modules\Variables\Storage\Variables_Repository; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Design_System_Service { private ?Kit $kit; public function __construct( ?Kit $kit = null ) { $this->kit = $kit; } public function deploy_global_classes( array $global_classes ): array { $items = isset( $global_classes['items'] ) && is_array( $global_classes['items'] ) ? $global_classes['items'] : []; $order = isset( $global_classes['order'] ) && is_array( $global_classes['order'] ) ? array_values( $global_classes['order'] ) : []; Global_Classes_Repository::make( $this->get_kit() ) ->put( $items, $order ); return [ 'items' => count( $items ), 'order' => count( $order ), ]; } public function deploy_global_variables( array $global_variables ): array { $kit = $this->get_kit(); if ( ! $kit ) { throw new \Exception( 'No active kit found' ); } $record = [ 'data' => isset( $global_variables['data'] ) && is_array( $global_variables['data'] ) ? $global_variables['data'] : [], 'watermark' => (int) ( $global_variables['watermark'] ?? 0 ), 'version' => (int) ( $global_variables['version'] ?? Variables_Constants::FORMAT_VERSION_V1 ), ]; $collection = Variables_Collection::hydrate( $record ); $watermark = $this->repository_for( $kit )->save( $collection ); if ( false === $watermark ) { throw new \Exception( 'Failed to update global variables' ); } return [ 'data' => count( $record['data'] ), 'watermark' => $watermark, 'version' => $record['version'], ]; } private function get_kit(): ?Kit { if ( ! $this->kit ) { $this->kit = Plugin::$instance->kits_manager->get_active_kit(); } return $this->kit; } private function repository_for( Kit $kit ): Variables_Repository { return new Variables_Repository( $kit ); } } app.php 0000644 00000022643 15233532517 0006052 0 ustar 00 <?php namespace Elementor\App; use Elementor\App\AdminMenuItems\Theme_Builder_Menu_Item; use Elementor\Core\Admin\Menu\Admin_Menu_Manager; use Elementor\Core\Experiments\Manager as ExperimentsManager; use Elementor\Modules\WebCli\Module as WebCLIModule; use Elementor\Core\Base\App as BaseApp; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\User; use Elementor\Utils; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Core\Utils\Assets_Config_Provider; use Elementor\Core\Utils\Collection; use Elementor\Core\Utils\Assets_Translation_Loader; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\App\AdminMenuItems\Editor_One_Theme_Builder_Menu; use Elementor\App\Modules\ImportExport\Module as ImportExportModule; use Elementor\App\Modules\KitLibrary\Module as KitLibraryModule; use Elementor\App\Modules\ImportExportCustomization\Module as ImportExportCustomizationModule; use Elementor\App\Modules\SiteEditor\Module as SiteEditorModule; use Elementor\App\Modules\Onboarding\Module as OnboardingModule; use Elementor\App\Modules\SiteBuilder\Module as SiteBuilderModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class App extends BaseApp { const PAGE_ID = 'elementor-app'; /** * Get module name. * * Retrieve the module name. * * @since 3.0.0 * @access public * * @return string Module name. */ public function get_name() { return 'app'; } public function get_base_url() { return admin_url( 'admin.php?page=' . self::PAGE_ID . '&ver=' . ELEMENTOR_VERSION ); } private function register_editor_one_menu( Menu_Data_Provider $menu_data_provider ) { $menu_data_provider->register_menu( new Editor_One_Theme_Builder_Menu() ); } public function fix_submenu( $menu ) { global $submenu; if ( is_multisite() && is_network_admin() ) { return $menu; } // Non admin role / custom wp menu. if ( empty( $submenu[ Source_Local::ADMIN_MENU_SLUG ] ) ) { return $menu; } // Hack to add a link to sub menu. foreach ( $submenu[ Source_Local::ADMIN_MENU_SLUG ] as &$item ) { if ( self::PAGE_ID === $item[2] ) { $item[2] = $this->get_settings( 'menu_url' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $item[4] = 'elementor-app-link'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } } return $menu; } public function is_current() { return ( ! empty( $_GET['page'] ) && self::PAGE_ID === $_GET['page'] ); } public function admin_init() { do_action( 'elementor/app/init', $this ); // Add the introduction and user settings only when it is needed (when loading the app and not in the editor or admin pages) $this->set_settings( 'user', [ 'introduction' => (object) User::get_introduction_meta(), 'is_administrator' => current_user_can( 'manage_options' ), 'restrictions' => Plugin::$instance->role_manager->get_user_restrictions_array(), ] ); $this->enqueue_assets(); remove_action( 'wp_print_styles', 'print_emoji_styles' ); // Setup default heartbeat options // TODO: Enable heartbeat. add_filter( 'heartbeat_settings', function( $settings ) { $settings['interval'] = 15; return $settings; } ); $this->render(); die; } protected function get_init_settings() { $referer = wp_get_referer(); return [ 'menu_url' => $this->get_base_url() . '#site-editor/promotion', 'assets_url' => ELEMENTOR_ASSETS_URL, 'pages_url' => admin_url( 'edit.php?post_type=page' ), 'return_url' => $referer ? $referer : admin_url(), 'hasPro' => Utils::has_pro(), 'admin_url' => admin_url(), 'login_url' => wp_login_url(), 'base_url' => $this->get_base_url(), 'home_url' => home_url(), 'promotion' => Filtered_Promotions_Manager::get_filtered_promotion_data( [ 'upgrade_url' => 'https://go.elementor.com/go-pro-theme-builder/' ], 'elementor/site-editor/promotion', 'upgrade_url' ), ]; } private function render() { require __DIR__ . '/view.php'; } /** * Get Elementor editor theme color preference. * * Retrieve the user theme color preference as defined by editor preferences manager. * * @since 3.0.0 * @access private * * @return string Preferred editor theme. */ private function get_elementor_ui_theme_preference() { $editor_preferences = SettingsManager::get_settings_managers( 'editorPreferences' ); return $editor_preferences->get_model()->get_settings( 'ui_theme' ); } /** * Enqueue dark theme detection script. * * Enqueues an inline script that detects user-agent settings for dark mode and adds a complimentary class to the body tag. * * @since 3.0.0 * @access private */ private function enqueue_dark_theme_detection_script() { if ( 'auto' === $this->get_elementor_ui_theme_preference() ) { wp_add_inline_script( 'elementor-app', 'if ( window.matchMedia && window.matchMedia( `(prefers-color-scheme: dark)` ).matches ) { document.body.classList.add( `eps-theme-dark` ); }' ); } } private function register_packages() { $assets_config_provider = ( new Assets_Config_Provider() ) ->set_path_resolver( function ( $name ) { return ELEMENTOR_ASSETS_PATH . "js/packages/{$name}/{$name}.asset.php"; } ); Collection::make( [ 'ui', 'icons', 'store', 'query', 'utils', 'events', 'onboarding', 'site-builder' ] ) ->each( function( $package ) use ( $assets_config_provider ) { $suffix = Utils::is_script_debug() ? '' : '.min'; $config = $assets_config_provider->load( $package )->get( $package ); if ( ! $config ) { return; } wp_register_script( $config['handle'], ELEMENTOR_ASSETS_URL . "js/packages/{$package}/{$package}{$suffix}.js", $config['deps'], ELEMENTOR_VERSION, true ); } ); } private function enqueue_assets() { Plugin::$instance->init_common(); $this->register_packages(); /** @var WebCLIModule $web_cli */ $web_cli = Plugin::$instance->modules_manager->get_modules( 'web-cli' ); $web_cli->register_scripts(); Plugin::$instance->common->register_scripts(); wp_register_style( 'select2', $this->get_css_assets_url( 'e-select2', 'assets/lib/e-select2/css/' ), [], '4.0.6-rc.1' ); Plugin::$instance->common->register_styles(); wp_register_style( 'select2', ELEMENTOR_ASSETS_URL . 'lib/e-select2/css/e-select2.css', [], '4.0.6-rc.1' ); wp_enqueue_style( 'elementor-app', $this->get_css_assets_url( 'app', null, 'default', true ), [ 'select2', 'elementor-icons', 'elementor-common', 'select2', ], ELEMENTOR_VERSION ); wp_enqueue_script( 'elementor-app-packages', $this->get_js_assets_url( 'app-packages' ), [ 'wp-i18n', 'react', ], ELEMENTOR_VERSION, true ); wp_register_script( 'select2', $this->get_js_assets_url( 'e-select2.full', 'assets/lib/e-select2/js/' ), [ 'jquery', ], '4.0.6-rc.1', true ); wp_enqueue_script( 'elementor-app', $this->get_js_assets_url( 'app' ), [ 'wp-url', 'wp-i18n', 'elementor-v2-ui', 'elementor-v2-icons', 'elementor-v2-onboarding', 'elementor-v2-site-builder', 'react', 'react-dom', 'select2', ], ELEMENTOR_VERSION, true ); $this->enqueue_dark_theme_detection_script(); Assets_Translation_Loader::for_handles( [ 'elementor-app-packages', 'elementor-app' ], 'elementor' ); $this->print_config(); } public function enqueue_app_loader() { wp_enqueue_script( 'elementor-app-loader', $this->get_js_assets_url( 'app-loader' ), [ 'elementor-common', ], ELEMENTOR_VERSION, true ); $this->print_config( 'elementor-app-loader' ); } private function register_import_export_customization_experiment() { Plugin::$instance->experiments->add_feature( [ 'name' => 'import-export-customization', 'title' => esc_html__( 'Import/Export Customization', 'elementor' ), 'description' => esc_html__( 'Enhanced import/export for website templates. Selectively include site content, templates, and settings with advanced granular control.', 'elementor' ), 'release_status' => ExperimentsManager::RELEASE_STATUS_BETA, 'default' => ExperimentsManager::STATE_ACTIVE, 'hidden' => true, 'mutable' => false, ] ); } public function __construct() { $this->register_import_export_customization_experiment(); $this->add_component( 'site-editor', new SiteEditorModule() ); $this->add_component( 'site-builder', new SiteBuilderModule() ); if ( current_user_can( 'manage_options' ) || Utils::is_wp_cli() ) { $this->add_component( 'import-export', new ImportExportModule() ); if ( Plugin::$instance->experiments->is_feature_active( 'import-export-customization' ) ) { $this->add_component( 'import-export-customization', new ImportExportCustomizationModule() ); } // Kit library is depended on import-export $this->add_component( 'kit-library', new KitLibraryModule() ); } $this->add_component( 'onboarding', new OnboardingModule() ); add_action( 'elementor/editor-one/menu/register', function ( Menu_Data_Provider $menu_data_provider ) { $this->register_editor_one_menu( $menu_data_provider ); } ); // Happens after WP plugin page validation. add_filter( 'add_menu_classes', [ $this, 'fix_submenu' ] ); if ( $this->is_current() ) { add_action( 'admin_init', [ $this, 'admin_init' ], 0 ); } else { add_action( 'elementor/common/after_register_scripts', [ $this, 'enqueue_app_loader' ] ); } } } admin-menu-items/theme-builder-menu-item.php 0000644 00000001072 15233532517 0015060 0 ustar 00 <?php namespace Elementor\App\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Theme_Builder_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__( 'Theme Builder', 'elementor' ); } public function get_capability() { return 'manage_options'; } } admin-menu-items/editor-one-theme-builder-menu.php 0000644 00000002530 15233532517 0016167 0 ustar 00 <?php namespace Elementor\App\AdminMenuItems; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_With_Custom_Url_Interface; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\App\App; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Theme_Builder_Menu implements Menu_Item_Interface, Admin_Menu_Item_With_Page, Menu_Item_With_Custom_Url_Interface { public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'Theme Builder', 'elementor' ); } public function get_position(): int { return 15; } public function get_slug(): string { return App::PAGE_ID; } public function get_menu_url(): string { return Menu_Data_Provider::instance()->get_theme_builder_url(); } public function get_group_id(): string { return Menu_Config::TEMPLATES_GROUP_ID; } public function get_page_title(): string { return esc_html__( 'Theme Builder', 'elementor' ); } public function render(): void { } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.32 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings