first commit
This commit is contained in:
283
wp-content/plugins/elementor-pro/core/admin/admin.php
Normal file
283
wp-content/plugins/elementor-pro/core/admin/admin.php
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Admin;
|
||||
|
||||
use Elementor\Core\Base\App;
|
||||
use Elementor\Rollback;
|
||||
use Elementor\Settings;
|
||||
use Elementor\Tools;
|
||||
use Elementor\Utils;
|
||||
use ElementorPro\Core\Utils as ProUtils;
|
||||
use ElementorPro\License\API;
|
||||
use ElementorPro\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Admin extends App {
|
||||
|
||||
/**
|
||||
* Get module name.
|
||||
*
|
||||
* Retrieve the module name.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string Module name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin styles.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_styles() {
|
||||
$suffix = Utils::is_script_debug() ? '' : '.min';
|
||||
|
||||
$direction_suffix = is_rtl() ? '-rtl' : '';
|
||||
|
||||
wp_register_style(
|
||||
'elementor-pro-admin',
|
||||
ELEMENTOR_PRO_ASSETS_URL . 'css/admin' . $direction_suffix . $suffix . '.css',
|
||||
[],
|
||||
ELEMENTOR_PRO_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_style( 'elementor-pro-admin' );
|
||||
}
|
||||
|
||||
public function enqueue_scripts() {
|
||||
$suffix = Utils::is_script_debug() ? '' : '.min';
|
||||
|
||||
wp_enqueue_script(
|
||||
'elementor-pro-admin',
|
||||
ELEMENTOR_PRO_URL . 'assets/js/admin' . $suffix . '.js',
|
||||
[
|
||||
'elementor-admin',
|
||||
],
|
||||
ELEMENTOR_PRO_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
$locale_settings = [];
|
||||
|
||||
/**
|
||||
* Localized admin settings.
|
||||
*
|
||||
* Filters the localized settings used in the admin as JavaScript variables.
|
||||
*
|
||||
* By default Elementor Pro passes some admin settings to be consumed as JavaScript
|
||||
* variables. This hook allows developers to add extra settings values to be consumed
|
||||
* using JavaScript in WordPress admin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $locale_settings Localized settings.
|
||||
*/
|
||||
$locale_settings = apply_filters( 'elementor_pro/admin/localize_settings', $locale_settings );
|
||||
|
||||
Utils::print_js_config(
|
||||
'elementor-pro-admin',
|
||||
'ElementorProConfig',
|
||||
$locale_settings
|
||||
);
|
||||
}
|
||||
|
||||
public function remove_go_pro_menu() {
|
||||
remove_action( 'admin_menu', [ Plugin::elementor()->settings, 'register_pro_menu' ], Settings::MENU_PRIORITY_GO_PRO );
|
||||
}
|
||||
|
||||
private function get_rollback_versions() {
|
||||
$rollback_versions = get_transient( 'elementor_pro_rollback_versions_' . ELEMENTOR_PRO_VERSION );
|
||||
|
||||
if ( false === $rollback_versions ) {
|
||||
$max_versions = 30;
|
||||
|
||||
$versions = apply_filters( 'elementor-pro/settings/rollback/versions', [] );
|
||||
|
||||
if ( empty( $versions ) ) {
|
||||
$versions = API::get_previous_versions();
|
||||
|
||||
if ( is_wp_error( $versions ) ) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
$rollback_versions = [];
|
||||
|
||||
$current_index = 0;
|
||||
foreach ( $versions as $version ) {
|
||||
if ( $max_versions <= $current_index ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$lowercase_version = strtolower( $version );
|
||||
$is_valid_rollback_version = ! preg_match( '/(trunk|beta|rc|dev)/i', $lowercase_version );
|
||||
|
||||
/**
|
||||
* Is valid rollback version.
|
||||
*
|
||||
* Filters whether the version of the rollback is valid or not.
|
||||
*
|
||||
* By default Elementor doesn't allow to rollback for trunk/beta/rc/dev versions.
|
||||
* This hook allows developers to enable a rollback for thise kind of versions by
|
||||
* returning `true`.
|
||||
*
|
||||
* @param bool $is_valid_rollback_version Whether a rollback version is valid.
|
||||
* @param array $lowercase_version A list of previous versions.
|
||||
*/
|
||||
$is_valid_rollback_version = apply_filters(
|
||||
'elementor-pro/settings/tools/rollback/is_valid_rollback_version',
|
||||
$is_valid_rollback_version,
|
||||
$lowercase_version
|
||||
);
|
||||
|
||||
if ( ! $is_valid_rollback_version ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( version_compare( $version, ELEMENTOR_VERSION, '>=' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$current_index++;
|
||||
$rollback_versions[] = $version;
|
||||
}
|
||||
|
||||
set_transient( 'elementor_pro_rollback_versions_' . ELEMENTOR_PRO_VERSION, $rollback_versions, WEEK_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $rollback_versions;
|
||||
}
|
||||
|
||||
public function register_admin_tools_fields( Tools $tools ) {
|
||||
$rollback_html = '<select class="elementor-rollback-select">';
|
||||
|
||||
foreach ( $this->get_rollback_versions() as $version ) {
|
||||
$rollback_html .= "<option value='{$version}'>$version</option>";
|
||||
}
|
||||
$rollback_html .= '</select>';
|
||||
|
||||
// Rollback
|
||||
$tools->add_fields( 'versions', 'rollback', [
|
||||
'rollback_pro_separator' => [
|
||||
'field_args' => [
|
||||
'type' => 'raw_html',
|
||||
'html' => '<hr>',
|
||||
],
|
||||
],
|
||||
'rollback_pro' => [
|
||||
'label' => esc_html__( 'Rollback Pro Version', 'elementor-pro' ),
|
||||
'field_args' => [
|
||||
'type' => 'raw_html',
|
||||
'html' => sprintf(
|
||||
$rollback_html . '<a data-placeholder-text="' . esc_html__( 'Reinstall', 'elementor-pro' ) . ' v{VERSION}" href="#" data-placeholder-url="%s" class="button elementor-button-spinner elementor-rollback-button">%s</a>',
|
||||
wp_nonce_url( admin_url( 'admin-post.php?action=elementor_pro_rollback&version=VERSION' ), 'elementor_pro_rollback' ),
|
||||
__( 'Reinstall', 'elementor-pro' )
|
||||
),
|
||||
'desc' => '<span style="color: red;">' . esc_html__( 'Warning: Please backup your database before making the rollback.', 'elementor-pro' ) . '</span>',
|
||||
],
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
public function post_elementor_pro_rollback() {
|
||||
check_admin_referer( 'elementor_pro_rollback' );
|
||||
|
||||
$rollback_versions = $this->get_rollback_versions();
|
||||
$version = ProUtils::_unstable_get_super_global_value( $_GET, 'version' );
|
||||
|
||||
if ( ! $version || ! in_array( $version, $rollback_versions, true ) ) {
|
||||
wp_die( esc_html__( 'Error occurred, The version selected is invalid. Try selecting different version.', 'elementor-pro' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to allow override the rollback process.
|
||||
* Should return an instance of `Rollback` class.
|
||||
*
|
||||
* @since 3.16.0
|
||||
*
|
||||
* @param Rollback|null $rollback The rollback instance.
|
||||
* @param string $version The version to roll back to.
|
||||
*/
|
||||
$rollback = apply_filters( 'elementor-pro/settings/rollback', null, $version );
|
||||
|
||||
if ( ! ( $rollback instanceof Rollback ) ) {
|
||||
$package_url = API::get_plugin_package_url( $version );
|
||||
|
||||
if ( is_wp_error( $package_url ) ) {
|
||||
wp_die( $package_url ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$rollback = new Rollback( [
|
||||
'version' => $version,
|
||||
'plugin_name' => ELEMENTOR_PRO_PLUGIN_BASE,
|
||||
'plugin_slug' => basename( ELEMENTOR_PRO__FILE__, '.php' ),
|
||||
'package_url' => $package_url,
|
||||
] );
|
||||
}
|
||||
|
||||
$rollback->run();
|
||||
|
||||
wp_die( '', esc_html__( 'Rollback to Previous Version', 'elementor-pro' ), [ 'response' => 200 ] );
|
||||
}
|
||||
|
||||
public function plugin_action_links( $links ) {
|
||||
unset( $links['go_pro'] );
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
public function plugin_row_meta( $plugin_meta, $plugin_file ) {
|
||||
if ( ELEMENTOR_PRO_PLUGIN_BASE === $plugin_file ) {
|
||||
$row_meta = [
|
||||
'changelog' => '<a href="https://go.elementor.com/pro-changelog/" title="' . esc_attr( esc_html__( 'View Elementor Pro Changelog', 'elementor-pro' ) ) . '" target="_blank">' . esc_html__( 'Changelog', 'elementor-pro' ) . '</a>',
|
||||
];
|
||||
|
||||
$plugin_meta = array_merge( $plugin_meta, $row_meta );
|
||||
}
|
||||
|
||||
return $plugin_meta;
|
||||
}
|
||||
|
||||
public function add_finder_items( array $categories ) {
|
||||
$settings_url = Settings::get_url();
|
||||
|
||||
$categories['settings']['items']['integrations'] = [
|
||||
'title' => esc_html__( 'Integrations', 'elementor-pro' ),
|
||||
'icon' => 'integration',
|
||||
'url' => $settings_url . '#tab-integrations',
|
||||
'keywords' => [ 'integrations', 'settings', 'typekit', 'facebook', 'recaptcha', 'mailchimp', 'drip', 'activecampaign', 'getresponse', 'convertkit', 'elementor' ],
|
||||
];
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->add_component( 'canary-deployment', new Canary_Deployment() );
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
|
||||
add_action( 'admin_menu', [ $this, 'remove_go_pro_menu' ], 0 );
|
||||
|
||||
add_action( 'elementor/admin/after_create_settings/' . Tools::PAGE_ID, [ $this, 'register_admin_tools_fields' ], 50 );
|
||||
|
||||
add_filter( 'plugin_action_links_' . ELEMENTOR_PLUGIN_BASE, [ $this, 'plugin_action_links' ], 50 );
|
||||
add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 2 );
|
||||
|
||||
add_filter( 'elementor/finder/categories', [ $this, 'add_finder_items' ] );
|
||||
|
||||
add_action( 'admin_post_elementor_pro_rollback', [ $this, 'post_elementor_pro_rollback' ] );
|
||||
add_action( 'in_plugin_update_message-' . ELEMENTOR_PRO_PLUGIN_BASE, function( $plugin_data ) {
|
||||
Plugin::elementor()->admin->version_update_warning( ELEMENTOR_PRO_VERSION, $plugin_data['new_version'] );
|
||||
} );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Admin;
|
||||
|
||||
use ElementorPro\License\API;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Canary_Deployment extends \Elementor\Core\Admin\Canary_Deployment {
|
||||
|
||||
const CURRENT_VERSION = ELEMENTOR_PRO_VERSION;
|
||||
const PLUGIN_BASE = ELEMENTOR_PRO_PLUGIN_BASE;
|
||||
|
||||
protected function get_canary_deployment_remote_info( $force ) {
|
||||
$version_info = API::get_version( false );
|
||||
$canary_info = [];
|
||||
|
||||
if ( ! is_wp_error( $version_info ) && ! empty( $version_info['canary_deployment'] ) ) {
|
||||
$canary_info = $version_info['canary_deployment'];
|
||||
}
|
||||
|
||||
return $canary_info;
|
||||
}
|
||||
}
|
||||
99
wp-content/plugins/elementor-pro/core/app/app.php
Normal file
99
wp-content/plugins/elementor-pro/core/app/app.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App;
|
||||
|
||||
use Elementor\Core\Base\App as BaseApp;
|
||||
use ElementorPro\Plugin;
|
||||
use ElementorPro\Core\App\Modules\SiteEditor\Module as SiteEditor;
|
||||
use ElementorPro\Core\App\Modules\KitLibrary\Module as KitLibrary;
|
||||
use ElementorPro\Core\App\Modules\Onboarding\Module as Onboarding;
|
||||
use ElementorPro\Core\App\Modules\ImportExport\Module as ImportExport;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class App extends BaseApp {
|
||||
/**
|
||||
* Get module name.
|
||||
*
|
||||
* Retrieve the module name.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @access public
|
||||
*
|
||||
* @return string Module name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'app-pro';
|
||||
}
|
||||
|
||||
public function init() {
|
||||
$this->enqueue_assets();
|
||||
}
|
||||
|
||||
public function set_menu_url() {
|
||||
Plugin::elementor()->app->set_settings( 'menu_url', Plugin::elementor()->app->get_base_url() . '#/site-editor' );
|
||||
}
|
||||
|
||||
protected function get_init_settings() {
|
||||
return [
|
||||
'baseUrl' => $this->get_assets_base_url(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_assets_base_url() {
|
||||
return ELEMENTOR_PRO_URL;
|
||||
}
|
||||
|
||||
private function enqueue_assets() {
|
||||
wp_enqueue_style(
|
||||
'elementor-pro-app',
|
||||
$this->get_css_assets_url( 'app', null, 'default', true ),
|
||||
[
|
||||
'elementor-app',
|
||||
'select2',
|
||||
],
|
||||
ELEMENTOR_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'elementor-pro-app',
|
||||
$this->get_js_assets_url( 'app' ),
|
||||
[
|
||||
'wp-i18n',
|
||||
'elementor-app-packages',
|
||||
'elementor-common',
|
||||
'select2',
|
||||
],
|
||||
ELEMENTOR_PRO_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_set_script_translations( 'elementor-pro-app', 'elementor-pro' );
|
||||
}
|
||||
|
||||
private function enqueue_config() {
|
||||
// If script didn't loaded, config is still relevant, enqueue without a file.
|
||||
if ( ! wp_script_is( 'elementor-pro-app' ) ) {
|
||||
wp_register_script( 'elementor-pro-app', false, [], ELEMENTOR_PRO_VERSION );
|
||||
wp_enqueue_script( 'elementor-pro-app' );
|
||||
}
|
||||
|
||||
$this->print_config( 'elementor-pro-app' );
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
$this->add_component( 'site-editor', new SiteEditor() );
|
||||
$this->add_component( 'kit-library', new KitLibrary() );
|
||||
$this->add_component( 'onboarding', new Onboarding() );
|
||||
$this->add_component( 'import-export', new ImportExport() );
|
||||
|
||||
add_action( 'elementor/app/init', [ $this, 'init' ] );
|
||||
|
||||
add_action( 'elementor/common/after_register_scripts', function () {
|
||||
$this->enqueue_config();
|
||||
} );
|
||||
|
||||
add_action( 'elementor/init', [ $this, 'set_menu_url' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import ConnectButtonUI from '../ui/connect-button';
|
||||
import { htmlDecodeTextContent, replaceUtmPlaceholders } from '../utils';
|
||||
|
||||
export default function useFeatureLock( featureName ) {
|
||||
const appConfig = elementorAppProConfig[ featureName ] ?? {},
|
||||
isLocked = appConfig.lock?.is_locked ?? false;
|
||||
|
||||
const buttonText = htmlDecodeTextContent( appConfig.lock?.button.text );
|
||||
const buttonLink = replaceUtmPlaceholders(
|
||||
appConfig.lock?.button.url ?? '',
|
||||
appConfig.utms ?? {},
|
||||
);
|
||||
|
||||
const ConnectButton = () => (
|
||||
<ConnectButtonUI text={ buttonText } url={ buttonLink } />
|
||||
);
|
||||
|
||||
return {
|
||||
isLocked,
|
||||
ConnectButton,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import Module from '../../modules/site-editor/assets/js/site-editor';
|
||||
|
||||
new Module();
|
||||
@@ -0,0 +1,48 @@
|
||||
import * as React from 'react';
|
||||
import { useRef, useEffect } from 'react';
|
||||
import { Button } from '@elementor/app-ui';
|
||||
import { arrayToClassName } from '../utils.js';
|
||||
|
||||
const ConnectButton = ( props ) => {
|
||||
const className = arrayToClassName( [
|
||||
'e-app-connect-button',
|
||||
props.className,
|
||||
] );
|
||||
|
||||
const buttonRef = useRef( null );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! buttonRef.current ) {
|
||||
return;
|
||||
}
|
||||
|
||||
jQuery( buttonRef.current ).elementorConnect();
|
||||
}, [] );
|
||||
|
||||
return (
|
||||
<Button
|
||||
{ ...props }
|
||||
elRef={ buttonRef }
|
||||
className={ className }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ConnectButton.propTypes = {
|
||||
...Button.propTypes,
|
||||
text: PropTypes.string.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
ConnectButton.defaultProps = {
|
||||
className: '',
|
||||
variant: 'contained',
|
||||
size: 'sm',
|
||||
color: 'cta',
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
text: __( 'Connect & Activate', 'elementor' ),
|
||||
};
|
||||
|
||||
export default React.memo( ConnectButton );
|
||||
29
wp-content/plugins/elementor-pro/core/app/assets/js/utils.js
Normal file
29
wp-content/plugins/elementor-pro/core/app/assets/js/utils.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copied from Core.
|
||||
export const arrayToClassName = ( array, action ) => {
|
||||
return array
|
||||
.filter( ( item ) => 'object' === typeof ( item ) ? Object.entries( item )[ 0 ][ 1 ] : item )
|
||||
.map( ( item ) => {
|
||||
const value = 'object' === typeof ( item ) ? Object.entries( item )[ 0 ][ 0 ] : item;
|
||||
|
||||
return action ? action( value ) : value;
|
||||
} )
|
||||
.join( ' ' );
|
||||
};
|
||||
|
||||
export const htmlDecodeTextContent = ( input ) => {
|
||||
const doc = new DOMParser().parseFromString( input, 'text/html' );
|
||||
return doc.documentElement.textContent;
|
||||
};
|
||||
|
||||
export const replaceUtmPlaceholders = ( link = '', utms = {} ) => {
|
||||
if ( ! link || ! utms ) {
|
||||
return link;
|
||||
}
|
||||
|
||||
Object.keys( utms ).forEach( ( key ) => {
|
||||
const match = new RegExp( `%%${ key }%%`, 'g' );
|
||||
link = link.replace( match, utms[ key ] );
|
||||
} );
|
||||
|
||||
return link;
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "../../modules/site-editor/assets/js/molecules/site-template.scss";
|
||||
@import "../../modules/site-editor/assets/js/pages/add-new.scss";
|
||||
@import "../../modules/site-editor/assets/js/pages/template-type.scss";
|
||||
@import "../../modules/site-editor/assets/js/pages/conditions/conditions.scss";
|
||||
@import "../../modules/site-editor/assets/js/molecules/back-button.scss";
|
||||
@import "../../modules/site-editor/assets/js/atoms/indicator-bullet.scss";
|
||||
@import "../../modules/site-editor/assets/js/atoms/preview-iframe.scss";
|
||||
@import "../../modules/site-editor/assets/js/site-editor.scss";
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\ImportExport;
|
||||
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use ElementorPro\Plugin;
|
||||
use ElementorPro\Modules\ThemeBuilder\Module as ThemeBuilderModule;
|
||||
use Elementor\App\Modules\ImportExport\Processes\Export;
|
||||
use Elementor\App\Modules\ImportExport\Processes\Import;
|
||||
use Elementor\App\Modules\ImportExport\Processes\Revert;
|
||||
use ElementorPro\Core\App\Modules\ImportExport\Runners\Import\Templates as ImportTemplates;
|
||||
use ElementorPro\Core\App\Modules\ImportExport\Runners\Export\Templates as ExportTemplates;
|
||||
use ElementorPro\Core\App\Modules\ImportExport\Runners\Revert\Templates as RevertTemplates;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Module extends BaseModule {
|
||||
|
||||
public function get_name() {
|
||||
return 'import-export';
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->add_actions();
|
||||
}
|
||||
|
||||
private function add_actions() {
|
||||
add_filter( 'elementor/import/get_default_settings_conflicts', function( array $conflicts, array $templates ) {
|
||||
return $this->apply_conditions_conflicts( $conflicts, $templates );
|
||||
}, 10, 2 );
|
||||
|
||||
add_action( 'elementor/import-export/import-kit', function( Import $import ) {
|
||||
$this->register_import_kit_runners( $import );
|
||||
} );
|
||||
|
||||
add_action( 'elementor/import-export/export-kit', function( Export $export ) {
|
||||
$this->register_export_kit_runners( $export );
|
||||
} );
|
||||
|
||||
add_action( 'elementor/import-export/revert-kit', function( Revert $revert ) {
|
||||
$this->register_revert_kit_runners( $revert );
|
||||
} );
|
||||
}
|
||||
|
||||
private function apply_conditions_conflicts( $conflicts, $templates ) {
|
||||
/** @var ThemeBuilderModule $theme_builder_module */
|
||||
$theme_builder_module = Plugin::instance()->modules_manager->get_modules( 'theme-builder' );
|
||||
|
||||
if ( ! $theme_builder_module ) {
|
||||
return $conflicts;
|
||||
}
|
||||
|
||||
return $conflicts + $theme_builder_module->get_conditions_conflicts( $templates );
|
||||
}
|
||||
|
||||
private function register_import_kit_runners( Import $import ) {
|
||||
$import->register( new ImportTemplates() );
|
||||
}
|
||||
|
||||
private function register_export_kit_runners( Export $export ) {
|
||||
$export->register( new ExportTemplates() );
|
||||
}
|
||||
|
||||
private function register_revert_kit_runners( Revert $revert ) {
|
||||
$revert->register( new RevertTemplates() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\App\Modules\ImportExport\Runners\Export;
|
||||
|
||||
use Elementor\App\Modules\ImportExport\Runners\Export\Export_Runner_Base;
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
|
||||
class Templates extends Export_Runner_Base {
|
||||
|
||||
public static function get_name() : string {
|
||||
return 'templates';
|
||||
}
|
||||
|
||||
public function should_export( array $data ) {
|
||||
return (
|
||||
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,130 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\App\Modules\ImportExport\Runners\Import;
|
||||
|
||||
use Elementor\App\Modules\ImportExport\Runners\Import\Import_Runner_Base;
|
||||
use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Plugin;
|
||||
use ElementorPro\Modules\ThemeBuilder\Classes\Conditions_Manager;
|
||||
use ElementorPro\Modules\ThemeBuilder\Module as ThemeBuilderModule;
|
||||
use ElementorPro\Plugin as ProPlugin;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
|
||||
class Templates extends Import_Runner_Base {
|
||||
|
||||
private $import_session_id;
|
||||
|
||||
private $templates_conditions = [];
|
||||
|
||||
public static function get_name() : string {
|
||||
return 'templates';
|
||||
}
|
||||
|
||||
public function should_import( array $data ) {
|
||||
return (
|
||||
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( $new_document->get_error_message() );
|
||||
}
|
||||
|
||||
$template_data['import_settings'] = $template_settings;
|
||||
$template_data['id'] = $id;
|
||||
|
||||
$this->set_templates_conditions( $template_data );
|
||||
|
||||
$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 [
|
||||
'template_conditions' => $this->templates_conditions,
|
||||
];
|
||||
}
|
||||
|
||||
private function set_templates_conditions( $template_data ) {
|
||||
$conditions = $template_data['import_settings']['conditions'] ?? [];
|
||||
|
||||
if ( empty( $conditions ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$condition = $conditions[0];
|
||||
|
||||
$condition = rtrim( implode( '/', $condition ), '/' );
|
||||
|
||||
/** @var ThemeBuilderModule $theme_builder_module */
|
||||
$theme_builder_module = ProPlugin::instance()->modules_manager->get_modules( 'theme-builder' );
|
||||
$conditions_manager = $theme_builder_module->get_conditions_manager();
|
||||
|
||||
$conflicts = $conditions_manager->get_conditions_conflicts_by_location(
|
||||
$condition,
|
||||
$template_data['import_settings']['location']
|
||||
);
|
||||
|
||||
foreach ( $conflicts as $template ) {
|
||||
$template_document = Plugin::$instance->documents->get( $template['template_id'] );
|
||||
|
||||
$template_conditions = $theme_builder_module->get_conditions_manager()->get_document_conditions( $template_document );
|
||||
|
||||
$this->templates_conditions[ $template['template_id'] ] = $template_conditions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\App\Modules\ImportExport\Runners\Revert;
|
||||
|
||||
use Elementor\App\Modules\ImportExport\Runners\Revert\Revert_Runner_Base;
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Plugin;
|
||||
use ElementorPro\Modules\ThemeBuilder\Module as ThemeBuilderModule;
|
||||
use ElementorPro\Plugin as ProPlugin;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/** @var ThemeBuilderModule $theme_builder_module */
|
||||
$theme_builder_module = ProPlugin::instance()->modules_manager->get_modules( 'theme-builder' );
|
||||
|
||||
$theme_builder_module->get_conditions_manager()->clear_cache();
|
||||
|
||||
$old_conditions = $data['runners']['templates']['template_conditions'] ?? [];
|
||||
|
||||
foreach ( $old_conditions as $template_id => $conditions ) {
|
||||
$theme_builder_module->get_conditions_manager()->save_conditions( $template_id, $conditions );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\KitLibrary;
|
||||
|
||||
use ElementorPro\Plugin;
|
||||
use ElementorPro\License\API;
|
||||
use ElementorPro\License\Admin;
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use ElementorPro\Core\Connect\Apps\Activate;
|
||||
use Elementor\Core\App\Modules\KitLibrary\Connect\Kit_Library;
|
||||
|
||||
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 set_kit_library_settings() {
|
||||
$common = Plugin::elementor()->common;
|
||||
$app = Plugin::elementor()->app;
|
||||
|
||||
$prev_settings = $app->get_settings( 'kit-library' );
|
||||
|
||||
// BC Support.
|
||||
if ( ! $prev_settings || ! $common ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Activate $activate */
|
||||
$activate = $common->get_component( 'connect' )->get_app( 'activate' );
|
||||
|
||||
/** @var Kit_Library $kit_library */
|
||||
$kit_library = $common->get_component( 'connect' )->get_app( 'kit-library' );
|
||||
|
||||
$app->set_settings( 'kit-library', array_merge( $prev_settings, [
|
||||
'is_pro' => true,
|
||||
'is_library_connected' => API::is_license_active() && $kit_library && $kit_library->is_connected(),
|
||||
'library_connect_url' => $activate->get_admin_url( 'authorize', [
|
||||
'utm_source' => 'kit-library',
|
||||
'utm_medium' => 'wp-dash',
|
||||
'utm_campaign' => 'connect-and-activate-license',
|
||||
'utm_term' => '%%page%%', // Will be replaced in the frontend.
|
||||
] ),
|
||||
'access_level' => API::get_library_access_level( 'kit' ),
|
||||
'access_tier' => API::get_access_tier(),
|
||||
] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $connect_info
|
||||
* @param $app
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function add_license_to_connect_info( array $connect_info, $app ) {
|
||||
$license_key = Admin::get_license_key();
|
||||
|
||||
// In elementor 3.3.0-beta it does not send the $app parameter and it should add the license.
|
||||
$bc_support = ! $app;
|
||||
$is_kit_library_request = $app && Kit_Library::class === get_class( $app );
|
||||
|
||||
if ( ! empty( $license_key ) && ( $bc_support || $is_kit_library_request ) ) {
|
||||
$connect_info['license'] = $license_key;
|
||||
}
|
||||
|
||||
return $connect_info;
|
||||
}
|
||||
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'elementor/init', function () {
|
||||
$this->set_kit_library_settings();
|
||||
}, 13 /** after elementor core */ );
|
||||
|
||||
add_filter( 'elementor/connect/additional-connect-info', function ( array $connect_info, $app = null ) {
|
||||
return $this->add_license_to_connect_info( $connect_info, $app );
|
||||
}, 10, 2 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\Onboarding;
|
||||
|
||||
use Elementor\Core\App\Modules\Onboarding\Module as Core_Onboarding_Module;
|
||||
use ElementorPro\Plugin;
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use ElementorPro\Core\Connect\Apps\Activate;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Module extends BaseModule {
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @since 3.6.0
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'onboarding';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Onboarding Settings
|
||||
*
|
||||
* Overrides the Onboarding App's Core settings with updated settings to accommodate for Elementor Pro.
|
||||
*
|
||||
* @since 3.6.0
|
||||
* @access private
|
||||
*/
|
||||
private function set_onboarding_settings() {
|
||||
$common = Plugin::elementor()->common;
|
||||
$app = Plugin::elementor()->app;
|
||||
$onboarding_settings = $app->get_settings( 'onboarding' );
|
||||
|
||||
// If the installed Elementor Core version does not include the Onboarding module, exit here.
|
||||
if ( ! $onboarding_settings ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Activate $activate */
|
||||
$activate = $common->get_component( 'connect' )->get_app( 'activate' );
|
||||
|
||||
$onboarding_settings['urls']['connect'] = $activate->get_admin_url( 'authorize', [
|
||||
'utm_source' => 'editor-app',
|
||||
'utm_campaign' => 'connect-account',
|
||||
'utm_medium' => 'wp-dash',
|
||||
'utm_term' => Core_Onboarding_Module::VERSION,
|
||||
'source' => 'generic',
|
||||
] );
|
||||
|
||||
$onboarding_settings['urls']['signUp'] = $activate->get_admin_url( 'authorize', [
|
||||
'utm_source' => 'editor-app',
|
||||
'utm_campaign' => 'connect-account',
|
||||
'utm_medium' => 'wp-dash',
|
||||
'utm_term' => Core_Onboarding_Module::VERSION,
|
||||
'source' => 'generic',
|
||||
'screen_hint' => 'signup',
|
||||
] );
|
||||
|
||||
$app->set_settings( 'onboarding', $onboarding_settings );
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'elementor/init', function () {
|
||||
$this->set_onboarding_settings();
|
||||
}, 13 /** after elementor core */ );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import './indicator-bullet.scss';
|
||||
|
||||
export const Indicator = ( props ) => {
|
||||
let className = 'eps-indicator-bullet';
|
||||
|
||||
if ( props.active ) {
|
||||
className += ` ${ className }--active`;
|
||||
}
|
||||
|
||||
return <i className={ className } />;
|
||||
};
|
||||
|
||||
Indicator.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
$eps-indicator-bullet-border-color: theme-colors(light);
|
||||
$eps-indicator-bullet-dark-border-color: dark-tints(500);
|
||||
|
||||
:root {
|
||||
--indicator-bullet-border-color: #{$eps-indicator-bullet-border-color};
|
||||
}
|
||||
|
||||
.eps-theme-dark {
|
||||
--indicator-bullet-border-color: #{$eps-indicator-bullet-dark-border-color};
|
||||
}
|
||||
|
||||
$eps-indicator-bullet-size: spacing(16) * 0.75;
|
||||
$eps-indicator-bullet-color: tints(300);
|
||||
$eps-indicator-bullet-color-active: theme-colors(success);
|
||||
$eps-indicator-bullet-box-shadow: $eps-box-shadow-1;
|
||||
$eps-indicator-bullet-radius: 100%;
|
||||
$eps-indicator-bullet-border: 2px solid var(--indicator-bullet-border-color);
|
||||
$eps-indicator-bullet-spacing: spacing(10);
|
||||
|
||||
.eps-indicator-bullet {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
width: $eps-indicator-bullet-size;
|
||||
height: $eps-indicator-bullet-size;
|
||||
box-shadow: $eps-indicator-bullet-box-shadow;
|
||||
background-color: $eps-indicator-bullet-color;
|
||||
border: $eps-indicator-bullet-border;
|
||||
border-radius: $eps-indicator-bullet-radius;
|
||||
margin-inline-end: $eps-indicator-bullet-spacing;
|
||||
|
||||
&--active {
|
||||
background-color: $eps-indicator-bullet-color-active;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import './preview-iframe.scss';
|
||||
|
||||
export default function PreviewIFrame( props ) {
|
||||
const ref = React.useRef( null ),
|
||||
previewBreakpoint = 1200,
|
||||
[ scale, setScale ] = React.useState( 1 ),
|
||||
[ height, setHeight ] = React.useState( 0 );
|
||||
|
||||
// In order to make sure that the iframe itself show the content in specific viewport,
|
||||
// and it should fit to the size of the card, there is a use of css props `scale` and `height`,
|
||||
// and another element that wraps the iframe to be the guidelines of the iframe sizes.
|
||||
React.useEffect( () => {
|
||||
const currentScale = ref.current.clientWidth / previewBreakpoint;
|
||||
|
||||
setScale( currentScale );
|
||||
setHeight( ref.current.clientHeight / currentScale );
|
||||
}, [] );
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ ref }
|
||||
className={ `site-editor__preview-iframe site-editor__preview-iframe--${ props.templateType }` }
|
||||
>
|
||||
<iframe
|
||||
title="preview"
|
||||
src={ props.src }
|
||||
className={ `site-editor__preview-iframe__iframe` }
|
||||
style={ { transform: `scale(${ scale })`, height, width: previewBreakpoint } }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PreviewIFrame.propTypes = {
|
||||
src: PropTypes.string.isRequired,
|
||||
templateType: PropTypes.string.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
.site-editor__preview-iframe {
|
||||
height: 50vh;
|
||||
position: relative;
|
||||
|
||||
&__iframe {
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
border: none;
|
||||
transform-origin: 0 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--header, &--footer{
|
||||
height: 15vh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
export class BaseContext extends React.Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
||||
this.state = {
|
||||
action: {
|
||||
current: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
errorMeta: {},
|
||||
},
|
||||
|
||||
updateActionState: this.updateActionState.bind( this ),
|
||||
resetActionState: this.resetActionState.bind( this ),
|
||||
};
|
||||
}
|
||||
|
||||
executeAction( name, handler ) {
|
||||
this.updateActionState( { current: name, loading: true, error: null, errorMeta: {} } );
|
||||
|
||||
return handler()
|
||||
.then( ( response ) => {
|
||||
this.resetActionState();
|
||||
|
||||
return Promise.resolve( response );
|
||||
} )
|
||||
.catch( ( error ) => {
|
||||
this.updateActionState( { current: name, loading: false, error: error.message, errorMeta: error } );
|
||||
|
||||
return Promise.reject( error );
|
||||
} );
|
||||
}
|
||||
|
||||
updateActionState( data ) {
|
||||
return this.setState( ( prev ) => ( {
|
||||
action: {
|
||||
...prev.action,
|
||||
...data,
|
||||
},
|
||||
} ) );
|
||||
}
|
||||
|
||||
resetActionState() {
|
||||
this.updateActionState( { current: null, loading: false, error: null, errorMeta: {} } );
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseContext;
|
||||
@@ -0,0 +1,296 @@
|
||||
import Condition from './models/condition';
|
||||
import ConditionsConfig from './services/conditions-config';
|
||||
import BaseContext from './base-context';
|
||||
import { TemplatesConditions, TemplatesConditionsConflicts } from '../data/commands';
|
||||
|
||||
export const Context = React.createContext();
|
||||
|
||||
export class ConditionsProvider extends BaseContext {
|
||||
static propTypes = {
|
||||
children: PropTypes.any.isRequired,
|
||||
currentTemplate: PropTypes.object.isRequired,
|
||||
onConditionsSaved: PropTypes.func.isRequired,
|
||||
validateConflicts: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
validateConflicts: true,
|
||||
};
|
||||
|
||||
static actions = {
|
||||
FETCH_CONFIG: 'fetch-config',
|
||||
SAVE: 'save',
|
||||
CHECK_CONFLICTS: 'check-conflicts',
|
||||
};
|
||||
|
||||
/**
|
||||
* Holds the conditions config object.
|
||||
*
|
||||
* @type {ConditionsConfig}
|
||||
*/
|
||||
conditionsConfig = null;
|
||||
|
||||
/**
|
||||
* ConditionsProvider constructor.
|
||||
*
|
||||
* @param {any} props
|
||||
*/
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
||||
this.state = {
|
||||
...this.state,
|
||||
|
||||
conditions: {},
|
||||
|
||||
updateConditionItemState: this.updateConditionItemState.bind( this ),
|
||||
removeConditionItemInState: this.removeConditionItemInState.bind( this ),
|
||||
createConditionItemInState: this.createConditionItemInState.bind( this ),
|
||||
findConditionItemInState: this.findConditionItemInState.bind( this ),
|
||||
|
||||
saveConditions: this.saveConditions.bind( this ),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the conditions config, then normalize the conditions and then setup titles for
|
||||
* the subIds.
|
||||
*/
|
||||
componentDidMount() {
|
||||
this.executeAction( ConditionsProvider.actions.FETCH_CONFIG, () => ConditionsConfig.create() )
|
||||
.then( ( conditionsConfig ) => this.conditionsConfig = conditionsConfig )
|
||||
.then( this.normalizeConditionsState.bind( this ) )
|
||||
.then( this.setSubIdTitles.bind( this ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a request to save the template conditions.
|
||||
*
|
||||
* @return {any} Saved conditions
|
||||
*/
|
||||
saveConditions() {
|
||||
const conditions = Object.values( this.state.conditions )
|
||||
.map( ( condition ) => condition.forDb() );
|
||||
|
||||
return this.executeAction(
|
||||
ConditionsProvider.actions.SAVE,
|
||||
() => $e.data.update( TemplatesConditions.signature, { conditions }, { id: this.props.currentTemplate.id } ),
|
||||
).then( () => {
|
||||
const contextConditions = Object.values( this.state.conditions )
|
||||
.map( ( condition ) => condition.forContext() );
|
||||
|
||||
this.props.onConditionsSaved( this.props.currentTemplate.id, {
|
||||
conditions: contextConditions,
|
||||
instances: this.conditionsConfig.calculateInstances( Object.values( this.state.conditions ) ),
|
||||
isActive: !! ( Object.keys( this.state.conditions ).length && 'publish' === this.props.currentTemplate.status ),
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for conflicts in the server and mark the condition if there
|
||||
* is a conflict.
|
||||
*
|
||||
* @param {any} condition
|
||||
*/
|
||||
checkConflicts( condition ) {
|
||||
return this.executeAction(
|
||||
ConditionsProvider.actions.CHECK_CONFLICTS,
|
||||
() => $e.data.get( TemplatesConditionsConflicts.signature, {
|
||||
post_id: this.props.currentTemplate.id,
|
||||
condition: condition.clone().toString(),
|
||||
} ),
|
||||
).then( ( response ) =>
|
||||
this.updateConditionItemState( condition.id, { conflictErrors: Object.values( response.data ) }, false ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetching subId titles.
|
||||
*
|
||||
* @param {any} condition
|
||||
* @return {Promise<unknown>} Titles
|
||||
*/
|
||||
fetchSubIdsTitles( condition ) {
|
||||
return new Promise( ( resolve ) => {
|
||||
return elementorCommon.ajax.loadObjects( {
|
||||
action: 'query_control_value_titles',
|
||||
ids: _.isArray( condition.subId ) ? condition.subId : [ condition.subId ],
|
||||
data: {
|
||||
get_titles: condition.subIdAutocomplete,
|
||||
unique_id: elementorCommon.helpers.getUniqueId(),
|
||||
},
|
||||
success( response ) {
|
||||
resolve( response );
|
||||
},
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the conditions from the template and normalize it to data structure
|
||||
* that the components can work with.
|
||||
*/
|
||||
normalizeConditionsState() {
|
||||
this.updateConditionsState( () => {
|
||||
return this.props.currentTemplate.conditions.reduce( ( current, condition ) => {
|
||||
const conditionObj = new Condition( {
|
||||
...condition,
|
||||
default: this.props.currentTemplate.defaultCondition,
|
||||
options: this.conditionsConfig.getOptions(),
|
||||
subOptions: this.conditionsConfig.getSubOptions( condition.name ),
|
||||
subIdAutocomplete: this.conditionsConfig.getSubIdAutocomplete( condition.sub ),
|
||||
supIdOptions: condition.subId ? [ { value: condition.subId, label: condition.subId } ] : [],
|
||||
} );
|
||||
|
||||
return {
|
||||
...current,
|
||||
[ conditionObj.id ]: conditionObj,
|
||||
};
|
||||
}, {} );
|
||||
} ).then( () => {
|
||||
Object.values( this.state.conditions ).forEach( ( condition ) => this.checkConflicts( condition ) );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set titles to the subIds,
|
||||
* for the first render of the component.
|
||||
*/
|
||||
setSubIdTitles() {
|
||||
return Object.values( this.state.conditions ).forEach( ( condition ) => {
|
||||
if ( ! condition.subId ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.fetchSubIdsTitles( condition )
|
||||
.then( ( response ) =>
|
||||
this.updateConditionItemState( condition.id, {
|
||||
subIdOptions: [ {
|
||||
label: Object.values( response )[ 0 ],
|
||||
value: condition.subId,
|
||||
} ],
|
||||
}, false ),
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state of specific condition item.
|
||||
*
|
||||
* @param {any} id
|
||||
* @param {any} args
|
||||
* @param {boolean} shouldCheckConflicts
|
||||
*/
|
||||
updateConditionItemState( id, args, shouldCheckConflicts = true ) {
|
||||
if ( args.name ) {
|
||||
args.subOptions = this.conditionsConfig.getSubOptions( args.name );
|
||||
}
|
||||
|
||||
if ( args.sub || args.name ) {
|
||||
args.subIdAutocomplete = this.conditionsConfig.getSubIdAutocomplete( args.sub );
|
||||
|
||||
// In case that the condition has been changed, it will set the options of the subId
|
||||
// to empty array to let select2 autocomplete handle the options.
|
||||
args.subIdOptions = [];
|
||||
}
|
||||
|
||||
this.updateConditionsState( ( prev ) => {
|
||||
const condition = prev[ id ];
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[ id ]: condition.clone().set( args ),
|
||||
};
|
||||
} ).then( () => {
|
||||
if ( shouldCheckConflicts ) {
|
||||
this.checkConflicts( this.findConditionItemInState( id ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a condition item from the state.
|
||||
*
|
||||
* @param {any} id
|
||||
*/
|
||||
removeConditionItemInState( id ) {
|
||||
this.updateConditionsState( ( prev ) => {
|
||||
const newConditions = { ...prev };
|
||||
|
||||
delete newConditions[ id ];
|
||||
|
||||
return newConditions;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new condition item into the state.
|
||||
*
|
||||
* @param {boolean} shouldCheckConflicts
|
||||
*/
|
||||
createConditionItemInState( shouldCheckConflicts = true ) {
|
||||
const defaultCondition = this.props.currentTemplate.defaultCondition,
|
||||
newCondition = new Condition( {
|
||||
name: defaultCondition,
|
||||
default: defaultCondition,
|
||||
options: this.conditionsConfig.getOptions(),
|
||||
subOptions: this.conditionsConfig.getSubOptions( defaultCondition ),
|
||||
subIdAutocomplete: this.conditionsConfig.getSubIdAutocomplete( '' ),
|
||||
} );
|
||||
|
||||
this.updateConditionsState( ( prev ) => ( { ...prev, [ newCondition.id ]: newCondition } ) )
|
||||
.then( () => {
|
||||
if ( shouldCheckConflicts ) {
|
||||
this.checkConflicts( newCondition );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a condition item from the conditions state.
|
||||
*
|
||||
* @param {any} id
|
||||
* @return {Condition|null} Condition
|
||||
*/
|
||||
findConditionItemInState( id ) {
|
||||
return Object.values( this.state.conditions ).find( ( c ) => c.id === id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the whole conditions state.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @return {Promise<undefined>} Conditions state
|
||||
*/
|
||||
updateConditionsState( callback ) {
|
||||
return new Promise( ( resolve ) =>
|
||||
this.setState( ( prev ) => ( { conditions: callback( prev.conditions ) } ), resolve ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the provider.
|
||||
*
|
||||
* @return {any} Element
|
||||
*/
|
||||
render() {
|
||||
if ( this.state.action.current === ConditionsProvider.actions.FETCH_CONFIG ) {
|
||||
if ( this.state.error ) {
|
||||
return <h3>{ __( 'Error:', 'elementor-pro' ) } { this.state.error }</h3>;
|
||||
}
|
||||
|
||||
if ( this.state.loading ) {
|
||||
return <h3>{ __( 'Loading', 'elementor-pro' ) }...</h3>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Context.Provider value={ this.state }>
|
||||
{ this.props.children }
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ConditionsProvider;
|
||||
@@ -0,0 +1,72 @@
|
||||
export default class Condition {
|
||||
id = elementorCommon.helpers.getUniqueId();
|
||||
default = '';
|
||||
type = 'include';
|
||||
name = '';
|
||||
sub = '';
|
||||
subId = '';
|
||||
options = [];
|
||||
subOptions = [];
|
||||
subIdAutocomplete = [];
|
||||
subIdOptions = [];
|
||||
conflictErrors = [];
|
||||
|
||||
constructor( args ) {
|
||||
this.set( args );
|
||||
}
|
||||
|
||||
set( args ) {
|
||||
Object.assign( this, args );
|
||||
return this;
|
||||
}
|
||||
|
||||
clone() {
|
||||
return Object.assign( new Condition(), this );
|
||||
}
|
||||
|
||||
remove( keys ) {
|
||||
if ( ! Array.isArray( keys ) ) {
|
||||
keys = [ keys ];
|
||||
}
|
||||
|
||||
keys.forEach( ( key ) => {
|
||||
delete this[ key ];
|
||||
} );
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
only( keys ) {
|
||||
if ( ! Array.isArray( keys ) ) {
|
||||
keys = [ keys ];
|
||||
}
|
||||
|
||||
const keysToRemove = Object.keys( this )
|
||||
.filter( ( conditionKey ) => ! keys.includes( conditionKey ) );
|
||||
|
||||
this.remove( keysToRemove );
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return JSON.stringify( this );
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.forDb().filter( ( item ) => item ).join( '/' );
|
||||
}
|
||||
|
||||
forDb() {
|
||||
return [ this.type, this.name, this.sub, this.subId ];
|
||||
}
|
||||
|
||||
forContext() {
|
||||
return {
|
||||
type: this.type,
|
||||
name: this.name,
|
||||
sub: this.sub,
|
||||
subId: this.subId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { ConditionsConfig as ConditionsConfigCommand } from '../../data/commands';
|
||||
|
||||
export class ConditionsConfig {
|
||||
static instance;
|
||||
|
||||
config = null;
|
||||
|
||||
constructor( config ) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<ConditionsConfig>} Conditions config
|
||||
*/
|
||||
static create() {
|
||||
if ( ConditionsConfig.instance ) {
|
||||
return Promise.resolve( ConditionsConfig.instance );
|
||||
}
|
||||
|
||||
return $e.data.get( ConditionsConfigCommand.signature, {}, { refresh: true } )
|
||||
.then( ( response ) => {
|
||||
ConditionsConfig.instance = new ConditionsConfig( response.data );
|
||||
|
||||
return ConditionsConfig.instance;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get main options for condition name.
|
||||
*
|
||||
* @return {Array} Condition options
|
||||
*/
|
||||
getOptions() {
|
||||
return this.getSubOptions( 'general', true )
|
||||
.map( ( { label, value } ) => {
|
||||
return {
|
||||
label,
|
||||
value,
|
||||
};
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sub options for the select.
|
||||
*
|
||||
* @param {string} itemName
|
||||
* @param {boolean} isSubItem
|
||||
* @return {Array} Sub options
|
||||
*/
|
||||
getSubOptions( itemName, isSubItem = false ) {
|
||||
const config = this.config[ itemName ];
|
||||
|
||||
if ( ! config ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{ label: config.all_label, value: isSubItem ? itemName : '' },
|
||||
...config.sub_conditions.map( ( subName ) => {
|
||||
const subConfig = this.config[ subName ];
|
||||
|
||||
return {
|
||||
label: subConfig.label,
|
||||
value: subName,
|
||||
children: subConfig.sub_conditions.length ? this.getSubOptions( subName, true ) : null,
|
||||
};
|
||||
} ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the autocomplete property from the conditions config
|
||||
*
|
||||
* @param {string} sub
|
||||
* @return {{}|any} Conditions autocomplete
|
||||
*/
|
||||
getSubIdAutocomplete( sub ) {
|
||||
const config = this.config[ sub ];
|
||||
|
||||
if ( ! config || ! ( 'object' === typeof ( config.controls ) ) ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const controls = Object.values( config.controls );
|
||||
|
||||
if ( ! controls?.[ 0 ]?.autocomplete ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return controls[ 0 ].autocomplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate instances from the conditions.
|
||||
*
|
||||
* @param {Array} conditions
|
||||
* @return {Object} Conditions Instances
|
||||
*/
|
||||
calculateInstances( conditions ) {
|
||||
let instances = conditions.reduce( ( current, condition ) => {
|
||||
if ( 'exclude' === condition.type ) {
|
||||
return current;
|
||||
}
|
||||
|
||||
const key = condition.sub || condition.name,
|
||||
config = this.config[ key ];
|
||||
|
||||
if ( ! config ) {
|
||||
return current;
|
||||
}
|
||||
|
||||
const instanceLabel = condition.subId
|
||||
? `${ config.label } #${ condition.subId }`
|
||||
: config.all_label;
|
||||
|
||||
return {
|
||||
...current,
|
||||
[ key ]: instanceLabel,
|
||||
};
|
||||
}, {} );
|
||||
|
||||
if ( 0 === Object.keys( instances ).length ) {
|
||||
instances = [ __( 'No instances', 'elementor-pro' ) ];
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
}
|
||||
|
||||
export default ConditionsConfig;
|
||||
@@ -0,0 +1,152 @@
|
||||
import BaseContext from './base-context';
|
||||
import { Templates } from '../data/commands';
|
||||
import Component from '../data/component';
|
||||
|
||||
export const Context = React.createContext();
|
||||
|
||||
export class TemplatesProvider extends BaseContext {
|
||||
static propTypes = {
|
||||
children: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static actions = {
|
||||
FETCH: 'fetch',
|
||||
DELETE: 'delete',
|
||||
UPDATE: 'update',
|
||||
IMPORT: 'import',
|
||||
};
|
||||
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
||||
this.state = {
|
||||
...this.state,
|
||||
action: {
|
||||
...this.state.action,
|
||||
current: TemplatesProvider.actions.FETCH,
|
||||
loading: true,
|
||||
},
|
||||
|
||||
templates: {},
|
||||
|
||||
updateTemplateItemState: this.updateTemplateItemState.bind( this ),
|
||||
findTemplateItemInState: this.findTemplateItemInState.bind( this ),
|
||||
|
||||
fetchTemplates: this.fetchTemplates.bind( this ),
|
||||
deleteTemplate: this.deleteTemplate.bind( this ),
|
||||
updateTemplate: this.updateTemplate.bind( this ),
|
||||
importTemplates: this.importTemplates.bind( this ),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchTemplates();
|
||||
}
|
||||
|
||||
importTemplates( { fileName, fileData } ) {
|
||||
return this.executeAction(
|
||||
TemplatesProvider.actions.IMPORT,
|
||||
() => $e.data.create( Templates.signature, { fileName, fileData } ),
|
||||
).then( ( response ) => {
|
||||
this.updateTemplatesState( ( prev ) => (
|
||||
{
|
||||
...prev,
|
||||
...Object.values( response.data ).reduce(
|
||||
( current, template ) => {
|
||||
if ( ! template.supportsSiteEditor ) {
|
||||
return current;
|
||||
}
|
||||
|
||||
return { ...current, [ template.id ]: template };
|
||||
}, {},
|
||||
),
|
||||
}
|
||||
) );
|
||||
|
||||
return response;
|
||||
} );
|
||||
}
|
||||
|
||||
deleteTemplate( id ) {
|
||||
return this.executeAction(
|
||||
TemplatesProvider.actions.DELETE,
|
||||
() => $e.data.delete( Templates.signature, { id } ),
|
||||
).then( () => {
|
||||
this.updateTemplatesState( ( prev ) => {
|
||||
const newTemplates = { ...prev };
|
||||
|
||||
delete newTemplates[ id ];
|
||||
|
||||
return newTemplates;
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
updateTemplate( id, args ) {
|
||||
return this.executeAction(
|
||||
TemplatesProvider.actions.UPDATE,
|
||||
() => $e.data.update( Templates.signature, args, { id } ),
|
||||
).then( ( response ) => {
|
||||
this.updateTemplateItemState( id, response.data );
|
||||
} );
|
||||
}
|
||||
|
||||
fetchTemplates() {
|
||||
return this.executeAction(
|
||||
TemplatesProvider.actions.FETCH,
|
||||
() => $e.data.get( Templates.signature, {}, { refresh: true } ),
|
||||
).then( ( response ) => {
|
||||
this.updateTemplatesState( () => Object.values( response.data ).reduce(
|
||||
( current, template ) => ( { ...current, [ template.id ]: template } ), {},
|
||||
), false );
|
||||
} );
|
||||
}
|
||||
|
||||
updateTemplateItemState( id, args ) {
|
||||
return this.updateTemplatesState( ( prev ) => {
|
||||
const template = {
|
||||
...prev[ id ],
|
||||
...args,
|
||||
};
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[ id ]: template,
|
||||
};
|
||||
} );
|
||||
}
|
||||
|
||||
updateTemplatesState( callback, clearCache = true ) {
|
||||
if ( clearCache ) {
|
||||
$e.data.deleteCache( $e.components.get( Component.namespace ), Templates.signature );
|
||||
}
|
||||
|
||||
return this.setState( ( prev ) => {
|
||||
return { templates: callback( prev.templates ) };
|
||||
} );
|
||||
}
|
||||
|
||||
findTemplateItemInState( id ) {
|
||||
return this.state.templates[ id ];
|
||||
}
|
||||
|
||||
render() {
|
||||
if ( this.state.action.current === TemplatesProvider.actions.FETCH ) {
|
||||
if ( this.state.action.error ) {
|
||||
return <h3>{ __( 'Error:', 'elementor-pro' ) } { this.state.action.error }</h3>;
|
||||
}
|
||||
|
||||
if ( this.state.action.loading ) {
|
||||
return <h3>{ __( 'Loading', 'elementor-pro' ) }...</h3>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Context.Provider value={ this.state }>
|
||||
{ this.props.children }
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplatesProvider;
|
||||
@@ -0,0 +1,9 @@
|
||||
export class ConditionsConfig extends $e.modules.CommandData {
|
||||
static signature = 'site-editor/conditions-config';
|
||||
|
||||
static getEndpointFormat() {
|
||||
return 'site-editor/conditions-config/{id}';
|
||||
}
|
||||
}
|
||||
|
||||
export default ConditionsConfig;
|
||||
@@ -0,0 +1,4 @@
|
||||
export { Templates } from './templates';
|
||||
export { ConditionsConfig } from './conditions-config';
|
||||
export { TemplatesConditions } from './templates-conditions';
|
||||
export { TemplatesConditionsConflicts } from './templates-conditions-conflicts';
|
||||
@@ -0,0 +1,9 @@
|
||||
export class TemplatesConditionsConflicts extends $e.modules.CommandData {
|
||||
static signature = 'site-editor/templates-conditions-conflicts';
|
||||
|
||||
static getEndpointFormat() {
|
||||
return `${ TemplatesConditionsConflicts.signature }/{id}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplatesConditionsConflicts;
|
||||
@@ -0,0 +1,9 @@
|
||||
export class TemplatesConditions extends $e.modules.CommandData {
|
||||
static signature = 'site-editor/templates-conditions';
|
||||
|
||||
static getEndpointFormat() {
|
||||
return 'site-editor/templates-conditions/{id}';
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplatesConditions;
|
||||
@@ -0,0 +1,9 @@
|
||||
export class Templates extends $e.modules.CommandData {
|
||||
static signature = 'site-editor/templates';
|
||||
|
||||
static getEndpointFormat() {
|
||||
return 'site-editor/templates/{id}';
|
||||
}
|
||||
}
|
||||
|
||||
export default Templates;
|
||||
@@ -0,0 +1,13 @@
|
||||
import * as dataCommands from './commands';
|
||||
|
||||
export default class Component extends $e.modules.ComponentBase {
|
||||
static namespace = 'site-editor';
|
||||
|
||||
getNamespace() {
|
||||
return this.constructor.namespace;
|
||||
}
|
||||
|
||||
defaultData() {
|
||||
return this.importCommands( dataCommands );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import Component from './data/component';
|
||||
import { Templates } from './data/commands';
|
||||
|
||||
export default class Module extends elementorModules.editor.utils.Module {
|
||||
onElementorInit() {
|
||||
const config = elementor.documents.getCurrent().config;
|
||||
|
||||
if ( config.support_site_editor ) {
|
||||
$e.components.register( new Component() );
|
||||
|
||||
$e.data.deleteCache( $e.components.get( Component.namespace ), Templates.signature );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Context as TemplatesContext } from '../context/templates';
|
||||
import useScreenshot, { SCREENSHOT_STATUS_SUCCEED, SCREENSHOT_STATUS_FAILED } from 'modules/screenshots/app/assets/js/hooks/use-screenshot';
|
||||
|
||||
/**
|
||||
* Wrapper function that was made to take screenshots specific for template.
|
||||
* it will capture a screenshot and update the templates context with the new screenshot.
|
||||
*
|
||||
* @param {any} templateType
|
||||
*/
|
||||
export default function useTemplatesScreenshot( templateType = null ) {
|
||||
const { updateTemplateItemState, templates } = React.useContext( TemplatesContext );
|
||||
|
||||
const templatesForScreenshot = Object.values( templates ).filter(
|
||||
( template ) => shouldScreenshotTemplate( template, templateType ),
|
||||
);
|
||||
|
||||
// Start to capture screenshots.
|
||||
const screenshot = useScreenshot( templatesForScreenshot );
|
||||
|
||||
// Update the thumbnail url when screenshot created.
|
||||
React.useEffect( () => {
|
||||
screenshot.posts
|
||||
.filter( ( post ) => post.status === SCREENSHOT_STATUS_SUCCEED )
|
||||
.forEach( ( post ) => updateTemplateItemState( post.id, { thumbnail: post.imageUrl } ) );
|
||||
}, [ screenshot.succeed ] );
|
||||
|
||||
// Update the screenshot url that was failed.
|
||||
// When the user will hit the route on the second time it will avoid trying to take another screenshot.
|
||||
React.useEffect( () => {
|
||||
screenshot.posts
|
||||
.filter( ( post ) => post.status === SCREENSHOT_STATUS_FAILED )
|
||||
.forEach( ( post ) => updateTemplateItemState( post.id, { screenshot_url: null } ) );
|
||||
}, [ screenshot.failed ] );
|
||||
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter handler.
|
||||
* will remove all the drafts and private and also will filter by template type if exists.
|
||||
*
|
||||
* @param {any} template
|
||||
* @param {any} templateType
|
||||
* @return {boolean} should screenshot template
|
||||
*/
|
||||
function shouldScreenshotTemplate( template, templateType = null ) {
|
||||
if ( templateType ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'publish' === template.status &&
|
||||
! template.thumbnail &&
|
||||
template.screenshot_url;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Button } from '@elementor/app-ui';
|
||||
|
||||
import './back-button.scss';
|
||||
|
||||
export default function BackButton( props ) {
|
||||
return (
|
||||
<div className="back-button-wrapper">
|
||||
<Button
|
||||
className="eps-back-button"
|
||||
text={ __( 'Back', 'elementor-pro' ) }
|
||||
icon="eicon-chevron-left"
|
||||
onClick={ props.onClick }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BackButton.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
BackButton.defaultProps = {
|
||||
onClick: () => history.back(),
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
.#{$eps-prefix}back-button {
|
||||
font-size: 14px;
|
||||
margin-block-end: spacing(24);
|
||||
|
||||
.#{$eps-prefix}icon {
|
||||
transform: getValueByDirection(rotate(0deg), rotate(180deg));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { CardBody } from '@elementor/app-ui';
|
||||
import SiteTemplateThumbnail from './site-template-thumbnail';
|
||||
import PreviewIFrame from '../atoms/preview-iframe';
|
||||
|
||||
export const SiteTemplateBody = ( props ) => {
|
||||
return (
|
||||
<CardBody>
|
||||
{
|
||||
props.extended
|
||||
? <PreviewIFrame src={ props.previewUrl } templateType={ props.type } />
|
||||
: (
|
||||
<SiteTemplateThumbnail
|
||||
id={ props.id }
|
||||
title={ props.title }
|
||||
type={ props.type }
|
||||
thumbnail={ props.thumbnail }
|
||||
placeholder={ props.placeholderUrl }
|
||||
/>
|
||||
)
|
||||
}
|
||||
</CardBody>
|
||||
);
|
||||
};
|
||||
|
||||
SiteTemplateBody.propTypes = {
|
||||
extended: PropTypes.bool,
|
||||
id: PropTypes.number,
|
||||
title: PropTypes.string,
|
||||
thumbnail: PropTypes.string,
|
||||
placeholderUrl: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
previewUrl: PropTypes.string,
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Button, CardFooter, Icon, Text } from '@elementor/app-ui';
|
||||
|
||||
export const SiteTemplateFooter = ( props ) => {
|
||||
const instances = Object.values( props.instances ).join( ', ' );
|
||||
|
||||
return (
|
||||
<CardFooter>
|
||||
<div className="e-site-template__instances">
|
||||
<Icon className="eicon-flow" />
|
||||
<Text tag="span" variant="sm"><b>{ __( 'Instances', 'elementor-pro' ) }:</b></Text>
|
||||
<Text className="e-site-template__instances-list" tag="span" variant="xxs"> { instances }</Text>
|
||||
<Button text={ __( 'Edit Conditions', 'elementor-pro' ) }
|
||||
className="e-site-template__edit-conditions"
|
||||
url={ `/site-editor/conditions/${ props.id }` } />
|
||||
</div>
|
||||
</CardFooter>
|
||||
);
|
||||
};
|
||||
|
||||
SiteTemplateFooter.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
instances: PropTypes.any,
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Button, CardHeader, Heading, Icon, Text } from '@elementor/app-ui';
|
||||
import PartActionsButtons from '../part-actions/dialogs-and-buttons';
|
||||
import { Indicator } from '../atoms/indicator-bullet';
|
||||
|
||||
export const SiteTemplateHeader = ( props ) => {
|
||||
const status = props.status && 'publish' !== props.status ? ` (${ props.status })` : '',
|
||||
title = props.title + status,
|
||||
ActionButtons = () => (
|
||||
<>
|
||||
<Button text={ __( 'Edit', 'elementor-pro' ) } icon="eicon-edit" className="e-site-template__edit-btn" size="sm" url={ props.editURL } />
|
||||
<PartActionsButtons { ... props } />
|
||||
</>
|
||||
),
|
||||
MetaDataIcon = ( innerProps ) => (
|
||||
<Text tag="span" className="e-site-template__meta-data">
|
||||
<Icon className={ innerProps.icon } />
|
||||
{ innerProps.content }
|
||||
</Text>
|
||||
),
|
||||
MetaData = () => (
|
||||
<>
|
||||
<MetaDataIcon icon="eicon-user-circle-o" content={ props.author } />
|
||||
<MetaDataIcon icon="eicon-clock-o" content={ props.modifiedDate } />
|
||||
</>
|
||||
),
|
||||
IndicatorDot = props.showInstances ? <Indicator active={ props.isActive } /> : '';
|
||||
|
||||
return (
|
||||
<CardHeader>
|
||||
{ IndicatorDot }
|
||||
<Heading tag="h1" title={ title } variant="text-sm" className="eps-card__headline">{ title }</Heading>
|
||||
{ props.extended && <MetaData /> }
|
||||
{ props.extended && <ActionButtons /> }
|
||||
</CardHeader>
|
||||
);
|
||||
};
|
||||
|
||||
SiteTemplateHeader.propTypes = {
|
||||
isActive: PropTypes.bool,
|
||||
author: PropTypes.string,
|
||||
editURL: PropTypes.string,
|
||||
extended: PropTypes.bool,
|
||||
modifiedDate: PropTypes.string,
|
||||
status: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
showInstances: PropTypes.bool,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Button, CardImage, CardOverlay } from '@elementor/app-ui';
|
||||
|
||||
export default function SiteTemplateThumbnail( props ) {
|
||||
return (
|
||||
<CardImage
|
||||
alt={ props.title }
|
||||
src={ props.thumbnail || props.placeholder }
|
||||
className={ ! props.thumbnail ? 'e-site-template__placeholder' : '' }
|
||||
>
|
||||
<CardOverlay className="e-site-template__overlay-preview">
|
||||
<Button
|
||||
className="e-site-template__overlay-preview-button"
|
||||
text={ __( 'Preview', 'elementor-pro' ) }
|
||||
icon="eicon-preview-medium"
|
||||
url={ `/site-editor/templates/${ props.type }/${ props.id }` }
|
||||
/>
|
||||
</CardOverlay>
|
||||
</CardImage>
|
||||
);
|
||||
}
|
||||
|
||||
SiteTemplateThumbnail.propTypes = {
|
||||
id: PropTypes.number,
|
||||
title: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
thumbnail: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Card } from '@elementor/app-ui';
|
||||
import { SiteTemplateHeader } from './site-template-header';
|
||||
import { SiteTemplateBody } from './site-template-body';
|
||||
import { SiteTemplateFooter } from './site-template-footer';
|
||||
|
||||
import './site-template.scss';
|
||||
|
||||
export default function SiteTemplate( props ) {
|
||||
const baseClassName = 'e-site-template',
|
||||
classes = [ baseClassName ],
|
||||
ref = React.useRef( null );
|
||||
|
||||
React.useEffect( () => {
|
||||
if ( ! props.isSelected ) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.current.scrollIntoView( {
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
} );
|
||||
}, [ props.isSelected ] );
|
||||
|
||||
if ( props.extended ) {
|
||||
classes.push( `${ baseClassName }--extended` );
|
||||
}
|
||||
|
||||
if ( props.aspectRatio ) {
|
||||
classes.push( `${ baseClassName }--${ props.aspectRatio }` );
|
||||
}
|
||||
|
||||
const CardFooter = props.extended && props.showInstances ? <SiteTemplateFooter { ...props } /> : '';
|
||||
|
||||
return (
|
||||
<Card className={ classes.join( ' ' ) } ref={ ref }>
|
||||
<SiteTemplateHeader { ... props } />
|
||||
<SiteTemplateBody { ... props } />
|
||||
{ CardFooter }
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
SiteTemplate.propTypes = {
|
||||
aspectRatio: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
extended: PropTypes.bool,
|
||||
id: PropTypes.number.isRequired,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string,
|
||||
thumbnail: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
type: PropTypes.string.isRequired,
|
||||
showInstances: PropTypes.bool,
|
||||
};
|
||||
|
||||
SiteTemplate.defaultProps = {
|
||||
isSelected: false,
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
$eps-meta-icon-color: tints(400);
|
||||
$eps-meta-icon-dark-color: dark-tints(200);
|
||||
$preview-button-height: spacing(30);
|
||||
$image-aspect-ratio: var(--card-image-aspect-ratio, #{$ratio-portrait});
|
||||
$overlay-preview-padding-block-start: calc(#{$image-aspect-ratio} - #{$preview-button-height});
|
||||
$aspect-ratio-wide: calc(100% * 0.1235);
|
||||
|
||||
:root {
|
||||
--eps-meta-icon-color: #{$eps-meta-icon-color};
|
||||
}
|
||||
|
||||
.eps-theme-dark {
|
||||
--eps-meta-icon-color: #{$eps-meta-icon-dark-color};
|
||||
}
|
||||
|
||||
.e-site-template {
|
||||
&__meta-data {
|
||||
margin-inline-start: spacing(10);
|
||||
@include text-truncate();
|
||||
font-size: type(text, xxs);
|
||||
|
||||
&:last-of-type {
|
||||
margin-inline-end: auto;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
margin-inline-start: spacing(16);
|
||||
}
|
||||
|
||||
.#{$eps-prefix}icon {
|
||||
margin-inline-end: spacing(5);
|
||||
color: var(--eps-meta-icon-color);
|
||||
font-size: type(text, sm);
|
||||
}
|
||||
}
|
||||
|
||||
&__placeholder {
|
||||
.#{$eps-prefix}card__image {
|
||||
filter: var(--placeholder-filter, none);
|
||||
}
|
||||
}
|
||||
|
||||
&__overlay-preview {
|
||||
padding-block-start: $overlay-preview-padding-block-start;
|
||||
position: relative;
|
||||
|
||||
&-button {
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: $preview-button-height;
|
||||
width: 100%;
|
||||
background-color: var(--card-background-color-hover);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-block-start: spacing(10);
|
||||
line-height: spacing(20);
|
||||
--button-background-color: var(--card-headline-color);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-block-start: $overlay-preview-padding-block-start;
|
||||
}
|
||||
|
||||
& > :not(:first-child) {
|
||||
margin-inline-start: spacing(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__edit-btn {
|
||||
margin-inline-end: spacing(20);
|
||||
|
||||
.#{$eps-prefix}icon {
|
||||
margin-inline-end: spacing(5);
|
||||
}
|
||||
}
|
||||
|
||||
&__instances {
|
||||
.#{$eps-prefix}icon {
|
||||
margin-inline-end: spacing(5);
|
||||
color: var(--eps-meta-icon-color);
|
||||
font-size: type(text, sm)
|
||||
}
|
||||
|
||||
&-list {
|
||||
@include text-truncate();
|
||||
}
|
||||
}
|
||||
|
||||
&__edit-conditions {
|
||||
margin-inline-start: spacing(16);
|
||||
text-decoration: underline;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
&--extended {
|
||||
.#{$eps-prefix}card {
|
||||
&__figure {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__headline {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--wide {
|
||||
--card-image-aspect-ratio: #{$aspect-ratio-wide};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { CssGrid, Dialog } from '@elementor/app-ui';
|
||||
import SiteTemplate from '../molecules/site-template';
|
||||
import { PartActionsDialogs } from '../part-actions/dialogs-and-buttons';
|
||||
import { Context as TemplatesContext } from '../context/templates';
|
||||
import useTemplatesScreenshot from '../hooks/use-templates-screenshot';
|
||||
|
||||
export default function SiteTemplates( props ) {
|
||||
const { templates: contextTemplates, action, resetActionState } = React.useContext( TemplatesContext );
|
||||
let gridColumns, templates;
|
||||
|
||||
// Make the templates object a memorize value, will re run again only if
|
||||
// templates has been changed, also sort the templates by `isActive`.
|
||||
templates = React.useMemo( () => {
|
||||
return Object.values( contextTemplates )
|
||||
.sort( ( a, b ) => {
|
||||
// This sort make sure to show first the active templates, second the
|
||||
// inactive templates that are not draft, and then the drafts,
|
||||
// in each category it sorts it inside by date.
|
||||
|
||||
if ( ! b.isActive && ! a.isActive ) {
|
||||
if (
|
||||
( 'draft' === b.status && 'draft' === a.status ) ||
|
||||
( 'draft' !== b.status && 'draft' !== a.status )
|
||||
) {
|
||||
return b.date < a.date ? 1 : -1;
|
||||
}
|
||||
|
||||
return 'draft' === a.status ? 1 : -1;
|
||||
}
|
||||
|
||||
if ( b.isActive && a.isActive ) {
|
||||
return b.date < a.date ? 1 : -1;
|
||||
}
|
||||
|
||||
return b.isActive ? 1 : -1;
|
||||
} );
|
||||
}, [ contextTemplates ] );
|
||||
|
||||
// Start to capture screenshots.
|
||||
useTemplatesScreenshot( props.type );
|
||||
|
||||
const siteTemplateConfig = {};
|
||||
|
||||
if ( props.type ) {
|
||||
templates = templates.filter( ( item ) => item.type === props.type );
|
||||
siteTemplateConfig.extended = true;
|
||||
siteTemplateConfig.type = props.type;
|
||||
|
||||
switch ( props.type ) {
|
||||
case 'header':
|
||||
case 'footer':
|
||||
gridColumns = 1;
|
||||
siteTemplateConfig.aspectRatio = 'wide';
|
||||
break;
|
||||
default:
|
||||
gridColumns = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! templates || ! templates.length ) {
|
||||
return <h3>{ __( 'No Templates found. Want to create one?', 'elementor-pro' ) }...</h3>;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="e-site-editor__site-templates">
|
||||
<PartActionsDialogs />
|
||||
{
|
||||
action.error &&
|
||||
<Dialog
|
||||
text={ action.error }
|
||||
dismissButtonText={ __( 'Go Back', 'elementor-pro' ) }
|
||||
dismissButtonOnClick={ resetActionState }
|
||||
approveButtonText={ __( 'Learn More', 'elementor-pro' ) }
|
||||
approveButtonColor="link"
|
||||
approveButtonUrl="https://go.elementor.com/app-theme-builder-template-load-issue"
|
||||
approveButtonTarget="_target"
|
||||
/>
|
||||
}
|
||||
<CssGrid columns={ gridColumns } spacing={ 24 } colMinWidth={ 200 }>
|
||||
{
|
||||
templates.map( ( item ) =>
|
||||
<SiteTemplate
|
||||
key={ item.id }
|
||||
{ ... item }
|
||||
{ ... siteTemplateConfig }
|
||||
isSelected={ parseInt( props.id ) === item.id } />,
|
||||
)
|
||||
}
|
||||
</CssGrid>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
SiteTemplates.propTypes = {
|
||||
type: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { AddNewButton, Heading, Grid, CardOverlay } from '@elementor/app-ui';
|
||||
import { SiteParts } from '@elementor/site-editor';
|
||||
import './add-new.scss';
|
||||
import { Context as TemplatesContext } from '../context/templates';
|
||||
import BackButton from '../molecules/back-button';
|
||||
import useFeatureLock from 'elementor-pro-app/hooks/use-feature-lock';
|
||||
|
||||
export default function AddNew() {
|
||||
const { templates } = React.useContext( TemplatesContext ),
|
||||
hasTemplates = 1 <= Object.keys( templates ).length;
|
||||
|
||||
const { isLocked, ConnectButton } = useFeatureLock( 'site-editor' );
|
||||
|
||||
/**
|
||||
* An hover element for each site part.
|
||||
*
|
||||
* @param {any} props
|
||||
*/
|
||||
const HoverElement = ( props ) => {
|
||||
if ( isLocked ) {
|
||||
return (
|
||||
<CardOverlay className="e-site-editor__promotion-overlay">
|
||||
<div className="e-site-editor__promotion-overlay__link">
|
||||
<i className="e-site-editor__promotion-overlay__icon eicon-lock" />
|
||||
</div>
|
||||
</CardOverlay>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<a href={ props.urls.create } className="eps-card__image-overlay eps-add-new__overlay">
|
||||
<AddNewButton hideText={ true } />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
HoverElement.propTypes = {
|
||||
urls: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="e-site-editor__add-new">
|
||||
<Grid container direction="column" className="e-site-editor__header">
|
||||
{ hasTemplates && <Grid item><BackButton /></Grid> }
|
||||
<Grid item container justify="space-between" alignItems="start">
|
||||
<Heading variant="h1">{ __( 'Start customizing every part of your site', 'elementor-pro' ) }</Heading>
|
||||
{ isLocked && <ConnectButton /> }
|
||||
</Grid>
|
||||
</Grid>
|
||||
<SiteParts hoverElement={ HoverElement } />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.eps-add-new__overlay {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 1;
|
||||
--card-image-overlay-background-color: transparent;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Button, Text } from '@elementor/app-ui';
|
||||
|
||||
export default function ConditionConflicts( props ) {
|
||||
if ( ! props.conflicts.length ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const conflictLinks = props.conflicts.map( ( conflict ) => {
|
||||
return (
|
||||
<Button
|
||||
key={ conflict.template_id }
|
||||
target="_blank"
|
||||
url={ conflict.edit_url }
|
||||
text={ conflict.template_title }
|
||||
/>
|
||||
);
|
||||
} );
|
||||
|
||||
return (
|
||||
<Text className="e-site-editor-conditions__conflict" variant="sm">
|
||||
{ __( 'Elementor recognized that you have set this location for other templates: ', 'elementor-pro' ) } { conflictLinks }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
ConditionConflicts.propTypes = {
|
||||
conflicts: PropTypes.array.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Select } from '@elementor/app-ui';
|
||||
|
||||
export default function ConditionName( props ) {
|
||||
// Hide for template types that has another default, like single & archive.
|
||||
if ( 'general' !== props.default ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const onChange = ( e ) => props.updateConditions( props.id, { name: e.target.value, sub: '', subId: '' } );
|
||||
|
||||
return (
|
||||
<div className="e-site-editor-conditions__input-wrapper">
|
||||
<Select options={ props.options } value={ props.name } onChange={ onChange } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ConditionName.propTypes = {
|
||||
updateConditions: PropTypes.func.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
options: PropTypes.array.isRequired,
|
||||
default: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
ConditionName.defaultProps = {
|
||||
name: '',
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Select2 } from '@elementor/app-ui';
|
||||
|
||||
/**
|
||||
* Main component.
|
||||
*
|
||||
* @param {any} props
|
||||
* @return {any} Element
|
||||
* @class
|
||||
*/
|
||||
export default function ConditionSubId( props ) {
|
||||
const settings = React.useMemo( () => (
|
||||
Object.keys( props.subIdAutocomplete ).length
|
||||
? getSettings( props.subIdAutocomplete )
|
||||
: null
|
||||
), [ props.subIdAutocomplete ] );
|
||||
|
||||
if ( ! props.sub || ! settings ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const onChange = ( e ) => props.updateConditions( props.id, { subId: e.target.value } );
|
||||
|
||||
return (
|
||||
<div className="e-site-editor-conditions__input-wrapper">
|
||||
<Select2
|
||||
onChange={ onChange }
|
||||
value={ props.subId }
|
||||
settings={ settings }
|
||||
options={ props.subIdOptions }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings for the select2 base on the autocomplete settings,
|
||||
* that passes as a prop
|
||||
*
|
||||
* @param {any} autocomplete
|
||||
* @return {Object} Settings
|
||||
*/
|
||||
function getSettings( autocomplete ) {
|
||||
return {
|
||||
allowClear: false,
|
||||
placeholder: __( 'All', 'elementor-pro' ),
|
||||
dir: elementorCommon.config.isRTL ? 'rtl' : 'ltr',
|
||||
ajax: {
|
||||
transport( params, success, failure ) {
|
||||
return elementorCommon.ajax.addRequest( 'pro_panel_posts_control_filter_autocomplete', {
|
||||
data: {
|
||||
q: params.data.q,
|
||||
autocomplete,
|
||||
},
|
||||
success,
|
||||
error: failure,
|
||||
} );
|
||||
},
|
||||
data( params ) {
|
||||
return {
|
||||
q: params.term,
|
||||
page: params.page,
|
||||
};
|
||||
},
|
||||
cache: true,
|
||||
},
|
||||
escapeMarkup( markup ) {
|
||||
return markup;
|
||||
},
|
||||
minimumInputLength: 1,
|
||||
};
|
||||
}
|
||||
|
||||
ConditionSubId.propTypes = {
|
||||
subIdAutocomplete: PropTypes.object,
|
||||
id: PropTypes.string.isRequired,
|
||||
sub: PropTypes.string,
|
||||
subId: PropTypes.string,
|
||||
updateConditions: PropTypes.func,
|
||||
subIdOptions: PropTypes.array,
|
||||
};
|
||||
|
||||
ConditionSubId.defaultProps = {
|
||||
subId: '',
|
||||
subIdOptions: [],
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Select } from '@elementor/app-ui';
|
||||
|
||||
export default function ConditionSub( props ) {
|
||||
if ( 'general' === props.name || ! props.subOptions.length ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const onChange = ( e ) => props.updateConditions( props.id, { sub: e.target.value, subId: '' } );
|
||||
|
||||
return (
|
||||
<div className="e-site-editor-conditions__input-wrapper">
|
||||
<Select options={ props.subOptions } value={ props.sub } onChange={ onChange } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ConditionSub.propTypes = {
|
||||
updateConditions: PropTypes.func.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
sub: PropTypes.string.isRequired,
|
||||
subOptions: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
ConditionSub.defaultProps = {
|
||||
sub: '',
|
||||
subOptions: {},
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Select } from '@elementor/app-ui';
|
||||
|
||||
export default function ConditionType( props ) {
|
||||
const wrapperRef = React.createRef();
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: __( 'Include', 'elementor-pro' ),
|
||||
value: 'include',
|
||||
},
|
||||
{
|
||||
label: __( 'Exclude', 'elementor-pro' ),
|
||||
value: 'exclude',
|
||||
},
|
||||
];
|
||||
|
||||
const onChange = ( e ) => {
|
||||
props.updateConditions( props.id, { type: e.target.value } );
|
||||
};
|
||||
|
||||
React.useEffect( () => {
|
||||
wrapperRef.current.setAttribute( 'data-elementor-condition-type', props.type );
|
||||
} );
|
||||
|
||||
return (
|
||||
<div className="e-site-editor-conditions__input-wrapper e-site-editor-conditions__input-wrapper--condition-type" ref={ wrapperRef }>
|
||||
<Select options={ options } value={ props.type } onChange={ onChange } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ConditionType.propTypes = {
|
||||
updateConditions: PropTypes.func.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
ConditionType.defaultProps = {
|
||||
type: '',
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
$e-site-editor-conditions-header-image-button-spacing: spacing(44);
|
||||
$e-site-editor-conditions-header-image-width: px-to-rem(70);
|
||||
|
||||
$e-site-editor-conditions-rows-max-width: px-to-rem(700);
|
||||
$e-site-editor-conditions-rows-y-spacing: spacing(44);
|
||||
|
||||
$e-site-editor-conditions-row-block-start-spacing: spacing(12);
|
||||
|
||||
$e-site-editor-conditions-remove-condition-color: tints(400);
|
||||
$e-site-editor-conditions-remove-condition-font-size: type(size, '18');
|
||||
|
||||
$e-site-editor-conditions-row-controls-spacing-end: spacing(10);
|
||||
$e-site-editor-conditions-row-controls-background: theme-colors(light);
|
||||
$e-site-editor-conditions-row-controls-dark-background: dark-tints(600);
|
||||
$e-site-editor-conditions-row-controls-radius: $eps-radius;
|
||||
|
||||
$e-site-editor-conditions-row-controls-border: $eps-border-width $eps-border-style tints(100);
|
||||
$e-site-editor-conditions-row-controls-dark-border: $eps-border-width $eps-border-style tints(700);
|
||||
//$e-site-editor-conditions-row-controls-dark-border: $eps-border-width $eps-border-style tints(725); //merge after 3.12 is out
|
||||
$e-site-editor-conditions-row-controls-error-border: $eps-border-width $eps-border-style theme-colors(danger);
|
||||
|
||||
$e-site-editor-conditions-conflict-block-start-spacing: spacing(5);
|
||||
$e-site-editor-conditions-conflict-color: theme-colors(danger);
|
||||
|
||||
$e-site-editor-add-button-margin-block-start: spacing(44);
|
||||
$e-site-editor-add-button-background-color: tints(500);
|
||||
$e-site-editor-add-button-background-dark-color: dark-tints(500);
|
||||
$e-site-editor-add-button-color: theme-colors(light);
|
||||
$e-site-editor-add-button-color-hover-background-color: tints(600);
|
||||
$e-site-editor-add-button-color-hover-dark-background-color: dark-tints(600);
|
||||
$e-site-editor-add-button-color-hover-color: theme-colors(light);
|
||||
|
||||
$e-site-editor-save-button-container-spacing: spacing(8);
|
||||
|
||||
$e-site-editor-input-wrapper-border-width: $eps-border-width;
|
||||
$e-site-editor-input-wrapper-border-style: $eps-border-style;
|
||||
$e-site-editor-input-wrapper-border-color: tints(100);
|
||||
$e-site-editor-input-wrapper-border-dark-color: dark-tints(700);
|
||||
//$e-site-editor-input-wrapper-border-dark-color: dark-tints(725); //merge after 3.12 is out
|
||||
$e-site-editor-input-wrapper-select-font-size: type(size, "12");
|
||||
$e-site-editor-input-wrapper-select-height: px-to-rem(40);
|
||||
$e-site-editor-input-wrapper-select-y-padding: spacing(10);
|
||||
$e-site-editor-input-wrapper-select-color: tints(700);
|
||||
//$e-site-editor-input-wrapper-select-color: tints(725); //merge after 3.12 is out
|
||||
$e-site-editor-input-wrapper-select-dark-color: dark-tints(200);
|
||||
$e-site-editor-input-wrapper-select-arrow-font-size: type(size, "12");
|
||||
$e-site-editor-input-wrapper-select-arrow-margin-end: spacing(10);
|
||||
|
||||
$e-site-editor-input-wrapper-condition-type-icon-start-spacing: spacing(12);
|
||||
$e-site-editor-input-wrapper-condition-type-icon-font-size: type(size, "15");
|
||||
$e-site-editor-input-wrapper-condition-type-icon-color: theme-colors(light);
|
||||
$e-site-editor-input-wrapper-condition-type-icon-z-index: z-index(dropdown);
|
||||
$e-site-editor-input-wrapper-condition-type-arrow-color: theme-colors(light);
|
||||
$e-site-editor-input-wrapper-condition-type-start-padding: px-to-rem(34);
|
||||
$e-site-editor-input-wrapper-condition-type-width: px-to-rem(120);
|
||||
$e-site-editor-input-wrapper-condition-type-font-size: type(size, '12');
|
||||
$e-site-editor-input-wrapper-condition-type-color: theme-colors(light);
|
||||
$e-site-editor-input-wrapper-condition-include-background-color: tints(500);
|
||||
$e-site-editor-input-wrapper-condition-include-background-dark-color: dark-tints(600);
|
||||
$e-site-editor-input-wrapper-condition-exclude-background-color: tints(400);
|
||||
$e-site-editor-input-wrapper-condition-exclude-background-dark-color: dark-tints(600);
|
||||
|
||||
$e-site-editor-input-select2-search-field-color: theme-elements-colors(text-base-color);
|
||||
$e-site-editor-input-select2-search-field-dark-color: theme-colors(light);
|
||||
|
||||
:root {
|
||||
--e-site-editor-conditions-row-controls-background: #{$e-site-editor-conditions-row-controls-background};
|
||||
--e-site-editor-input-wrapper-border-color: #{$e-site-editor-input-wrapper-border-color};
|
||||
--e-site-editor-input-wrapper-select-color: #{$e-site-editor-input-wrapper-select-color};
|
||||
--e-site-editor-conditions-row-controls-border: #{$e-site-editor-conditions-row-controls-border};
|
||||
--e-site-editor-add-button-background-color: #{$e-site-editor-add-button-background-color};
|
||||
--e-site-editor-add-button-color-hover-background-color: #{$e-site-editor-add-button-color-hover-background-color};
|
||||
--e-site-editor-input-wrapper-condition-include-background-color:
|
||||
#{$e-site-editor-input-wrapper-condition-include-background-color};
|
||||
--e-site-editor-input-wrapper-condition-exclude-background-color:
|
||||
#{$e-site-editor-input-wrapper-condition-exclude-background-color};
|
||||
|
||||
--e-site-editor-input-select2-search-field-color: #{$e-site-editor-input-select2-search-field-color}
|
||||
}
|
||||
|
||||
.eps-theme-dark {
|
||||
--select2-selection-background-color: tints(600);
|
||||
--e-site-editor-conditions-row-controls-background: #{$e-site-editor-conditions-row-controls-dark-background};
|
||||
--e-site-editor-input-wrapper-border-color: #{$e-site-editor-input-wrapper-border-dark-color};
|
||||
--e-site-editor-input-wrapper-select-color: #{$e-site-editor-input-wrapper-select-dark-color};
|
||||
--e-site-editor-conditions-row-controls-border: #{$e-site-editor-conditions-row-controls-dark-border};
|
||||
--e-site-editor-add-button-background-color: #{$e-site-editor-add-button-background-dark-color};
|
||||
--e-site-editor-add-button-color-hover-background-color: #{$e-site-editor-add-button-color-hover-dark-background-color};
|
||||
--e-site-editor-input-wrapper-condition-include-background-color:
|
||||
#{$e-site-editor-input-wrapper-condition-include-background-dark-color};
|
||||
--e-site-editor-input-wrapper-condition-exclude-background-color:
|
||||
#{$e-site-editor-input-wrapper-condition-exclude-background-dark-color};
|
||||
--e-site-editor-input-select2-search-field-color: #{$e-site-editor-input-select2-search-field-dark-color}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Context as ConditionsContext, ConditionsProvider } from '../../context/conditions';
|
||||
import { Button, Dialog } from '@elementor/app-ui';
|
||||
import ConditionType from './condition-type';
|
||||
import ConditionName from './condition-name';
|
||||
import ConditionSub from './condition-sub';
|
||||
import ConditionSubId from './condition-sub-id';
|
||||
import ConditionConflicts from './condition-conflicts';
|
||||
|
||||
export default function ConditionsRows( props ) {
|
||||
const {
|
||||
conditions,
|
||||
createConditionItemInState: create,
|
||||
updateConditionItemState: update,
|
||||
removeConditionItemInState: remove,
|
||||
saveConditions: save,
|
||||
action,
|
||||
resetActionState,
|
||||
} = React.useContext( ConditionsContext );
|
||||
|
||||
const rows = Object.values( conditions ).map( ( condition ) =>
|
||||
<div key={ condition.id }>
|
||||
<div className="e-site-editor-conditions__row">
|
||||
<div
|
||||
className={ `e-site-editor-conditions__row-controls ${ condition.conflictErrors.length && 'e-site-editor-conditions__row-controls--error' }` }>
|
||||
<ConditionType { ...condition } updateConditions={ update } />
|
||||
<div className="e-site-editor-conditions__row-controls-inner">
|
||||
<ConditionName { ...condition } updateConditions={ update } />
|
||||
<ConditionSub { ...condition } updateConditions={ update } />
|
||||
<ConditionSubId { ...condition } updateConditions={ update } />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="e-site-editor-conditions__remove-condition"
|
||||
text={ __( 'Delete', 'elementor-pro' ) }
|
||||
icon="eicon-close"
|
||||
hideText={ true }
|
||||
onClick={ () => remove( condition.id ) }
|
||||
/>
|
||||
</div>
|
||||
<ConditionConflicts conflicts={ condition.conflictErrors } />
|
||||
</div>,
|
||||
);
|
||||
|
||||
const isSaving = action.current === ConditionsProvider.actions.SAVE && action.loading;
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
action.error &&
|
||||
<Dialog
|
||||
text={ action.error }
|
||||
dismissButtonText={ __( 'Go Back', 'elementor-pro' ) }
|
||||
dismissButtonOnClick={ resetActionState }
|
||||
approveButtonText={ __( 'Learn More', 'elementor-pro' ) }
|
||||
approveButtonColor="link"
|
||||
approveButtonUrl="https://go.elementor.com/app-theme-builder-conditions-load-issue"
|
||||
approveButtonTarget="_target"
|
||||
/>
|
||||
}
|
||||
<div className="e-site-editor-conditions__rows">
|
||||
{ rows }
|
||||
</div>
|
||||
<div className="e-site-editor-conditions__add-button-container">
|
||||
<Button
|
||||
className="e-site-editor-conditions__add-button"
|
||||
variant="contained"
|
||||
size="lg"
|
||||
text={ __( 'Add Condition', 'elementor-pro' ) }
|
||||
onClick={ create }
|
||||
/>
|
||||
</div>
|
||||
<div className="e-site-editor-conditions__footer">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="lg"
|
||||
hideText={ isSaving }
|
||||
icon={ isSaving ? 'eicon-loading eicon-animation-spin' : '' }
|
||||
text={ __( 'Save & Close', 'elementor-pro' ) }
|
||||
onClick={ () => save().then( props.onAfterSave ) }
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ConditionsRows.propTypes = {
|
||||
onAfterSave: PropTypes.func,
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Heading, Text } from '@elementor/app-ui';
|
||||
import ConditionsProvider from '../../context/conditions';
|
||||
import { Context as TemplatesContext } from '../../context/templates';
|
||||
import ConditionsRows from './conditions-rows';
|
||||
|
||||
import './conditions.scss';
|
||||
import BackButton from '../../molecules/back-button';
|
||||
|
||||
export default function Conditions( props ) {
|
||||
const { findTemplateItemInState, updateTemplateItemState } = React.useContext( TemplatesContext ),
|
||||
template = findTemplateItemInState( parseInt( props.id ) );
|
||||
|
||||
if ( ! template ) {
|
||||
return <div>{ __( 'Not Found', 'elementor-pro' ) }</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="e-site-editor-conditions">
|
||||
<BackButton />
|
||||
<div className="e-site-editor-conditions__header">
|
||||
<img
|
||||
className="e-site-editor-conditions__header-image"
|
||||
src={ `${ elementorAppProConfig.baseUrl }/modules/theme-builder/assets/images/conditions-tab.svg` }
|
||||
alt={ __( 'Import template', 'elementor-pro' ) }
|
||||
/>
|
||||
<Heading variant="h1" tag="h1">
|
||||
{ __( 'Where Do You Want to Display Your Template?', 'elementor-pro' ) }
|
||||
</Heading>
|
||||
<Text variant="p">
|
||||
{ __( 'Set the conditions that determine where your template is used throughout your site.', 'elementor-pro' ) }
|
||||
<br />
|
||||
{ __( 'For example, choose \'Entire Site\' to display the template across your site.', 'elementor-pro' ) }
|
||||
</Text>
|
||||
</div>
|
||||
<ConditionsProvider currentTemplate={ template } onConditionsSaved={ updateTemplateItemState }>
|
||||
<ConditionsRows onAfterSave={ () => history.back() } />
|
||||
</ConditionsProvider>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Conditions.propTypes = {
|
||||
id: PropTypes.string,
|
||||
};
|
||||
@@ -0,0 +1,187 @@
|
||||
@import "conditions-api";
|
||||
|
||||
.e-site-editor-conditions {
|
||||
&__header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__header-image {
|
||||
display: block;
|
||||
margin: 0 auto $e-site-editor-conditions-header-image-button-spacing;
|
||||
width: $e-site-editor-conditions-header-image-width;
|
||||
}
|
||||
|
||||
&__rows {
|
||||
margin: $e-site-editor-conditions-rows-y-spacing auto;
|
||||
max-width: $e-site-editor-conditions-rows-max-width;
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
margin-block-start: $e-site-editor-conditions-row-block-start-spacing;
|
||||
}
|
||||
|
||||
&__remove-condition {
|
||||
color: $e-site-editor-conditions-remove-condition-color;
|
||||
font-size: $e-site-editor-conditions-remove-condition-font-size;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__row-controls {
|
||||
overflow: hidden;
|
||||
margin-inline-end: $e-site-editor-conditions-row-controls-spacing-end;
|
||||
background-color: var(--e-site-editor-conditions-row-controls-background);
|
||||
display: flex;
|
||||
width: 100%;
|
||||
border: var(--e-site-editor-conditions-row-controls-border);
|
||||
border-radius: $e-site-editor-conditions-row-controls-radius;
|
||||
|
||||
&--error {
|
||||
border: $e-site-editor-conditions-row-controls-error-border;
|
||||
}
|
||||
}
|
||||
|
||||
&__conflict {
|
||||
text-align: center;
|
||||
margin-block-start: $e-site-editor-conditions-conflict-block-start-spacing;
|
||||
color: $e-site-editor-conditions-conflict-color;
|
||||
}
|
||||
|
||||
&__row-controls-inner {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
div {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__add-button-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__add-button {
|
||||
margin-block-start: $e-site-editor-add-button-margin-block-start;
|
||||
background-color: var(--e-site-editor-add-button-background-color);
|
||||
color: $e-site-editor-add-button-color;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--e-site-editor-add-button-color-hover-background-color);
|
||||
color: $e-site-editor-add-button-color-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
padding: $e-site-editor-save-button-container-spacing;
|
||||
border-block-start: 1px solid var(--hr-color);
|
||||
}
|
||||
|
||||
&__input-wrapper {
|
||||
position: relative;
|
||||
padding-inline-start: $e-site-editor-input-wrapper-border-width $e-site-editor-input-wrapper-border-style;
|
||||
border-color: var(--e-site-editor-input-wrapper-border-color);
|
||||
|
||||
&:first-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
font-size: $e-site-editor-input-wrapper-select-font-size;
|
||||
height: $e-site-editor-input-wrapper-select-height;
|
||||
border-width: 0;
|
||||
padding: 0 $e-site-editor-input-wrapper-select-y-padding;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
color: var(--e-site-editor-input-wrapper-select-color);
|
||||
outline: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&:after {
|
||||
font-family: eicons;
|
||||
content: '\e8ad';
|
||||
font-size: $e-site-editor-input-wrapper-select-arrow-font-size;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
@include end($e-site-editor-input-wrapper-select-arrow-margin-end);
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single {
|
||||
border: none;
|
||||
line-height: $e-site-editor-input-wrapper-select-height;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||
line-height: $e-site-editor-input-wrapper-select-height;
|
||||
font-size: $e-site-editor-input-wrapper-select-font-size;
|
||||
}
|
||||
|
||||
.select2-selection {
|
||||
outline: none;
|
||||
background: transparent;
|
||||
height: $e-site-editor-input-wrapper-select-height;
|
||||
}
|
||||
|
||||
.select2-selection__arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__input-wrapper--condition-type {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
font-family: eicons;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
@include start($e-site-editor-input-wrapper-condition-type-icon-start-spacing);
|
||||
font-size: $e-site-editor-input-wrapper-condition-type-icon-font-size;
|
||||
//color: $e-site-editor-input-wrapper-condition-type-icon-color;
|
||||
pointer-events: none;
|
||||
z-index: $e-site-editor-input-wrapper-condition-type-icon-z-index;
|
||||
}
|
||||
|
||||
select {
|
||||
text-transform: uppercase;
|
||||
padding-inline-start: $e-site-editor-input-wrapper-condition-type-start-padding;
|
||||
width: $e-site-editor-input-wrapper-condition-type-width;
|
||||
font-size: $e-site-editor-input-wrapper-condition-type-font-size;
|
||||
border-inline-end: $e-site-editor-input-wrapper-border-width $e-site-editor-input-wrapper-border-style;
|
||||
border-color: var(--e-site-editor-input-wrapper-border-color);
|
||||
}
|
||||
|
||||
&[data-elementor-condition-type="include"] {
|
||||
&:before {
|
||||
content: '\e8cc';
|
||||
}
|
||||
}
|
||||
|
||||
&[data-elementor-condition-type="exclude"] {
|
||||
&:before {
|
||||
content: '\e8cd';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a temporary fix that handles dark mode in select2.
|
||||
// TODO: Remove it if already handled by the Select2 component.
|
||||
.select2-search__field {
|
||||
background-color: transparent;
|
||||
color: var(--e-site-editor-input-select2-search-field-color);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { DropZone, Dialog, Checkbox } from '@elementor/app-ui';
|
||||
import { Context as TemplatesContext, TemplatesProvider } from '../context/templates';
|
||||
import BackButton from '../molecules/back-button';
|
||||
import { useConfirmAction as useConfirmActionBase } from '@elementor/hooks';
|
||||
|
||||
// The hook `useConfirmAction` comes from the core plugin, so it is possible that it is not available.
|
||||
const useConfirmActionFallback = ( { action } ) => ( {
|
||||
runAction: action,
|
||||
dialog: { isOpen: false },
|
||||
} );
|
||||
|
||||
const useConfirmAction = useConfirmActionBase ?? useConfirmActionFallback;
|
||||
|
||||
export default function Import() {
|
||||
const { importTemplates, action, resetActionState } = React.useContext( TemplatesContext ),
|
||||
[ importedTemplate, setImportedTemplate ] = React.useState( null ),
|
||||
isImport = action.current === TemplatesProvider.actions.IMPORT,
|
||||
isUploading = isImport && action.loading,
|
||||
hasError = isImport && action.error;
|
||||
|
||||
const upload = React.useCallback( ( file ) => {
|
||||
if ( isUploading ) {
|
||||
return;
|
||||
}
|
||||
|
||||
readFile( file )
|
||||
.then( ( fileData ) => importTemplates( { fileName: file.name, fileData } ) )
|
||||
.then( ( response ) => {
|
||||
// For now it show a dialog for the first template ONLY!
|
||||
setImportedTemplate( response.data[ 0 ] );
|
||||
} );
|
||||
}, [] );
|
||||
|
||||
const {
|
||||
runAction: uploadFile,
|
||||
dialog,
|
||||
checkbox,
|
||||
} = useConfirmAction( {
|
||||
doNotShowAgainKey: 'upload_json_warning_generic_message',
|
||||
action: upload,
|
||||
} );
|
||||
|
||||
return (
|
||||
<section className="site-editor__import">
|
||||
{
|
||||
importedTemplate &&
|
||||
<Dialog
|
||||
title={ __( 'Your template was imported', 'elementor-pro' ) }
|
||||
approveButtonText={ __( 'Preview', 'elementor-pro' ) }
|
||||
approveButtonUrl={ importedTemplate.url }
|
||||
approveButtonTarget="_blank"
|
||||
dismissButtonText={ __( 'Edit', 'elementor-pro' ) }
|
||||
dismissButtonUrl={ importedTemplate.editURL }
|
||||
dismissButtonTarget="_top"
|
||||
onClose={ () => setImportedTemplate( null ) }
|
||||
/>
|
||||
}
|
||||
{
|
||||
hasError &&
|
||||
<Dialog
|
||||
title={ action.error }
|
||||
approveButtonText={ __( 'Learn More', 'elementor-pro' ) }
|
||||
approveButtonUrl="https://go.elementor.com/app-theme-builder-import-issue"
|
||||
approveButtonTarget="_blank"
|
||||
approveButtonColor="link"
|
||||
dismissButtonText={ __( 'Go Back', 'elementor-pro' ) }
|
||||
dismissButtonOnClick={ resetActionState }
|
||||
onClose={ resetActionState }
|
||||
/>
|
||||
}
|
||||
{
|
||||
dialog.isOpen &&
|
||||
<Dialog
|
||||
title={ __( 'Warning: JSON or ZIP files may be unsafe', 'elementor-pro' ) }
|
||||
text={ __( 'Uploading JSON or ZIP files from unknown sources can be harmful and put your site at risk. For maximum safety, upload only JSON or ZIP files from trusted sources.', 'elementor-pro' ) }
|
||||
approveButtonColor="link"
|
||||
approveButtonText={ __( 'Continue', 'elementor-pro' ) }
|
||||
approveButtonOnClick={ dialog.approve }
|
||||
dismissButtonText={ __( 'Cancel', 'elementor-pro' ) }
|
||||
dismissButtonOnClick={ dialog.dismiss }
|
||||
onClose={ dialog.dismiss }
|
||||
>
|
||||
<label htmlFor="do-not-show-upload-json-warning-again" style={ { display: 'flex', alignItems: 'center', gap: '5px' } }>
|
||||
<Checkbox
|
||||
id="do-not-show-upload-json-warning-again"
|
||||
type="checkbox"
|
||||
value={ checkbox.isChecked }
|
||||
onChange={ ( event ) => checkbox.setIsChecked( !! event.target.checked ) }
|
||||
/>
|
||||
{ __( 'Do not show this message again', 'elementor-pro' ) }
|
||||
</label>
|
||||
</Dialog>
|
||||
}
|
||||
<BackButton />
|
||||
<DropZone
|
||||
heading={ __( 'Import Template To Your Library', 'elementor-pro' ) }
|
||||
text={ __( 'Drag & Drop your .JSON or .zip template file', 'elementor-pro' ) }
|
||||
secondaryText={ __( 'or', 'elementor-pro' ) }
|
||||
onFileSelect={ uploadFile }
|
||||
isLoading={ isUploading }
|
||||
filetypes={ [ 'zip', 'json' ] }
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function readFile( file ) {
|
||||
return new Promise( ( ( resolve ) => {
|
||||
const fileReader = new FileReader();
|
||||
|
||||
fileReader.readAsDataURL( file );
|
||||
|
||||
fileReader.onload = ( event ) => {
|
||||
// Replace the mime type that prepended to the base64 with empty string and return a
|
||||
// resolved promise only with the base64 string.
|
||||
resolve( event.target.result.replace( /^[^,]+,/, '' ) );
|
||||
};
|
||||
} ) );
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { TemplateTypesContext } from '@elementor/site-editor';
|
||||
import { AddNewButton, Grid, Heading, NotFound } from '@elementor/app-ui';
|
||||
import SiteTemplates from '../organisms/site-templates';
|
||||
import useFeatureLock from 'elementor-pro-app/hooks/use-feature-lock';
|
||||
|
||||
import './template-type.scss';
|
||||
|
||||
export default function TemplateType( props ) {
|
||||
const { templateTypes } = React.useContext( TemplateTypesContext ),
|
||||
currentType = templateTypes.find( ( item ) => item.type === props.type ),
|
||||
{ isLocked, ConnectButton } = useFeatureLock( 'site-editor' );
|
||||
|
||||
if ( ! currentType ) {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={ `e-site-editor__templates e-site-editor__templates--type-${ props.type }` }>
|
||||
<Grid className="page-header" container justify="space-between">
|
||||
<Heading variant="h1">{ currentType.page_title }</Heading>
|
||||
{
|
||||
isLocked
|
||||
? <ConnectButton />
|
||||
: <AddNewButton url={ currentType.urls.create } text={ __( 'Add New', 'elementor-pro' ) } />
|
||||
}
|
||||
</Grid>
|
||||
<hr className="eps-separator" />
|
||||
<SiteTemplates type={ currentType.type } id={ props.id } />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
TemplateType.propTypes = {
|
||||
type: PropTypes.string,
|
||||
page_title: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
.e-site-editor__templates {
|
||||
.page-header {
|
||||
margin-block-end: spacing(10);
|
||||
|
||||
> a {
|
||||
align-self: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.eps-separator {
|
||||
margin-block-end: spacing(44);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import SiteTemplates from '../organisms/site-templates';
|
||||
import { AddNewButton, Grid } from '@elementor/app-ui';
|
||||
import useFeatureLock from 'elementor-pro-app/hooks/use-feature-lock';
|
||||
|
||||
export default function Templates() {
|
||||
const { isLocked, ConnectButton } = useFeatureLock( 'site-editor' );
|
||||
|
||||
return (
|
||||
<section className="e-site-editor__site-templates">
|
||||
<Grid container justify="space-between" alignItems="start" className="page-header">
|
||||
<h1>{ __( 'Your Site\'s Global Parts', 'elementor-pro' ) }</h1>
|
||||
{
|
||||
isLocked
|
||||
? <ConnectButton />
|
||||
: <AddNewButton url="/site-editor/add-new" />
|
||||
}
|
||||
</Grid>
|
||||
<hr className="eps-separator" />
|
||||
<SiteTemplates />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Dialog } from '@elementor/app-ui';
|
||||
import { Context as TemplatesContext } from '../context/templates';
|
||||
|
||||
export default function DialogDelete( props ) {
|
||||
const { deleteTemplate, findTemplateItemInState } = React.useContext( TemplatesContext );
|
||||
|
||||
const closeDialog = ( shouldUpdate ) => {
|
||||
props.setId( null );
|
||||
|
||||
if ( shouldUpdate ) {
|
||||
deleteTemplate( props.id );
|
||||
}
|
||||
};
|
||||
|
||||
if ( ! props.id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const template = findTemplateItemInState( props.id );
|
||||
return (
|
||||
<Dialog
|
||||
title={ __( 'Move Item To Trash', 'elementor-pro' ) }
|
||||
text={ __( 'Are you sure you want to move this item to trash:', 'elementor-pro' ) + ` "${ template.title }"` }
|
||||
onSubmit={ () => closeDialog( true ) }
|
||||
approveButtonText={ __( 'Move to Trash', 'elementor-pro' ) }
|
||||
approveButtonOnClick={ () => closeDialog( true ) }
|
||||
approveButtonColor="danger"
|
||||
dismissButtonText={ __( 'Cancel', 'elementor-pro' ) }
|
||||
dismissButtonOnClick={ () => closeDialog() }
|
||||
onClose={ () => closeDialog() }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
DialogDelete.propTypes = {
|
||||
id: PropTypes.number,
|
||||
setId: PropTypes.func.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Dialog } from '@elementor/app-ui';
|
||||
import { Context as TemplatesContext } from '../context/templates';
|
||||
|
||||
export default function DialogRename( props ) {
|
||||
const { findTemplateItemInState, updateTemplate } = React.useContext( TemplatesContext ),
|
||||
template = findTemplateItemInState( props.id );
|
||||
|
||||
const [ title, setTitle ] = React.useState( '' );
|
||||
|
||||
useEffect( () => {
|
||||
// The "title" state should be updated if the template title changed.
|
||||
if ( template ) {
|
||||
setTitle( template.title );
|
||||
}
|
||||
}, [ template ] );
|
||||
|
||||
const closeDialog = ( shouldUpdate ) => {
|
||||
props.setId( null );
|
||||
|
||||
if ( shouldUpdate ) {
|
||||
updateTemplate( props.id, { post_title: title } );
|
||||
}
|
||||
};
|
||||
|
||||
if ( ! props.id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={ __( 'Rename Site Part', 'elementor-pro' ) }
|
||||
approveButtonText={ __( 'Change', 'elementor-pro' ) }
|
||||
onSubmit={ () => closeDialog( true ) }
|
||||
approveButtonOnClick={ () => closeDialog( true ) }
|
||||
approveButtonColor="primary"
|
||||
dismissButtonText={ __( 'Cancel', 'elementor-pro' ) }
|
||||
dismissButtonOnClick={ () => closeDialog() }
|
||||
onClose={ () => closeDialog() }
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
className="eps-input eps-input-text eps-input--block"
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
value={ title }
|
||||
onChange={ ( e ) => setTitle( e.target.value ) }
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
DialogRename.propTypes = {
|
||||
id: PropTypes.number,
|
||||
setId: PropTypes.func.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import DialogRename from './dialog-rename';
|
||||
import DialogDelete from './dialog-delete';
|
||||
import { Button, Popover } from '@elementor/app-ui';
|
||||
|
||||
export const handlers = {
|
||||
rename: null,
|
||||
delete: null,
|
||||
};
|
||||
|
||||
// TODO: Think about refactor to portals: https://reactjs.org/docs/portals.html
|
||||
export function PartActionsDialogs() {
|
||||
const [ DialogRenameId, setDialogRenameId ] = React.useState( null );
|
||||
const [ DialogDeleteId, setDialogDeleteId ] = React.useState( null );
|
||||
|
||||
handlers.rename = setDialogRenameId;
|
||||
handlers.delete = setDialogDeleteId;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogRename id={ DialogRenameId } setId={ setDialogRenameId } />
|
||||
<DialogDelete id={ DialogDeleteId } setId={ setDialogDeleteId } />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PartActionsButtons( props ) {
|
||||
const [ showMenu, setShowMenu ] = React.useState( false );
|
||||
|
||||
let SiteTemplatePopover = '';
|
||||
|
||||
if ( showMenu ) {
|
||||
SiteTemplatePopover = (
|
||||
<Popover closeFunction={ () => setShowMenu( ! showMenu ) }>
|
||||
<li>
|
||||
<Button
|
||||
className="eps-popover__item"
|
||||
icon="eicon-sign-out"
|
||||
text={ __( 'Export', 'elementor-pro' ) }
|
||||
url={ props.exportLink }
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<Button
|
||||
className="eps-popover__item eps-popover__item--danger"
|
||||
icon="eicon-trash-o"
|
||||
text={ __( 'Trash', 'elementor-pro' ) }
|
||||
onClick={ () => handlers.delete( props.id ) }
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<Button
|
||||
className="eps-popover__item"
|
||||
icon="eicon-edit"
|
||||
text={ __( 'Rename', 'elementor-pro' ) }
|
||||
onClick={ () => handlers.rename( props.id ) }
|
||||
/>
|
||||
</li>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="eps-popover__container">
|
||||
<Button
|
||||
text={ __( 'Toggle', 'elementor-pro' ) }
|
||||
hideText={ true }
|
||||
icon="eicon-ellipsis-h"
|
||||
size="lg"
|
||||
onClick={ () => setShowMenu( ! showMenu ) }
|
||||
/>
|
||||
{ SiteTemplatePopover }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PartActionsButtons.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
exportLink: PropTypes.string.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Router, LocationProvider, Redirect } from '@reach/router';
|
||||
import Templates from './pages/templates';
|
||||
import TemplateType from './pages/template-type';
|
||||
import AddNew from './pages/add-new';
|
||||
import Conditions from './pages/conditions/conditions';
|
||||
import Import from './pages/import';
|
||||
import TemplatesProvider, { Context as TemplatesContext } from './context/templates';
|
||||
import { Layout, AllPartsButton, NotFound } from '@elementor/site-editor';
|
||||
import { ErrorBoundary, Grid, Button } from '@elementor/app-ui';
|
||||
import router from '@elementor/router';
|
||||
import Component from './data/component';
|
||||
import useFeatureLock from 'elementor-pro-app/hooks/use-feature-lock';
|
||||
|
||||
import './site-editor.scss';
|
||||
|
||||
function SiteEditor() {
|
||||
const { isLocked } = useFeatureLock( 'site-editor' );
|
||||
|
||||
const basePath = 'site-editor';
|
||||
const headerButtons = [
|
||||
{
|
||||
id: 'import',
|
||||
text: __( 'import', 'elementor-pro' ),
|
||||
hideText: true,
|
||||
icon: 'eicon-upload-circle-o',
|
||||
onClick: () => router.appHistory.navigate( basePath + '/import' ),
|
||||
},
|
||||
];
|
||||
|
||||
// Remove Core cache.
|
||||
elementorCommon.ajax.invalidateCache( {
|
||||
unique_id: 'app_site_editor_template_types',
|
||||
} );
|
||||
|
||||
const SiteEditorDefault = () => {
|
||||
const { templates } = React.useContext( TemplatesContext );
|
||||
|
||||
if ( Object.keys( templates ).length ) {
|
||||
return <Redirect from={ '/' } to={ '/' + basePath + '/templates' } noThrow={ true } />;
|
||||
}
|
||||
|
||||
return <Redirect from={ '/' } to={ '/' + basePath + '/add-new' } noThrow={ true } />;
|
||||
};
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
title={ __( 'Theme Builder could not be loaded', 'elementor-pro' ) }
|
||||
learnMoreUrl="https://go.elementor.com/app-theme-builder-load-issue"
|
||||
>
|
||||
<Layout allPartsButton={ <AllPartsButton url={ '/' + basePath } /> } headerButtons={ headerButtons } titleRedirectRoute={ '/' + basePath } promotion={ isLocked }>
|
||||
<Grid container className="e-site-editor__content_container">
|
||||
<Grid item className="e-site-editor__content_container_main">
|
||||
<TemplatesProvider>
|
||||
<LocationProvider history={ router.appHistory }>
|
||||
<Router>
|
||||
<SiteEditorDefault path={ basePath } />
|
||||
<Templates path={ basePath + '/templates' } />
|
||||
<TemplateType path={ basePath + '/templates/:type/*id' } />
|
||||
<AddNew path={ basePath + '/add-new' } />
|
||||
<Conditions path={ basePath + '/conditions/:id' } />
|
||||
<Import path={ basePath + '/import' } />
|
||||
<NotFound default />
|
||||
</Router>
|
||||
</LocationProvider>
|
||||
</TemplatesProvider>
|
||||
</Grid>
|
||||
<Grid item className="e-site-editor__content_container_secondary">
|
||||
<Button
|
||||
text={ __( 'Switch to table view', 'elementor-pro' ) }
|
||||
url={ elementorAppProConfig[ 'site-editor' ]?.urls?.legacy_view }
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Layout>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export default class Module {
|
||||
constructor() {
|
||||
elementorCommon.debug.addURLToWatch( 'elementor-pro/assets' );
|
||||
|
||||
$e.components.register( new Component() );
|
||||
|
||||
router.addRoute( {
|
||||
path: '/site-editor/*',
|
||||
component: SiteEditor,
|
||||
} );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
.e-site-editor__content_container {
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.e-site-editor__content_container_main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.e-site-editor__content_container_secondary {
|
||||
margin: 0 auto;
|
||||
padding-block-start: spacing(44);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\SiteEditor\Data;
|
||||
|
||||
use ElementorPro\Plugin;
|
||||
use Elementor\Data\Base\Controller as Controller_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Controller extends Controller_Base {
|
||||
public function get_name() {
|
||||
return 'site-editor';
|
||||
}
|
||||
|
||||
public function register_endpoints() {
|
||||
$this->register_endpoint( Endpoints\Templates::class );
|
||||
$this->register_endpoint( Endpoints\Conditions_Config::class );
|
||||
$this->register_endpoint( Endpoints\Templates_Conditions::class );
|
||||
$this->register_endpoint( Endpoints\Templates_Conditions_Conflicts::class );
|
||||
}
|
||||
|
||||
public function get_permission_callback( $request ) {
|
||||
return Plugin::elementor()->kits_manager->get_active_kit()->is_editable_by_current_user();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\SiteEditor\Data\Endpoints;
|
||||
|
||||
use Elementor\Data\Base\Endpoint;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Base_Endpoint extends Endpoint {
|
||||
/**
|
||||
* Check if post is lock.
|
||||
*
|
||||
* @param $post_id
|
||||
*
|
||||
* @return bool|false|int
|
||||
*/
|
||||
protected function is_post_lock( $post_id ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/post.php';
|
||||
|
||||
return wp_check_post_lock( $post_id );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\SiteEditor\Data\Endpoints;
|
||||
|
||||
use ElementorPro\Modules\ThemeBuilder\Module as ThemeBuilderModule;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Conditions_Config extends Base_Endpoint {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'conditions-config';
|
||||
}
|
||||
|
||||
public function get_items( $request ) {
|
||||
$conditions_manager = ThemeBuilderModule::instance()->get_conditions_manager();
|
||||
|
||||
return $conditions_manager->get_conditions_config();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\SiteEditor\Data\Endpoints;
|
||||
|
||||
use ElementorPro\Plugin;
|
||||
use ElementorPro\Core\App\Modules\SiteEditor\Module;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Template_Types extends Base_Endpoint {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'template-types';
|
||||
}
|
||||
|
||||
public function get_items( $request ) {
|
||||
/** @var Module $site_editor_module */
|
||||
$site_editor_module = Plugin::instance()->app->get_component( 'site-editor' );
|
||||
|
||||
return $site_editor_module->get_template_types();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\SiteEditor\Data\Endpoints;
|
||||
|
||||
use ElementorPro\Plugin;
|
||||
use ElementorPro\Modules\ThemeBuilder\Module;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Templates_Conditions_Conflicts extends Base_Endpoint {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'templates-conditions-conflicts';
|
||||
}
|
||||
|
||||
public function get_items( $request ) {
|
||||
/** @var Module $theme_builder */
|
||||
$theme_builder = Plugin::instance()->modules_manager->get_modules( 'theme-builder' );
|
||||
|
||||
return $theme_builder
|
||||
->get_conditions_manager()
|
||||
->get_conditions_conflicts( intval( $request['post_id'] ), $request['condition'] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\SiteEditor\Data\Endpoints;
|
||||
|
||||
use ElementorPro\Plugin;
|
||||
use Elementor\Core\Utils\Exceptions;
|
||||
use ElementorPro\Modules\ThemeBuilder\Module;
|
||||
use ElementorPro\Core\App\Modules\SiteEditor\Data\Responses\Lock_Error_Response;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Templates_Conditions extends Base_Endpoint {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'templates-conditions';
|
||||
}
|
||||
|
||||
protected function register() {
|
||||
$this->register_item_route();
|
||||
$this->register_item_route( \WP_REST_Server::EDITABLE );
|
||||
}
|
||||
|
||||
public function get_item( $template_id, $request ) {
|
||||
return $this->get_conditions( $template_id );
|
||||
}
|
||||
|
||||
public function update_item( $template_id, $request ) {
|
||||
$lock_by_user_id = $this->is_post_lock( $template_id );
|
||||
|
||||
if ( $lock_by_user_id ) {
|
||||
return new Lock_Error_Response( $lock_by_user_id );
|
||||
}
|
||||
|
||||
$data = $request->get_body_params();
|
||||
|
||||
if ( ! isset( $data['conditions'] ) ) {
|
||||
$data['conditions'] = [];
|
||||
}
|
||||
|
||||
$is_saved = $this->save_conditions( $template_id, $data['conditions'] );
|
||||
|
||||
if ( ! $is_saved ) {
|
||||
return new \WP_Error(
|
||||
'conditions',
|
||||
__( 'Error while saving conditions.', 'elementor-pro' ),
|
||||
[ 'status' => Exceptions::INTERNAL_SERVER_ERROR ]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function get_conditions( $post_id ) {
|
||||
$document = \Elementor\Plugin::$instance->documents->get( $post_id );
|
||||
|
||||
/** @var Module $theme_builder */
|
||||
$theme_builder = Plugin::instance()->modules_manager->get_modules( 'theme-builder' );
|
||||
|
||||
return $theme_builder
|
||||
->get_conditions_manager()
|
||||
->get_document_conditions( $document );
|
||||
}
|
||||
|
||||
protected function save_conditions( $post_id, $conditions ) {
|
||||
/** @var Module $theme_builder */
|
||||
$theme_builder = Plugin::instance()->modules_manager->get_modules( 'theme-builder' );
|
||||
|
||||
$is_saved = $theme_builder
|
||||
->get_conditions_manager()
|
||||
->save_conditions( $post_id, $conditions );
|
||||
|
||||
if ( ! $is_saved ) {
|
||||
return new \WP_Error(
|
||||
'conditions_save',
|
||||
__( 'Cannot save those conditions.', 'elementor-pro' ),
|
||||
[ 'status' => Exceptions::INTERNAL_SERVER_ERROR ]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\SiteEditor\Data\Endpoints;
|
||||
|
||||
use ElementorPro\Plugin;
|
||||
use Elementor\Core\Utils\Exceptions;
|
||||
use Elementor\TemplateLibrary\Manager as TemplateManager;
|
||||
use ElementorPro\Modules\ThemeBuilder\Documents\Theme_Document;
|
||||
use ElementorPro\Modules\ThemeBuilder\Classes\Conditions_Manager;
|
||||
use ElementorPro\Modules\ThemeBuilder\Module as ThemeBuilderModule;
|
||||
use ElementorPro\Modules\ThemeBuilder\Classes\Templates_Types_Manager;
|
||||
use ElementorPro\Core\App\Modules\SiteEditor\Render_Mode_Template_Preview;
|
||||
use ElementorPro\Core\App\Modules\SiteEditor\Data\Responses\Lock_Error_Response;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Templates extends Base_Endpoint {
|
||||
|
||||
/**
|
||||
* @var TemplateManager
|
||||
*/
|
||||
private $templates_manager;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $document_types;
|
||||
|
||||
public function __construct( $controller ) {
|
||||
parent::__construct( $controller );
|
||||
|
||||
$this->templates_manager = Plugin::elementor()->templates_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'templates';
|
||||
}
|
||||
|
||||
protected function register() {
|
||||
parent::register();
|
||||
|
||||
$this->register_item_route( \WP_REST_Server::DELETABLE );
|
||||
$this->register_item_route( \WP_REST_Server::EDITABLE );
|
||||
$this->register_items_route( \WP_REST_Server::CREATABLE );
|
||||
}
|
||||
|
||||
public function get_items( $request ) {
|
||||
$templates = $this->templates_manager->get_source( 'local' )->get_items( [
|
||||
'type' => array_keys( $this->get_documents_types() ),
|
||||
'post_status' => 'any',
|
||||
'orderby' => 'post_date',
|
||||
'order' => 'DESC',
|
||||
] );
|
||||
|
||||
return $this->normalize_templates_json( $templates );
|
||||
}
|
||||
|
||||
public function create_items( $request ) {
|
||||
$response = $this->templates_manager->import_template( $request->get_body_params() );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new \WP_Error( 'file', $response->get_error_message(), [ 'status' => Exceptions::BAD_REQUEST ] );
|
||||
}
|
||||
|
||||
return $this->normalize_templates_json( $response );
|
||||
}
|
||||
|
||||
public function update_item( $id, $request ) {
|
||||
$lock_by_user_id = $this->is_post_lock( $id );
|
||||
|
||||
if ( $lock_by_user_id ) {
|
||||
return new Lock_Error_Response( $lock_by_user_id );
|
||||
}
|
||||
|
||||
wp_update_post( array_merge( [
|
||||
'ID' => $id,
|
||||
], $request->get_body_params() ) );
|
||||
|
||||
return $this->normalize_template_json_item(
|
||||
$this->templates_manager->get_source( 'local' )->get_item( $id )
|
||||
);
|
||||
}
|
||||
|
||||
public function delete_item( $id, $request ) {
|
||||
$lock_by_user_id = $this->is_post_lock( $id );
|
||||
|
||||
if ( $lock_by_user_id ) {
|
||||
return new Lock_Error_Response( $lock_by_user_id );
|
||||
}
|
||||
|
||||
return ! ! wp_trash_post( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function get_documents_types() {
|
||||
if ( ! $this->document_types ) {
|
||||
/** @var Templates_Types_Manager $types_manager */
|
||||
$types_manager = ThemeBuilderModule::instance()->get_types_manager();
|
||||
|
||||
$this->document_types = $types_manager->get_types_config( [
|
||||
'support_site_editor' => true,
|
||||
] );
|
||||
}
|
||||
|
||||
return $this->document_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $templates
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function normalize_templates_json( $templates ) {
|
||||
return array_map( [ $this, 'normalize_template_json_item' ], $templates );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $template
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function normalize_template_json_item( $template ) {
|
||||
/** @var Conditions_Manager $conditions_manager */
|
||||
$conditions_manager = Plugin::instance()->modules_manager->get_modules( 'theme-builder' )->get_conditions_manager();
|
||||
|
||||
/** @var Theme_Document $document */
|
||||
$document = Plugin::elementor()->documents->get( $template['template_id'] );
|
||||
|
||||
$supports_site_editor = $document::get_property( 'support_site_editor' );
|
||||
|
||||
// Supports also a non site editor parts.
|
||||
if ( ! $supports_site_editor ) {
|
||||
return [
|
||||
'id' => $template['template_id'],
|
||||
'url' => $template['url'],
|
||||
'editURL' => $document->get_edit_url(),
|
||||
'supportsSiteEditor' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$types = $this->get_documents_types();
|
||||
|
||||
$template['instances'] = $conditions_manager->get_document_instances( $template['template_id'] );
|
||||
$template['defaultCondition'] = $types[ $template['type'] ]['condition_type'];
|
||||
|
||||
$has_instances = ! empty( $template['instances'] );
|
||||
$is_active = false;
|
||||
|
||||
if ( ! $has_instances ) {
|
||||
$template['instances'] = [ 'no_instances' => esc_html__( 'No instances', 'elementor-pro' ) ];
|
||||
} else {
|
||||
$is_active = 'publish' === $template['status'];
|
||||
}
|
||||
|
||||
if ( ! $template['thumbnail'] ) {
|
||||
$template['thumbnail'] = '';
|
||||
}
|
||||
|
||||
$site_editor_config = $document->get_site_editor_config();
|
||||
|
||||
$data = array_merge( $template, [
|
||||
'id' => $template['template_id'],
|
||||
'exportLink' => $template['export_link'],
|
||||
'modifiedDate' => $template['human_modified_date'],
|
||||
'editURL' => $document->get_edit_url(),
|
||||
'conditions' => array_map( function ( $condition ) {
|
||||
return array_merge( $condition, [
|
||||
'sub' => $condition['sub_name'],
|
||||
'subId' => $condition['sub_id'],
|
||||
] );
|
||||
}, $conditions_manager->get_document_conditions( $document ) ),
|
||||
'isActive' => $is_active,
|
||||
'type' => $this->calculate_template_type( $template['type'], $template['instances'] ),
|
||||
'previewUrl' => $this->get_preview_url( $template['template_id'] ),
|
||||
'placeholderUrl' => $site_editor_config['urls']['thumbnail'],
|
||||
'pageLayout' => $site_editor_config['page_layout'],
|
||||
'supportsSiteEditor' => true,
|
||||
'showInstances' => $site_editor_config['show_instances'],
|
||||
] );
|
||||
|
||||
/**
|
||||
* Template data.
|
||||
*
|
||||
* Filters the data returned by Elementor API as JSON.
|
||||
*
|
||||
* By default Elementor API returns data in a JSON format that enables the
|
||||
* builder to work properly. This hook allows developers to alter the data
|
||||
* returned by the API to add new elements.
|
||||
*
|
||||
* @param array $data Template data.
|
||||
*/
|
||||
$data = apply_filters( 'elementor-pro/site-editor/data/template', $data );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $type
|
||||
* @param $instances
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function calculate_template_type( $type, $instances ) {
|
||||
$condition_to_type_map = [
|
||||
'front_page' => 'single-page',
|
||||
'child_of' => 'single-page',
|
||||
'page' => 'single-page',
|
||||
'not_found404' => 'error-404',
|
||||
'search' => 'search-results',
|
||||
];
|
||||
|
||||
// "single" type was split into "single-page", "single-post" and "404".
|
||||
// this section supports all the old templates that was created as "single".
|
||||
if ( 'single' === $type ) {
|
||||
// By default show it under single-post.
|
||||
$type = 'single-post';
|
||||
|
||||
foreach ( $instances as $condition_name => $condition_label ) {
|
||||
if ( isset( $condition_to_type_map[ $condition_name ] ) ) {
|
||||
$type = $condition_to_type_map[ $condition_name ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
private function get_preview_url( $post_id ) {
|
||||
return Render_Mode_Template_Preview::get_url( $post_id );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\SiteEditor\Data\Responses;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Lock_Error_Response extends \WP_Error {
|
||||
public function __construct( $user_id ) {
|
||||
$user = get_user_by( 'ID', $user_id );
|
||||
|
||||
parent::__construct(
|
||||
'post_lock',
|
||||
sprintf(
|
||||
/* translators: %s: User display name. */
|
||||
esc_html__( '%s is currently editing this template, please try again later', 'elementor-pro' ),
|
||||
$user->display_name
|
||||
),
|
||||
[
|
||||
'status' => 403,
|
||||
'locked_by_user_id' => $user_id,
|
||||
'locked_by_user_name' => $user->display_name,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\SiteEditor;
|
||||
|
||||
use Elementor\Core\Admin\Menu\Admin_Menu_Manager;
|
||||
use Elementor\Core\Experiments\Manager as ExperimentsManager;
|
||||
use Elementor\Core\Frontend\Render_Mode_Manager;
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
use ElementorPro\Core\App\Modules\SiteEditor\Data\Controller;
|
||||
use ElementorPro\Core\Behaviors\Feature_Lock;
|
||||
use ElementorPro\Modules\ThemeBuilder\AdminMenuItems\Theme_Builder_Menu_Item;
|
||||
use ElementorPro\Modules\ThemeBuilder\Module as Theme_Builder_Table_View;
|
||||
use ElementorPro\Modules\ThemeBuilder\Module as ThemeBuilderModule;
|
||||
use ElementorPro\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Site Editor Module
|
||||
*
|
||||
* Responsible for initializing Elementor Pro App functionality
|
||||
*/
|
||||
class Module extends BaseModule {
|
||||
/**
|
||||
* @var Feature_Lock
|
||||
*/
|
||||
private $lock;
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'site-editor';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function get_template_types() {
|
||||
// Same as admin menu capabilities.
|
||||
if ( ! current_user_can( 'publish_posts' ) ) {
|
||||
throw new \Exception( 'Access denied' );
|
||||
}
|
||||
|
||||
$document_types = Plugin::elementor()->documents->get_document_types( [
|
||||
'support_site_editor' => true,
|
||||
] );
|
||||
|
||||
// Keep 404 at end of array.
|
||||
$error_404 = $document_types['error-404'];
|
||||
unset( $document_types['error-404'] );
|
||||
$document_types['error-404'] = $error_404;
|
||||
|
||||
// Currently the `single` itself is not supported in site editor.
|
||||
// Don't use `support_site_editor=false` in order to support documents that extend it.
|
||||
unset( $document_types['single'] );
|
||||
|
||||
$types = [];
|
||||
|
||||
foreach ( $document_types as $type => $class ) {
|
||||
$types[] = $class::get_site_editor_config();
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register ajax actions.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param Ajax $ajax
|
||||
*/
|
||||
public function register_ajax_actions( Ajax $ajax ) {
|
||||
$ajax->register_ajax_action( 'app_site_editor_template_types', [ $this, 'get_template_types' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Render_Mode_Manager $manager
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function register_render_mode( Render_Mode_Manager $manager ) {
|
||||
$manager->register_render_mode( Render_Mode_Template_Preview::class );
|
||||
}
|
||||
|
||||
protected function get_init_settings() {
|
||||
$settings = [
|
||||
'urls' => [
|
||||
'legacy_view' => add_query_arg( 'tabs_group', ThemeBuilderModule::ADMIN_LIBRARY_TAB_GROUP, admin_url( Source_Local::ADMIN_MENU_SLUG ) ),
|
||||
],
|
||||
'utms' => [
|
||||
'utm_source' => 'theme-builder',
|
||||
'utm_medium' => 'wp-dash',
|
||||
],
|
||||
];
|
||||
|
||||
if ( $this->lock->is_locked() ) {
|
||||
$settings['lock'] = $this->lock->get_config();
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function add_default_new_site_editor_experiments( ExperimentsManager $manager ) {
|
||||
$manager->add_feature( [
|
||||
'name' => 'theme_builder_v2',
|
||||
'title' => __( 'Default to New Theme Builder', 'elementor-pro' ),
|
||||
'description' => __( 'Entering the Theme Builder through WP Dashboard > Templates > Theme Builder opens the New theme builder by default. But don’t worry, you can always view the WP styled version of the screen with a simple click of a button.', 'elementor-pro' ),
|
||||
'release_status' => ExperimentsManager::RELEASE_STATUS_STABLE,
|
||||
'default' => ExperimentsManager::STATE_ACTIVE,
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site editor url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_site_editor_url() : string {
|
||||
return Plugin::elementor()->app->get_base_url() . '#/site-editor';
|
||||
}
|
||||
|
||||
private function register_site_editor_menu() {
|
||||
$experiments_manager = Plugin::elementor()->experiments;
|
||||
|
||||
// Unique case when the experiments manager is not initialized yet.
|
||||
if ( ! $experiments_manager || ! $experiments_manager->is_feature_active( 'theme_builder_v2' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the old theme builder link and add the new one.
|
||||
remove_submenu_page(
|
||||
Source_Local::ADMIN_MENU_SLUG,
|
||||
add_query_arg( 'tabs_group', ThemeBuilderModule::ADMIN_LIBRARY_TAB_GROUP, Source_Local::ADMIN_MENU_SLUG )
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
Source_Local::ADMIN_MENU_SLUG,
|
||||
'',
|
||||
__( 'Theme Builder', 'elementor-pro' ),
|
||||
'publish_posts',
|
||||
$this->get_site_editor_url()
|
||||
);
|
||||
}
|
||||
|
||||
private function register_admin_menu( Admin_Menu_Manager $admin_menu_manager ) {
|
||||
$experiments_manager = Plugin::elementor()->experiments;
|
||||
|
||||
// Unique case when the experiments manager is not initialized yet.
|
||||
if ( ! $experiments_manager || ! $experiments_manager->is_feature_active( 'theme_builder_v2' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$admin_menu_manager->unregister( add_query_arg( 'tabs_group', ThemeBuilderModule::ADMIN_LIBRARY_TAB_GROUP, Source_Local::ADMIN_MENU_SLUG ) );
|
||||
|
||||
$admin_menu_manager->register(
|
||||
$this->get_site_editor_url(),
|
||||
new Theme_Builder_Menu_Item()
|
||||
);
|
||||
}
|
||||
|
||||
private function add_finder_item( array $categories ) {
|
||||
if ( ! Plugin::elementor()->experiments->is_feature_active( 'theme_builder_v2' ) ) {
|
||||
return $categories;
|
||||
}
|
||||
|
||||
// Replace the old theme builder "create-new" link with the new site-editor.
|
||||
$categories['create']['items']['theme-template'] = [
|
||||
'title' => __( 'Add New Theme Template', 'elementor-pro' ),
|
||||
'icon' => 'plus-circle-o',
|
||||
'url' => $this->get_site_editor_url() . '/add-new',
|
||||
'keywords' => [ 'template', 'theme', 'new', 'create' ],
|
||||
];
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module constructor.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->lock = new Feature_Lock( [ 'type' => 'theme-builder' ] );
|
||||
|
||||
Plugin::elementor()->data_manager->register_controller( Controller::class );
|
||||
|
||||
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ], 11 /* Override core actions */ );
|
||||
add_action( 'elementor/frontend/render_mode/register', [ $this, 'register_render_mode' ] );
|
||||
|
||||
add_action( 'elementor/experiments/default-features-registered', function ( ExperimentsManager $manager ) {
|
||||
$this->add_default_new_site_editor_experiments( $manager );
|
||||
} );
|
||||
|
||||
add_action( 'elementor/admin/menu/register', function ( Admin_Menu_Manager $admin_menu ) {
|
||||
$this->register_admin_menu( $admin_menu );
|
||||
}, Theme_Builder_Table_View::ADMIN_MENU_PRIORITY + 1 );
|
||||
|
||||
// TODO: BC - Remove after `Admin_Menu_Manager` will be the standard.
|
||||
add_action( 'admin_menu', function () {
|
||||
if ( did_action( 'elementor/admin/menu/register' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->register_site_editor_menu();
|
||||
}, 23 /* After old theme builder */ );
|
||||
|
||||
add_filter( 'elementor/finder/categories', function ( array $categories ) {
|
||||
return $this->add_finder_item( $categories );
|
||||
}, 11 /* After old theme builder */ );
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\App\Modules\SiteEditor;
|
||||
|
||||
use Elementor\Core\Frontend\RenderModes\Render_Mode_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Render_Mode_Template_Preview extends Render_Mode_Base {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return 'template-preview';
|
||||
}
|
||||
|
||||
public function filter_template() {
|
||||
return ELEMENTOR_PATH . 'modules/page-templates/templates/canvas.php';
|
||||
}
|
||||
|
||||
public function prepare_render() {
|
||||
parent::prepare_render();
|
||||
|
||||
show_admin_bar( false );
|
||||
|
||||
remove_filter(
|
||||
'the_content',
|
||||
[ \ElementorPro\Modules\ThemeBuilder\Module::instance()->get_locations_manager(), 'builder_wrapper' ],
|
||||
9999999
|
||||
);
|
||||
|
||||
add_filter( 'template_include', [ $this, 'filter_template' ] );
|
||||
|
||||
add_action( 'wp_head', [ $this, 'render_pointer_event_style' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* disable all the interactions in the preview render mode.
|
||||
*/
|
||||
public function render_pointer_event_style() {
|
||||
echo '<style> html { pointer-events: none; } </style>';
|
||||
}
|
||||
|
||||
public function is_static() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Behaviors;
|
||||
|
||||
use ElementorPro\License\API;
|
||||
use ElementorPro\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Feature_Lock implements Temp_Lock_Behavior {
|
||||
|
||||
private $config;
|
||||
|
||||
public function __construct( $config = [] ) {
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function is_locked() {
|
||||
return ! API::is_license_active();
|
||||
}
|
||||
|
||||
public function get_config() {
|
||||
$utm_args = [
|
||||
'utm_source' => '%%utm_source%%', // Will be replaced in the frontend.
|
||||
'utm_medium' => '%%utm_medium%%',
|
||||
'utm_campaign' => API::is_license_expired()
|
||||
? 'renew-license'
|
||||
: 'connect-and-activate-license',
|
||||
'utm_term' => $this->config['type'],
|
||||
];
|
||||
|
||||
$connect_url = Plugin::instance()->license_admin->get_connect_url( $utm_args );
|
||||
|
||||
$renew_url = add_query_arg( $utm_args, 'https://my.elementor.com/subscriptions/' );
|
||||
|
||||
return [
|
||||
'is_locked' => $this->is_locked(),
|
||||
'badge' => [
|
||||
'icon' => 'eicon-lock',
|
||||
'text' => esc_html__( 'Pro', 'elementor-pro' ),
|
||||
],
|
||||
'content' => [
|
||||
'heading' => esc_html__( 'You need an active Elementor Pro license', 'elementor-pro' ),
|
||||
'description' => esc_html__( 'Your Elementor Pro license is inactive. To access premium Elementor widgets, templates, support & plugin updates activate your Pro license.', 'elementor-pro' ),
|
||||
],
|
||||
'button' => [
|
||||
'text' => API::is_license_expired()
|
||||
? esc_html__( 'Renew now', 'elementor-pro' )
|
||||
: esc_html__( 'Connect & Activate', 'elementor-pro' ),
|
||||
|
||||
'url' => API::is_license_expired()
|
||||
? $renew_url
|
||||
: $connect_url,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Behaviors;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
// TODO: Used here for testing. Should be removed when it'll be available in the Core.
|
||||
interface Temp_Lock_Behavior {
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked();
|
||||
|
||||
/**
|
||||
* @return array {
|
||||
*
|
||||
* @type bool $is_locked
|
||||
*
|
||||
* @type array $badge {
|
||||
* @type string $icon
|
||||
* @type string $text
|
||||
* }
|
||||
*
|
||||
* @type array $content {
|
||||
* @type string $heading
|
||||
* @type string $description
|
||||
* }
|
||||
*
|
||||
* @type array $button {
|
||||
* @type string $text
|
||||
* @type string $url
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*/
|
||||
public function get_config();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Compatibility;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Compatibility {
|
||||
|
||||
public static function register_actions() {
|
||||
add_action( 'init', [ __CLASS__, 'on_init' ] );
|
||||
}
|
||||
|
||||
public static function on_init() {
|
||||
static::translate_press();
|
||||
}
|
||||
|
||||
private static function translate_press() {
|
||||
if ( ! class_exists( 'TRP_Translate_Press' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'elementor_pro/license/api/use_home_url', '__return_false' );
|
||||
}
|
||||
}
|
||||
139
wp-content/plugins/elementor-pro/core/connect/apps/activate.php
Normal file
139
wp-content/plugins/elementor-pro/core/connect/apps/activate.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Connect\Apps;
|
||||
|
||||
use Elementor\Core\Common\Modules\Connect\Apps\Common_App;
|
||||
use ElementorPro\License;
|
||||
use ElementorPro\License\API;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Activate extends Common_App {
|
||||
public function get_title() {
|
||||
return esc_html__( 'Activate', 'elementor-pro' );
|
||||
}
|
||||
|
||||
public function get_slug() {
|
||||
return 'activate';
|
||||
}
|
||||
|
||||
protected function after_connect() {
|
||||
$this->action_activate_license();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function action_authorize() {
|
||||
// In case the first connect was not from Activate App - require a new authorization.
|
||||
if ( $this->is_connected() && ! License\Admin::get_license_key() ) {
|
||||
$this->disconnect();
|
||||
}
|
||||
|
||||
parent::action_authorize();
|
||||
}
|
||||
|
||||
public function action_activate_pro() {
|
||||
$this->action_activate_license();
|
||||
}
|
||||
|
||||
public function action_switch_license() {
|
||||
$this->disconnect();
|
||||
$this->action_authorize();
|
||||
}
|
||||
|
||||
public function action_deactivate() {
|
||||
License\Admin::deactivate();
|
||||
$this->disconnect();
|
||||
wp_safe_redirect( License\Admin::get_url() );
|
||||
die;
|
||||
}
|
||||
|
||||
public function action_activate_license() {
|
||||
if ( ! $this->is_connected() ) {
|
||||
$this->add_notice( esc_html__( 'Please connect to Elementor in order to activate license.', 'elementor-pro' ), 'error' );
|
||||
|
||||
$this->redirect_to_admin_page();
|
||||
}
|
||||
|
||||
$license = $this->request( 'get_connected_license' );
|
||||
|
||||
if ( empty( $license ) ) {
|
||||
// TODO: add suggestions how to check/resolve.
|
||||
wp_die( 'License not found for user ' . esc_attr( $this->get( 'user' )->email ), esc_html__( 'Elementor Pro', 'elementor-pro' ), [
|
||||
'back_link' => true,
|
||||
] );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $license ) ) {
|
||||
wp_die( $license, esc_html__( 'Elementor Pro', 'elementor-pro' ), [ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
'back_link' => true,
|
||||
] );
|
||||
}
|
||||
|
||||
$license_key = trim( $license->key );
|
||||
|
||||
if ( empty( $license_key ) ) {
|
||||
wp_die( esc_html__( 'License key is missing.', 'elementor-pro' ), esc_html__( 'Elementor Pro', 'elementor-pro' ), [
|
||||
'back_link' => true,
|
||||
] );
|
||||
}
|
||||
|
||||
$data = License\API::activate_license( $license_key );
|
||||
|
||||
if ( is_wp_error( $data ) ) {
|
||||
wp_die( sprintf( '%s (%s) ', $data->get_error_message(), $data->get_error_code() ), esc_html__( 'Elementor Pro', 'elementor-pro' ), [ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
'back_link' => true,
|
||||
] );
|
||||
}
|
||||
|
||||
if ( empty( $data['success'] ) ) {
|
||||
$error_msg = License\API::get_error_message( $data['error'] ); // get_error_message() escapes html
|
||||
|
||||
wp_die( $error_msg, esc_html__( 'Elementor Pro', 'elementor-pro' ), [ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
'back_link' => true,
|
||||
] );
|
||||
}
|
||||
|
||||
License\Admin::set_license_key( $license_key );
|
||||
|
||||
License\API::set_license_data( $data );
|
||||
|
||||
$this->add_notice( esc_html__( 'License has been activated successfully.', 'elementor-pro' ) );
|
||||
|
||||
$this->redirect_to_admin_page( License\Admin::get_url() );
|
||||
die;
|
||||
}
|
||||
|
||||
public function action_reset() {
|
||||
if ( current_user_can( 'manage_options' ) ) {
|
||||
delete_option( 'elementor_pro_license_key' );
|
||||
delete_transient( 'elementor_pro_license_data' );
|
||||
}
|
||||
|
||||
$this->redirect_to_admin_page();
|
||||
}
|
||||
|
||||
protected function get_popup_success_event_data() {
|
||||
return [
|
||||
'templates_access_level' => API::get_library_access_level( 'template' ),
|
||||
'kits_access_level' => API::get_library_access_level( 'kit' ),
|
||||
'access_tier' => API::get_access_tier(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_app_info() {
|
||||
return [
|
||||
'license_data' => [
|
||||
'label' => 'License Data',
|
||||
'value' => get_option( '_elementor_pro_license_data' ),
|
||||
],
|
||||
'license_key' => [
|
||||
'label' => 'License Key',
|
||||
'value' => get_option( 'elementor_pro_license_key' ),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
28
wp-content/plugins/elementor-pro/core/connect/manager.php
Normal file
28
wp-content/plugins/elementor-pro/core/connect/manager.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Connect;
|
||||
|
||||
use ElementorPro\Core\Connect\Apps\Activate;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Manager {
|
||||
|
||||
/**
|
||||
* @param \Elementor\Core\Common\Modules\Connect\Module $apps_manager
|
||||
*/
|
||||
public function register_apps( $apps_manager ) {
|
||||
$apps = [
|
||||
'activate' => Activate::get_class_name(),
|
||||
];
|
||||
|
||||
foreach ( $apps as $slug => $class ) {
|
||||
$apps_manager->register_app( $slug, $class );
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'elementor/connect/apps/register', [ $this, 'register_apps' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Database;
|
||||
|
||||
use ElementorPro\Core\Utils\Collection;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Base_Database_Updater {
|
||||
/**
|
||||
* Run all the 'up' method of the migrations classes if needed, and update the db version.
|
||||
*
|
||||
* @param bool $force When passing true, it ignores the current version and run all the up migrations.
|
||||
*/
|
||||
public function up( $force = false ) {
|
||||
$installed_version = $this->get_installed_version();
|
||||
|
||||
// Up to date. Nothing to do.
|
||||
if ( ! $force && $this->get_db_version() <= $installed_version ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$migrations = $this->get_collected_migrations();
|
||||
|
||||
if ( ! $force ) {
|
||||
$migrations = $migrations->filter( function ( $_, $version ) use ( $installed_version ) {
|
||||
// Filter all the migrations that already done.
|
||||
return $version > $installed_version;
|
||||
} );
|
||||
}
|
||||
|
||||
$migrations->map( function ( Base_Migration $migration, $version ) {
|
||||
$migration->up();
|
||||
|
||||
// In case some migration failed it updates version every migration.
|
||||
$this->update_db_version_option( $version );
|
||||
} );
|
||||
|
||||
$this->update_db_version_option( $this->get_db_version() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all the 'down' method of the migrations classes if can, and update the db version.
|
||||
*
|
||||
* @param bool $force When passing true, it ignores the current version and run all the down migrations.
|
||||
*/
|
||||
public function down( $force = false ) {
|
||||
$installed_version = $this->get_installed_version();
|
||||
|
||||
$migrations = $this->get_collected_migrations();
|
||||
|
||||
if ( ! $force ) {
|
||||
$migrations = $migrations->filter( function ( $_, $version ) use ( $installed_version ) {
|
||||
// Filter all the migrations that was not installed.
|
||||
return $version <= $installed_version;
|
||||
} );
|
||||
}
|
||||
|
||||
$migrations->reverse( true )
|
||||
->map( function ( Base_Migration $migration, $version ) {
|
||||
$migration->down();
|
||||
|
||||
// In case some migration failed it updates version every migration.
|
||||
$this->update_db_version_option( $version );
|
||||
} );
|
||||
|
||||
$this->update_db_version_option( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks to activate the migrations.
|
||||
*/
|
||||
public function register() {
|
||||
add_action( 'admin_init', function () {
|
||||
$this->up();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the version in the users DB.
|
||||
*
|
||||
* @param $version
|
||||
*/
|
||||
protected function update_db_version_option( $version ) {
|
||||
update_option( $this->get_db_version_option_name(), $version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version that already installed.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_installed_version() {
|
||||
return intval( get_option( $this->get_db_version_option_name() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all migrations inside a Collection.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
protected function get_collected_migrations() {
|
||||
return new Collection( $this->get_migrations() );
|
||||
}
|
||||
|
||||
/**
|
||||
* The most updated version of the DB.
|
||||
*
|
||||
* @return numeric
|
||||
*/
|
||||
abstract protected function get_db_version();
|
||||
|
||||
/**
|
||||
* The name of the option that saves the current user DB version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_db_version_option_name();
|
||||
|
||||
/**
|
||||
* Array of migration classes.
|
||||
*
|
||||
* @return Base_Migration[]
|
||||
*/
|
||||
abstract protected function get_migrations();
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Database;
|
||||
|
||||
use Elementor\Core\Utils\Collection;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Base_Migration {
|
||||
/*
|
||||
* @see https://github.com/WordPress/WordPress/blob/d2694aa46647af48d1bcaff48a4f6cac7f5cf470/wp-admin/includes/schema.php#L49
|
||||
*/
|
||||
const MAX_INDEX_LENGTH = 191;
|
||||
|
||||
/**
|
||||
* @var \wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* @param \wpdb|null $wpdb_instance
|
||||
*/
|
||||
public function __construct( \wpdb $wpdb_instance = null ) {
|
||||
if ( ! $wpdb_instance ) {
|
||||
global $wpdb;
|
||||
|
||||
$this->wpdb = $wpdb;
|
||||
} else {
|
||||
$this->wpdb = $wpdb_instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when upgrading the database
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function up();
|
||||
|
||||
/**
|
||||
* Runs when downgrading the database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function down();
|
||||
|
||||
/**
|
||||
* A util to run SQL for creating tables.
|
||||
*
|
||||
* @param $table_name
|
||||
* @param array $columns
|
||||
*/
|
||||
protected function create_table( $table_name, array $columns ) {
|
||||
$table_name = "{$this->wpdb->prefix}{$table_name}";
|
||||
|
||||
$columns_sql = ( new Collection( $columns ) )
|
||||
->map( function( $definition, $col_name ) {
|
||||
return "`{$col_name}` {$definition}";
|
||||
} )
|
||||
->implode( ', ' );
|
||||
|
||||
$query = "CREATE TABLE `{$table_name}` ({$columns_sql}) {$this->wpdb->get_charset_collate()};";
|
||||
|
||||
$this->run_db_delta( $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add columns.
|
||||
*
|
||||
* @param $table_name
|
||||
* @param array $columns
|
||||
*/
|
||||
protected function add_columns( $table_name, array $columns ) {
|
||||
$table_name = "{$this->wpdb->prefix}{$table_name}";
|
||||
|
||||
$add_columns_sql = ( new Collection( $columns ) )
|
||||
->map( function ( $definition, $column_name ) {
|
||||
return "ADD COLUMN `{$column_name}` {$definition}";
|
||||
} )
|
||||
->implode( ', ' );
|
||||
|
||||
$this->wpdb->query( "ALTER TABLE `{$table_name}` {$add_columns_sql};" ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop columns
|
||||
*
|
||||
* @param $table_name
|
||||
* @param array $columns
|
||||
*/
|
||||
protected function drop_columns( $table_name, array $columns ) {
|
||||
$table_name = "{$this->wpdb->prefix}{$table_name}";
|
||||
|
||||
$drop_columns_sql = ( new Collection( $columns ) )
|
||||
->map( function ( $column_name ) {
|
||||
return "DROP COLUMN `{$column_name}`";
|
||||
} )
|
||||
->implode( ', ' );
|
||||
|
||||
$this->wpdb->query( "ALTER TABLE `{$table_name}` {$drop_columns_sql};" ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* A util to run SQL for dropping tables.
|
||||
*
|
||||
* @param $table_name
|
||||
*/
|
||||
protected function drop_table( $table_name ) {
|
||||
$table_name = "{$this->wpdb->prefix}{$table_name}";
|
||||
|
||||
$query = "DROP TABLE IF EXISTS `{$table_name}`;";
|
||||
|
||||
// Safe query that shouldn't be escaped.
|
||||
$this->wpdb->query( $query ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* A util to run SQL for creating indexes.
|
||||
*
|
||||
* @param $table_name
|
||||
* @param array $column_names
|
||||
*/
|
||||
protected function create_indexes( $table_name, array $column_names ) {
|
||||
$max_index_length = static::MAX_INDEX_LENGTH;
|
||||
$table_name = "{$this->wpdb->prefix}{$table_name}";
|
||||
|
||||
// Safe query that shouldn't be escaped.
|
||||
$column_definition = $this->get_column_definition( $table_name );
|
||||
|
||||
if ( ! $column_definition ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$should_set_max_length_index = ( new Collection( $column_definition ) )
|
||||
->filter( function ( $value ) {
|
||||
preg_match( '/\((\d+)\)/', $value['Type'], $match );
|
||||
|
||||
return ( isset( $match[1] ) && intval( $match[1] ) > Base_Migration::MAX_INDEX_LENGTH )
|
||||
|| in_array( strtolower( $value['Type'] ), [ 'text', 'longtext' ], true );
|
||||
} )
|
||||
->pluck( 'Field' )
|
||||
->values();
|
||||
|
||||
$indexes_sql = ( new Collection( $column_names ) )
|
||||
->map( function( $col_name ) use ( $should_set_max_length_index, $max_index_length ) {
|
||||
$max_index_length_sql = '';
|
||||
|
||||
if ( in_array( $col_name, $should_set_max_length_index, true ) ) {
|
||||
$max_index_length_sql = " ({$max_index_length})";
|
||||
}
|
||||
|
||||
return "ADD INDEX `{$col_name}_index` (`{$col_name}`{$max_index_length_sql})";
|
||||
} )
|
||||
->implode( ', ' );
|
||||
|
||||
// Safe query that shouldn't be escaped.
|
||||
$this->wpdb->query( "ALTER TABLE `{$table_name}` {$indexes_sql};" ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $table_name
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_column_definition( $table_name ) {
|
||||
return $this->wpdb->get_results( "SHOW COLUMNS FROM `{$table_name}`;", ARRAY_A ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs global dbDelta function (wrapped into method to allowing mock for testing).
|
||||
*
|
||||
* @param $query
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function run_db_delta( $query ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
|
||||
return dbDelta( $query );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Database;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* JOIN clause builder.
|
||||
*
|
||||
* Essentially, it uses the regular Builder's capabilities while wrapping some method
|
||||
* for syntactic sugar and better readability.
|
||||
*/
|
||||
class Join_Clause extends Query_Builder {
|
||||
|
||||
// JOIN types.
|
||||
const TYPE_INNER = 'inner';
|
||||
const TYPE_LEFT = 'left';
|
||||
const TYPE_RIGHT = 'right';
|
||||
|
||||
/**
|
||||
* JOIN type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Join_Clause constructor.
|
||||
*
|
||||
* @param string $type - JOIN type.
|
||||
* @param \wpdb|null $connection - MySQL connection to use.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $type, \wpdb $connection = null ) {
|
||||
parent::__construct( $connection );
|
||||
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @uses `$this->where()`.
|
||||
*
|
||||
* @return Join_Clause
|
||||
*/
|
||||
public function on( $column, $operator, $value, $and_or = self::RELATION_AND ) {
|
||||
return $this->where( $column, $operator, $value, $and_or );
|
||||
}
|
||||
|
||||
/**
|
||||
* @shortcut `$this->on()`.
|
||||
*
|
||||
* @return Join_Clause
|
||||
*/
|
||||
public function or_on( $first, $operator, $second ) {
|
||||
return $this->on( $first, $operator, $second, self::RELATION_OR );
|
||||
}
|
||||
|
||||
/**
|
||||
* @uses `$this->where_column()`.
|
||||
*
|
||||
* @return Join_Clause
|
||||
*/
|
||||
public function on_column( $first, $operator, $second, $and_or = self::RELATION_AND ) {
|
||||
return $this->where_column( $first, $operator, $second, $and_or );
|
||||
}
|
||||
|
||||
/**
|
||||
* @shortcut `$this->on_column()`.
|
||||
*
|
||||
* @return Join_Clause
|
||||
*/
|
||||
public function or_on_column( $first, $operator, $second ) {
|
||||
return $this->on_column( $first, $operator, $second, self::RELATION_OR );
|
||||
}
|
||||
}
|
||||
160
wp-content/plugins/elementor-pro/core/database/model-base.php
Normal file
160
wp-content/plugins/elementor-pro/core/database/model-base.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Database;
|
||||
|
||||
use ElementorPro\Core\Utils\Collection;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Model_Base implements \JsonSerializable {
|
||||
|
||||
// Casting types.
|
||||
const TYPE_BOOLEAN = 'boolean';
|
||||
const TYPE_COLLECTION = 'collection';
|
||||
const TYPE_INTEGER = 'integer';
|
||||
const TYPE_STRING = 'string';
|
||||
const TYPE_JSON = 'json';
|
||||
const TYPE_DATETIME = 'datetime';
|
||||
const TYPE_DATETIME_GMT = 'datetime_gmt';
|
||||
|
||||
/**
|
||||
* Casts array.
|
||||
* Used to automatically cast values from DB to the appropriate property type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $casts = [];
|
||||
|
||||
/**
|
||||
* Model_Base constructor.
|
||||
*
|
||||
* @param array $fields - Fields from the DB to fill.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( array $fields ) {
|
||||
foreach ( $fields as $key => $value ) {
|
||||
if ( ! property_exists( $this, $key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->{$key} = ( empty( static::$casts[ $key ] ) )
|
||||
? $value
|
||||
: static::cast( $value, static::$casts[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model's table name.
|
||||
* Throws an exception by default in order to require implementation,
|
||||
* since abstract static functions are not allowed.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_table() {
|
||||
throw new \Exception( 'You must implement `get_table()` inside ' . static::class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Query Builder for the model's table.
|
||||
*
|
||||
* @param \wpdb|null $connection - MySQL connection to use.
|
||||
*
|
||||
* @return Query_Builder
|
||||
*/
|
||||
public static function query( \wpdb $connection = null ) {
|
||||
$builder = new Model_Query_Builder( static::class, $connection );
|
||||
|
||||
return $builder->from( static::get_table() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast value into specific type.
|
||||
*
|
||||
* @param $value - Value to cast.
|
||||
* @param $type - Type to cast into.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function cast( $value, $type ) {
|
||||
if ( null === $value ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ( $type ) {
|
||||
case self::TYPE_BOOLEAN:
|
||||
return boolval( $value );
|
||||
|
||||
case self::TYPE_COLLECTION:
|
||||
return new Collection( $value );
|
||||
|
||||
case self::TYPE_INTEGER:
|
||||
return intval( $value );
|
||||
|
||||
case self::TYPE_STRING:
|
||||
return strval( $value );
|
||||
|
||||
case self::TYPE_JSON:
|
||||
return json_decode( $value, true );
|
||||
|
||||
case self::TYPE_DATETIME:
|
||||
return new \DateTime( $value );
|
||||
|
||||
case self::TYPE_DATETIME_GMT:
|
||||
return new \DateTime( $value, new \DateTimeZone( 'GMT' ) );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast a model property value into a JSON compatible data type.
|
||||
*
|
||||
* @param $value - Value to cast.
|
||||
* @param $type - Type to cast into.
|
||||
* @param $property_name - The model property name.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function json_serialize_property( $value, $type, $property_name ) {
|
||||
switch ( $type ) {
|
||||
case self::TYPE_DATETIME:
|
||||
case self::TYPE_DATETIME_GMT:
|
||||
/** @var \DateTime $value */
|
||||
return $value->format( 'c' );
|
||||
}
|
||||
|
||||
/** @var mixed $value */
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return ( new Collection( (array) $this ) )
|
||||
->map( function ( $_, $key ) {
|
||||
$value = $this->{$key};
|
||||
|
||||
$type = array_key_exists( $key, static::$casts )
|
||||
? static::$casts[ $key ]
|
||||
: null;
|
||||
|
||||
if ( null === $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Can be overridden by child model.
|
||||
$value = static::json_serialize_property( $value, $type, $key );
|
||||
|
||||
if ( $value instanceof \JsonSerializable ) {
|
||||
return $value->jsonSerialize();
|
||||
}
|
||||
|
||||
return $value;
|
||||
} )
|
||||
->all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Database;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Model_Query_Builder extends Query_Builder {
|
||||
/**
|
||||
* The Query Builder associated model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* Whether the returned value should be hydrated into a model.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $return_as_model = true;
|
||||
|
||||
/**
|
||||
* Model_Query_Builder constructor.
|
||||
*
|
||||
* @param string $model_classname - Model to use inside the builder.
|
||||
* @param \wpdb|null $connection - MySQL connection.
|
||||
*/
|
||||
public function __construct( $model_classname, \wpdb $connection = null ) {
|
||||
$this->set_model( $model_classname );
|
||||
|
||||
parent::__construct( $connection );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the model the generated from the query builder.
|
||||
*
|
||||
* @param $model_classname
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function set_model( $model_classname ) {
|
||||
$this->model = $model_classname;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable model hydration.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function disable_model_initiation() {
|
||||
$this->return_as_model = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable hydration before calling the original count.
|
||||
*
|
||||
* @param string $column
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count( $column = '*' ) {
|
||||
$this->disable_model_initiation();
|
||||
|
||||
return parent::count( $column );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable hydration before calling the original pluck.
|
||||
*
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function pluck( $column = null ) {
|
||||
$this->disable_model_initiation();
|
||||
|
||||
return parent::pluck( $column );
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the parent `get()` and make Models from the results.
|
||||
*
|
||||
* @return \ElementorPro\Core\Utils\Collection
|
||||
*/
|
||||
public function get() {
|
||||
$items = parent::get();
|
||||
|
||||
if ( ! $this->return_as_model ) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
// Convert the SQL results to Model instances.
|
||||
return $items->map( function ( $comment ) {
|
||||
return new $this->model( $comment );
|
||||
} );
|
||||
}
|
||||
}
|
||||
1342
wp-content/plugins/elementor-pro/core/database/query-builder.php
Normal file
1342
wp-content/plugins/elementor-pro/core/database/query-builder.php
Normal file
File diff suppressed because it is too large
Load Diff
189
wp-content/plugins/elementor-pro/core/editor/editor.php
Normal file
189
wp-content/plugins/elementor-pro/core/editor/editor.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Editor;
|
||||
|
||||
use Elementor\Core\Base\App;
|
||||
use Elementor\Core\Utils\Assets_Config_Provider;
|
||||
use Elementor\Core\Utils\Assets_Translation_Loader;
|
||||
use ElementorPro\License\Admin as License_Admin;
|
||||
use ElementorPro\License\API as License_API;
|
||||
use ElementorPro\Plugin;
|
||||
use ElementorPro\Modules\DisplayConditions\Module as Display_Conditions_Module;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Editor extends App {
|
||||
const EDITOR_V2_PACKAGES = [
|
||||
'editor-documents-extended',
|
||||
'editor-site-navigation-extended',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get app name.
|
||||
*
|
||||
* Retrieve the app name.
|
||||
*
|
||||
* @return string app name.
|
||||
* @since 2.6.0
|
||||
* @access public
|
||||
*
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'pro-editor';
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'elementor/init', [ $this, 'on_elementor_init' ] );
|
||||
add_action( 'elementor/editor/init', [ $this, 'on_elementor_editor_init' ] );
|
||||
add_action( 'elementor/editor/after_enqueue_styles', [ $this, 'enqueue_editor_styles' ] );
|
||||
add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue_editor_scripts' ] );
|
||||
add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
|
||||
|
||||
add_filter( 'elementor/editor/panel/get_pro_details', function( $get_pro_details ) {
|
||||
if ( defined( '\Elementor\Modules\Apps\Module::PAGE_ID' ) ) {
|
||||
$get_pro_details['link'] = admin_url( 'admin.php?page=' . \Elementor\Modules\Apps\Module::PAGE_ID );
|
||||
$get_pro_details['message'] = __( 'Extend Elementor With Add-ons', 'elementor-pro' );
|
||||
$get_pro_details['button_text'] = __( 'Explore Add-ons', 'elementor-pro' );
|
||||
}
|
||||
|
||||
return $get_pro_details;
|
||||
} );
|
||||
|
||||
add_action( 'elementor/editor/v2/scripts/enqueue', function () {
|
||||
$this->enqueue_editor_v2_scripts();
|
||||
} );
|
||||
}
|
||||
|
||||
public function get_init_settings() {
|
||||
$settings = [
|
||||
'isActive' => License_API::is_license_active(),
|
||||
'urls' => [
|
||||
'modules' => ELEMENTOR_PRO_MODULES_URL,
|
||||
'connect' => License_Admin::get_url(),
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Localized editor settings.
|
||||
*
|
||||
* Filters the localized settings used in the editor as JavaScript variables.
|
||||
*
|
||||
* By default Elementor Pro passes some editor settings to be consumed as JavaScript
|
||||
* variables. This hook allows developers to add extra settings values to be consumed
|
||||
* using JavaScript in the editor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $settings Localized editor settings.
|
||||
*/
|
||||
$settings = apply_filters( 'elementor_pro/editor/localize_settings', $settings );
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
public function enqueue_editor_styles() {
|
||||
wp_enqueue_style(
|
||||
'elementor-pro',
|
||||
$this->get_css_assets_url( 'editor', null, 'default', true ),
|
||||
[
|
||||
'elementor-editor',
|
||||
],
|
||||
ELEMENTOR_PRO_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
public function enqueue_editor_scripts() {
|
||||
wp_enqueue_script(
|
||||
'elementor-pro',
|
||||
$this->get_js_assets_url( 'editor' ),
|
||||
[
|
||||
'backbone-marionette',
|
||||
'elementor-common',
|
||||
'elementor-editor-modules',
|
||||
'elementor-editor-document',
|
||||
],
|
||||
ELEMENTOR_PRO_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_set_script_translations( 'elementor-pro', 'elementor-pro' );
|
||||
|
||||
$this->print_config( 'elementor-pro' );
|
||||
}
|
||||
|
||||
public function enqueue_editor_v2_scripts() {
|
||||
$assets_config = ( new Assets_Config_Provider() )
|
||||
->set_path_resolver( function ( $name ) {
|
||||
return ELEMENTOR_PRO_ASSETS_PATH . "js/packages/{$name}/{$name}.asset.php";
|
||||
} );
|
||||
|
||||
$packages = apply_filters( 'elementor-pro/editor/v2/packages', self::EDITOR_V2_PACKAGES );
|
||||
|
||||
foreach ( $packages as $package ) {
|
||||
$assets_config->load( $package );
|
||||
}
|
||||
|
||||
foreach ( $assets_config->all() as $package => $config ) {
|
||||
wp_enqueue_script(
|
||||
$config['handle'],
|
||||
$this->get_js_assets_url( "packages/{$package}/{$package}" ),
|
||||
$config['deps'],
|
||||
ELEMENTOR_PRO_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_set_script_translations( $config['handle'], 'elementor-pro' );
|
||||
}
|
||||
|
||||
if ( class_exists( Assets_Translation_Loader::class ) ) {
|
||||
$packages_handles = $assets_config->pluck( 'handle' )->all();
|
||||
|
||||
Assets_Translation_Loader::for_handles( $packages_handles );
|
||||
}
|
||||
}
|
||||
|
||||
public function localize_settings( array $settings ) {
|
||||
$settings['elementPromotionURL'] = Plugin::instance()->license_admin->get_connect_url([
|
||||
'utm_source' => '%s', // Will be replaced in the frontend to the widget name
|
||||
'utm_medium' => 'wp-dash',
|
||||
'utm_campaign' => 'connect-and-activate-license',
|
||||
'utm_content' => 'editor-widget-promotion',
|
||||
]);
|
||||
|
||||
$settings['dynamicPromotionURL'] = Plugin::instance()->license_admin->get_connect_url( [
|
||||
'utm_source' => '%s', // Will be replaced in the frontend to the control name
|
||||
'utm_medium' => 'wp-dash',
|
||||
'utm_campaign' => 'connect-and-activate-license',
|
||||
'utm_content' => 'editor-dynamic-promotion',
|
||||
] );
|
||||
|
||||
if ( ! isset( $settings['promotionWidgets'] ) ) {
|
||||
$settings['promotionWidgets'] = License_API::get_promotion_widgets();
|
||||
}
|
||||
|
||||
if ( Display_Conditions_Module::can_use_display_conditions() && Display_Conditions_Module::is_experiment_active() ) {
|
||||
$settings['displayConditions'] = Display_Conditions_Module::instance()
|
||||
->get_conditions_manager()
|
||||
->get_conditions_config();
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
public function on_elementor_init() {
|
||||
Plugin::elementor()->editor->notice_bar = new Notice_Bar();
|
||||
|
||||
if ( isset( Plugin::elementor()->editor->promotion ) ) {
|
||||
Plugin::elementor()->editor->promotion = new Promotion();
|
||||
}
|
||||
}
|
||||
|
||||
public function on_elementor_editor_init() {
|
||||
Plugin::elementor()->common->add_template( __DIR__ . '/template.php' );
|
||||
}
|
||||
|
||||
protected function get_assets_base_url() {
|
||||
return ELEMENTOR_PRO_URL;
|
||||
}
|
||||
}
|
||||
109
wp-content/plugins/elementor-pro/core/editor/notice-bar.php
Normal file
109
wp-content/plugins/elementor-pro/core/editor/notice-bar.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Editor;
|
||||
|
||||
use Elementor\Core\Editor\Notice_Bar as Base_Notice_Bar;
|
||||
use ElementorPro\License\Admin;
|
||||
use ElementorPro\License\API as License_API;
|
||||
use ElementorPro\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Notice_Bar extends Base_Notice_Bar {
|
||||
|
||||
const ELEMENTOR_PRO_EDITOR_GO_PRO_TRIAL_ABOUT_TO_EXPIRE_LICENSE_NOTICE_DISMISSED = '_elementor_pro_editor_go_pro_trial_about_to_expire_license_notice_dismissed';
|
||||
const ELEMENTOR_PRO_EDITOR_GO_PRO_TRIAL_EXPIRED_LICENSE_NOTICE_DISMISSED = '_elementor_pro_editor_go_pro_trial_expired_license_notice_dismissed';
|
||||
const ELEMENTOR_PRO_EDITOR_RENEW_LICENSE_NOTICE_DISMISSED = '_elementor_pro_editor_renew_license_notice_dismissed';
|
||||
const ELEMENTOR_PRO_EDITOR_ACTIVATE_LICENSE_NOTICE_DISMISSED = '_elementor_pro_editor_activate_license_notice_dismissed';
|
||||
const ELEMENTOR_PRO_EDITOR_RENEW_ABOUT_TO_EXPIRE_LICENSE_NOTICE_DISMISSED = '_elementor_pro_editor_renew_about_to_expire_license_notice_dismissed';
|
||||
|
||||
protected function get_init_settings() {
|
||||
$license_data = License_API::get_license_data();
|
||||
$license_admin = Plugin::instance()->license_admin;
|
||||
|
||||
if ( License_API::is_license_active() && License_API::is_licence_pro_trial() ) {
|
||||
return [
|
||||
'option_key' => self::ELEMENTOR_PRO_EDITOR_GO_PRO_TRIAL_ABOUT_TO_EXPIRE_LICENSE_NOTICE_DISMISSED,
|
||||
'message' =>
|
||||
esc_html__( 'Heads up! You are using a free trial. Want to enjoy Pro widgets & templates for a whole year?', 'elementor-pro' )
|
||||
. sprintf( ' <a href="https://my.elementor.com/upgrade-subscription/?utm_source=editor-notice-bar&utm_medium=wp-dash&utm_campaign=pro-trial&utm_content=trial-period" target="_blank">%s</a>', esc_html__( 'Go Pro now', 'elementor-pro' ) ),
|
||||
'action_title' => '',
|
||||
'action_url' => '',
|
||||
'muted_period' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if ( License_API::is_license_expired() && License_API::is_licence_pro_trial() ) {
|
||||
return [
|
||||
'option_key' => self::ELEMENTOR_PRO_EDITOR_GO_PRO_TRIAL_EXPIRED_LICENSE_NOTICE_DISMISSED,
|
||||
'message' => esc_html__( 'Your trial has expired. Miss your favorite Elementor Pro features?', 'elementor-pro' )
|
||||
. sprintf( ' <a href="https://my.elementor.com/upgrade-subscription/?utm_source=editor-notice-bar&utm_medium=wp-dash&utm_campaign=pro-trial&utm_content=trial-expired" target="_blank">%s</a>', esc_html__( 'Upgrade now', 'elementor-pro' ) ),
|
||||
'action_title' => '',
|
||||
'action_url' => '',
|
||||
'muted_period' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if ( License_API::is_license_expired() ) {
|
||||
return [
|
||||
'option_key' => self::ELEMENTOR_PRO_EDITOR_RENEW_LICENSE_NOTICE_DISMISSED,
|
||||
'icon' => 'eicon-lock',
|
||||
'message' => esc_html__(
|
||||
'Renew to unlock all Elementor Pro features',
|
||||
'elementor-pro'
|
||||
),
|
||||
'action_title' => esc_html__( 'Renew now', 'elementor-pro' ),
|
||||
'action_url' => 'https://go.elementor.com/editor-notice-bar-renew/',
|
||||
'secondary_message' => esc_html__(
|
||||
'Already renewed?',
|
||||
'elementor-pro'
|
||||
),
|
||||
'secondary_action_title' => esc_html__( 'Reload Editor', 'elementor-pro' ),
|
||||
'secondary_action_url' => Admin::get_url() . '&redirect-to-document=' . Plugin::elementor()->documents->get_current()->get_id(),
|
||||
'secondary_action_target' => '_self',
|
||||
'muted_period' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if ( ! License_API::is_license_active() ) {
|
||||
return [
|
||||
'option_key' => self::ELEMENTOR_PRO_EDITOR_ACTIVATE_LICENSE_NOTICE_DISMISSED,
|
||||
'message' => esc_html__( 'Activate Your License and Get Access to Premium Elementor Templates, Support & Plugin Updates.', 'elementor-pro' ),
|
||||
'action_title' => esc_html__( 'Connect & Activate', 'elementor-pro' ),
|
||||
'action_url' => $license_admin->get_connect_url( [
|
||||
'mode' => 'popup',
|
||||
'callback_id' => 'editor-pro-activate',
|
||||
|
||||
// UTM
|
||||
'utm_source' => 'editor-notice-bar',
|
||||
'utm_medium' => 'wp-dash',
|
||||
'utm_campaign' => 'connect-and-activate-license',
|
||||
] ),
|
||||
'muted_period' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if ( ! License_API::is_license_about_to_expire() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( isset( $license_data['renewal_discount'] ) && 0 < $license_data['renewal_discount'] ) {
|
||||
$message = sprintf(
|
||||
/* translators: %s: Renewal discount. */
|
||||
esc_html__( 'Your Elementor Pro license is about to expire. Renew now and get an exclusive, time-limited %s discount.', 'elementor-pro' ),
|
||||
$license_data['renewal_discount'] . '%'
|
||||
);
|
||||
} else {
|
||||
$message = esc_html__( 'Your Elementor Pro license is about to expire. Renew now and get updates, support, Pro widgets & templates for another year.', 'elementor-pro' );
|
||||
}
|
||||
|
||||
return [
|
||||
'option_key' => self::ELEMENTOR_PRO_EDITOR_RENEW_ABOUT_TO_EXPIRE_LICENSE_NOTICE_DISMISSED,
|
||||
'message' => $message,
|
||||
'action_title' => esc_html__( 'Renew now', 'elementor-pro' ),
|
||||
'action_url' => 'https://go.elementor.com/editor-notice-bar-renew/',
|
||||
'muted_period' => 1,
|
||||
];
|
||||
}
|
||||
}
|
||||
67
wp-content/plugins/elementor-pro/core/editor/promotion.php
Normal file
67
wp-content/plugins/elementor-pro/core/editor/promotion.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Editor;
|
||||
|
||||
use ElementorPro\License\API;
|
||||
use ElementorPro\License\Admin;
|
||||
use Elementor\Core\Editor\Promotion as Base_Promotion;
|
||||
use ElementorPro\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Promotion extends Base_Promotion {
|
||||
public function get_elements_promotion() {
|
||||
if ( API::is_need_to_show_upgrade_promotion() ) {
|
||||
return $this->get_elements_promotion__higher_tiers();
|
||||
}
|
||||
|
||||
if ( API::is_license_active() ) {
|
||||
return parent::get_elements_promotion();
|
||||
}
|
||||
|
||||
return $this->get_elements_promotion__default();
|
||||
}
|
||||
|
||||
private function get_elements_promotion__default() {
|
||||
$is_license_expired = API::is_license_expired();
|
||||
|
||||
return [
|
||||
/* translators: %s: Widget title. */
|
||||
'title' => __( '%s Widget', 'elementor-pro' ),
|
||||
'content' => $is_license_expired
|
||||
/* translators: %s: Widget title. */
|
||||
? __(
|
||||
'Renew your Elementor Pro subscription to get %s and dozens more Pro widgets to expand your web-creation toolbox.',
|
||||
'elementor-pro'
|
||||
)
|
||||
/* translators: %s: Widget title. */
|
||||
: __(
|
||||
'Use %s widget and dozens more pro features to extend your toolbox and build sites faster and better.',
|
||||
'elementor-pro'
|
||||
),
|
||||
'action_button' => $is_license_expired ? [
|
||||
'text' => __( 'Renew now', 'elementor-pro' ),
|
||||
'url' => 'https://my.elementor.com/subscriptions/?utm_source=%s-pro-widget&utm_medium=wp-dash&utm_campaign=renew-license',
|
||||
'classes' => [ 'elementor-button', 'elementor-button-brand' ],
|
||||
] : [
|
||||
'text' => __( 'Connect & Activate', 'elementor-pro' ),
|
||||
'url' => Admin::get_url(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function get_elements_promotion__higher_tiers() {
|
||||
return [
|
||||
/* translators: %s: Widget title. */
|
||||
'title' => __( '%s Widget', 'elementor-pro' ),
|
||||
/* translators: %s: Widget title. */
|
||||
'content' => __( 'Upgrade to Elementor Pro Advanced to get the %s widget as well as additional professional and ecommerce widgets.', 'elementor-pro' ),
|
||||
'action_button' => [
|
||||
'text' => __( 'Upgrade now', 'elementor-pro' ),
|
||||
'url' => 'https://go.elementor.com/go-pro-advanced-%s',
|
||||
'classes' => [ 'elementor-button', 'elementor-button-brand', 'go-pro' ],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
16
wp-content/plugins/elementor-pro/core/editor/template.php
Normal file
16
wp-content/plugins/elementor-pro/core/editor/template.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
use ElementorPro\License\Admin as LicenseAdmin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
?>
|
||||
<script type="text/template" id="tmpl-elementor-pro-template-library-activate-license-button">
|
||||
<a class="elementor-template-library-template-action elementor-button go-pro" href="<?php
|
||||
// PHPCS - the function LicenseAdmin::get_url() is safe.
|
||||
echo LicenseAdmin::get_url(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" target="_blank">
|
||||
<i class="eicon-external-link-square"></i>
|
||||
<span class="elementor-button-title"><?php echo esc_html__( 'Activate License', 'elementor-pro' ); ?></span>
|
||||
</a>
|
||||
</script>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Actions;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Action_Base {
|
||||
|
||||
/**
|
||||
* Validate a payload.
|
||||
*
|
||||
* @param mixed $payload - Payload object instance.
|
||||
*
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Validation_Failed_Exception
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function validate( $payload );
|
||||
|
||||
/**
|
||||
* Apply the action.
|
||||
*
|
||||
* @param mixed $payload - Payload object instance.
|
||||
*
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Failed_Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function apply( $payload );
|
||||
|
||||
/**
|
||||
* Run the action.
|
||||
*
|
||||
* @param mixed $payload - Payload object instance.
|
||||
*
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Validation_Failed_Exception
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Failed_Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run( $payload ) {
|
||||
$this->validate( $payload );
|
||||
$this->apply( $payload );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Actions\Email;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Email_Address {
|
||||
|
||||
/**
|
||||
* Recipient email address.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $address;
|
||||
|
||||
/**
|
||||
* Recipient name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Email_Address constructor.
|
||||
*
|
||||
* @param string $address
|
||||
* @param string $name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $address, $name ) {
|
||||
$this->address = (string) $address;
|
||||
$this->name = (string) $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an email to be ready for header (e.g. `Recipient Name <user@email.com>` or `user@email.com`)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format() {
|
||||
if ( ! empty( $this->name ) ) {
|
||||
return sprintf( '%s <%s>', $this->name, $this->address );
|
||||
}
|
||||
|
||||
return sprintf( '%s', $this->address );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Actions\Email;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Email_Message {
|
||||
|
||||
/**
|
||||
* Email sender.
|
||||
*
|
||||
* @var Email_Address
|
||||
*/
|
||||
public $from;
|
||||
|
||||
/**
|
||||
* Email recipient.
|
||||
*
|
||||
* @var Email_Address
|
||||
*/
|
||||
public $to;
|
||||
|
||||
/**
|
||||
* Email reply to address.
|
||||
*
|
||||
* @var Email_Address[]
|
||||
*/
|
||||
public $reply_to = [];
|
||||
|
||||
/**
|
||||
* Email CC recipient.
|
||||
*
|
||||
* @var Email_Address[]
|
||||
*/
|
||||
public $cc = [];
|
||||
|
||||
/**
|
||||
* Email BCC recipient.
|
||||
*
|
||||
* @var Email_Address[]
|
||||
*/
|
||||
public $bcc = [];
|
||||
|
||||
/**
|
||||
* Email subject.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $subject;
|
||||
|
||||
/**
|
||||
* Email content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content_type;
|
||||
|
||||
/**
|
||||
* Email body.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $body;
|
||||
|
||||
/**
|
||||
* Email attachments.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $attachments = [];
|
||||
|
||||
/**
|
||||
* Email_Message constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
// Set defaults.
|
||||
$this->from( get_bloginfo( 'admin_email' ), get_bloginfo( 'name' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email sender.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function from( $email, $name = null ) {
|
||||
$this->from = new Email_Address( $email, $name );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email recipient.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function to( $email, $name = null ) {
|
||||
$this->to = new Email_Address( $email, $name );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a reply to.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reply_to( $email, $name = null ) {
|
||||
$this->reply_to[] = new Email_Address( $email, $name );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a CC.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cc( $email, $name = null ) {
|
||||
$this->cc[] = new Email_Address( $email, $name );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a BCC.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function bcc( $email, $name = null ) {
|
||||
$this->bcc[] = new Email_Address( $email, $name );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email subject.
|
||||
*
|
||||
* @param string $subject
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function subject( $subject ) {
|
||||
$this->subject = (string) $subject;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email content type.
|
||||
*
|
||||
* @param string $content_type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function content_type( $content_type ) {
|
||||
$this->content_type = (string) $content_type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email body using plain text.
|
||||
*
|
||||
* @param string $body
|
||||
* @param string $content_type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function body( $body, $content_type = 'text/html' ) {
|
||||
$this->body = (string) $body;
|
||||
|
||||
return $this->content_type( $content_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email body using a view.
|
||||
*
|
||||
* @param string $path - View path,
|
||||
* @param array $data - Data that will be passes to the view.
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function view( $path, $data = [] ) {
|
||||
if ( ! is_file( $path ) ) {
|
||||
throw new \Exception( "`{$path}` is not a valid view." );
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
// Inspired from Laravel's view mechanism:
|
||||
// [1] https://github.dev/illuminate/filesystem/blob/b179f9ea3b3195d1f4b5ae2aee67e42eac6ceb5e/Filesystem.php#L98
|
||||
// [2] https://github.dev/illuminate/view/blob/6dd315634a44450c5e443fa8735d4a526833fad3/Engines/PhpEngine.php#L48
|
||||
call_user_func( function( $__view_path, $__view_data ) {
|
||||
extract( $__view_data, EXTR_SKIP ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
|
||||
|
||||
unset( $__view_data );
|
||||
|
||||
// `$__view_data` keys are available in the file as variables.
|
||||
require $__view_path;
|
||||
}, $path, $data );
|
||||
|
||||
$this->body = ob_get_clean();
|
||||
|
||||
return $this->content_type( 'text/html' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an attachment.
|
||||
*
|
||||
* @param string $path - Attachment path on the server.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function attach( $path ) {
|
||||
$this->attachments[] = (string) $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Actions\Email;
|
||||
|
||||
use ElementorPro\Core\Integrations\Actions\Action_Base;
|
||||
use ElementorPro\Core\Integrations\Exceptions\Action_Failed_Exception;
|
||||
use ElementorPro\Core\Integrations\Exceptions\Action_Validation_Failed_Exception;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Email extends Action_Base {
|
||||
|
||||
/**
|
||||
* @param Email_Message $payload
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function apply( $payload ) {
|
||||
// Set default headers.
|
||||
$headers = [
|
||||
sprintf( 'Content-Type: %s; charset=UTF-8', $payload->content_type ),
|
||||
sprintf( 'From: %s', $payload->from->format() ),
|
||||
];
|
||||
|
||||
foreach ( $payload->reply_to as $recipient ) {
|
||||
$headers[] = sprintf( 'Reply-To: %s', $recipient->format() );
|
||||
}
|
||||
|
||||
// Set CC headers.
|
||||
$cc_headers = [];
|
||||
|
||||
foreach ( $payload->cc as $recipient ) {
|
||||
$cc_headers[] = sprintf( 'Cc: %s', $recipient->format() );
|
||||
}
|
||||
|
||||
// Send email.
|
||||
$this->send_mail(
|
||||
$payload->to->format(),
|
||||
$payload->subject,
|
||||
$payload->body,
|
||||
implode( PHP_EOL, array_merge( $headers, $cc_headers ) ),
|
||||
$payload->attachments
|
||||
);
|
||||
|
||||
// Send BCC emails.
|
||||
foreach ( $payload->bcc as $bcc ) {
|
||||
$this->send_mail(
|
||||
$bcc->format(),
|
||||
$payload->subject,
|
||||
$payload->body,
|
||||
implode( PHP_EOL, $headers ),
|
||||
$payload->attachments
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @alias `$this->run()`
|
||||
*
|
||||
* @param Email_Message $payload
|
||||
*
|
||||
* @return void
|
||||
*@throws \Exception
|
||||
*
|
||||
*/
|
||||
public function send( Email_Message $payload ) {
|
||||
$this->run( $payload );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the email message DTO.
|
||||
*
|
||||
* @param Email_Message $payload
|
||||
*
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Validation_Failed_Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function validate( $payload ) {
|
||||
$required_fields = [
|
||||
'from',
|
||||
'to',
|
||||
'subject',
|
||||
'body',
|
||||
'content_type',
|
||||
];
|
||||
|
||||
foreach ( $required_fields as $field ) {
|
||||
if ( empty( $payload->{$field} ) ) {
|
||||
throw new Action_Validation_Failed_Exception(
|
||||
static::class,
|
||||
"`Email_Message::\${$field}` is required."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `wp_mail()`. Used for testing.
|
||||
*
|
||||
* @param mixed ...$args
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function send_mail( ...$args ) {
|
||||
add_action( 'wp_mail_failed', [ $this, 'on_wp_mail_error' ] );
|
||||
|
||||
wp_mail( ...$args );
|
||||
|
||||
remove_action( 'wp_mail_failed', [ $this, 'on_wp_mail_error' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw exception on `wp_mail()` error.
|
||||
*
|
||||
* @param \WP_Error $error
|
||||
*
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Failed_Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function on_wp_mail_error( \WP_Error $error ) {
|
||||
throw new Action_Failed_Exception( static::class, '`wp_mail()` cannot send email', $error );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Exceptions;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Action_Failed_Exception extends Exception_Base {
|
||||
|
||||
protected function format_message( $message ) {
|
||||
return sprintf(
|
||||
'Action `%s` failed to run: %s',
|
||||
$this->action,
|
||||
$message
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Exceptions;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Action_Validation_Failed_Exception extends Exception_Base {
|
||||
|
||||
protected function format_message( $message ) {
|
||||
return sprintf(
|
||||
'Action `%s` failed validation: %s',
|
||||
$this->action,
|
||||
$message
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Exceptions;
|
||||
|
||||
use ElementorPro\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Exception_Base extends \Exception {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $meta = [];
|
||||
|
||||
/**
|
||||
* Get a formatted message specific to the current exception type.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function format_message( $message );
|
||||
|
||||
/**
|
||||
* Exception_Base constructor.
|
||||
*
|
||||
* @param string $action - Action name that failed (ideally the class name, e.g. Email::class).
|
||||
* @param string $message - Message to show.
|
||||
* @param array $meta - Exception meta data. Used for logging.
|
||||
*
|
||||
*/
|
||||
public function __construct( $action, $message = '', $meta = [] ) {
|
||||
$this->action = $action;
|
||||
$this->meta = $meta;
|
||||
|
||||
$message = $this->format_message( $message );
|
||||
|
||||
parent::__construct( $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the exception to Elementor's log.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function log() {
|
||||
Plugin::elementor()->logger->get_logger()->error( $this->getMessage(), [ 'meta' => $this->meta ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return sprintf(
|
||||
'%s: %s',
|
||||
__CLASS__,
|
||||
$this->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations;
|
||||
|
||||
use ElementorPro\Core\Integrations\Actions\Action_Base;
|
||||
use ElementorPro\Core\Integrations\Actions\Email\Email;
|
||||
use ElementorPro\Core\Integrations\Actions\Email\Email_Message;
|
||||
use ElementorPro\Core\Integrations\Exceptions\Action_Failed_Exception;
|
||||
use ElementorPro\Core\Integrations\Exceptions\Action_Validation_Failed_Exception;
|
||||
use ElementorPro\Core\Utils\Registrar;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Integrations_Manager {
|
||||
|
||||
/**
|
||||
* Registered action types.
|
||||
*
|
||||
* @var Registrar
|
||||
*/
|
||||
protected $actions_registrar;
|
||||
|
||||
/**
|
||||
* Integrations_Manager constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->actions_registrar = new Registrar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an action instance.
|
||||
*
|
||||
* @shortcut `Registrar->get()`.
|
||||
*
|
||||
* @return \ElementorPro\Core\Integrations\Actions\Action_Base|null
|
||||
*/
|
||||
public function get_action( $id ) {
|
||||
if ( ! $this->is_initialized() ) {
|
||||
$this->init_actions();
|
||||
}
|
||||
|
||||
return $this->actions_registrar->get( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an action for a selected payload.
|
||||
*
|
||||
* @param array|mixed $payloads - Payloads instances to run the actions on.
|
||||
* @param null|string $id - If `$payloads` is not an array, a custom action ID can be provided.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run( $payloads, $id = null ) {
|
||||
if ( ! is_array( $payloads ) ) {
|
||||
$payloads = $id ? [ $id => $payloads ] : [ $payloads ];
|
||||
}
|
||||
|
||||
foreach ( $payloads as $key => $payload ) {
|
||||
// Get the action ID for the provided payload type.
|
||||
$action_id = is_numeric( $key ) ? get_class( $payload ) : $key;
|
||||
|
||||
/**
|
||||
* @type Action_Base $action
|
||||
*/
|
||||
$action = $this->get_action( $action_id );
|
||||
|
||||
if ( ! $action ) {
|
||||
throw new \Exception( "{$action_id} doesn't have an associated `Action`." );
|
||||
}
|
||||
|
||||
if ( ! ( $action instanceof Action_Base ) ) {
|
||||
$action_class = get_class( $action );
|
||||
|
||||
throw new \Exception( "{$action_class} is not a valid `Action_Base`." );
|
||||
}
|
||||
|
||||
try {
|
||||
$action->run( $payload );
|
||||
} catch ( Action_Validation_Failed_Exception $e ) {
|
||||
$e->log();
|
||||
} catch ( Action_Failed_Exception $e ) {
|
||||
$e->log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the manager actions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init_actions() {
|
||||
add_action( 'elementor_pro/core/integrations/actions/register', function ( Registrar $actions_registrar ) {
|
||||
$actions_registrar->register( new Email(), Email_Message::class );
|
||||
} );
|
||||
|
||||
do_action( 'elementor_pro/core/integrations/actions/register', $this->actions_registrar );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the manager is initialized.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_initialized() {
|
||||
return ! ! did_action( 'elementor_pro/core/integrations/actions/register' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Isolation;
|
||||
|
||||
interface Wordpress_Adapter_Interface {
|
||||
public function has_post_thumbnail();
|
||||
public function get_comments_number();
|
||||
public function is_author( $author = ''): bool;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Isolation;
|
||||
|
||||
class Wordpress_Adapter implements Wordpress_Adapter_Interface {
|
||||
|
||||
public function has_post_thumbnail(): bool {
|
||||
return has_post_thumbnail();
|
||||
}
|
||||
|
||||
public function get_comments_number() {
|
||||
return get_comments_number();
|
||||
}
|
||||
|
||||
public function is_author( $author = '' ): bool {
|
||||
return is_author( $author );
|
||||
}
|
||||
}
|
||||
114
wp-content/plugins/elementor-pro/core/modules-manager.php
Normal file
114
wp-content/plugins/elementor-pro/core/modules-manager.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core;
|
||||
|
||||
use ElementorPro\Plugin;
|
||||
use ElementorPro\Base\Module_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
final class Modules_Manager {
|
||||
/**
|
||||
* @var Module_Base[]
|
||||
*/
|
||||
private $modules = [];
|
||||
|
||||
public function __construct() {
|
||||
$modules = [
|
||||
'query-control',
|
||||
'custom-attributes',
|
||||
'custom-css',
|
||||
'page-transitions',
|
||||
// role-manager Must be before Global Widget
|
||||
'role-manager',
|
||||
'global-widget',
|
||||
'assets-manager',
|
||||
'popup',
|
||||
'motion-fx',
|
||||
'usage',
|
||||
'screenshots',
|
||||
'compatibility-tag',
|
||||
'admin-top-bar',
|
||||
'notes',
|
||||
'announcements',
|
||||
'display-conditions',
|
||||
'element-manager',
|
||||
|
||||
// Modules with Widgets.
|
||||
'theme-builder',
|
||||
'loop-builder',
|
||||
'posts',
|
||||
'gallery',
|
||||
'forms',
|
||||
'slides',
|
||||
'nav-menu',
|
||||
'animated-headline',
|
||||
'hotspot',
|
||||
'pricing',
|
||||
'flip-box',
|
||||
'call-to-action',
|
||||
'carousel',
|
||||
'table-of-contents',
|
||||
'countdown',
|
||||
'share-buttons',
|
||||
'theme-elements',
|
||||
'blockquote',
|
||||
'custom-code',
|
||||
'woocommerce',
|
||||
'social',
|
||||
'library',
|
||||
'dynamic-tags',
|
||||
'scroll-snap',
|
||||
'sticky',
|
||||
'wp-cli',
|
||||
'lottie',
|
||||
'code-highlight',
|
||||
'video-playlist',
|
||||
'payments',
|
||||
'progress-tracker',
|
||||
'mega-menu',
|
||||
'nested-carousel',
|
||||
'loop-filter',
|
||||
'tiers',
|
||||
];
|
||||
|
||||
foreach ( $modules as $module_name ) {
|
||||
$class_name = str_replace( '-', ' ', $module_name );
|
||||
$class_name = str_replace( ' ', '', ucwords( $class_name ) );
|
||||
$class_name = '\ElementorPro\Modules\\' . $class_name . '\Module';
|
||||
|
||||
/** @var Module_Base $class_name */
|
||||
$experimental_data = $class_name::get_experimental_data();
|
||||
|
||||
if ( $experimental_data ) {
|
||||
Plugin::elementor()->experiments->add_feature( $experimental_data );
|
||||
|
||||
if ( ! Plugin::elementor()->experiments->is_feature_active( $experimental_data['name'] ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $class_name::is_active() ) {
|
||||
$this->modules[ $module_name ] = $class_name::instance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $module_name
|
||||
*
|
||||
* @return Module_Base|Module_Base[]
|
||||
*/
|
||||
public function get_modules( $module_name ) {
|
||||
if ( $module_name ) {
|
||||
if ( isset( $this->modules[ $module_name ] ) ) {
|
||||
return $this->modules[ $module_name ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->modules;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Notifications;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Notification {
|
||||
|
||||
/**
|
||||
* Get the payloads of the notification data shape (e.g. `Email_Message`, `Database_Message`). Those will automatically
|
||||
* be sent over to the appropriate `Actions` under the `Integration_Manager` (using the `notify()` method).
|
||||
* This method is also used to determine notification channels based on user ($notifiable) preferences.
|
||||
*
|
||||
* Returned shape:
|
||||
* [
|
||||
* $payload1_instance,
|
||||
* $payload2_instance,
|
||||
* ]
|
||||
*
|
||||
* @param \ElementorPro\Core\Notifications\Traits\Notifiable $notifiable - The notified model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payloads( $notifiable ) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Notifications;
|
||||
|
||||
use ElementorPro\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Notifications_Manager {
|
||||
|
||||
/**
|
||||
* Send a notification.
|
||||
*
|
||||
* @param \ElementorPro\Core\Notifications\Notification $notification
|
||||
* @param $notifiable
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function send( Notification $notification, $notifiable ) {
|
||||
$payloads = $notification->get_payloads( $notifiable );
|
||||
|
||||
Plugin::instance()->integrations->run( $payloads );
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Notifications\Traits;
|
||||
|
||||
use ElementorPro\Core\Notifications\Notification;
|
||||
use ElementorPro\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
trait Notifiable {
|
||||
|
||||
/**
|
||||
* Notify a Model with a notification.
|
||||
* Syntactic sugar for sending notifications via the `Notifications_Manager`.
|
||||
*
|
||||
* Usage:
|
||||
* $model->notify( new User_Created_Notification( $new_user ) );
|
||||
*
|
||||
* @param Notification $notification - Notification to send.
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notify( Notification $notification ) {
|
||||
Plugin::instance()->notifications->send( $notification, $this );
|
||||
}
|
||||
}
|
||||
23
wp-content/plugins/elementor-pro/core/php-api.php
Normal file
23
wp-content/plugins/elementor-pro/core/php-api.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is responsible for the interaction with PHP Core API.
|
||||
* The main benefit is making it easy to mock in testing.
|
||||
*/
|
||||
class PHP_Api {
|
||||
|
||||
/**
|
||||
* @param $from
|
||||
* @param $to
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function move_uploaded_file( $from, $to ) {
|
||||
return @ move_uploaded_file( $from, $to );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user