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\Utils as ElementorUtils;
use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
* 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.
* Assigning the import process to a property, so we can use the process from outside the class.
* Assigning the revert process to a property, so we can use the process from outside the class.
public function get_name() {
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 $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' ),
'label' => esc_html__( 'Website Templates', 'elementor' ),
'callback' => function() {
$this->render_import_export_tab_content();
* Render the import/export tab content.
private function render_import_export_tab_content() {
'title' => esc_html__( 'Export this website', 'elementor' ),
'url' => Plugin::$instance->app->get_base_url() . '#/export',
'text' => esc_html__( 'Export', 'elementor' ),
'description' => esc_html__( 'You can download this website as a .zip file, or upload it to the library.', 'elementor' ),
'title' => esc_html__( 'Import website templates', 'elementor' ),
'url' => Plugin::$instance->app->get_base_url() . '#/import',
'text' => esc_html__( 'Import', 'elementor' ),
'description' => esc_html__( 'You can import design and settings from a .zip file or choose from the library.', 'elementor' ),
if ( Plugin::$instance->experiments->is_feature_active( 'cloud-library' ) ) {
$content_data['import']['button_secondary'] = [
'url' => Plugin::$instance->app->get_base_url() . '#/kit-library/cloud',
'text' => esc_html__( 'Import from library', 'elementor' ),
$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 ) ) {
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'] ),
! empty( $penultimate_imported_kit['kit_title'] ) ? $penultimate_imported_kit['kit_title'] : esc_html__( 'imported kit', 'elementor' ),
gmdate( $date_format, $penultimate_imported_kit['start_timestamp'] )
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'] ),
<div class="tab-import-export-kit__content">
<p class="tab-import-export-kit__info">
'%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' ),
<div class="tab-import-export-kit__wrapper">
<?php foreach ( $content_data as $data ) {
$this->print_item_content( $data );
if ( $should_show_revert_section ) {
'href' => $this->get_revert_href(),
'id' => 'elementor-import-export__revert_kit',
<div class="tab-import-export-kit__revert">
<?php echo esc_html__( 'Remove the most recent Website Template', 'elementor' ); ?>
<p class="tab-import-export-kit__info">
<?php ElementorUtils::print_unescaped_internal_string( $revert_text ); ?>
<?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' ); ?>
private function print_item_content( $data ) {
$is_cloud_kits_feature_active = Plugin::$instance->experiments->is_feature_active( 'cloud-library' );
if ( $is_cloud_kits_feature_active ) { ?>
<div class="tab-import-export-kit__container">
<div class="tab-import-export-kit__box">
<h2><?php ElementorUtils::print_unescaped_internal_string( $data['title'] ); ?></h2>
<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>
<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 href="<?php ElementorUtils::print_unescaped_internal_string( $data['button']['url'] ); ?>" class="elementor-button e-primary">
<?php ElementorUtils::print_unescaped_internal_string( $data['button']['text'] ); ?>
<div class="tab-import-export-kit__container">
<div class="tab-import-export-kit__box">
<h2><?php ElementorUtils::print_unescaped_internal_string( $data['title'] ); ?></h2>
<a href="<?php ElementorUtils::print_unescaped_internal_string( $data['button']['url'] ); ?>" class="elementor-button e-primary">
<?php ElementorUtils::print_unescaped_internal_string( $data['button']['text'] ); ?>
<p><?php ElementorUtils::print_unescaped_internal_string( $data['description'] ); ?></p>
<a href="<?php ElementorUtils::print_unescaped_internal_string( $data['link']['url'] ); ?>" target="_blank"><?php ElementorUtils::print_unescaped_internal_string( $data['link']['text'] ); ?></a>
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
private function maybe_add_referrer_param( string $href ): string {
$param_name = 'referrer_kit';
if ( empty( $_GET[ $param_name ] ) ) {
return add_query_arg( $param_name, sanitize_key( $_GET[ $param_name ] ), $href );
* Render the last kit thumbnail if exists
* @param $last_imported_kit
private function render_last_kit_thumbnail( $last_imported_kit ) {
if ( empty( $last_imported_kit['kit_thumbnail'] ) ) {
<div class="tab-import-export-kit__kit-item-row">
<article class="tab-import-export-kit__kit-item">
<?php echo esc_html( $last_imported_kit['kit_title'] ); ?>
src="<?php echo esc_url( $last_imported_kit['kit_thumbnail'] ); ?>"
alt="<?php echo esc_attr( $last_imported_kit['kit_title'] ); ?>"
* 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'.
public function upload_kit( $file, $referrer, $kit_id = null ) {
$this->ensure_writing_permissions();
$this->import = new Import( $file, [
'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.
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 );
'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)
public function import_kit_by_runner( string $session_id, string $runner_name ): array {
$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();
* 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.)
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 );
* 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' ) );
* Register appropriate actions.
private function register_actions() {
add_action( 'admin_init', function() {
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' ] );
$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 );
add_filter( 'elementor/import/kit/result', function( $result ) {
if ( ! empty( $result['file_url'] ) ) {
'file_name' => $this->get_remote_kit_zip( $result['file_url'] ),
'referrer' => static::REFERRER_KIT_LIBRARY,
'file_url' => $result['file_url'],