first commit
This commit is contained in:
206
wp-content/plugins/elementor/CONTRIBUTING.md
Normal file
206
wp-content/plugins/elementor/CONTRIBUTING.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# Contributing
|
||||
|
||||
A guide on how to get started contributing code to the Elementor plugin.
|
||||
|
||||
Before diving into this repository, make sure you have a basic understanding of Elementor and its architecture.
|
||||
|
||||
## Architecture
|
||||
If you are interested in learning more about the architecture of Elementor, please refer to the documentation in the `docs/` directory.
|
||||
|
||||
## Repository structure
|
||||
|
||||
The repository is structured as follows:
|
||||
|
||||
```
|
||||
@/elementor
|
||||
├── app/
|
||||
│ ├── admin-menu-items/
|
||||
│ ├── assets/
|
||||
│ │ ├── js/
|
||||
│ │ └── styles/
|
||||
│ ├── modules/
|
||||
│ │ ├── import-export/
|
||||
│ │ ├── kit-library/
|
||||
│ │ ├── onboarding/
|
||||
│ │ └── site-editor/
|
||||
│ ├── app.php
|
||||
│ └── view.php
|
||||
├── core/
|
||||
│ ├── admin/
|
||||
│ ├── base/
|
||||
│ ├── editor/
|
||||
│ ├── frontend/
|
||||
│ ├── settings/
|
||||
│ ├── utils/
|
||||
│ └── ...
|
||||
├── includes/
|
||||
│ ├── controls/
|
||||
│ ├── widgets/
|
||||
│ ├── managers/
|
||||
│ └── ...
|
||||
├── modules/ (Feature modules)
|
||||
│ ├── ai/
|
||||
│ ├── atomic-widgets/
|
||||
│ ├── floating-buttons/
|
||||
│ ├── global-classes/
|
||||
│ ├── nested-elements/
|
||||
│ └── ...
|
||||
├── assets/ (Static assets)
|
||||
│ ├── css/
|
||||
│ ├── js/
|
||||
│ ├── images/
|
||||
│ └── lib/
|
||||
├── packages/ (V4 packages)
|
||||
│ ├── packages/
|
||||
│ ├── tests/
|
||||
│ └── package.json
|
||||
├── tests/ (Test suites)
|
||||
│ ├── playwright/
|
||||
│ ├── phpunit/
|
||||
│ ├── jest/
|
||||
│ └── qunit/
|
||||
├── docs/ (Documentation)
|
||||
├── elementor.php (Main plugin file)
|
||||
├── package.json
|
||||
└── composer.json
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
|
||||
To get started with development:
|
||||
|
||||
1. Clone the repository
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
npm run prepare-environment
|
||||
```
|
||||
|
||||
3. Start development:
|
||||
```bash
|
||||
npm run watch
|
||||
```
|
||||
|
||||
This will start the development environment with file watching enabled.
|
||||
|
||||
## Test, Lint & Build
|
||||
|
||||
### Testing
|
||||
|
||||
To run PHP tests:
|
||||
```bash
|
||||
npm run test:php
|
||||
```
|
||||
|
||||
To run JavaScript tests:
|
||||
```bash
|
||||
npm run test:jest
|
||||
```
|
||||
|
||||
To run Playwright end-to-end tests:
|
||||
```bash
|
||||
npm run start-local-server
|
||||
npm run test:playwright
|
||||
or
|
||||
npm run test:playwright:*
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
You can run the linter by executing:
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
This command uses ESLint for JavaScript/TypeScript files and includes package linting.
|
||||
|
||||
### Building
|
||||
|
||||
To build the project for production:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
For development builds:
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
To build packages:
|
||||
```bash
|
||||
npm run build:packages
|
||||
```
|
||||
|
||||
## Development Commands
|
||||
|
||||
- `npm run start` - Full build and setup (dev mode)
|
||||
- `npm run watch` - Start development with file watching
|
||||
- `npm run scripts` - Build JavaScript assets
|
||||
- `npm run scripts:watch` - Watch JavaScript files
|
||||
- `npm run styles` - Build CSS assets
|
||||
- `npm run styles:watch` - Watch CSS files
|
||||
- `npm run build:packages` - Build frontend packages
|
||||
- `npm run build:tools` - Build development tools
|
||||
|
||||
## Testing Environment Setup
|
||||
|
||||
To set up the testing environment:
|
||||
```bash
|
||||
npm run setup:testing
|
||||
```
|
||||
|
||||
To restart the testing environment:
|
||||
```bash
|
||||
npm run restart:testing
|
||||
```
|
||||
|
||||
## Commit message conventions
|
||||
|
||||
This repository uses [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), so please make sure to follow this convention to keep consistency in the repository.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Maintainers merge pull requests by squashing all commits and editing the commit message if necessary using the GitHub user interface.
|
||||
|
||||
Ensure you choose an appropriate commit message, and exercise caution when dealing with changes that may disrupt existing functionality.
|
||||
|
||||
Additionally, remember to include tests for your modifications to ensure comprehensive coverage and maintain code quality.
|
||||
|
||||
## Working with Packages
|
||||
|
||||
The `packages/` directory contains frontend packages that can be developed separately:
|
||||
|
||||
1. Navigate to the packages directory:
|
||||
```bash
|
||||
cd packages
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
npm ci
|
||||
```
|
||||
|
||||
3. Start development:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
When working on the main plugin with packages, use:
|
||||
```bash
|
||||
npm run watch
|
||||
```
|
||||
|
||||
This will automatically handle package building and watching.
|
||||
|
||||
## Code Quality
|
||||
|
||||
- Follow WordPress coding standards
|
||||
- Use meaningful commit messages
|
||||
- Write tests for new features
|
||||
- Update documentation when needed
|
||||
- Ensure backward compatibility when possible
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Check the `docs/` directory for detailed documentation
|
||||
- Review existing code for patterns and conventions
|
||||
- Ask questions in pull requests for clarification
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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 {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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';
|
||||
}
|
||||
}
|
||||
345
wp-content/plugins/elementor/app/app.php
Normal file
345
wp-content/plugins/elementor/app/app.php
Normal file
@@ -0,0 +1,345 @@
|
||||
<?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;
|
||||
|
||||
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_admin_menu( Admin_Menu_Manager $admin_menu ) {
|
||||
if ( ! $this->is_editor_one_active() ) {
|
||||
$admin_menu->register( static::PAGE_ID, new Theme_Builder_Menu_Item() );
|
||||
}
|
||||
}
|
||||
|
||||
private function register_editor_one_menu( Menu_Data_Provider $menu_data_provider ) {
|
||||
$menu_data_provider->register_menu( new Editor_One_Theme_Builder_Menu() );
|
||||
}
|
||||
|
||||
private function is_editor_one_active(): bool {
|
||||
return (bool) Plugin::instance()->modules_manager->get_modules( 'editor-one' );
|
||||
}
|
||||
|
||||
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' ] )
|
||||
->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',
|
||||
'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() );
|
||||
|
||||
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/admin/menu/register', function ( Admin_Menu_Manager $admin_menu ) {
|
||||
$this->register_admin_menu( $admin_menu );
|
||||
}, Source_Local::ADMIN_MENU_PRIORITY + 10 );
|
||||
|
||||
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' ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\App\Modules\ImportExportCustomization\Data;
|
||||
|
||||
use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Export;
|
||||
use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Upload;
|
||||
use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Import;
|
||||
use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Import_Runner;
|
||||
use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Process_Media;
|
||||
use Elementor\App\Modules\ImportExportCustomization\Data\Routes\Revert;
|
||||
|
||||
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 Upload() )->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 Process_Media() )->register_route( self::API_NAMESPACE, self::API_BASE );
|
||||
( new Revert() )->register_route( self::API_NAMESPACE, self::API_BASE );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?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' => [],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?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,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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,
|
||||
],
|
||||
'settings' => [
|
||||
'type' => 'object',
|
||||
'description' => 'Import settings',
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?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();
|
||||
|
||||
$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',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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 [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?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,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,792 @@
|
||||
<?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\System_Info\Reporters\Server;
|
||||
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 ) {
|
||||
$is_editor_one_enabled = Plugin::$instance->experiments->is_feature_active( 'e_editor_one' );
|
||||
$container_classes = 'tab-import-export-kit__container';
|
||||
if ( $is_editor_one_enabled ) {
|
||||
$container_classes .= ' 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' ),
|
||||
];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
<?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']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,843 @@
|
||||
<?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 = [
|
||||
'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,
|
||||
];
|
||||
|
||||
$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,
|
||||
'customization' => $this->settings_customization,
|
||||
'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,
|
||||
'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 {
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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 );
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
namespace Elementor\App\Modules\ImportExportCustomization\Runners\Export;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Site_Settings extends Export_Runner_Base {
|
||||
const ALLOWED_SETTINGS = [
|
||||
'theme',
|
||||
'globalColors',
|
||||
'globalFonts',
|
||||
'themeStyleSettings',
|
||||
'generalSettings',
|
||||
'experiments',
|
||||
'customCode',
|
||||
'customIcons',
|
||||
'customFonts',
|
||||
];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return [
|
||||
'files' => [
|
||||
'path' => 'site-settings',
|
||||
'data' => $kit_data,
|
||||
],
|
||||
'manifest' => [
|
||||
$manifest_data,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return $this->export_all( $data, ! empty( $customization['theme'] ) );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
<?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',
|
||||
];
|
||||
|
||||
/**
|
||||
* @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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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 ) {}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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 );
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,999 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,799 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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 );
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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 ) {}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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 );
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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 ) { }
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
140
wp-content/plugins/elementor/app/modules/import-export/utils.php
Normal file
140
wp-content/plugins/elementor/app/modules/import-export/utils.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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' ),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Elementor\App\Modules\KitLibrary;
|
||||
|
||||
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Kit_Library_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__( 'Website Templates', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_capability() {
|
||||
return 'manage_options';
|
||||
}
|
||||
}
|
||||
192
wp-content/plugins/elementor/app/modules/kit-library/module.php
Normal file
192
wp-content/plugins/elementor/app/modules/kit-library/module.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
namespace Elementor\App\Modules\KitLibrary;
|
||||
|
||||
use Elementor\App\Modules\KitLibrary\Data\Repository;
|
||||
use Elementor\Core\Admin\Menu\Admin_Menu_Manager;
|
||||
use Elementor\Core\Admin\Menu\Main as MainMenu;
|
||||
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_admin_menu( MainMenu $menu ) {
|
||||
$menu->add_submenu( [
|
||||
'page_title' => esc_html__( 'Website Templates', 'elementor' ),
|
||||
'menu_title' => '<span id="e-admin-menu__kit-library">' . esc_html__( 'Website Templates', 'elementor' ) . '</span>',
|
||||
'menu_slug' => Plugin::$instance->app->get_base_url() . '&source=wp_db_templates_menu#/kit-library',
|
||||
'index' => 40,
|
||||
] );
|
||||
}
|
||||
|
||||
private function register_admin_menu_legacy( Admin_Menu_Manager $admin_menu ) {
|
||||
if ( ! $this->is_editor_one_active() ) {
|
||||
$admin_menu->register(
|
||||
Plugin::$instance->app->get_base_url() . '&source=wp_db_templates_menu#/kit-library',
|
||||
new Kit_Library_Menu_Item()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 is_editor_one_active(): bool {
|
||||
return (bool) Plugin::instance()->modules_manager->get_modules( 'editor-one' );
|
||||
}
|
||||
|
||||
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(),
|
||||
] );
|
||||
}
|
||||
|
||||
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/admin/menu/register', function( Admin_Menu_Manager $admin_menu ) {
|
||||
$this->register_admin_menu_legacy( $admin_menu );
|
||||
}, Source_Local::ADMIN_MENU_PRIORITY + 30 );
|
||||
|
||||
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 ) . ';' );
|
||||
}
|
||||
}
|
||||
}
|
||||
32
wp-content/plugins/elementor/app/modules/onboarding/api.php
Normal file
32
wp-content/plugins/elementor/app/modules/onboarding/api.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Elementor\App\Modules\Onboarding;
|
||||
|
||||
use Elementor\Includes\EditorAssetsAPI;
|
||||
|
||||
class API {
|
||||
protected EditorAssetsAPI $editor_assets_api;
|
||||
|
||||
public function __construct( EditorAssetsAPI $editor_assets_api ) {
|
||||
$this->editor_assets_api = $editor_assets_api;
|
||||
}
|
||||
|
||||
public function get_ab_testing_data( $force_request = false ): array {
|
||||
$assets_data = $this->editor_assets_api->get_assets_data( $force_request );
|
||||
|
||||
return $this->extract_ab_testing_config( $assets_data );
|
||||
}
|
||||
|
||||
private function extract_ab_testing_config( array $json_data ): array {
|
||||
if ( empty( $json_data ) || ! is_array( $json_data ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $json_data[0] ?? [];
|
||||
}
|
||||
|
||||
public function is_experiment_enabled( string $experiment_key, $force_request = false ): bool {
|
||||
$ab_testing_data = $this->get_ab_testing_data( $force_request );
|
||||
|
||||
return $ab_testing_data['coreOnboarding'][ $experiment_key ] ?? false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\App\Modules\Onboarding;
|
||||
|
||||
use Elementor\Tracker;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Features_Usage {
|
||||
|
||||
const ONBOARDING_FEATURES_OPTION = '_elementor_onboarding_features';
|
||||
|
||||
public function register() {
|
||||
if ( ! Tracker::is_allow_track() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'elementor/tracker/send_tracking_data_params', function ( array $params ) {
|
||||
$params['usages']['onboarding_features'] = $this->get_usage_data();
|
||||
|
||||
return $params;
|
||||
} );
|
||||
}
|
||||
|
||||
public function save_onboarding_features( $raw_post_data ) {
|
||||
if ( empty( $raw_post_data ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_data = json_decode( $raw_post_data, true );
|
||||
|
||||
if ( empty( $post_data['features'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_option( static::ONBOARDING_FEATURES_OPTION, $post_data['features'] );
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'payload' => [],
|
||||
];
|
||||
}
|
||||
|
||||
private function get_usage_data() {
|
||||
return get_option( static::ONBOARDING_FEATURES_OPTION, [] );
|
||||
}
|
||||
}
|
||||
564
wp-content/plugins/elementor/app/modules/onboarding/module.php
Normal file
564
wp-content/plugins/elementor/app/modules/onboarding/module.php
Normal file
@@ -0,0 +1,564 @@
|
||||
<?php
|
||||
namespace Elementor\App\Modules\Onboarding;
|
||||
|
||||
use Automatic_Upgrader_Skin;
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||||
use Elementor\Core\Common\Modules\Connect\Apps\Library;
|
||||
use Elementor\Core\Files\Uploads_Manager;
|
||||
use Elementor\Includes\EditorAssetsAPI;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Utils;
|
||||
use Plugin_Upgrader;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Onboarding Module
|
||||
*
|
||||
* Responsible for initializing Elementor App functionality
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
class Module extends BaseModule {
|
||||
|
||||
const VERSION = '1.0.0';
|
||||
const ONBOARDING_OPTION = 'elementor_onboarded';
|
||||
|
||||
const EXPERIMENT_EMPHASIZE_CONNECT_BENEFITS = 'emphasizeConnectBenefits101';
|
||||
const EXPERIMENT_EMPHASIZE_THEME_VALUE_AUDIENCE_202 = 'emphasizeThemeValueAudience202';
|
||||
const EXPERIMENT_UPDATE_COPY_VISUALS = 'updateCopyVisuals401';
|
||||
const EXPERIMENT_REDUCE_HIERARCHY_BLANK_OPTION = 'reduceHierarchyBlankOption402';
|
||||
|
||||
private ?API $editor_assets_api = null;
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* @since 3.6.0
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'onboarding';
|
||||
}
|
||||
|
||||
private function is_experiment_enabled( string $experiment_key ) {
|
||||
$editor_assets_api = $this->get_editor_assets_api();
|
||||
|
||||
if ( null === $editor_assets_api ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $editor_assets_api->is_experiment_enabled( $experiment_key );
|
||||
}
|
||||
|
||||
private function is_hello_theme_activated(): bool {
|
||||
$current_theme = get_option( 'template' );
|
||||
$hello_theme_variants = [ 'hello-elementor', 'hello-biz', 'hello-commerce', 'hello-theme' ];
|
||||
|
||||
return in_array( $current_theme, $hello_theme_variants, true );
|
||||
}
|
||||
|
||||
private function get_editor_assets_api(): ?API {
|
||||
if ( null !== $this->editor_assets_api ) {
|
||||
return $this->editor_assets_api;
|
||||
}
|
||||
|
||||
$editor_assets_api_instance = new EditorAssetsAPI( $this->get_editor_assets_api_config() );
|
||||
$this->editor_assets_api = new API( $editor_assets_api_instance );
|
||||
|
||||
return $this->editor_assets_api;
|
||||
}
|
||||
|
||||
private function get_editor_assets_api_config(): array {
|
||||
return [
|
||||
EditorAssetsAPI::ASSETS_DATA_URL => 'https://assets.elementor.com/ab-testing/v1/ab-testing.json',
|
||||
EditorAssetsAPI::ASSETS_DATA_TRANSIENT_KEY => '_elementor_ab_testing_data',
|
||||
EditorAssetsAPI::ASSETS_DATA_KEY => 'ab-testing',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Onboarding Settings
|
||||
*
|
||||
* Creates an array of module settings that is localized into the JS App config.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
private function set_onboarding_settings() {
|
||||
if ( ! Plugin::$instance->common ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the published pages and posts
|
||||
$pages_and_posts = new \WP_Query( [
|
||||
'post_type' => [ 'page', 'post' ],
|
||||
'post_status' => 'publish',
|
||||
'update_post_meta_cache' => false,
|
||||
'update_post_term_cache' => false,
|
||||
'no_found_rows' => true,
|
||||
] );
|
||||
|
||||
$custom_site_logo_id = get_theme_mod( 'custom_logo' );
|
||||
$custom_logo_src = wp_get_attachment_image_src( $custom_site_logo_id, 'full' );
|
||||
$site_name = get_option( 'blogname', '' );
|
||||
|
||||
$hello_theme = wp_get_theme( 'hello-elementor' );
|
||||
$hello_theme_errors = is_object( $hello_theme->errors() ) ? $hello_theme->errors()->errors : [];
|
||||
|
||||
/** @var Library $library */
|
||||
$library = Plugin::$instance->common->get_component( 'connect' )->get_app( 'library' );
|
||||
$is_editor_one_active = Plugin::$instance->experiments->is_feature_active( 'e_editor_one' );
|
||||
|
||||
Plugin::$instance->app->set_settings( 'onboarding', [
|
||||
'eventPlacement' => 'Onboarding wizard',
|
||||
'onboardingAlreadyRan' => get_option( self::ONBOARDING_OPTION ),
|
||||
'onboardingVersion' => self::VERSION,
|
||||
'isLibraryConnected' => $library->is_connected(),
|
||||
// Used to check if the Hello Elementor theme is installed but not activated.
|
||||
'helloInstalled' => empty( $hello_theme_errors['theme_not_found'] ),
|
||||
'helloActivated' => $this->is_hello_theme_activated(),
|
||||
// The "Use Hello theme on my site" checkbox should be checked by default only if this condition is met.
|
||||
'helloOptOut' => count( $pages_and_posts->posts ) < 5,
|
||||
'siteName' => esc_html( $site_name ),
|
||||
'isUnfilteredFilesEnabled' => Uploads_Manager::are_unfiltered_uploads_enabled(),
|
||||
'isEditorOneActive' => $is_editor_one_active,
|
||||
'urls' => [
|
||||
'kitLibrary' => Plugin::$instance->app->get_base_url() . '&source=onboarding#/kit-library?order[direction]=desc&order[by]=featuredIndex',
|
||||
'sitePlanner' => add_query_arg( [
|
||||
'type' => 'editor',
|
||||
'siteUrl' => esc_url( home_url() ),
|
||||
'siteName' => esc_html( $site_name ),
|
||||
'siteDescription' => esc_html( get_bloginfo( 'description' ) ),
|
||||
'siteLanguage' => get_locale(),
|
||||
], 'https://planner.elementor.com/onboarding.html' ),
|
||||
'createNewPage' => Plugin::$instance->documents->get_create_new_post_url(),
|
||||
'connect' => $library->get_admin_url( 'authorize', [
|
||||
'utm_source' => 'onboarding-wizard',
|
||||
'utm_campaign' => 'connect-account',
|
||||
'utm_medium' => 'wp-dash',
|
||||
'utm_term' => self::VERSION,
|
||||
'source' => 'generic',
|
||||
] ),
|
||||
'upgrade' => 'https://go.elementor.com/go-pro-onboarding-wizard-upgrade/',
|
||||
'signUp' => $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' => 'signup',
|
||||
] ),
|
||||
'uploadPro' => Plugin::$instance->app->get_base_url() . '#/onboarding/uploadAndInstallPro?mode=popup',
|
||||
],
|
||||
'siteLogo' => [
|
||||
'id' => $custom_site_logo_id,
|
||||
'url' => $custom_logo_src ? $custom_logo_src[0] : '',
|
||||
],
|
||||
'utms' => [
|
||||
'connectTopBar' => '&utm_content=top-bar',
|
||||
'connectCta' => '&utm_content=cta-button',
|
||||
'connectCtaLink' => '&utm_content=cta-link',
|
||||
'downloadPro' => '?utm_source=onboarding-wizard&utm_campaign=my-account-subscriptions&utm_medium=wp-dash&utm_content=import-pro-plugin&utm_term=' . self::VERSION,
|
||||
],
|
||||
'nonce' => wp_create_nonce( 'onboarding' ),
|
||||
'experiment' => true,
|
||||
'isExperiment101Enabled' => $this->is_experiment_enabled( self::EXPERIMENT_EMPHASIZE_CONNECT_BENEFITS ),
|
||||
'isExperiment202Enabled' => $this->is_experiment_enabled( self::EXPERIMENT_EMPHASIZE_THEME_VALUE_AUDIENCE_202 ),
|
||||
'isExperiment401Enabled' => $this->is_experiment_enabled( self::EXPERIMENT_UPDATE_COPY_VISUALS ),
|
||||
'isExperiment402Enabled' => $this->is_experiment_enabled( self::EXPERIMENT_REDUCE_HIERARCHY_BLANK_OPTION ),
|
||||
'experimentNames' => [
|
||||
'101' => self::EXPERIMENT_EMPHASIZE_CONNECT_BENEFITS,
|
||||
'202' => self::EXPERIMENT_EMPHASIZE_THEME_VALUE_AUDIENCE_202,
|
||||
'401' => self::EXPERIMENT_UPDATE_COPY_VISUALS,
|
||||
'402' => self::EXPERIMENT_REDUCE_HIERARCHY_BLANK_OPTION,
|
||||
],
|
||||
'pageSubheading' => $is_editor_one_active ? __( 'Choose the capabilities you need to bring your vision to life', 'elementor' ) : __( 'Which Elementor Pro features do you need to bring your creative vision to life?', 'elementor' ),
|
||||
'pageHeading' => $is_editor_one_active ? __( ' Elevate your website with additional features.', 'elementor' ) : __( 'Elevate your website with additional Pro features.', 'elementor' ),
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Permission Error Response
|
||||
*
|
||||
* Returns the response that is returned when the user's capabilities are not sufficient for performing an action.
|
||||
*
|
||||
* @since 3.6.4
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_permission_error_response() {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'payload' => [
|
||||
'error_message' => esc_html__( 'You do not have permission to perform this action.', 'elementor' ),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe Update Site Logo
|
||||
*
|
||||
* If a new name is provided, it will be updated as the Site Name.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function maybe_update_site_name() {
|
||||
$problem_error = [
|
||||
'status' => 'error',
|
||||
'payload' => [
|
||||
'error_message' => esc_html__( 'There was a problem setting your site name.', 'elementor' ),
|
||||
],
|
||||
];
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( empty( $_POST['data'] ) ) {
|
||||
return $problem_error;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$data = json_decode( Utils::get_super_global_value( $_POST, 'data' ), true );
|
||||
|
||||
if ( ! isset( $data['siteName'] ) ) {
|
||||
return $problem_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Onboarding Site Name
|
||||
*
|
||||
* Filters the new site name passed by the user to update in Elementor's onboarding process.
|
||||
* Elementor runs `esc_html()` on the Site Name passed by the user for security reasons. If a user wants to
|
||||
* include special characters in their site name, they can use this filter to override it.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param string Escaped new site name
|
||||
*/
|
||||
$new_site_name = apply_filters( 'elementor/onboarding/site-name', $data['siteName'] );
|
||||
|
||||
// The site name is sanitized in `update_options()`
|
||||
update_option( 'blogname', $new_site_name );
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'payload' => [
|
||||
'siteNameUpdated' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe Update Site Logo
|
||||
*
|
||||
* If an image attachment ID is provided, it will be updated as the Site Logo Theme Mod.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function maybe_update_site_logo() {
|
||||
if ( ! current_user_can( 'edit_theme_options' ) ) {
|
||||
return $this->get_permission_error_response();
|
||||
}
|
||||
|
||||
$data_error = [
|
||||
'status' => 'error',
|
||||
'payload' => [
|
||||
'error_message' => esc_html__( 'There was a problem setting your site logo.', 'elementor' ),
|
||||
],
|
||||
];
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( empty( $_POST['data'] ) ) {
|
||||
return $data_error;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$data = json_decode( Utils::get_super_global_value( $_POST, 'data' ), true );
|
||||
|
||||
// If there is no attachment ID passed or it is not a valid ID, exit here.
|
||||
if ( empty( $data['attachmentId'] ) ) {
|
||||
return $data_error;
|
||||
}
|
||||
|
||||
$absint_attachment_id = absint( $data['attachmentId'] );
|
||||
|
||||
if ( 0 === $absint_attachment_id ) {
|
||||
return $data_error;
|
||||
}
|
||||
|
||||
$attachment_url = wp_get_attachment_url( $data['attachmentId'] );
|
||||
|
||||
// Check if the attachment exists. If it does not, exit here.
|
||||
if ( ! $attachment_url ) {
|
||||
return $data_error;
|
||||
}
|
||||
|
||||
set_theme_mod( 'custom_logo', $absint_attachment_id );
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'payload' => [
|
||||
'siteLogoUpdated' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe Upload Logo Image
|
||||
*
|
||||
* If an image file upload is provided, and it passes validation, it will be uploaded to the site's Media Library.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function maybe_upload_logo_image() {
|
||||
$error_message = esc_html__( 'There was a problem uploading your file.', 'elementor' );
|
||||
|
||||
$file = Utils::get_super_global_value( $_FILES, 'fileToUpload' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( ! is_array( $file ) || empty( $file['type'] ) ) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'payload' => [
|
||||
'error_message' => $error_message,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// If the user has allowed it, set the Request's state as an "Elementor Upload" request, in order to add
|
||||
// support for non-standard file uploads.
|
||||
if ( 'image/svg+xml' === $file['type'] ) {
|
||||
if ( Uploads_Manager::are_unfiltered_uploads_enabled() ) {
|
||||
Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
|
||||
} else {
|
||||
wp_send_json_error( 'To upload SVG files, you must allow uploading unfiltered files.' );
|
||||
}
|
||||
}
|
||||
|
||||
// If the image is an SVG file, sanitation is performed during the import (upload) process.
|
||||
$image_attachment = Plugin::$instance->templates_manager->get_import_images_instance()->import( $file );
|
||||
|
||||
if ( 'image/svg+xml' === $file['type'] && Uploads_Manager::are_unfiltered_uploads_enabled() ) {
|
||||
// Reset Upload state.
|
||||
Plugin::$instance->uploads_manager->set_elementor_upload_state( false );
|
||||
}
|
||||
|
||||
if ( $image_attachment && ! is_wp_error( $image_attachment ) ) {
|
||||
$result = [
|
||||
'status' => 'success',
|
||||
'payload' => [
|
||||
'imageAttachment' => $image_attachment,
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$result = [
|
||||
'status' => 'error',
|
||||
'payload' => [
|
||||
'error_message' => $error_message,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate Hello Theme
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function maybe_activate_hello_theme() {
|
||||
if ( ! current_user_can( 'switch_themes' ) ) {
|
||||
return $this->get_permission_error_response();
|
||||
}
|
||||
|
||||
$theme_slug = Utils::get_super_global_value( $_POST, 'theme_slug' ) ?? 'hello-biz'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$allowed_themes = [ 'hello-elementor', 'hello-biz' ];
|
||||
if ( ! in_array( $theme_slug, $allowed_themes, true ) ) {
|
||||
$theme_slug = 'hello-biz';
|
||||
}
|
||||
|
||||
switch_theme( $theme_slug );
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'payload' => [
|
||||
'helloThemeActivated' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload and Install Elementor Pro
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function upload_and_install_pro() {
|
||||
if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) {
|
||||
return $this->get_permission_error_response();
|
||||
}
|
||||
|
||||
$error_message = esc_html__( 'There was a problem uploading your file.', 'elementor' );
|
||||
|
||||
$file = Utils::get_super_global_value( $_FILES, 'fileToUpload' ) ?? []; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( ! is_array( $file ) || empty( $file['type'] ) ) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'payload' => [
|
||||
'error_message' => $error_message,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
if ( ! class_exists( 'Automatic_Upgrader_Skin' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
}
|
||||
|
||||
$skin = new Automatic_Upgrader_Skin();
|
||||
$upgrader = new Plugin_Upgrader( $skin );
|
||||
$upload_result = $upgrader->install( $file['tmp_name'], [ 'overwrite_package' => false ] );
|
||||
|
||||
if ( ! $upload_result || is_wp_error( $upload_result ) ) {
|
||||
$result = [
|
||||
'status' => 'error',
|
||||
'payload' => [
|
||||
'error_message' => $error_message,
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$activated = activate_plugin( WP_PLUGIN_DIR . '/elementor-pro/elementor-pro.php', false, false, true );
|
||||
|
||||
if ( ! is_wp_error( $activated ) ) {
|
||||
$result = [
|
||||
'status' => 'success',
|
||||
'payload' => [
|
||||
'elementorProInstalled' => true,
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$result = [
|
||||
'status' => 'error',
|
||||
'payload' => [
|
||||
'error_message' => $error_message,
|
||||
'elementorProInstalled' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function maybe_update_onboarding_db_option() {
|
||||
$db_option = get_option( self::ONBOARDING_OPTION );
|
||||
|
||||
if ( ! $db_option ) {
|
||||
update_option( self::ONBOARDING_OPTION, true );
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'payload' => 'onboarding DB',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe Handle Ajax
|
||||
*
|
||||
* This method checks if there are any AJAX actions being
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
private function maybe_handle_ajax() {
|
||||
$result = [];
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
switch ( Utils::get_super_global_value( $_POST, 'action' ) ) {
|
||||
case 'elementor_update_site_name':
|
||||
// If no value is passed for any reason, no need to update the site name.
|
||||
$result = $this->maybe_update_site_name();
|
||||
break;
|
||||
case 'elementor_update_site_logo':
|
||||
$result = $this->maybe_update_site_logo();
|
||||
break;
|
||||
case 'elementor_upload_site_logo':
|
||||
$result = $this->maybe_upload_logo_image();
|
||||
break;
|
||||
case 'elementor_activate_hello_theme':
|
||||
$result = $this->maybe_activate_hello_theme();
|
||||
break;
|
||||
case 'elementor_upload_and_install_pro':
|
||||
$result = $this->upload_and_install_pro();
|
||||
break;
|
||||
case 'elementor_update_onboarding_option':
|
||||
$result = $this->maybe_update_onboarding_db_option();
|
||||
break;
|
||||
case 'elementor_save_onboarding_features':
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$result = $this->get_component( 'features_usage' )->save_onboarding_features( Utils::get_super_global_value( $_POST, 'data' ) ?? [] );
|
||||
}
|
||||
|
||||
if ( ! empty( $result ) ) {
|
||||
if ( 'success' === $result['status'] ) {
|
||||
wp_send_json_success( $result['payload'] );
|
||||
} else {
|
||||
wp_send_json_error( $result['payload'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
$this->add_component( 'features_usage', new Features_Usage() );
|
||||
|
||||
add_action( 'elementor/init', function() {
|
||||
// Only load when viewing the onboarding app.
|
||||
if ( Plugin::$instance->app->is_current() ) {
|
||||
$this->set_onboarding_settings();
|
||||
// Needed for installing the Hello Elementor theme.
|
||||
wp_enqueue_script( 'updates' );
|
||||
// Needed for uploading Logo from WP Media Library.
|
||||
wp_enqueue_media();
|
||||
}
|
||||
}, 12 );
|
||||
|
||||
// Needed for uploading Logo from WP Media Library. The 'admin_menu' hook is used because it runs before
|
||||
// 'admin_init', and the App triggers printing footer scripts on 'admin_init' at priority 0.
|
||||
add_action( 'admin_menu', function () {
|
||||
add_action( 'wp_print_footer_scripts', function () {
|
||||
if ( function_exists( 'wp_print_media_templates' ) ) {
|
||||
wp_print_media_templates();
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
add_action( 'admin_init', function() {
|
||||
if ( wp_doing_ajax() &&
|
||||
isset( $_POST['action'] ) &&
|
||||
isset( $_POST['_nonce'] ) &&
|
||||
wp_verify_nonce( Utils::get_super_global_value( $_POST, '_nonce' ), Ajax::NONCE_KEY ) &&
|
||||
current_user_can( 'manage_options' )
|
||||
) {
|
||||
$this->maybe_handle_ajax();
|
||||
}
|
||||
} );
|
||||
|
||||
$this->get_component( 'features_usage' )->register();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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)
|
||||
}
|
||||
}
|
||||
31
wp-content/plugins/elementor/app/view.php
Normal file
31
wp-content/plugins/elementor/app/view.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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>
|
||||
34
wp-content/plugins/elementor/assets/css/admin-bar.css
Normal file
34
wp-content/plugins/elementor/assets/css/admin-bar.css
Normal file
@@ -0,0 +1,34 @@
|
||||
#wp-admin-bar-elementor_edit_page > .ab-item::before {
|
||||
content: "\e813";
|
||||
font-family: eicons;
|
||||
inset-block-start: 3px;
|
||||
font-size: 18px;
|
||||
}
|
||||
#wp-admin-bar-elementor_edit_page .ab-submenu .ab-item {
|
||||
display: flex;
|
||||
width: 200px;
|
||||
}
|
||||
#wp-admin-bar-elementor_edit_page .elementor-edit-link-title {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
#wp-admin-bar-elementor_edit_page .elementor-edit-link-type {
|
||||
background: #3f444b;
|
||||
font-size: 11px;
|
||||
line-height: 9px;
|
||||
margin-block-start: 6px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#wp-admin-bar-elementor_inspector > .ab-item::before {
|
||||
content: "\f348";
|
||||
inset-block-start: 2px;
|
||||
}
|
||||
|
||||
#wpadminbar * {
|
||||
font-style: normal;
|
||||
}
|
||||
/*# sourceMappingURL=admin-bar.css.map */
|
||||
1
wp-content/plugins/elementor/assets/css/admin-bar.min.css
vendored
Normal file
1
wp-content/plugins/elementor/assets/css/admin-bar.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#wp-admin-bar-elementor_edit_page>.ab-item:before{content:"\e813";font-family:eicons;font-size:18px;inset-block-start:3px}#wp-admin-bar-elementor_edit_page .ab-submenu .ab-item{display:flex;width:200px}#wp-admin-bar-elementor_edit_page .elementor-edit-link-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:100%}#wp-admin-bar-elementor_edit_page .elementor-edit-link-type{background:#3f444b;border-radius:3px;font-size:11px;line-height:9px;margin-block-start:6px;padding:4px 8px}#wp-admin-bar-elementor_inspector>.ab-item:before{content:"\f348";inset-block-start:2px}#wpadminbar *{font-style:normal}
|
||||
164
wp-content/plugins/elementor/assets/css/admin-top-bar.css
Normal file
164
wp-content/plugins/elementor/assets/css/admin-top-bar.css
Normal file
@@ -0,0 +1,164 @@
|
||||
#e-dashboard-widget-admin-top-bar {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#e-admin-top-bar-root {
|
||||
font-family: var(--e-a-font-family);
|
||||
background: var(--e-a-bg-default);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.03);
|
||||
display: none;
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-inline-end: 0;
|
||||
width: calc(100% - 160px);
|
||||
z-index: 1;
|
||||
}
|
||||
body.folded #e-admin-top-bar-root {
|
||||
width: calc(100% - 36px);
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
}
|
||||
#e-admin-top-bar-root .page-title-action {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
padding: 8px 16px;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: var(--e-a-border-radius);
|
||||
background-color: var(--e-a-btn-bg);
|
||||
color: var(--e-a-btn-color-invert);
|
||||
transition: var(--e-a-transition-hover);
|
||||
}
|
||||
#e-admin-top-bar-root .page-title-action:hover {
|
||||
background-color: var(--e-a-btn-bg-hover);
|
||||
color: var(--e-a-btn-color-invert);
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__heading {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-inline-end: 40px;
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__main-area {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__main-area button {
|
||||
margin: 0 4px;
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__secondary-area {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__heading-title {
|
||||
color: var(--e-a-color-txt);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
padding: 0 8px;
|
||||
line-height: normal;
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__main-area-buttons {
|
||||
display: inline-flex;
|
||||
gap: 5px;
|
||||
}
|
||||
#e-admin-top-bar-root.e-admin-top-bar--active {
|
||||
display: block;
|
||||
}
|
||||
#e-admin-top-bar-root.e-admin-top-bar--active:not(.e-admin-top-bar--editor-one) ~ #wpbody #wpbody-content {
|
||||
margin-block-start: 50px;
|
||||
}
|
||||
#e-admin-top-bar-root.e-admin-top-bar--active:not(.e-admin-top-bar--editor-one) ~ #wpbody .wrap {
|
||||
clear: both;
|
||||
padding-block-start: 10px;
|
||||
}
|
||||
#e-admin-top-bar-root.e-admin-top-bar--active:not(.e-admin-top-bar--editor-one) ~ #wpbody .wrap h1 {
|
||||
display: none;
|
||||
}
|
||||
#e-admin-top-bar-root:not(.e-admin-top-bar--active) ~ #wpbody .wrap h1, #e-admin-top-bar-root:not(.e-admin-top-bar--active) ~ #wpbody .wrap .page-title-action {
|
||||
display: inline-block;
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__bar-button {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
margin: 0 10px;
|
||||
text-decoration: none;
|
||||
color: var(--e-a-color-txt);
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__bar-button.accent {
|
||||
color: var(--e-a-color-accent);
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__bar-button.accent:hover .e-admin-top-bar__bar-button-title,
|
||||
#e-admin-top-bar-root .e-admin-top-bar__bar-button.accent:hover .e-admin-top-bar__bar-button-icon {
|
||||
color: var(--e-a-color-accent);
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__bar-button .crown-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-icon {
|
||||
margin: 0 4px;
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-title,
|
||||
#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-icon {
|
||||
color: var(--e-a-color-txt-hover);
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__bar-button-title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin: 0 4px;
|
||||
line-height: normal;
|
||||
}
|
||||
#e-admin-top-bar-root:not(.e-admin-top-bar--editor-one) ~ #wpbody .wrap h1, #e-admin-top-bar-root:not(.e-admin-top-bar--editor-one) ~ #wpbody .wrap .page-title-action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 960px) {
|
||||
#e-admin-top-bar-root {
|
||||
width: calc(100% - 36px);
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 782px) {
|
||||
#e-admin-top-bar-root {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 600px) {
|
||||
#e-admin-top-bar-root {
|
||||
top: 46px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
#e-admin-top-bar-root .e-admin-top-bar__main-area-buttons {
|
||||
position: absolute;
|
||||
top: calc(100% + 10px);
|
||||
}
|
||||
#e-admin-top-bar-root {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons {
|
||||
display: none;
|
||||
}
|
||||
#e-admin-top-bar-root .e-admin-top-bar__secondary-area > .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
/*# sourceMappingURL=admin-top-bar.css.map */
|
||||
1
wp-content/plugins/elementor/assets/css/admin-top-bar.min.css
vendored
Normal file
1
wp-content/plugins/elementor/assets/css/admin-top-bar.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#e-dashboard-widget-admin-top-bar{opacity:0;pointer-events:none;position:absolute}#e-admin-top-bar-root{background:var(--e-a-bg-default);box-shadow:0 4px 6px rgba(0,0,0,.03);display:none;font-family:var(--e-a-font-family);inset-block-start:0;inset-inline-end:0;position:absolute;width:calc(100% - 160px);z-index:1}body.folded #e-admin-top-bar-root{width:calc(100% - 36px)}#e-admin-top-bar-root .e-admin-top-bar{display:flex;height:50px;justify-content:space-between;padding:0 16px}#e-admin-top-bar-root .page-title-action{background-color:var(--e-a-btn-bg);border:none;border-radius:var(--e-a-border-radius);color:var(--e-a-btn-color-invert);font-size:12px;font-weight:500;line-height:1.2;outline:none;padding:8px 16px;text-decoration:none;text-transform:uppercase;transition:var(--e-a-transition-hover)}#e-admin-top-bar-root .page-title-action:hover{background-color:var(--e-a-btn-bg-hover);color:var(--e-a-btn-color-invert)}#e-admin-top-bar-root .e-admin-top-bar__heading{margin-inline-end:40px}#e-admin-top-bar-root .e-admin-top-bar__heading,#e-admin-top-bar-root .e-admin-top-bar__main-area{align-items:center;display:inline-flex;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__main-area button{margin:0 4px}#e-admin-top-bar-root .e-admin-top-bar__secondary-area{align-items:center;display:inline-flex;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__heading-title{color:var(--e-a-color-txt);font-size:15px;font-weight:700;line-height:normal;padding:0 8px}#e-admin-top-bar-root .e-admin-top-bar__main-area-buttons{display:inline-flex;gap:5px}#e-admin-top-bar-root.e-admin-top-bar--active{display:block}#e-admin-top-bar-root.e-admin-top-bar--active:not(.e-admin-top-bar--editor-one)~#wpbody #wpbody-content{margin-block-start:50px}#e-admin-top-bar-root.e-admin-top-bar--active:not(.e-admin-top-bar--editor-one)~#wpbody .wrap{clear:both;padding-block-start:10px}#e-admin-top-bar-root.e-admin-top-bar--active:not(.e-admin-top-bar--editor-one)~#wpbody .wrap h1{display:none}#e-admin-top-bar-root:not(.e-admin-top-bar--active)~#wpbody .wrap .page-title-action,#e-admin-top-bar-root:not(.e-admin-top-bar--active)~#wpbody .wrap h1{display:inline-block}#e-admin-top-bar-root .e-admin-top-bar__bar-button{align-items:center;color:var(--e-a-color-txt);cursor:pointer;display:inline-flex;justify-content:center;margin:0 10px;text-decoration:none}#e-admin-top-bar-root .e-admin-top-bar__bar-button.accent,#e-admin-top-bar-root .e-admin-top-bar__bar-button.accent:hover .e-admin-top-bar__bar-button-icon,#e-admin-top-bar-root .e-admin-top-bar__bar-button.accent:hover .e-admin-top-bar__bar-button-title{color:var(--e-a-color-accent)}#e-admin-top-bar-root .e-admin-top-bar__bar-button .crown-icon{font-size:14px}#e-admin-top-bar-root .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-icon{margin:0 4px}#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-icon,#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-title{color:var(--e-a-color-txt-hover)}#e-admin-top-bar-root .e-admin-top-bar__bar-button-title{font-size:13px;font-weight:500;line-height:normal;margin:0 4px}#e-admin-top-bar-root:not(.e-admin-top-bar--editor-one)~#wpbody .wrap .page-title-action,#e-admin-top-bar-root:not(.e-admin-top-bar--editor-one)~#wpbody .wrap h1{display:none}@media screen and (max-width:960px){#e-admin-top-bar-root{width:calc(100% - 36px)}}@media screen and (max-width:782px){#e-admin-top-bar-root{width:100%}}@media screen and (max-width:600px){#e-admin-top-bar-root{top:46px}}@media (max-width:768px){#e-admin-top-bar-root .e-admin-top-bar__main-area-buttons{position:absolute;top:calc(100% + 10px)}#e-admin-top-bar-root{align-items:center;display:inline-flex;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons,#e-admin-top-bar-root .e-admin-top-bar__secondary-area>.e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-title{display:none}}@media (min-width:768px){#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons{display:flex}}
|
||||
2955
wp-content/plugins/elementor/assets/css/admin.css
Normal file
2955
wp-content/plugins/elementor/assets/css/admin.css
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user