File manager - Edit - /home/dubair8/saluempire.com/app.tar
Back
modules/onboarding/module.php 0000755 00000003605 15111627625 0012351 0 ustar 00 <?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 */ ); } } modules/import-export/runners/revert/templates.php 0000755 00000003403 15111627625 0016570 0 ustar 00 <?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 ); } } } modules/import-export/runners/import/templates.php 0000755 00000007445 15111627625 0016605 0 ustar 00 <?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; } } } modules/import-export/runners/export/templates.php 0000755 00000002733 15111627625 0016607 0 ustar 00 <?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, ], ]; } } modules/import-export/module.php 0000755 00000004350 15111627625 0013056 0 ustar 00 <?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() ); } } modules/site-editor/render-mode-template-preview.php 0000755 00000002077 15111627625 0016665 0 ustar 00 <?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; } } modules/site-editor/data/endpoints/template-types.php 0000755 00000001105 15111627625 0017054 0 ustar 00 <?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(); } } modules/site-editor/data/endpoints/conditions-config.php 0000755 00000001034 15111627625 0017514 0 ustar 00 <?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(); } } modules/site-editor/data/endpoints/templates-conditions.php 0000755 00000004207 15111627625 0020252 0 ustar 00 <?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; } } modules/site-editor/data/endpoints/templates-conditions-conflicts.php 0000755 00000001271 15111627625 0022232 0 ustar 00 <?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'] ); } } modules/site-editor/data/endpoints/templates.php 0000755 00000015223 15111627625 0016103 0 ustar 00 <?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 ); } } modules/site-editor/data/endpoints/base-endpoint.php 0000755 00000000726 15111627625 0016637 0 ustar 00 <?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 ); } } modules/site-editor/data/controller.php 0000755 00000001423 15111627625 0014262 0 ustar 00 <?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(); } } modules/site-editor/data/responses/lock-error-response.php 0000755 00000001170 15111627625 0020032 0 ustar 00 <?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, ] ); } } modules/site-editor/assets/js/site-editor.scss 0000755 00000000401 15111627625 0015513 0 ustar 00 .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); } modules/site-editor/assets/js/molecules/site-template-body.js 0000755 00000001427 15111627625 0020435 0 ustar 00 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, }; modules/site-editor/assets/js/molecules/back-button.js 0000755 00000000711 15111627625 0017131 0 ustar 00 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(), }; modules/site-editor/assets/js/molecules/site-template-header.js 0000755 00000003046 15111627625 0020727 0 ustar 00 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, }; modules/site-editor/assets/js/molecules/back-button.scss 0000755 00000000254 15111627625 0017472 0 ustar 00 .#{$eps-prefix}back-button { font-size: 14px; margin-block-end: spacing(24); .#{$eps-prefix}icon { transform: getValueByDirection(rotate(0deg), rotate(180deg)); } } modules/site-editor/assets/js/molecules/site-template.js 0000755 00000002723 15111627625 0017502 0 ustar 00 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, }; modules/site-editor/assets/js/molecules/site-template-thumbnail.js 0000755 00000001436 15111627625 0021463 0 ustar 00 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, }; modules/site-editor/assets/js/molecules/site-template-footer.js 0000755 00000001416 15111627625 0020774 0 ustar 00 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, }; modules/site-editor/assets/js/molecules/site-template.scss 0000755 00000004361 15111627625 0020041 0 ustar 00 $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}; } } modules/site-editor/assets/js/atoms/indicator-bullet.js 0000755 00000000431 15111627625 0017313 0 ustar 00 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, }; modules/site-editor/assets/js/atoms/preview-iframe.js 0000755 00000002211 15111627625 0016772 0 ustar 00 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, }; modules/site-editor/assets/js/atoms/indicator-bullet.scss 0000755 00000002110 15111627625 0017646 0 ustar 00 $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; } } modules/site-editor/assets/js/atoms/preview-iframe.scss 0000755 00000000345 15111627625 0017337 0 ustar 00 .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; } } modules/site-editor/assets/js/part-actions/dialog-delete.js 0000755 00000002113 15111627625 0020031 0 ustar 00 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, }; modules/site-editor/assets/js/part-actions/dialog-rename.js 0000755 00000002724 15111627625 0020046 0 ustar 00 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, }; modules/site-editor/assets/js/part-actions/dialogs-and-buttons.js 0000755 00000003671 15111627625 0021222 0 ustar 00 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, }; modules/site-editor/assets/js/editor.js 0000755 00000000652 15111627625 0014222 0 ustar 00 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 ); } } } modules/site-editor/assets/js/hooks/use-templates-screenshot.js 0000755 00000003626 15111627625 0021026 0 ustar 00 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; } modules/site-editor/assets/js/data/commands/templates.js 0000755 00000000313 15111627625 0017436 0 ustar 00 export class Templates extends $e.modules.CommandData { static signature = 'site-editor/templates'; static getEndpointFormat() { return 'site-editor/templates/{id}'; } } export default Templates; modules/site-editor/assets/js/data/commands/templates-conditions.js 0000755 00000000365 15111627625 0021614 0 ustar 00 export class TemplatesConditions extends $e.modules.CommandData { static signature = 'site-editor/templates-conditions'; static getEndpointFormat() { return 'site-editor/templates-conditions/{id}'; } } export default TemplatesConditions; modules/site-editor/assets/js/data/commands/templates-conditions-conflicts.js 0000755 00000000434 15111627625 0023573 0 ustar 00 export class TemplatesConditionsConflicts extends $e.modules.CommandData { static signature = 'site-editor/templates-conditions-conflicts'; static getEndpointFormat() { return `${ TemplatesConditionsConflicts.signature }/{id}`; } } export default TemplatesConditionsConflicts; modules/site-editor/assets/js/data/commands/index.js 0000755 00000000360 15111627625 0016551 0 ustar 00 export { Templates } from './templates'; export { ConditionsConfig } from './conditions-config'; export { TemplatesConditions } from './templates-conditions'; export { TemplatesConditionsConflicts } from './templates-conditions-conflicts'; modules/site-editor/assets/js/data/commands/conditions-config.js 0000755 00000000351 15111627625 0021056 0 ustar 00 export class ConditionsConfig extends $e.modules.CommandData { static signature = 'site-editor/conditions-config'; static getEndpointFormat() { return 'site-editor/conditions-config/{id}'; } } export default ConditionsConfig; modules/site-editor/assets/js/data/component.js 0000755 00000000422 15111627625 0015642 0 ustar 00 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 ); } } modules/site-editor/assets/js/site-editor.js 0000755 00000005750 15111627625 0015170 0 ustar 00 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, } ); } } modules/site-editor/assets/js/context/base-context.js 0000755 00000002021 15111627625 0017004 0 ustar 00 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; modules/site-editor/assets/js/context/templates.js 0000755 00000006744 15111627625 0016426 0 ustar 00 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; modules/site-editor/assets/js/context/models/condition.js 0000755 00000002162 15111627625 0017667 0 ustar 00 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, }; } } modules/site-editor/assets/js/context/services/conditions-config.js 0000755 00000005270 15111627625 0021660 0 ustar 00 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; modules/site-editor/assets/js/context/conditions.js 0000755 00000017735 15111627625 0016603 0 ustar 00 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; modules/site-editor/assets/js/pages/add-new.scss 0000755 00000000234 15111627625 0015705 0 ustar 00 .eps-add-new__overlay { display: flex; align-items: center; justify-content: center; opacity: 1; --card-image-overlay-background-color: transparent; } modules/site-editor/assets/js/pages/template-type.scss 0000755 00000000257 15111627625 0017165 0 ustar 00 .e-site-editor__templates { .page-header { margin-block-end: spacing(10); > a { align-self: baseline; } } .eps-separator { margin-block-end: spacing(44); } } modules/site-editor/assets/js/pages/add-new.js 0000755 00000003216 15111627625 0015351 0 ustar 00 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> ); } modules/site-editor/assets/js/pages/templates.js 0000755 00000001300 15111627625 0016020 0 ustar 00 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> ); } modules/site-editor/assets/js/pages/template-type.js 0000755 00000002330 15111627625 0016620 0 ustar 00 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, }; modules/site-editor/assets/js/pages/import.js 0000755 00000010310 15111627625 0015335 0 ustar 00 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 = React.useMemo( () => action.current === TemplatesProvider.actions.IMPORT, [ action ] ), isUploading = React.useMemo( () => isImport && action.loading, [ action ] ), hasError = React.useMemo( () => isImport && action.error, [ action ] ); 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( /^[^,]+,/, '' ) ); }; } ) ); } modules/site-editor/assets/js/pages/conditions/condition-sub.js 0000755 00000001330 15111627625 0020753 0 ustar 00 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: {}, }; modules/site-editor/assets/js/pages/conditions/condition-conflicts.js 0000755 00000001244 15111627625 0022152 0 ustar 00 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, }; modules/site-editor/assets/js/pages/conditions/conditions.scss 0000755 00000011517 15111627625 0020716 0 ustar 00 @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); } modules/site-editor/assets/js/pages/conditions/conditions-rows.js 0000755 00000005544 15111627625 0021352 0 ustar 00 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, }; modules/site-editor/assets/js/pages/conditions/conditions.js 0000755 00000003150 15111627625 0020351 0 ustar 00 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, }; modules/site-editor/assets/js/pages/conditions/conditions-api.scss 0000755 00000013411 15111627625 0021460 0 ustar 00 $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} } modules/site-editor/assets/js/pages/conditions/condition-sub-id.js 0000755 00000003403 15111627625 0021350 0 ustar 00 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: [], }; modules/site-editor/assets/js/pages/conditions/condition-type.js 0000755 00000001666 15111627625 0021157 0 ustar 00 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: '', }; modules/site-editor/assets/js/pages/conditions/condition-name.js 0000755 00000001407 15111627625 0021107 0 ustar 00 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: '', }; modules/site-editor/assets/js/organisms/site-templates.js 0000755 00000005454 15111627625 0017703 0 ustar 00 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, }; modules/site-editor/module.php 0000755 00000015076 15111627625 0012464 0 ustar 00 <?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 */ ); } } modules/kit-library/module.php 0000755 00000004640 15111627625 0012460 0 ustar 00 <?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' ), ] ) ); } /** * @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 ); } } app.php 0000755 00000004573 15111627625 0006057 0 ustar 00 <?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' ] ); } } assets/js/index.js 0000755 00000000125 15111627625 0010136 0 ustar 00 import Module from '../../modules/site-editor/assets/js/site-editor'; new Module(); assets/js/ui/connect-button.js 0000755 00000001643 15111627625 0012414 0 ustar 00 import React, { 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 ); assets/js/hooks/use-feature-lock.js 0000755 00000001155 15111627625 0013331 0 ustar 00 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, }; } assets/js/utils.js 0000755 00000001452 15111627625 0010173 0 ustar 00 // 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; }; assets/styles/app-imports.scss 0000755 00000001104 15111627625 0012546 0 ustar 00 @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";
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings