Файловый менеджер - Редактировать - /var/www/xthruster/html/wp-content/uploads/flags/core.tar
Назад
documents-manager.php 0000644 00000045757 14717626151 0010722 0 ustar 00 <?php namespace Elementor\Core; use Elementor\Core\Base\Document; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\DocumentTypes\Page; use Elementor\Core\DocumentTypes\Post; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Elementor documents manager. * * Elementor documents manager handler class is responsible for registering and * managing Elementor documents. * * @since 2.0.0 */ class Documents_Manager { /** * Registered types. * * Holds the list of all the registered types. * * @since 2.0.0 * @access protected * * @var Document[] */ protected $types = []; /** * Registered documents. * * Holds the list of all the registered documents. * * @since 2.0.0 * @access protected * * @var Document[] */ protected $documents = []; /** * Current document. * * Holds the current document. * * @since 2.0.0 * @access protected * * @var Document */ protected $current_doc; /** * Switched data. * * Holds the current document when changing to the requested post. * * @since 2.0.0 * @access protected * * @var array */ protected $switched_data = []; protected $cpt = []; /** * Documents manager constructor. * * Initializing the Elementor documents manager. * * @since 2.0.0 * @access public */ public function __construct() { add_action( 'elementor/documents/register', [ $this, 'register_default_types' ], 0 ); add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); add_filter( 'post_row_actions', [ $this, 'filter_post_row_actions' ], 11, 2 ); add_filter( 'page_row_actions', [ $this, 'filter_post_row_actions' ], 11, 2 ); add_filter( 'user_has_cap', [ $this, 'remove_user_edit_cap' ], 10, 3 ); add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] ); } /** * Register ajax actions. * * Process ajax action handles when saving data and discarding changes. * * Fired by `elementor/ajax/register_actions` action. * * @since 2.0.0 * @access public * * @param Ajax $ajax_manager An instance of the ajax manager. */ public function register_ajax_actions( $ajax_manager ) { $ajax_manager->register_ajax_action( 'save_builder', [ $this, 'ajax_save' ] ); $ajax_manager->register_ajax_action( 'discard_changes', [ $this, 'ajax_discard_changes' ] ); $ajax_manager->register_ajax_action( 'get_document_config', [ $this, 'ajax_get_document_config' ] ); } /** * Register default types. * * Registers the default document types. * * @since 2.0.0 * @access public */ public function register_default_types() { $default_types = [ 'post' => Post::get_class_full_name(), // BC. 'wp-post' => Post::get_class_full_name(), 'wp-page' => Page::get_class_full_name(), ]; foreach ( $default_types as $type => $class ) { $this->register_document_type( $type, $class ); } } /** * Register document type. * * Registers a single document. * * @since 2.0.0 * @access public * * @param string $type Document type name. * @param string $class The name of the class that registers the document type. * Full name with the namespace. * * @return Documents_Manager The updated document manager instance. */ public function register_document_type( $type, $class ) { $this->types[ $type ] = $class; $cpt = $class::get_property( 'cpt' ); if ( $cpt ) { foreach ( $cpt as $post_type ) { $this->cpt[ $post_type ] = $type; } } if ( $class::get_property( 'register_type' ) ) { Source_Local::add_template_type( $type ); } return $this; } /** * Get document. * * Retrieve the document data based on a post ID. * * @since 2.0.0 * @access public * * @param int $post_id Post ID. * @param bool $from_cache Optional. Whether to retrieve cached data. Default is true. * * @return false|Document Document data or false if post ID was not entered. */ public function get( $post_id, $from_cache = true ) { $this->register_types(); $post_id = absint( $post_id ); if ( ! $post_id || ! get_post( $post_id ) ) { return false; } /** * Retrieve document post ID. * * Filters the document post ID. * * @since 2.0.7 * * @param int $post_id The post ID of the document. */ $post_id = apply_filters( 'elementor/documents/get/post_id', $post_id ); if ( ! $from_cache || ! isset( $this->documents[ $post_id ] ) ) { $doc_type = $this->get_doc_type_by_id( $post_id ); $doc_type_class = $this->get_document_type( $doc_type ); $this->documents[ $post_id ] = new $doc_type_class( [ 'post_id' => $post_id, ] ); } return $this->documents[ $post_id ]; } /** * Retrieve a document after checking it exist and allowed to edit. * * @since 3.13.0 * * @param int $post_id The post ID of the document. * * @return Document * @throws \Exception */ public function get_with_permissions( $id ): Document { $document = $this->get( $id ); if ( ! $document ) { throw new \Exception( 'Not found.' ); } if ( ! $document->is_editable_by_current_user() ) { throw new \Exception( 'Access denied.' ); } return $document; } /** * A `void` version for `get_with_permissions`. * * @param $id * @return void * @throws \Exception */ public function check_permissions( $id ) { $this->get_with_permissions( $id ); } /** * Get document or autosave. * * Retrieve either the document or the autosave. * * @since 2.0.0 * @access public * * @param int $id Optional. Post ID. Default is `0`. * @param int $user_id Optional. User ID. Default is `0`. * * @return false|Document The document if it exist, False otherwise. */ public function get_doc_or_auto_save( $id, $user_id = 0 ) { $document = $this->get( $id ); if ( $document && $document->get_autosave_id( $user_id ) ) { $document = $document->get_autosave( $user_id ); } return $document; } /** * Get document for frontend. * * Retrieve the document for frontend use. * * @since 2.0.0 * @access public * * @param int $post_id Optional. Post ID. Default is `0`. * * @return false|Document The document if it exist, False otherwise. */ public function get_doc_for_frontend( $post_id ) { $preview_id = (int) Utils::get_super_global_value( $_GET, 'preview_id' ); $is_preview = is_preview() && $post_id === $preview_id; $is_nonce_verify = wp_verify_nonce( Utils::get_super_global_value( $_GET, 'preview_nonce' ), 'post_preview_' . $preview_id ); if ( ( $is_preview && $is_nonce_verify ) || Plugin::$instance->preview->is_preview_mode() ) { $document = $this->get_doc_or_auto_save( $post_id, get_current_user_id() ); } else { $document = $this->get( $post_id ); } return $document; } /** * Get document type. * * Retrieve the type of any given document. * * @since 2.0.0 * @access public * * @param string $type * * @param string $fallback * * @return Document|bool The type of the document. */ public function get_document_type( $type, $fallback = 'post' ) { $types = $this->get_document_types(); if ( isset( $types[ $type ] ) ) { return $types[ $type ]; } if ( isset( $types[ $fallback ] ) ) { return $types[ $fallback ]; } return false; } /** * Get document types. * * Retrieve the all the registered document types. * * @since 2.0.0 * @access public * * @param array $args Optional. An array of key => value arguments to match against * the properties. Default is empty array. * @param string $operator Optional. The logical operation to perform. 'or' means only one * element from the array needs to match; 'and' means all elements * must match; 'not' means no elements may match. Default 'and'. * * @return Document[] All the registered document types. */ public function get_document_types( $args = [], $operator = 'and' ) { $this->register_types(); if ( ! empty( $args ) ) { $types_properties = $this->get_types_properties(); $filtered = wp_filter_object_list( $types_properties, $args, $operator ); return array_intersect_key( $this->types, $filtered ); } return $this->types; } /** * Get document types with their properties. * * @return array A list of properties arrays indexed by the type. */ public function get_types_properties() { $types_properties = []; foreach ( $this->get_document_types() as $type => $class ) { $types_properties[ $type ] = $class::get_properties(); } return $types_properties; } /** * Create a document. * * Create a new document using any given parameters. * * @since 2.0.0 * @access public * * @param string $type Document type. * @param array $post_data An array containing the post data. * @param array $meta_data An array containing the post meta data. * * @return Document The type of the document. */ public function create( $type, $post_data = [], $meta_data = [] ) { $class = $this->get_document_type( $type, false ); if ( ! $class ) { return new \WP_Error( 500, sprintf( 'Type %s does not exist.', $type ) ); } if ( empty( $post_data['post_title'] ) ) { $post_data['post_title'] = esc_html__( 'Elementor', 'elementor' ); if ( 'post' !== $type ) { $post_data['post_title'] = sprintf( /* translators: %s: Document title. */ __( 'Elementor %s', 'elementor' ), call_user_func( [ $class, 'get_title' ] ) ); } $update_title = true; } $meta_data['_elementor_edit_mode'] = 'builder'; // Save the type as-is for plugins that hooked at `wp_insert_post`. $meta_data[ Document::TYPE_META_KEY ] = $type; $post_data['meta_input'] = $meta_data; $post_types = $class::get_property( 'cpt' ); if ( ! empty( $post_types[0] ) && empty( $post_data['post_type'] ) ) { $post_data['post_type'] = $post_types[0]; } $post_id = wp_insert_post( $post_data ); if ( ! empty( $update_title ) ) { $post_data['ID'] = $post_id; $post_data['post_title'] .= ' #' . $post_id; // The meta doesn't need update. unset( $post_data['meta_input'] ); wp_update_post( $post_data ); } /** @var Document $document */ $document = new $class( [ 'post_id' => $post_id, ] ); // Let the $document to re-save the template type by his way + version. $document->save( [] ); return $document; } /** * Remove user edit capabilities if document is not editable. * * Filters the user capabilities to disable editing in admin. * * @param array $allcaps An array of all the user's capabilities. * @param array $caps Actual capabilities for meta capability. * @param array $args Optional parameters passed to has_cap(), typically object ID. * * @return array */ public function remove_user_edit_cap( $allcaps, $caps, $args ) { global $pagenow; if ( ! in_array( $pagenow, [ 'post.php', 'edit.php' ], true ) ) { return $allcaps; } // Don't touch not existing or not allowed caps. if ( empty( $caps[0] ) || empty( $allcaps[ $caps[0] ] ) ) { return $allcaps; } $capability = $args[0]; if ( 'edit_post' !== $capability ) { return $allcaps; } if ( empty( $args[2] ) ) { return $allcaps; } $post_id = $args[2]; $document = Plugin::$instance->documents->get( $post_id ); if ( ! $document ) { return $allcaps; } $allcaps[ $caps[0] ] = $document::get_property( 'is_editable' ); return $allcaps; } /** * Filter Post Row Actions. * * Let the Document to filter the array of row action links on the Posts list table. * * @param array $actions * @param \WP_Post $post * * @return array */ public function filter_post_row_actions( $actions, $post ) { $document = $this->get( $post->ID ); if ( $document ) { $actions = $document->filter_admin_row_actions( $actions ); } return $actions; } /** * Save document data using ajax. * * Save the document on the builder using ajax, when saving the changes, and refresh the editor. * * @since 2.0.0 * @access public * * @param $request Post ID. * * @throws \Exception If current user don't have permissions to edit the post or the post is not using Elementor. * * @return array The document data after saving. */ public function ajax_save( $request ) { $document = $this->get( $request['editor_post_id'] ); if ( ! $document->is_built_with_elementor() || ! $document->is_editable_by_current_user() ) { throw new \Exception( 'Access denied.' ); } $this->switch_to_document( $document ); // Set the post as global post. Plugin::$instance->db->switch_to_post( $document->get_post()->ID ); $status = Document::STATUS_DRAFT; if ( isset( $request['status'] ) && in_array( $request['status'], [ Document::STATUS_PUBLISH, Document::STATUS_PRIVATE, Document::STATUS_PENDING, Document::STATUS_AUTOSAVE ], true ) ) { $status = $request['status']; } if ( Document::STATUS_AUTOSAVE === $status ) { // If the post is a draft - save the `autosave` to the original draft. // Allow a revision only if the original post is already published. if ( in_array( $document->get_post()->post_status, [ Document::STATUS_PUBLISH, Document::STATUS_PRIVATE ], true ) ) { $document = $document->get_autosave( 0, true ); } } // Set default page template because the footer-saver doesn't send default values, // But if the template was changed from canvas to default - it needed to save. if ( Utils::is_cpt_custom_templates_supported() && ! isset( $request['settings']['template'] ) ) { $request['settings']['template'] = 'default'; } $data = [ 'elements' => $request['elements'], 'settings' => $request['settings'], ]; $document->save( $data ); $post = $document->get_post(); $main_post = $document->get_main_post(); // Refresh after save. $document = $this->get( $post->ID, false ); $return_data = [ 'status' => $post->post_status, 'config' => [ 'document' => [ 'last_edited' => $document->get_last_edited(), 'urls' => [ 'wp_preview' => $document->get_wp_preview_url(), ], ], ], ]; $post_status_object = get_post_status_object( $main_post->post_status ); if ( $post_status_object ) { $return_data['config']['document']['status'] = [ 'value' => $post_status_object->name, 'label' => $post_status_object->label, ]; } /** * Returned documents ajax saved data. * * Filters the ajax data returned when saving the post on the builder. * * @since 2.0.0 * * @param array $return_data The returned data. * @param Document $document The document instance. */ $return_data = apply_filters( 'elementor/documents/ajax_save/return_data', $return_data, $document ); return $return_data; } /** * Ajax discard changes. * * Load the document data from an autosave, deleting unsaved changes. * * @param $request * * @return bool True if changes discarded, False otherwise. * @throws \Exception * * @since 2.0.0 * @access public * */ public function ajax_discard_changes( $request ) { $document = $this->get_with_permissions( $request['editor_post_id'] ); $autosave = $document->get_autosave(); if ( $autosave ) { $success = $autosave->delete(); } else { $success = true; } return $success; } public function ajax_get_document_config( $request ) { $post_id = absint( $request['id'] ); Plugin::$instance->editor->set_post_id( $post_id ); $document = $this->get_doc_or_auto_save( $post_id ); if ( ! $document ) { throw new \Exception( 'Not found.' ); } if ( ! $document->is_editable_by_current_user() ) { throw new \Exception( 'Access denied.' ); } // Set the global data like $post, $authordata and etc Plugin::$instance->db->switch_to_post( $post_id ); $this->switch_to_document( $document ); // Change mode to Builder $document->set_is_built_with_elementor( true ); $doc_config = $document->get_config(); return $doc_config; } /** * Switch to document. * * Change the document to any new given document type. * * @since 2.0.0 * @access public * * @param Document $document The document to switch to. */ public function switch_to_document( $document ) { // If is already switched, or is the same post, return. if ( $this->current_doc === $document ) { $this->switched_data[] = false; return; } $this->switched_data[] = [ 'switched_doc' => $document, 'original_doc' => $this->current_doc, // Note, it can be null if the global isn't set ]; $this->current_doc = $document; } /** * Restore document. * * Rollback to the original document. * * @since 2.0.0 * @access public */ public function restore_document() { $data = array_pop( $this->switched_data ); // If not switched, return. if ( ! $data ) { return; } $this->current_doc = $data['original_doc']; } /** * Get current document. * * Retrieve the current document. * * @since 2.0.0 * @access public * * @return Document The current document. */ public function get_current() { return $this->current_doc; } public function localize_settings( $settings ) { $translations = []; foreach ( $this->get_document_types() as $type => $class ) { $translations[ $type ] = $class::get_title(); } return array_replace_recursive( $settings, [ 'i18n' => $translations, ] ); } private function register_types() { if ( ! did_action( 'elementor/documents/register' ) ) { /** * Register Elementor documents. * * @since 2.0.0 * * @param Documents_Manager $this The document manager instance. */ do_action( 'elementor/documents/register', $this ); } } /** * Get create new post URL. * * Retrieve a custom URL for creating a new post/page using Elementor. * * @param string $post_type Optional. Post type slug. Default is 'page'. * @param string|null $template_type Optional. Query arg 'template_type'. Default is null. * * @return string A URL for creating new post using Elementor. */ public static function get_create_new_post_url( $post_type = 'page', $template_type = null ) { $query_args = [ 'action' => 'elementor_new_post', 'post_type' => $post_type, ]; if ( $template_type ) { $query_args['template_type'] = $template_type; } $new_post_url = add_query_arg( $query_args, admin_url( 'edit.php' ) ); $new_post_url = add_query_arg( '_wpnonce', wp_create_nonce( 'elementor_action_new_post' ), $new_post_url ); return $new_post_url; } private function get_doc_type_by_id( $post_id ) { // Auto-save inherits from the original post. if ( wp_is_post_autosave( $post_id ) ) { $post_id = wp_get_post_parent_id( $post_id ); } // Content built with Elementor. $template_type = get_post_meta( $post_id, Document::TYPE_META_KEY, true ); if ( $template_type && isset( $this->types[ $template_type ] ) ) { return $template_type; } // Elementor installation on a site with existing content (which doesn't contain Elementor's meta). $post_type = get_post_type( $post_id ); return $this->cpt[ $post_type ] ?? 'post'; } } responsive/files/frontend.php 0000644 00000011550 14717626151 0012407 0 ustar 00 <?php namespace Elementor\Core\Responsive\Files; use Elementor\Core\Breakpoints\Breakpoint; use Elementor\Core\Files\Base; use Elementor\Core\Responsive\Responsive; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Frontend extends Base { const META_KEY = 'elementor-custom-breakpoints-files'; private $template_file; /** * @since 2.1.0 * @access public */ public function __construct( $file_name, $template_file = null ) { $this->template_file = $template_file; parent::__construct( $file_name ); } /** * @since 2.1.0 * @access public */ public function parse_content() { $breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints(); $breakpoints_keys = array_keys( $breakpoints ); $file_content = Utils::file_get_contents( $this->template_file ); // The regex pattern parses placeholders located in the frontend _templates.scss file. $file_content = preg_replace_callback( '/ELEMENTOR_SCREEN_([A-Z_]+)(?:_(MIN|MAX|NEXT))/', function ( $placeholder_data ) use ( $breakpoints_keys, $breakpoints ) { // Handle BC for legacy template files and Elementor Pro builds. $placeholder_data = $this->maybe_convert_placeholder_data( $placeholder_data ); $breakpoint_index = array_search( strtolower( $placeholder_data[1] ), $breakpoints_keys, true ); if ( 'DESKTOP' === $placeholder_data[1] ) { if ( 'MIN' === $placeholder_data[2] ) { $value = Plugin::$instance->breakpoints->get_desktop_min_point(); } elseif ( isset( $breakpoints['widescreen'] ) ) { // If the 'widescreen' breakpoint is active, the Desktop's max value is the Widescreen breakpoint - 1px. $value = $breakpoints['widescreen']->get_value() - 1; } else { // If the 'widescreen' breakpoint is not active, the Desktop device should not have a max value. $value = 99999; } } elseif ( false === $breakpoint_index ) { // If the breakpoint in the placeholder is not active - use a -1 value for the media query, to make // sure the setting is printed (to avoid a PHP error) but doesn't apply. return -1; } elseif ( 'WIDESCREEN' === $placeholder_data[1] ) { $value = $breakpoints['widescreen']->get_value(); } else { $breakpoint_index = array_search( strtolower( $placeholder_data[1] ), $breakpoints_keys, true ); $is_max_point = 'MAX' === $placeholder_data[2]; // If the placeholder capture is `MOBILE_NEXT` or `TABLET_NEXT`, the original breakpoint value is used. if ( ! $is_max_point && 'NEXT' !== $placeholder_data[2] ) { $breakpoint_index--; } $value = $breakpoints[ $breakpoints_keys[ $breakpoint_index ] ]->get_value(); if ( ! $is_max_point ) { $value++; } } return $value . 'px'; }, $file_content ); return $file_content; } /** * Load meta. * * Retrieve the file meta data. * * @since 2.1.0 * @access protected */ protected function load_meta() { $option = $this->load_meta_option(); $file_meta_key = $this->get_file_meta_key(); if ( empty( $option[ $file_meta_key ] ) ) { return []; } return $option[ $file_meta_key ]; } /** * Update meta. * * Update the file meta data. * * @since 2.1.0 * @access protected * * @param array $meta New meta data. */ protected function update_meta( $meta ) { $option = $this->load_meta_option(); $option[ $this->get_file_meta_key() ] = $meta; update_option( static::META_KEY, $option ); } /** * Delete meta. * * Delete the file meta data. * * @since 2.1.0 * @access protected */ protected function delete_meta() { $option = $this->load_meta_option(); $file_meta_key = $this->get_file_meta_key(); if ( isset( $option[ $file_meta_key ] ) ) { unset( $option[ $file_meta_key ] ); } if ( $option ) { update_option( static::META_KEY, $option ); } else { delete_option( static::META_KEY ); } } /** * @since 2.1.0 * @access private */ private function get_file_meta_key() { return pathinfo( $this->get_file_name(), PATHINFO_FILENAME ); } /** * @since 2.1.0 * @access private */ private function load_meta_option() { $option = get_option( static::META_KEY ); if ( ! $option ) { $option = []; } return $option; } /** * Maybe Convert Placeholder Data * * Converts responsive placeholders in Elementor CSS template files from the legacy format into the new format. * Used for backwards compatibility for old Pro versions that were built with an Elementor Core version <3.2.0. * * @since 3.2.3 * * @param $placeholder_data * @return mixed */ private function maybe_convert_placeholder_data( $placeholder_data ) { switch ( $placeholder_data[1] ) { case 'SM': $placeholder_data[1] = 'MOBILE'; break; case 'MD': $placeholder_data[1] = 'TABLET'; break; case 'LG': $placeholder_data[1] = 'DESKTOP'; } return $placeholder_data; } } responsive/responsive.php 0000644 00000010571 14717626151 0011665 0 ustar 00 <?php namespace Elementor\Core\Responsive; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Modules\DevTools\Deprecation; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor responsive. * * Elementor responsive handler class is responsible for setting up Elementor * responsive breakpoints. * * @since 1.0.0 * @deprecated 3.2.0 */ class Responsive { /** * The Elementor breakpoint prefix. * * @deprecated 3.2.0 */ const BREAKPOINT_OPTION_PREFIX = 'viewport_'; /** * Default breakpoints. * * Holds the default responsive breakpoints. * * @since 1.0.0 * @deprecated 3.2.0 * @access private * @static * * @var array Default breakpoints. */ private static $default_breakpoints = [ 'xs' => 0, 'sm' => 480, 'md' => 768, 'lg' => 1025, 'xl' => 1440, 'xxl' => 1600, ]; /** * Editable breakpoint keys. * * Holds the editable breakpoint keys. * * @since 1.0.0 * @deprecated 3.2.0 * @access private * @static * * @var array Editable breakpoint keys. */ private static $editable_breakpoints_keys = [ 'md', 'lg', ]; /** * Get default breakpoints. * * Retrieve the default responsive breakpoints. * * @since 1.0.0 * @deprecated 3.2.0 Use `Elementor\Core\Breakpoints\Manager::get_default_config()` instead. * @access public * @static * * @return array Default breakpoints. */ public static function get_default_breakpoints() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.2.0', 'Elementor\Core\Breakpoints\Manager::get_default_config()' ); return self::$default_breakpoints; } /** * Get editable breakpoints. * * Retrieve the editable breakpoints. * * @since 1.0.0 * @deprecated 3.2.0 * @access public * @static * * @return array Editable breakpoints. */ public static function get_editable_breakpoints() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.2.0' ); return array_intersect_key( self::get_breakpoints(), array_flip( self::$editable_breakpoints_keys ) ); } /** * Get breakpoints. * * Retrieve the responsive breakpoints. * * @since 1.0.0 * @deprecated 3.2.0 * @access public * @static * * @return array Responsive breakpoints. */ public static function get_breakpoints() { return array_reduce( array_keys( self::$default_breakpoints ), function( $new_array, $breakpoint_key ) { if ( ! in_array( $breakpoint_key, self::$editable_breakpoints_keys ) ) { $new_array[ $breakpoint_key ] = self::$default_breakpoints[ $breakpoint_key ]; } else { $saved_option = Plugin::$instance->kits_manager->get_current_settings( self::BREAKPOINT_OPTION_PREFIX . $breakpoint_key ); $new_array[ $breakpoint_key ] = $saved_option ? (int) $saved_option : self::$default_breakpoints[ $breakpoint_key ]; } return $new_array; }, [] ); } /** * @since 2.1.0 * @deprecated 3.2.0 Use `Plugin::$instance->breakpoints->has_custom_breakpoints()` instead. * @access public * @static */ public static function has_custom_breakpoints() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.2.0', 'Plugin::$instance->breakpoints->has_custom_breakpoints()' ); return ! ! array_diff( self::$default_breakpoints, self::get_breakpoints() ); } /** * @since 2.1.0 * @deprecated 3.2.0 Use `Elementor\Core\Breakpoints\Manager::get_stylesheet_templates_path()` instead. * @access public * @static */ public static function get_stylesheet_templates_path() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.2.0', 'Elementor\Core\Breakpoints\Manager::get_stylesheet_templates_path()' ); return Breakpoints_Manager::get_stylesheet_templates_path(); } /** * @since 2.1.0 * @deprecated 3.2.0 Use `Elementor\Core\Breakpoints\Manager::compile_stylesheet_templates()` instead. * @access public * @static */ public static function compile_stylesheet_templates() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.2.0', 'Elementor\Core\Breakpoints\Manager::compile_stylesheet_templates()' ); Breakpoints_Manager::compile_stylesheet_templates(); } } frontend/performance.php 0000644 00000001435 14717626151 0011412 0 ustar 00 <?php namespace Elementor\Core\Frontend; use Elementor\Plugin; class Performance { private static $use_style_controls = false; private static $is_frontend = null; public static function set_use_style_controls( bool $bool ): void { static::$use_style_controls = $bool; } public static function is_use_style_controls(): bool { return static::$use_style_controls; } public static function should_optimize_controls() { if ( null === static::$is_frontend ) { static::$is_frontend = ( ! is_admin() && ! Plugin::$instance->preview->is_preview_mode() ); } return static::$is_frontend; } public static function is_optimized_control_loading_feature_enabled(): bool { return Plugin::$instance->experiments->is_feature_active( 'e_optimized_control_loading' ); } } frontend/render-modes/render-mode-normal.php 0000644 00000000632 14717626151 0015162 0 ustar 00 <?php namespace Elementor\Core\Frontend\RenderModes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Render_Mode_Normal extends Render_Mode_Base { /** * @return string */ public static function get_name() { return 'normal'; } /** * Anyone can access the normal render mode. * * @return bool */ public function get_permissions_callback() { return true; } } frontend/render-modes/render-mode-base.php 0000644 00000004147 14717626151 0014611 0 ustar 00 <?php namespace Elementor\Core\Frontend\RenderModes; use Elementor\Plugin; use Elementor\Core\Base\Document; use Elementor\Core\Frontend\Render_Mode_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Render_Mode_Base { const ENQUEUE_SCRIPTS_PRIORITY = 10; const ENQUEUE_STYLES_PRIORITY = 10; /** * @var int */ protected $post_id; /** * @var Document */ protected $document; /** * Render_Mode_Base constructor. * * @param $post_id */ public function __construct( $post_id ) { $this->post_id = intval( $post_id ); } /** * Returns the key name of the class. * * @return string * @throws \Exception */ public static function get_name() { throw new \Exception( 'You must implements `get_name` static method in ' . static::class ); } /** * @param $post_id * * @return string * @throws \Exception */ public static function get_url( $post_id ) { return Render_Mode_Manager::get_base_url( $post_id, static::get_name() ); } /** * Runs before the render, by default load scripts and styles. */ public function prepare_render() { add_action( 'wp_enqueue_scripts', function () { $this->enqueue_scripts(); }, static::ENQUEUE_SCRIPTS_PRIORITY ); add_action( 'wp_enqueue_scripts', function () { $this->enqueue_styles(); }, static::ENQUEUE_STYLES_PRIORITY ); } /** * By default do not do anything. */ protected function enqueue_scripts() { // } /** * By default do not do anything. */ protected function enqueue_styles() { // } /** * Check if the current user has permissions for the current render mode. * * @return bool */ public function get_permissions_callback() { return $this->get_document()->is_editable_by_current_user(); } /** * Checks if the current render mode is static render, By default returns false. * * @return bool */ public function is_static() { return false; } /** * @return Document */ public function get_document() { if ( ! $this->document ) { $this->document = Plugin::$instance->documents->get( $this->post_id ); } return $this->document; } } frontend/render-mode-manager.php 0000644 00000007416 14717626151 0012727 0 ustar 00 <?php namespace Elementor\Core\Frontend; use Elementor\Core\Frontend\RenderModes\Render_Mode_Base; use Elementor\Core\Frontend\RenderModes\Render_Mode_Normal; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Render_Mode_Manager { const QUERY_STRING_PARAM_NAME = 'render_mode'; const QUERY_STRING_POST_ID = 'post_id'; const QUERY_STRING_NONCE_PARAM_NAME = 'render_mode_nonce'; const NONCE_ACTION_PATTERN = 'render_mode_{post_id}'; /** * @var Render_Mode_Base */ private $current; /** * @var Render_Mode_Base[] */ private $render_modes = []; /** * @param $post_id * @param $render_mode_name * * @return string */ public static function get_base_url( $post_id, $render_mode_name ) { return add_query_arg( [ self::QUERY_STRING_POST_ID => $post_id, self::QUERY_STRING_PARAM_NAME => $render_mode_name, self::QUERY_STRING_NONCE_PARAM_NAME => wp_create_nonce( self::get_nonce_action( $post_id ) ), 'ver' => time(), ], get_permalink( $post_id ) ); } /** * @param $post_id * * @return string */ public static function get_nonce_action( $post_id ) { return str_replace( '{post_id}', $post_id, self::NONCE_ACTION_PATTERN ); } /** * Register a new render mode into the render mode manager. * * @param $class_name * * @return $this * @throws \Exception */ public function register_render_mode( $class_name ) { if ( ! is_subclass_of( $class_name, Render_Mode_Base::class ) ) { throw new \Exception( "'{$class_name}' must extend 'Render_Mode_Base'." ); } $this->render_modes[ $class_name::get_name() ] = $class_name; return $this; } /** * Get the current render mode. * * @return Render_Mode_Base */ public function get_current() { return $this->current; } /** * @param Render_Mode_Base $render_mode * * @return $this */ private function set_current( Render_Mode_Base $render_mode ) { $this->current = $render_mode; return $this; } /** * Set render mode. * * @return $this */ private function choose_render_mode() { $post_id = null; $key = null; $nonce = null; if ( isset( $_GET[ self::QUERY_STRING_POST_ID ] ) ) { $post_id = $_GET[ self::QUERY_STRING_POST_ID ]; // phpcs:ignore -- Nonce will be checked next line. } if ( isset( $_GET[ self::QUERY_STRING_NONCE_PARAM_NAME ] ) ) { $nonce = $_GET[ self::QUERY_STRING_NONCE_PARAM_NAME ]; // phpcs:ignore -- Nonce will be checked next line. } if ( isset( $_GET[ self::QUERY_STRING_PARAM_NAME ] ) ) { $key = $_GET[ self::QUERY_STRING_PARAM_NAME ]; // phpcs:ignore -- Nonce will be checked next line. } if ( $post_id && $nonce && wp_verify_nonce( $nonce, self::get_nonce_action( $post_id ) ) && $key && array_key_exists( $key, $this->render_modes ) ) { $this->set_current( new $this->render_modes[ $key ]( $post_id ) ); } else { $this->set_current( new Render_Mode_Normal( $post_id ) ); } return $this; } /** * Add actions base on the current render. * * @throws \Requests_Exception_HTTP_403 * @throws Status403 */ private function add_current_actions() { if ( ! $this->current->get_permissions_callback() ) { // WP >= 6.2-alpha if ( class_exists( '\WpOrg\Requests\Exception\Http\Status403' ) ) { throw new \WpOrg\Requests\Exception\Http\Status403(); } else { throw new \Requests_Exception_HTTP_403(); } } // Run when 'template-redirect' actually because the the class is instantiate when 'template-redirect' run. $this->current->prepare_render(); } /** * Render_Mode_Manager constructor. * * @throws \Exception */ public function __construct() { $this->register_render_mode( Render_Mode_Normal::class ); do_action( 'elementor/frontend/render_mode/register', $this ); $this->choose_render_mode(); $this->add_current_actions(); } } wp-api.php 0000644 00000002335 14717626151 0006467 0 ustar 00 <?php namespace Elementor\Core; use Elementor\Core\Utils\Collection; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * This class is responsible for the interaction with WordPress Core API. * The main benefit is making it easy to mock in testing * and it can help to create unit tests without the hustle of mocking WordPress itself. */ class Wp_Api { /** * @var Collection */ private $plugins; /** * @return Collection */ public function get_plugins() { if ( ! $this->plugins ) { $this->plugins = new Collection( get_plugins() ); } return $this->plugins; } /** * @return Collection */ public function get_active_plugins() { return $this->get_plugins() ->only( get_option( 'active_plugins' ) ); } /** * @return object|array */ public function plugins_api( $action, $args ) { return plugins_api( $action, $args ); } /** * @return bool */ public function is_plugin_active( $plugin ) { return is_plugin_active( $plugin ); } /** * @return bool|int|null|true */ public function activate_plugin( $plugin ) { return activate_plugin( $plugin ); } public function wp_attachment_is_image( $post = null ) { return wp_attachment_is_image( $post ); } } behaviors/interfaces/lock-behavior.php 0000644 00000001174 14717626151 0014124 0 ustar 00 <?php namespace Elementor\Core\Behaviors\Interfaces; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } interface Lock_Behavior { /** * @return bool */ public function is_locked(); /** * @return array { * * @type bool $is_locked * * @type array $badge { * @type string $icon * @type string $text * } * * @type array $content { * @type string $heading * @type string $description * } * * @type array $button { * @type string $text * @type string $url * } * * } */ public function get_config(); } breakpoints/manager.php 0000644 00000036633 14717626151 0011235 0 ustar 00 <?php namespace Elementor\Core\Breakpoints; use Elementor\Core\Base\Module; use Elementor\Core\Kits\Documents\Tabs\Settings_Layout; use Elementor\Core\Responsive\Files\Frontend; use Elementor\Modules\DevTools\Deprecation; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Manager extends Module { const BREAKPOINT_SETTING_PREFIX = 'viewport_'; const BREAKPOINT_KEY_MOBILE = 'mobile'; const BREAKPOINT_KEY_MOBILE_EXTRA = 'mobile_extra'; const BREAKPOINT_KEY_TABLET = 'tablet'; const BREAKPOINT_KEY_TABLET_EXTRA = 'tablet_extra'; const BREAKPOINT_KEY_LAPTOP = 'laptop'; const BREAKPOINT_KEY_DESKTOP = 'desktop'; const BREAKPOINT_KEY_WIDESCREEN = 'widescreen'; /** * Breakpoints * * An array containing instances of the all of the system's available breakpoints. * * @since 3.2.0 * @access private * * @var Breakpoint[] */ private $breakpoints; /** * Active Breakpoints * * An array containing instances of the enabled breakpoints. * * @since 3.2.0 * @access private * * @var Breakpoint[] */ private $active_breakpoints; /** * Responsive Control Duplication Mode. * * Determines the current responsive control generation mode. * Options are: * -- 'on': Responsive controls are duplicated in `add_responsive_control()`. * -- 'off': Responsive controls are NOT duplicated in `add_responsive_control()`. * -- 'dynamic': Responsive controls are only duplicated if their config contains `'dynamic' => 'active' => true`. * * When generating Post CSS, the mode is set to 'on'. When generating Dynamic CSS, the mode is set to 'dynamic'. * * default value is 'off'. * * @since 3.4.0 * @access private * * @var string */ private $responsive_control_duplication_mode = 'off'; private $icons_map; /** * Has Custom Breakpoints * * A flag that holds a cached value that indicates if there are active custom-breakpoints. * * @since 3.5.0 * @access private * * @var boolean */ private $has_custom_breakpoints; public function get_name() { return 'breakpoints'; } /** * Get Breakpoints * * Retrieve the array containing instances of all breakpoints existing in the system, or a single breakpoint if a * name is passed. * * @since 3.2.0 * * @param $breakpoint_name * @return Breakpoint[]|Breakpoint */ public function get_breakpoints( $breakpoint_name = null ) { if ( ! $this->breakpoints ) { $this->init_breakpoints(); } return self::get_items( $this->breakpoints, $breakpoint_name ); } /** * Get Active Breakpoints * * Retrieve the array of --enabled-- breakpoints, or a single breakpoint if a name is passed. * * @since 3.2.0 * * @param string|null $breakpoint_name * @return Breakpoint[]|Breakpoint */ public function get_active_breakpoints( $breakpoint_name = null ) { if ( ! $this->active_breakpoints ) { $this->init_active_breakpoints(); } return self::get_items( $this->active_breakpoints, $breakpoint_name ); } /** * Get Active Devices List * * Retrieve an array containing the keys of all active devices, including 'desktop'. * * @since 3.2.0 * * @param array $args * @return array */ public function get_active_devices_list( $args = [] ) { $default_args = [ 'add_desktop' => true, 'reverse' => false, 'desktop_first' => false, ]; $args = array_merge( $default_args, $args ); $active_devices = array_keys( Plugin::$instance->breakpoints->get_active_breakpoints() ); if ( $args['add_desktop'] ) { // Insert the 'desktop' device in the correct position. if ( ! $args['desktop_first'] && in_array( 'widescreen', $active_devices, true ) ) { $widescreen_index = array_search( 'widescreen', $active_devices, true ); array_splice( $active_devices, $widescreen_index, 0, [ 'desktop' ] ); } else { $active_devices[] = 'desktop'; } } if ( $args['reverse'] ) { $active_devices = array_reverse( $active_devices ); } return $active_devices; } /** Has Custom Breakpoints * * Checks whether there are currently custom breakpoints saved in the database. * Returns true if there are breakpoint values saved in the active kit. * If the user has activated any additional custom breakpoints (mobile extra, tablet extra, laptop, widescreen) - * that is considered as having custom breakpoints. * * @since 3.2.0 * * @return boolean */ public function has_custom_breakpoints() { if ( isset( $this->has_custom_breakpoints ) ) { return $this->has_custom_breakpoints; } $breakpoints = $this->get_active_breakpoints(); $additional_breakpoints = [ self::BREAKPOINT_KEY_MOBILE_EXTRA, self::BREAKPOINT_KEY_TABLET_EXTRA, self::BREAKPOINT_KEY_LAPTOP, self::BREAKPOINT_KEY_WIDESCREEN, ]; foreach ( $breakpoints as $breakpoint_name => $breakpoint ) { if ( in_array( $breakpoint_name, $additional_breakpoints, true ) ) { $this->has_custom_breakpoints = true; return true; } /** @var Breakpoint $breakpoint */ if ( $breakpoint->is_custom() ) { $this->has_custom_breakpoints = true; return true; } } $this->has_custom_breakpoints = false; return false; } /** * Get Device Min Breakpoint * * For a given device, return the minimum possible breakpoint. Except for the cases of mobile and widescreen * devices, A device's min breakpoint is determined by the previous device's max breakpoint + 1px. * * @since 3.2.0 * * @param string $device_name * @return int the min breakpoint of the passed device */ public function get_device_min_breakpoint( $device_name ) { if ( 'desktop' === $device_name ) { return $this->get_desktop_min_point(); } $active_breakpoints = $this->get_active_breakpoints(); $current_device_breakpoint = $active_breakpoints[ $device_name ]; // Since this method is called multiple times, usage of class variables is to memory and processing time. // Get only the keys for active breakpoints. $breakpoint_keys = array_keys( $active_breakpoints ); if ( $breakpoint_keys[0] === $device_name ) { // For the lowest breakpoint, the min point is always 320. $min_breakpoint = 320; } elseif ( 'min' === $current_device_breakpoint->get_direction() ) { // 'min-width' breakpoints only have a minimum point. The breakpoint value itself the device min point. $min_breakpoint = $current_device_breakpoint->get_value(); } else { // This block handles all other devices. $device_name_index = array_search( $device_name, $breakpoint_keys, true ); $previous_index = $device_name_index - 1; $previous_breakpoint_key = $breakpoint_keys[ $previous_index ]; /** @var Breakpoint $previous_breakpoint */ $previous_breakpoint = $active_breakpoints[ $previous_breakpoint_key ]; $min_breakpoint = $previous_breakpoint->get_value() + 1; } return $min_breakpoint; } /** * Get Desktop Min Breakpoint * * Returns the minimum possible breakpoint for the default (desktop) device. * * @since 3.2.0 * * @return int the min breakpoint of the passed device */ public function get_desktop_min_point() { $active_breakpoints = $this->get_active_breakpoints(); $desktop_previous_device = $this->get_desktop_previous_device_key(); return $active_breakpoints[ $desktop_previous_device ]->get_value() + 1; } public function refresh() { unset( $this->has_custom_breakpoints ); $this->init_breakpoints(); $this->init_active_breakpoints(); } /** * Get Responsive Icons Classes Map * * If a $device parameter is passed, this method retrieves the device's icon class list (the ones attached to the `<i>` * element). If no parameter is passed, it returns an array of devices containing each device's icon class list. * * This method was created because 'mobile_extra' and 'tablet_extra' breakpoint icons need to be tilted by 90 * degrees, and this tilt is achieved in CSS via the class `eicon-tilted`. * * @since 3.4.0 * * @return array|string */ public function get_responsive_icons_classes_map( $device = null ) { if ( ! $this->icons_map ) { $this->icons_map = [ 'mobile' => 'eicon-device-mobile', 'mobile_extra' => 'eicon-device-mobile eicon-tilted', 'tablet' => 'eicon-device-tablet', 'tablet_extra' => 'eicon-device-tablet eicon-tilted', 'laptop' => 'eicon-device-laptop', 'desktop' => 'eicon-device-desktop', 'widescreen' => 'eicon-device-wide', ]; } return self::get_items( $this->icons_map, $device ); } /** * Get Default Config * * Retrieve the default breakpoints config array. The 'selector' property is used for CSS generation (the * Stylesheet::add_device() method). * * @return array */ public static function get_default_config() { return [ self::BREAKPOINT_KEY_MOBILE => [ 'label' => esc_html__( 'Mobile Portrait', 'elementor' ), 'default_value' => 767, 'direction' => 'max', ], self::BREAKPOINT_KEY_MOBILE_EXTRA => [ 'label' => esc_html__( 'Mobile Landscape', 'elementor' ), 'default_value' => 880, 'direction' => 'max', ], self::BREAKPOINT_KEY_TABLET => [ 'label' => esc_html__( 'Tablet Portrait', 'elementor' ), 'default_value' => 1024, 'direction' => 'max', ], self::BREAKPOINT_KEY_TABLET_EXTRA => [ 'label' => esc_html__( 'Tablet Landscape', 'elementor' ), 'default_value' => 1200, 'direction' => 'max', ], self::BREAKPOINT_KEY_LAPTOP => [ 'label' => esc_html__( 'Laptop', 'elementor' ), 'default_value' => 1366, 'direction' => 'max', ], self::BREAKPOINT_KEY_WIDESCREEN => [ 'label' => esc_html__( 'Widescreen', 'elementor' ), 'default_value' => 2400, 'direction' => 'min', ], ]; } /** * Get Breakpoints Config * * Iterates over an array of all of the system's breakpoints (both active and inactive), queries each breakpoint's * class instance, and generates an array containing data on each breakpoint: its label, current value, direction * ('min'/'max') and whether it is enabled or not. * * @return array */ public function get_breakpoints_config() { $breakpoints = $this->get_breakpoints(); $config = []; foreach ( $breakpoints as $breakpoint_name => $breakpoint ) { $config[ $breakpoint_name ] = [ 'label' => $breakpoint->get_label(), 'value' => $breakpoint->get_value(), 'default_value' => $breakpoint->get_default_value(), 'direction' => $breakpoint->get_direction(), 'is_enabled' => $breakpoint->is_enabled(), ]; } return $config; } /** * Get Responsive Control Duplication Mode * * Retrieve the value of the $responsive_control_duplication_mode private class variable. * See the variable's PHPDoc for details. * * @since 3.4.0 * @access public */ public function get_responsive_control_duplication_mode() { return $this->responsive_control_duplication_mode; } /** * Set Responsive Control Duplication Mode * * Sets the value of the $responsive_control_duplication_mode private class variable. * See the variable's PHPDoc for details. * * @since 3.4.0 * * @access public * @param string $mode */ public function set_responsive_control_duplication_mode( $mode ) { $this->responsive_control_duplication_mode = $mode; } /** * Get Stylesheet Templates Path * * @since 3.2.0 * @access public * @static */ public static function get_stylesheet_templates_path() { return ELEMENTOR_ASSETS_PATH . 'css/templates/'; } /** * Compile Stylesheet Templates * * @since 3.2.0 * @access public * @static */ public static function compile_stylesheet_templates() { foreach ( self::get_stylesheet_templates() as $file_name => $template_path ) { $file = new Frontend( $file_name, $template_path ); $file->update(); } } /** * Init Breakpoints * * Creates the breakpoints array, containing instances of each breakpoint. Returns an array of ALL breakpoints, * both enabled and disabled. * * @since 3.2.0 */ private function init_breakpoints() { $breakpoints = []; $setting_prefix = self::BREAKPOINT_SETTING_PREFIX; $active_breakpoint_keys = [ $setting_prefix . self::BREAKPOINT_KEY_MOBILE, $setting_prefix . self::BREAKPOINT_KEY_TABLET, ]; if ( Plugin::$instance->experiments->is_feature_active( 'additional_custom_breakpoints' ) ) { $kit_active_id = Plugin::$instance->kits_manager->get_active_id(); // Get the breakpoint settings saved in the kit directly from the DB to avoid initializing the kit too early. $raw_kit_settings = get_post_meta( $kit_active_id, '_elementor_page_settings', true ); // If there is an existing kit with an active breakpoints value saved, use it. if ( isset( $raw_kit_settings[ Settings_Layout::ACTIVE_BREAKPOINTS_CONTROL_ID ] ) ) { $active_breakpoint_keys = $raw_kit_settings[ Settings_Layout::ACTIVE_BREAKPOINTS_CONTROL_ID ]; } } $default_config = self::get_default_config(); foreach ( $default_config as $breakpoint_name => $breakpoint_config ) { $args = [ 'name' => $breakpoint_name ] + $breakpoint_config; // Make sure the two default breakpoints (mobile, tablet) are always enabled. if ( self::BREAKPOINT_KEY_MOBILE === $breakpoint_name || self::BREAKPOINT_KEY_TABLET === $breakpoint_name ) { // Make sure the default Mobile and Tablet breakpoints are always enabled. $args['is_enabled'] = true; } else { // If the breakpoint is in the active breakpoints array, make sure it's instantiated as enabled. $args['is_enabled'] = in_array( $setting_prefix . $breakpoint_name, $active_breakpoint_keys, true ); } $breakpoints[ $breakpoint_name ] = new Breakpoint( $args ); } $this->breakpoints = $breakpoints; } /** * Init Active Breakpoints * * Create/Refresh the array of --enabled-- breakpoints. * * @since 3.2.0 */ private function init_active_breakpoints() { $this->active_breakpoints = array_filter( $this->get_breakpoints(), function( $breakpoint ) { /** @var Breakpoint $breakpoint */ return $breakpoint->is_enabled(); } ); } private function get_desktop_previous_device_key() { $config_array_keys = array_keys( $this->get_active_breakpoints() ); $num_of_devices = count( $config_array_keys ); // If the widescreen breakpoint is active, the device that's previous to desktop is the last one before // widescreen. if ( self::BREAKPOINT_KEY_WIDESCREEN === $config_array_keys[ $num_of_devices - 1 ] ) { $desktop_previous_device = $config_array_keys[ $num_of_devices - 2 ]; } else { // If the widescreen breakpoint isn't active, we just take the last device returned by the config. $desktop_previous_device = $config_array_keys[ $num_of_devices - 1 ]; } return $desktop_previous_device; } /** * Get Stylesheet Templates * * @since 3.2.0 * @access private * @static */ private static function get_stylesheet_templates() { $templates_paths = glob( self::get_stylesheet_templates_path() . '*.css' ); $templates = []; foreach ( $templates_paths as $template_path ) { $file_name = 'custom-' . basename( $template_path ); $templates[ $file_name ] = $template_path; } $deprecated_hook = 'elementor/core/responsive/get_stylesheet_templates'; $replacement_hook = 'elementor/core/breakpoints/get_stylesheet_template'; /** * @type Deprecation $deprecation_module */ $deprecation_module = Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation; // TODO: REMOVE THIS DEPRECATED HOOK IN ELEMENTOR v3.10.0/v4.0.0 $templates = $deprecation_module->apply_deprecated_filter( $deprecated_hook, [ $templates ], '3.2.0', $replacement_hook ); return apply_filters( $replacement_hook, $templates ); } } breakpoints/breakpoint.php 0000644 00000005525 14717626151 0011755 0 ustar 00 <?php namespace Elementor\Core\Breakpoints; use Elementor\Core\Base\Base_Object; use Elementor\Plugin; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Breakpoint extends Base_Object { private $name; private $label; private $default_value; private $db_key; private $value; private $is_custom; private $direction = 'max'; private $is_enabled = false; private $config; /** * Get Name * * @since 3.2.0 * * @return string */ public function get_name() { return $this->name; } /** * Is Enabled * * Check if the breakpoint is enabled or not. The breakpoint instance receives this data from * the Breakpoints Manager. * * @return bool $is_enabled class variable */ public function is_enabled() { return $this->is_enabled; } /** * Get Label * * Retrieve the breakpoint label. * * @since 3.2.0 * * @return string $label class variable */ public function get_label() { return $this->label; } /** * Get Value * * Retrieve the saved breakpoint value. * * @since 3.2.0 * * @return int $value class variable */ public function get_value() { if ( ! $this->value ) { $this->init_value(); } return $this->value; } /** * Is Custom * * Check if the breakpoint's value is a custom or default value. * * @since 3.2.0 * * @return bool $is_custom class variable */ public function is_custom() { if ( ! $this->is_custom ) { $this->get_value(); } return $this->is_custom; } /** * Get Default Value * * Returns the Breakpoint's default value. * * @since 3.2.0 * * @return int $default_value class variable */ public function get_default_value() { return $this->default_value; } /** * Get Direction * * Returns the Breakpoint's direction ('min'/'max'). * * @since 3.2.0 * * @return string $direction class variable */ public function get_direction() { return $this->direction; } /** * Set Value * * Set the `$value` class variable and the `$is_custom` class variable. * * @since 3.2.0 * * @return int $value class variable */ private function init_value() { $cached_value = Plugin::$instance->kits_manager->get_current_settings( $this->db_key ); if ( $cached_value ) { $this->value = (int) $cached_value; $this->is_custom = $this->value !== $this->default_value; } else { $this->value = $this->default_value; $this->is_custom = false; } return $this->value; } public function __construct( $args ) { $this->name = $args['name']; $this->label = $args['label']; // Used for CSS generation $this->db_key = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX . $args['name']; $this->direction = $args['direction']; $this->is_enabled = $args['is_enabled']; $this->default_value = $args['default_value']; } } document-types/page-base.php 0000644 00000016412 14717626151 0012077 0 ustar 00 <?php namespace Elementor\Core\DocumentTypes; use Elementor\Controls_Manager; use Elementor\Core\Base\Document; use Elementor\Group_Control_Background; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class PageBase extends Document { /** * Get Properties * * Return the document configuration properties. * * @since 2.0.8 * @access public * @static * * @return array */ public static function get_properties() { $properties = parent::get_properties(); $properties['admin_tab_group'] = ''; $properties['support_wp_page_templates'] = true; return $properties; } /** * @since 2.1.2 * @access protected * @static */ protected static function get_editor_panel_categories() { return Utils::array_inject( parent::get_editor_panel_categories(), 'theme-elements', [ 'theme-elements-single' => [ 'title' => esc_html__( 'Single', 'elementor' ), 'active' => false, 'promotion' => [ 'url' => esc_url( 'https://go.elementor.com/go-pro-section-single-widget-panel/' ), ], ], ] ); } /** * @since 2.0.0 * @access public */ public function get_css_wrapper_selector() { return 'body.elementor-page-' . $this->get_main_id(); } /** * @since 3.1.0 * @access protected */ protected function register_controls() { parent::register_controls(); static::register_hide_title_control( $this ); static::register_post_fields_control( $this ); static::register_style_controls( $this ); } /** * @since 2.0.0 * @access public * @static * @param Document $document */ public static function register_hide_title_control( $document ) { $document->start_injection( [ 'of' => 'post_status', 'fallback' => [ 'of' => 'post_title', ], ] ); $document->add_control( 'hide_title', [ 'label' => esc_html__( 'Hide Title', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'description' => sprintf( /* translators: 1: Link open tag, 2: Link close tag. */ esc_html__( 'Set a different selector for the title in the %1$sLayout panel%2$s.', 'elementor' ), '<a href="javascript: $e.run( \'panel/global/open\' ).then( () => $e.route( \'panel/global/settings-layout\' ) )">', '</a>' ), 'separator' => 'before', 'selectors' => [ ':root' => '--page-title-display: none', ], ] ); $document->end_injection(); } /** * @since 2.0.0 * @access public * @static * @param Document $document */ public static function register_style_controls( $document ) { $document->start_controls_section( 'section_page_style', [ 'label' => esc_html__( 'Body Style', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $document->add_responsive_control( 'margin', [ 'label' => esc_html__( 'Margin', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}}' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}', ], ] ); $document->add_responsive_control( 'padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}}' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}', ], ] ); $document->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'background', 'separator' => 'before', 'fields_options' => [ 'image' => [ // Currently isn't supported. 'dynamic' => [ 'active' => false, ], ], ], ] ); $document->end_controls_section(); Plugin::$instance->controls_manager->add_custom_css_controls( $document ); } public static function get_labels() : array { $plural_label = static::get_plural_title(); $singular_label = static::get_title(); $labels = [ 'name' => $plural_label, // Already translated. 'singular_name' => $singular_label, // Already translated. 'all_items' => sprintf( __( 'All %s', 'elementor' ), $plural_label ), 'add_new' => esc_html__( 'Add New', 'elementor' ), 'add_new_item' => sprintf( __( 'Add New %s', 'elementor' ), $singular_label ), 'edit_item' => sprintf( __( 'Edit %s', 'elementor' ), $singular_label ), 'new_item' => sprintf( __( 'New %s', 'elementor' ), $singular_label ), 'view_item' => sprintf( __( 'View %s', 'elementor' ), $singular_label ), 'search_items' => sprintf( __( 'Search %s', 'elementor' ), $plural_label ), 'not_found' => sprintf( __( 'No %s found.', 'elementor' ), strtolower( $plural_label ) ), 'not_found_in_trash' => sprintf( __( 'No %s found in Trash.', 'elementor' ), strtolower( $plural_label ) ), 'parent_item_colon' => '', 'menu_name' => $plural_label, ]; return $labels; } /** * @since 2.0.0 * @access public * @static * @param Document $document */ public static function register_post_fields_control( $document ) { $document->start_injection( [ 'of' => 'post_status', 'fallback' => [ 'of' => 'post_title', ], ] ); if ( post_type_supports( $document->post->post_type, 'excerpt' ) ) { $document->add_control( 'post_excerpt', [ 'label' => esc_html__( 'Excerpt', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'default' => $document->post->post_excerpt, 'separator' => 'before', 'ai' => [ 'type' => 'excerpt', ], ] ); } if ( current_theme_supports( 'post-thumbnails' ) && post_type_supports( $document->post->post_type, 'thumbnail' ) ) { $document->add_control( 'post_featured_image', [ 'label' => esc_html__( 'Featured Image', 'elementor' ), 'type' => Controls_Manager::MEDIA, 'default' => [ 'id' => get_post_thumbnail_id(), 'url' => (string) get_the_post_thumbnail_url( $document->post->ID ), ], 'separator' => 'before', ] ); } if ( is_post_type_hierarchical( $document->post->post_type ) ) { $document->add_control( 'menu_order', [ 'label' => esc_html__( 'Order', 'elementor' ), 'type' => Controls_Manager::NUMBER, 'default' => $document->post->menu_order, 'separator' => 'before', ] ); } if ( post_type_supports( $document->post->post_type, 'comments' ) ) { $document->add_control( 'comment_status', [ 'label' => esc_html__( 'Allow Comments', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'open', 'default' => $document->post->comment_status, 'separator' => 'before', ] ); } $document->end_injection(); } /** * @since 2.0.0 * @access public * * @param array $data * * @throws \Exception */ public function __construct( array $data = [] ) { if ( $data ) { $template = get_post_meta( $data['post_id'], '_wp_page_template', true ); if ( empty( $template ) ) { $template = 'default'; } $data['settings']['template'] = $template; } parent::__construct( $data ); } protected function get_remote_library_config() { $config = parent::get_remote_library_config(); $config['category'] = ''; $config['type'] = 'block'; $config['default_route'] = 'templates/blocks'; return $config; } } document-types/page.php 0000644 00000005216 14717626151 0011167 0 ustar 00 <?php namespace Elementor\Core\DocumentTypes; use Elementor\Core\Base\Document; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Page extends PageBase { const URL_TYPE = 'site_settings'; const SITE_IDENTITY_TAB = 'settings-site-identity'; /** * Get Properties * * Return the page document configuration properties. * * @access public * @static * * @return array */ public static function get_properties() { $properties = parent::get_properties(); $properties['cpt'] = [ 'page' ]; $properties['support_kit'] = true; return $properties; } /** * Get Type * * Return the page document type. * * @return string */ public static function get_type() { return 'wp-page'; } /** * Get Title * * Return the page document title. * * @access public * @static * * @return string */ public static function get_title() { return esc_html__( 'Page', 'elementor' ); } /** * Get Plural Title * * Return the page document plural title. * * @access public * @static * * @return string */ public static function get_plural_title() { return esc_html__( 'Pages', 'elementor' ); } public static function get_site_settings_url_config( $active_tab_id = null ) { $existing_elementor_page = self::get_elementor_page(); $site_settings_url = $existing_elementor_page ? self::get_elementor_edit_url( $existing_elementor_page->ID, [ 'active-tab' => $active_tab_id ] ) : self::get_create_new_editor_page_url( $active_tab_id ); return [ 'new_page' => empty( $existing_elementor_page ), 'url' => $site_settings_url, 'type' => static::URL_TYPE, ]; } public static function get_create_new_editor_page_url( $active_tab = null ): string { $active_kit_id = Plugin::$instance->kits_manager->get_active_id(); $args = []; if ( ! empty( $active_kit_id ) ) { $args['active-document'] = $active_kit_id; } if ( $active_tab ) { $args['active-tab'] = $active_tab; } return add_query_arg( $args, Plugin::$instance->documents->get_create_new_post_url( 'page' ) ); } private static function get_elementor_edit_url( int $post_id, $args = [] ): string { $active_kit_id = Plugin::$instance->kits_manager->get_active_id(); if ( ! empty( $active_kit_id ) ) { $args['active-document'] = $active_kit_id; } $page = new self( [ 'post_id' => $post_id ] ); return add_query_arg( $args, $page->get_edit_url() ); } public static function get_elementor_page() { return get_pages( [ 'meta_key' => Document::BUILT_WITH_ELEMENTOR_META_KEY, 'sort_order' => 'asc', 'sort_column' => 'post_date', 'number' => 1, ] )[0] ?? null; } } document-types/post.php 0000644 00000002050 14717626151 0011231 0 ustar 00 <?php namespace Elementor\Core\DocumentTypes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Post extends PageBase { /** * Get Properties * * Return the post document configuration properties. * * @access public * @static * * @return array */ public static function get_properties() { $properties = parent::get_properties(); $properties['support_kit'] = true; $properties['cpt'] = [ 'post' ]; return $properties; } /** * Get Type * * Return the post document type. * * @return string */ public static function get_type() { return 'wp-post'; } /** * Get Title * * Return the post document title. * * @access public * @static * * @return string */ public static function get_title() { return esc_html__( 'Post', 'elementor' ); } /** * Get Plural Title * * Return the post document plural title. * * @access public * @static * * @return string */ public static function get_plural_title() { return esc_html__( 'Posts', 'elementor' ); } } page-assets/data-managers/base.php 0000644 00000016043 14717626151 0013125 0 ustar 00 <?php namespace Elementor\Core\Page_Assets\Data_Managers; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Assets Data. * * @since 3.3.0 */ abstract class Base { const ASSETS_DATA_KEY = '_elementor_assets_data'; /** * @var array */ protected $assets_data; /** * @var string */ protected $content_type; /** * @var string */ protected $assets_category; /** * @var array */ private $assets_config; /** * @var array */ private $files_data; /** * Get Asset Content. * * Responsible for extracting the asset data from a certain file. * Will be triggered automatically when the asset data does not exist, or when the asset version was changed. * * @since 3.3.0 * @access public * * @return string */ abstract protected function get_asset_content(); /** * Get Asset Key. * * The asset data will be saved in the DB under this key. * * @since 3.3.0 * @access protected * * @return string */ protected function get_key() { return $this->assets_config['key']; } /** * Get Relative Version. * * The asset data will be re-evaluated according the version number. * * @since 3.3.0 * @access protected * * @return string */ protected function get_version() { return $this->assets_config['version']; } /** * Get Asset Path. * * The asset data will be extracted from the file path. * * @since 3.3.0 * @access protected * * @return string */ protected function get_file_path() { return $this->assets_config['file_path']; } /** * Get Config Data. * * Holds a unique data relevant for the specific assets category type. * * @since 3.3.0 * @access protected * * @return string|array */ protected function get_config_data( $key = '' ) { if ( isset( $this->assets_config['data'] ) ) { if ( $key ) { if ( isset( $this->assets_config['data'][ $key ] ) ) { return $this->assets_config['data'][ $key ]; } return ''; } return $this->assets_config['data']; } return []; } /** * Set Asset Data. * * Responsible for setting the current asset data. * * @since 3.3.0 * @access protected * * @return void */ protected function set_asset_data( $asset_key ) { if ( ! isset( $this->assets_data[ $asset_key ] ) ) { $this->assets_data[ $asset_key ] = []; } $this->assets_data[ $asset_key ]['content'] = $this->get_asset_content(); $this->assets_data[ $asset_key ]['version'] = $this->get_version(); $this->save_asset_data( $asset_key ); } /** * Save Asset Data. * * Responsible for saving the asset data in the DB. * * @since 3.3.0 * @access protected * * @param string $asset_key * * @return void */ protected function save_asset_data( $asset_key ) { $assets_data = $this->get_saved_assets_data(); $content_type = $this->content_type; $assets_category = $this->assets_category; $assets_data[ $content_type ][ $assets_category ][ $asset_key ] = $this->assets_data[ $asset_key ]; update_option( self::ASSETS_DATA_KEY, $assets_data ); } /** * Is Asset Version Changed. * * Responsible for comparing the saved asset data version to the current relative version. * * @since 3.3.0 * @access protected * * @param string $asset_key * * @return boolean */ protected function is_asset_version_changed( $version ) { return $this->get_version() !== $version; } /** * Get File Data. * * Getting a file content or size. * * @since 3.3.0 * @access protected * * @param string $data_type (exists|content|size) * @param string $file_key - In case that the same file data is needed for multiple assets (like a JSON file), the file data key should be the same for all shared assets to make sure that the file is being read only once. * * @return string|number */ protected function get_file_data( $data_type, $file_key = '' ) { $asset_key = $file_key ? $file_key : $this->get_key(); if ( isset( $this->files_data[ $asset_key ][ $data_type ] ) ) { return $this->files_data[ $asset_key ][ $data_type ]; } if ( ! isset( $this->files_data[ $asset_key ] ) ) { $this->files_data[ $asset_key ] = []; } $asset_path = $this->get_file_path(); if ( 'exists' === $data_type ) { $data = file_exists( $asset_path ); } elseif ( 'content' === $data_type ) { $data = Utils::file_get_contents( $asset_path ); if ( ! $data ) { $data = ''; } } elseif ( 'size' === $data_type ) { $data = file_exists( $asset_path ) ? filesize( $asset_path ) : 0; } $this->files_data[ $asset_key ][ $data_type ] = $data; return $data; } /** * Get Saved Assets Data. * * Getting the assets data from the DB. * * @since 3.3.0 * @access protected * * @return array */ protected function get_saved_assets_data() { $assets_data = get_option( self::ASSETS_DATA_KEY, [] ); $content_type = $this->content_type; $assets_category = $this->assets_category; if ( ! isset( $assets_data[ $content_type ] ) ) { $assets_data[ $content_type ] = []; } if ( ! isset( $assets_data[ $content_type ][ $assets_category ] ) ) { $assets_data[ $content_type ][ $assets_category ] = []; } return $assets_data; } /** * Get Config. * * Getting the assets data config. * * @since 3.5.0 * @access protected * * @return array */ protected function get_config( $data ) { return []; } /** * Init Asset Data. * * Initialize the asset data and handles the asset content updates when needed. * * @since 3.3.0 * @access public * * @param array $config { * @type string 'key' * @type string 'version' * @type string 'file_path' * @type array 'data' * } * * @return void */ public function init_asset_data( $config ) { $this->assets_config = $config; $asset_key = $config['key']; $asset_data = isset( $this->assets_data[ $asset_key ] ) ? $this->assets_data[ $asset_key ] : []; if ( ! $asset_data || $this->is_asset_version_changed( $asset_data['version'] ) ) { $this->set_asset_data( $asset_key ); } } /** * Get Asset Data From Config. * * Getting the asset data content from config. * * @since 3.3.0 * @access public * * @param array $config { * @type string 'key' * @type string 'version' * @type string 'file_path' * @type array 'data' * } * * @return mixed */ public function get_asset_data_from_config( array $config ) { $this->init_asset_data( $config ); $asset_key = $config['key']; return $this->assets_data[ $asset_key ]['content']; } /** * Get Asset Data. * * Getting the asset data content. * * @since 3.5.0 * @access public * * @param array $data * * @return mixed */ public function get_asset_data( array $data ) { $config = $this->get_config( $data ); return $this->get_asset_data_from_config( $config ); } public function __construct() { $assets_data = $this->get_saved_assets_data(); $content_type = $this->content_type; $assets_category = $this->assets_category; $this->assets_data = $assets_data[ $content_type ][ $assets_category ]; } } page-assets/data-managers/font-icon-svg/base.php 0000644 00000000673 14717626151 0015620 0 ustar 00 <?php namespace Elementor\Core\Page_Assets\Data_Managers\Font_Icon_Svg; use Elementor\Core\Page_Assets\Data_Managers\Base as Data_Managers_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Font Icon Svg Base. * * @since 3.4.0 */ class Base extends Data_Managers_Base { protected $content_type = 'svg'; protected $assets_category = 'font-icon'; protected function get_asset_content() {} } page-assets/data-managers/font-icon-svg/manager.php 0000644 00000002064 14717626151 0016314 0 ustar 00 <?php namespace Elementor\Core\Page_Assets\Data_Managers\Font_Icon_Svg; use Elementor\Core\Base\Base_Object; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Font Icon Svg Manager. * * @since 3.4.0 */ class Manager extends Base_Object { private static $data = []; private static function get_data() { if ( ! self::$data ) { self::$data = [ 'font-awesome' => [ 'regex' => '/^fa-/', 'manager' => new Font_Awesome(), ], 'eicons' => [ 'regex' => '/^eicons$/', 'manager' => new E_Icons(), ], ]; } return self::$data; } public static function get_font_icon_svg_data( $icon ) { $data = self::get_data(); $font_family = $icon['font_family']; $font_family_manager = $data[ $font_family ]['manager']; return $font_family_manager->get_asset_data( $icon ); } public static function get_font_family( $icon_library ) { foreach ( self::get_data() as $family => $data ) { if ( preg_match( $data['regex'], $icon_library ) ) { return $family; } } return ''; } } page-assets/data-managers/font-icon-svg/e-icons.php 0000644 00000002011 14717626151 0016227 0 ustar 00 <?php namespace Elementor\Core\Page_Assets\Data_Managers\Font_Icon_Svg; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * E-Icons Svg. * * @since 3.5.0 */ class E_Icons extends Base { const LIBRARY_CURRENT_VERSION = '5.13.0'; protected function get_config( $icon ) { return [ 'key' => $icon['value'], 'version' => self::LIBRARY_CURRENT_VERSION, 'file_path' => ELEMENTOR_ASSETS_PATH . 'lib/eicons/eicons.json', 'data' => [ 'icon_data' => [ 'name' => $icon['value'], 'library' => $icon['library'], ], ], ]; } protected function get_asset_content() { $icon_data = $this->get_config_data( 'icon_data' ); $file_data = json_decode( $this->get_file_data( 'content', $icon_data['library'] ), true ); $icon_name = str_replace( 'eicon-', '', $icon_data['name'] ); $svg_data = $file_data[ $icon_name ]; return [ 'width' => $svg_data['width'], 'height' => $svg_data['height'], 'path' => $svg_data['path'], 'key' => $this->get_key(), ]; } } page-assets/data-managers/font-icon-svg/font-awesome.php 0000644 00000002420 14717626151 0017302 0 ustar 00 <?php namespace Elementor\Core\Page_Assets\Data_Managers\Font_Icon_Svg; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Font Awesome Icon Svg. * * @since 3.4.0 */ class Font_Awesome extends Base { const LIBRARY_CURRENT_VERSION = '5.15.3'; protected function get_config( $icon ) { preg_match( '/fa(.*) fa-/', $icon['value'], $icon_name_matches ); $icon_name = str_replace( $icon_name_matches[0], '', $icon['value'] ); $icon_key = str_replace( ' fa-', '-', $icon['value'] ); $icon_file_name = str_replace( 'fa-', '', $icon['library'] ); return [ 'key' => $icon_key, 'version' => self::LIBRARY_CURRENT_VERSION, 'file_path' => ELEMENTOR_ASSETS_PATH . 'lib/font-awesome/json/' . $icon_file_name . '.json', 'data' => [ 'icon_data' => [ 'name' => $icon_name, 'library' => $icon['library'], ], ], ]; } protected function get_asset_content() { $icon_data = $this->get_config_data( 'icon_data' ); $file_data = json_decode( $this->get_file_data( 'content', $icon_data['library'] ), true ); $icon_name = $icon_data['name']; $svg_data = $file_data['icons'][ $icon_name ]; return [ 'width' => $svg_data[0], 'height' => $svg_data[1], 'path' => $svg_data[4], 'key' => $this->get_key(), ]; } } page-assets/data-managers/responsive-widgets.php 0000644 00000001153 14717626151 0016050 0 ustar 00 <?php namespace Elementor\Core\Page_Assets\Data_Managers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Responsive Widgets Data. * * @since 3.5.0 */ class Responsive_Widgets extends Base { const RESPONSIVE_WIDGETS_DATABASE_KEY = 'responsive-widgets'; const RESPONSIVE_WIDGETS_FILE_PATH = 'data/responsive-widgets.json'; protected $content_type = 'json'; protected $assets_category = 'widgets'; protected function get_asset_content() { $data = $this->get_file_data( 'content' ); if ( $data ) { $data = json_decode( $data, true ); } return $data; } } page-assets/data-managers/widgets-css.php 0000644 00000001150 14717626151 0014440 0 ustar 00 <?php namespace Elementor\Core\Page_Assets\Data_Managers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Assets Data. * * @since 3.3.0 */ class Widgets_Css extends Base { protected $content_type = 'css'; protected $assets_category = 'widgets'; protected function get_asset_content() { $asset_css_file_exists = $this->get_file_data( 'exists' ); $widget_css = ''; if ( $asset_css_file_exists ) { $asset_url = $this->get_config_data( 'file_url' ); $widget_css = sprintf( '<link rel="stylesheet" href="%s">', $asset_url ); } return $widget_css; } } page-assets/loader.php 0000644 00000013144 14717626151 0010754 0 ustar 00 <?php namespace Elementor\Core\Page_Assets; use Elementor\Core\Base\Module; use Elementor\Plugin; use Elementor\Control_Animation; use Elementor\Control_Exit_Animation; use Elementor\Control_Hover_Animation; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Elementor assets loader. * * A class that is responsible for conditionally enqueuing styles and script assets that were pre-enabled. * * @since 3.3.0 */ class Loader extends Module { private array $assets = []; public function get_name(): string { return 'assets-loader'; } private function init_assets(): void { $assets = [ 'styles' => $this->init_styles(), 'scripts' => [], ]; $this->assets = $assets; } private function init_styles(): array { $styles = [ // TODO: Remove the 'e-animations' registration in v3.26.0 [ED-15471]. 'e-animations' => [ 'src' => $this->get_css_assets_url( 'animations', 'assets/lib/animations/', true ), 'version' => ELEMENTOR_VERSION, 'dependencies' => [], ], 'e-shapes' => [ 'src' => $this->get_css_assets_url( 'shapes', 'assets/css/conditionals/' ), 'version' => ELEMENTOR_VERSION, 'dependencies' => [], ], 'e-swiper' => [ 'src' => $this->get_css_assets_url( 'e-swiper', 'assets/css/conditionals/' ), 'version' => ELEMENTOR_VERSION, 'dependencies' => [ 'swiper' ], ], 'swiper' => [ 'src' => $this->get_css_assets_url( 'swiper', $this->getSwiperPath() ), 'version' => $this->getSwiperVersion(), 'dependencies' => [], ], ]; return array_merge( $styles, $this->get_animation_styles() ); } private function getSwiperPath(): string { return Plugin::$instance->experiments->is_feature_active( 'e_swiper_latest' ) ? 'assets/lib/swiper/v8/css/' : 'assets/lib/swiper/css/'; } private function getSwiperVersion(): string { return Plugin::$instance->experiments->is_feature_active( 'e_swiper_latest' ) ? '8.4.5' : '5.3.6'; } private function get_animations(): array { $grouped_animations = Control_Animation::get_default_animations(); $grouped_animations['hover'] = Control_Hover_Animation::get_default_animations(); $exit_animations = Control_Exit_Animation::get_default_animations(); $grouped_animations = array_merge( $grouped_animations, $exit_animations ); $animations = []; foreach ( $grouped_animations as $group_label => $group ) { foreach ( $group as $animation_key => $animation_label ) { $animations[ $animation_key ] = $group_label; } } return $animations; } private function get_animation_styles(): array { $animations = $this->get_animations(); $styles = []; foreach ( $animations as $animation => $group_label ) { $style_prefix = 'hover' === $group_label ? 'e-animation-' : ''; $styles[ 'e-animation-' . $animation ] = [ 'src' => $this->get_css_assets_url( $style_prefix . $animation, 'assets/lib/animations/styles/' ), 'version' => ELEMENTOR_VERSION, 'dependencies' => [], ]; } return $styles; } public function get_assets(): array { if ( ! $this->assets ) { $this->init_assets(); } return $this->assets; } /** * @param array $assets { * @type array 'styles' * @type array 'scripts' * } */ public function enable_assets( array $assets_data ): void { if ( ! $this->assets ) { $this->init_assets(); } foreach ( $assets_data as $assets_type => $assets_list ) { foreach ( $assets_list as $asset_name ) { $this->assets[ $assets_type ][ $asset_name ]['enabled'] = true; if ( 'scripts' === $assets_type ) { wp_enqueue_script( $asset_name ); } else { wp_enqueue_style( $asset_name ); } } } } /** * @param array $assets { * @type array 'styles' * @type array 'scripts' * } */ public function add_assets( array $assets ): void { if ( ! $this->assets ) { $this->init_assets(); } $this->assets = array_replace_recursive( $this->assets, $assets ); } /** * @deprecated 3.22.0 */ public function enqueue_assets(): void { $assets = $this->get_assets(); $is_preview_mode = Plugin::$instance->preview->is_preview_mode(); foreach ( $assets as $assets_type => $assets_type_data ) { foreach ( $assets_type_data as $asset_name => $asset_data ) { if ( empty( $asset_data['src'] ) ) { continue; } if ( ! empty( $asset_data['enabled'] ) || $is_preview_mode ) { if ( 'scripts' === $assets_type ) { wp_enqueue_script( $asset_name, $asset_data['src'], $asset_data['dependencies'], $asset_data['version'], true ); } else { // TODO: Remove the 'e-animations' registration in v3.26.0 [ED-15471]. if ( $this->skip_animations_style( $asset_name ) ) { continue; } wp_enqueue_style( $asset_name, $asset_data['src'], $asset_data['dependencies'], $asset_data['version'] ); } } } } } // TODO: Remove the 'e-animations' registration in v3.26.0 [ED-15471]. private function skip_animations_style( $asset_name ): bool { $is_preview = Plugin::$instance->preview->is_preview_mode(); return $is_preview && 'e-animations' === $asset_name; } private function register_assets(): void { $assets = $this->get_assets(); foreach ( $assets as $assets_type => $assets_type_data ) { foreach ( $assets_type_data as $asset_name => $asset_data ) { if ( 'scripts' === $assets_type ) { wp_register_script( $asset_name, $asset_data['src'], $asset_data['dependencies'], $asset_data['version'], true ); } else { wp_register_style( $asset_name, $asset_data['src'], $asset_data['dependencies'], $asset_data['version'] ); } } } } public function __construct() { parent::__construct(); $this->register_assets(); } } kits/manager.php 0000644 00000032533 14717626151 0007661 0 ustar 00 <?php namespace Elementor\Core\Kits; use Elementor\Core\Base\Document; use Elementor\Core\Kits\Controls\Repeater; use Elementor\Core\Kits\Documents\Tabs\Global_Colors; use Elementor\Core\Kits\Documents\Tabs\Global_Typography; use Elementor\Plugin; use Elementor\Core\Files\CSS\Post as Post_CSS; use Elementor\Core\Files\CSS\Post_Preview as Post_Preview; use Elementor\Core\Documents_Manager; use Elementor\Core\Kits\Documents\Kit; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Manager { const OPTION_ACTIVE = 'elementor_active_kit'; const OPTION_PREVIOUS = 'elementor_previous_kit'; const E_HASH_COMMAND_OPEN_SITE_SETTINGS = 'e:run:panel/global/open'; private $should_skip_trash_kit_confirmation = false; public function get_active_id() { return get_option( self::OPTION_ACTIVE ); } public function get_previous_id() { return get_option( self::OPTION_PREVIOUS ); } public function get_kit( $kit_id ) { $kit = Plugin::$instance->documents->get( $kit_id ); if ( ! $this->is_valid_kit( $kit ) ) { return $this->get_empty_kit_instance(); } return $kit; } public function get_active_kit() { return $this->get_kit( $this->get_active_id() ); } public function get_active_kit_for_frontend() { $kit = Plugin::$instance->documents->get_doc_for_frontend( $this->get_active_id() ); if ( ! $this->is_valid_kit( $kit ) ) { return $this->get_empty_kit_instance(); } return $kit; } /** * @param $kit * * @return bool */ private function is_valid_kit( $kit ) { return $kit && $kit instanceof Kit && 'trash' !== $kit->get_main_post()->post_status; } /** * Returns an empty kit for situation when there is no kit in the site. * * @return Kit * @throws \Exception */ private function get_empty_kit_instance() { return new Kit( [ 'settings' => [], 'post_id' => 0, ] ); } /** * Checks if specific post is a kit. * * @param $post_id * * @return bool */ public function is_kit( $post_id ) { $document = Plugin::$instance->documents->get( $post_id ); return $document && $document instanceof Kit && ! $document->is_revision(); } /** * Init kit controls. * * A temp solution in order to avoid init kit group control from within another group control. * * After moving the `default_font` to the kit, the Typography group control cause initialize the kit controls at: https://github.com/elementor/elementor/blob/e6e1db9eddef7e3c1a5b2ba0c2338e2af2a3bfe3/includes/controls/groups/typography.php#L91 * and because the group control is a singleton, its args are changed to the last kit group control. */ public function init_kit_controls() { $this->get_active_kit_for_frontend()->get_settings(); } public function get_current_settings( $setting = null ) { $kit = $this->get_active_kit_for_frontend(); if ( ! $kit ) { return ''; } return $kit->get_settings( $setting ); } public function create( array $kit_data = [], array $kit_meta_data = [] ) { $default_kit_data = [ 'post_status' => 'publish', ]; $kit_data = array_merge( $default_kit_data, $kit_data ); $kit_data['post_type'] = Source_Local::CPT; $kit = Plugin::$instance->documents->create( 'kit', $kit_data, $kit_meta_data ); if ( isset( $kit_data['settings'] ) ) { $kit->save( [ 'settings' => $kit_data['settings'] ] ); } return $kit->get_id(); } public function create_new_kit( $kit_name = '', $settings = [], $active = true ) { $kit_name = $kit_name ? $kit_name : esc_html__( 'Custom', 'elementor' ); $id = $this->create( [ 'post_title' => $kit_name, 'settings' => $settings, ] ); if ( $active ) { update_option( self::OPTION_PREVIOUS, $this->get_active_id() ); update_option( self::OPTION_ACTIVE, $id ); } return $id; } public function create_default() { return $this->create( [ 'post_title' => esc_html__( 'Default Kit', 'elementor' ), ] ); } /** * Create a default kit if needed. * * This action runs on activation hook, all the Plugin components do not exists and * the Document manager and Kits manager instances cannot be used. * * @return int|void|\WP_Error */ public static function create_default_kit() { if ( get_option( self::OPTION_ACTIVE ) ) { return; } $id = wp_insert_post( [ 'post_title' => esc_html__( 'Default Kit', 'elementor' ), 'post_type' => Source_Local::CPT, 'post_status' => 'publish', 'meta_input' => [ '_elementor_edit_mode' => 'builder', Document::TYPE_META_KEY => 'kit', ], ] ); update_option( self::OPTION_ACTIVE, $id ); return $id; } /** * @param $imported_kit_id int The id of the imported kit that should be deleted. * @param $active_kit_id int The id of the kit that should set as 'active_kit' after the deletion. * @param $previous_kit_id int The id of the kit that should set as 'previous_kit' after the deletion. * @return void */ public function revert( int $imported_kit_id, int $active_kit_id, int $previous_kit_id ) { // If the kit that should set as active is not a valid kit then abort the revert. if ( ! $this->is_kit( $active_kit_id ) ) { return; } // This a hacky solution to avoid from the revert process to be interrupted by the `trash_kit_confirmation`. $this->should_skip_trash_kit_confirmation = true; $kit = $this->get_kit( $imported_kit_id ); $kit->force_delete(); $this->should_skip_trash_kit_confirmation = false; update_option( self::OPTION_ACTIVE, $active_kit_id ); if ( $this->is_kit( $previous_kit_id ) ) { update_option( self::OPTION_PREVIOUS, $previous_kit_id ); } } /** * @param Documents_Manager $documents_manager */ public function register_document( $documents_manager ) { $documents_manager->register_document_type( 'kit', Kit::get_class_full_name() ); } public function localize_settings( $settings ) { $kit = $this->get_active_kit(); $kit_controls = $kit->get_controls(); $design_system_controls = [ 'colors' => $kit_controls['system_colors']['fields'], 'typography' => $kit_controls['system_typography']['fields'], ]; $settings = array_replace_recursive( $settings, [ 'kit_id' => $kit->get_main_id(), 'kit_config' => [ 'typography_prefix' => Global_Typography::TYPOGRAPHY_GROUP_PREFIX, 'design_system_controls' => $design_system_controls, ], 'user' => [ 'can_edit_kit' => $kit->is_editable_by_current_user(), ], ] ); return $settings; } public function preview_enqueue_styles() { $kit = $this->get_kit_for_frontend(); if ( $kit ) { // On preview, the global style is not enqueued. $this->frontend_before_enqueue_styles(); Plugin::$instance->frontend->print_fonts_links(); } } public function frontend_before_enqueue_styles() { $kit = $this->get_kit_for_frontend(); if ( $kit ) { if ( $kit->is_autosave() ) { $css_file = Post_Preview::create( $kit->get_id() ); } else { $css_file = Post_CSS::create( $kit->get_id() ); } $css_file->enqueue(); } } public function render_panel_html() { require __DIR__ . '/views/panel.php'; } public function get_kit_for_frontend() { $kit = false; $active_kit = $this->get_active_kit(); $is_kit_preview = is_preview() && isset( $_GET['preview_id'] ) && $active_kit->get_main_id() === (int) $_GET['preview_id']; if ( $is_kit_preview ) { $kit = Plugin::$instance->documents->get_doc_or_auto_save( $active_kit->get_main_id(), get_current_user_id() ); } elseif ( null !== $active_kit->get_main_post() && 'publish' === $active_kit->get_main_post()->post_status ) { $kit = $active_kit; } return $kit; } public function update_kit_settings_based_on_option( $key, $value ) { /** @var Kit $active_kit */ $active_kit = $this->get_active_kit(); if ( $active_kit->is_saving() ) { return; } $active_kit->update_settings( [ $key => $value ] ); } /** * Map Scheme To Global * * Convert a given scheme value to its corresponding default global value * * @param string $type 'color'/'typography' * @param $value * @return mixed */ private function map_scheme_to_global( $type, $value ) { $schemes_to_globals_map = [ 'color' => [ '1' => Global_Colors::COLOR_PRIMARY, '2' => Global_Colors::COLOR_SECONDARY, '3' => Global_Colors::COLOR_TEXT, '4' => Global_Colors::COLOR_ACCENT, ], 'typography' => [ '1' => Global_Typography::TYPOGRAPHY_PRIMARY, '2' => Global_Typography::TYPOGRAPHY_SECONDARY, '3' => Global_Typography::TYPOGRAPHY_TEXT, '4' => Global_Typography::TYPOGRAPHY_ACCENT, ], ]; return $schemes_to_globals_map[ $type ][ $value ]; } /** * Convert Scheme to Default Global * * If a control has a scheme property, convert it to a default Global. * * @param $scheme - Control scheme property * @return array - Control/group control args * @since 3.0.0 * @access public */ public function convert_scheme_to_global( $scheme ) { if ( isset( $scheme['type'] ) && isset( $scheme['value'] ) ) { //_deprecated_argument( $args['scheme'], '3.0.0', 'Schemes are now deprecated - use $args[\'global\'] instead.' ); return $this->map_scheme_to_global( $scheme['type'], $scheme['value'] ); } // Typography control 'scheme' properties usually only include the string with the typography value ('1'-'4'). return $this->map_scheme_to_global( 'typography', $scheme ); } public function register_controls() { $controls_manager = Plugin::$instance->controls_manager; $controls_manager->register( new Repeater() ); } public function is_custom_colors_enabled() { return ! get_option( 'elementor_disable_color_schemes' ); } public function is_custom_typography_enabled() { return ! get_option( 'elementor_disable_typography_schemes' ); } /** * Add kit wrapper body class. * * It should be added even for non Elementor pages, * in order to support embedded templates. */ private function add_body_class() { $kit = $this->get_kit_for_frontend(); if ( $kit ) { Plugin::$instance->frontend->add_body_class( 'elementor-kit-' . $kit->get_main_id() ); } } /** * Send a confirm message before move a kit to trash, or if delete permanently not for trash. * * @param $post_id * @param false $is_permanently_delete */ private function before_delete_kit( $post_id, $is_permanently_delete = false ) { if ( $this->should_skip_trash_kit_confirmation ) { return; } $document = Plugin::$instance->documents->get( $post_id ); if ( ! $document || ! $this->is_kit( $post_id ) || isset( $_GET['force_delete_kit'] ) || // phpcs:ignore -- nonce validation is not require here. ( $is_permanently_delete && $document->is_trash() ) ) { return; } ob_start(); require __DIR__ . '/views/trash-kit-confirmation.php'; $confirmation_content = ob_get_clean(); // PHPCS - the content does not contain user input value. wp_die( new \WP_Error( 'cant_delete_kit', $confirmation_content ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Add 'Edit with elementor -> Site Settings' in admin bar. * * @param [] $admin_bar_config * * @return array $admin_bar_config */ private function add_menu_in_admin_bar( $admin_bar_config ) { $document = Plugin::$instance->documents->get( get_the_ID() ); if ( ! $document || ! $document->is_built_with_elementor() ) { $recent_edited_post = Utils::get_recently_edited_posts_query( [ 'posts_per_page' => 1, ] ); if ( $recent_edited_post->post_count ) { $posts = $recent_edited_post->get_posts(); $document = Plugin::$instance->documents->get( reset( $posts )->ID ); } } if ( $document ) { $document_edit_url = add_query_arg( [ 'active-document' => $this->get_active_id(), ], $document->get_edit_url() ); $admin_bar_config['elementor_edit_page']['children'][] = [ 'id' => 'elementor_site_settings', 'title' => esc_html__( 'Site Settings', 'elementor' ), 'sub_title' => esc_html__( 'Site', 'elementor' ), 'href' => $document_edit_url, 'class' => 'elementor-site-settings', 'parent_class' => 'elementor-second-section', ]; } return $admin_bar_config; } public function __construct() { add_action( 'elementor/documents/register', [ $this, 'register_document' ] ); add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] ); add_filter( 'elementor/editor/footer', [ $this, 'render_panel_html' ] ); add_action( 'elementor/frontend/after_enqueue_styles', [ $this, 'frontend_before_enqueue_styles' ], 0 ); add_action( 'elementor/preview/enqueue_styles', [ $this, 'preview_enqueue_styles' ], 0 ); add_action( 'elementor/controls/register', [ $this, 'register_controls' ] ); add_action( 'wp_trash_post', function ( $post_id ) { $this->before_delete_kit( $post_id ); } ); add_action( 'before_delete_post', function ( $post_id ) { $this->before_delete_kit( $post_id, true ); } ); add_action( 'update_option_blogname', function ( $old_value, $value ) { $this->update_kit_settings_based_on_option( 'site_name', $value ); }, 10, 2 ); add_action( 'update_option_blogdescription', function ( $old_value, $value ) { $this->update_kit_settings_based_on_option( 'site_description', $value ); }, 10, 2 ); add_action( 'wp_head', function() { $this->add_body_class(); } ); add_filter( 'elementor/frontend/admin_bar/settings', function ( $admin_bar_config ) { return $this->add_menu_in_admin_bar( $admin_bar_config ); }, 9 /* Before site-editor (theme-builder) */ ); } } kits/views/trash-kit-confirmation.php 0000644 00000003157 14717626151 0014000 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @var int $post_id * @var boolean $is_permanently_delete */ $config_url = add_query_arg( [ 'force_delete_kit' => '1' ], get_delete_post_link( $post_id, '', $is_permanently_delete ) ); ?> <h4> <?php echo esc_html__( 'Are you sure you want to delete your Site Settings?', 'elementor' ); ?> </h4> <p> <?php echo esc_html__( 'By removing this template you will delete your entire Site Settings. If this template is deleted, all associated settings: Global Colors & Fonts, Theme Style, Layout, Background, and Lightbox settings will be removed from your existing site. This action can not be undone.', 'elementor' ); ?> </p> <br/> <a class="btn btn-danger" href="<?php // PHPCS - the link is generated by WordPress. echo $config_url; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"> <?php echo esc_html__( 'Delete', 'elementor' ); ?> </a> <a class="btn btn-primary" href="javascript:history.back()"> <?php echo esc_html__( 'Keep my settings', 'elementor' ); ?> </a> <style> /* In WordPress "die" screen there is very basic style, so the current css is required for basic button styles. */ .btn { text-decoration: none; padding: 9px 20px; font-weight: 500; border-radius: 3px; } .btn-danger { display: inline-block; color: #a00; } .btn-danger:hover, .btn-danger:focus, .btn-danger:active { color: #dc3232; } .btn-primary { color: #fff; background-color: #007cba; margin: 0 10px; } .btn-primary:hover, .btn-primary:focus, .btn-primary:active { background-color: #0071a1; color: #fff; } </style> kits/views/panel.php 0000644 00000003213 14717626151 0010474 0 ustar 00 <script type="text/template" id="tmpl-elementor-kit-panel"> <main id="elementor-kit__panel-content__wrapper" class="elementor-panel-content-wrapper"></main> </script> <script type="text/template" id="tmpl-elementor-kit-panel-content"> <div id="elementor-kit-panel-content-controls"></div> <# const tabConfig = $e.components.get( 'panel/global' ).getActiveTabConfig(); if ( tabConfig.helpUrl ) { #> <div id="elementor-panel__editor__help"> <a id="elementor-panel__editor__help__link" href="{{ tabConfig.helpUrl }}" target="_blank"> <?php echo esc_html__( 'Need Help', 'elementor' ); ?> <i class="eicon-help-o"></i> </a> </div> <# } if ( tabConfig.additionalContent ) { #> {{{ tabConfig.additionalContent }}} <# } #> </script> <script type="text/template" id="tmpl-elementor-global-style-repeater-row"> <# let removeClass = 'remove', removeIcon = 'eicon-trash-o'; if ( ! itemActions.remove ) { removeClass += '--disabled'; removeIcon = 'eicon-disable-trash-o' } #> <# if ( itemActions.sort ) { #> <button class="elementor-repeater-row-tool elementor-repeater-row-tools elementor-repeater-tool-sort"> <i class="eicon-cursor-move" aria-hidden="true"></i> <span class="elementor-screen-only"><?php echo esc_html__( 'Reorder', 'elementor' ); ?></span> </button> <# } #> <button class="elementor-repeater-row-tool elementor-repeater-tool-{{{ removeClass }}}"> <i class="{{{ removeIcon }}}" aria-hidden="true"></i> <# if ( itemActions.remove ) { #> <span class="elementor-screen-only"><?php echo esc_html__( 'Remove', 'elementor' ); ?></span> <# } #> </button> <div class="elementor-repeater-row-controls"></div> </script> kits/controls/repeater.php 0000644 00000003142 14717626151 0011713 0 ustar 00 <?php namespace Elementor\Core\Kits\Controls; use Elementor\Control_Repeater; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Repeater extends Control_Repeater { const CONTROL_TYPE = 'global-style-repeater'; /** * Get control type. * * Retrieve the control type, in this case `global-style-repeater`. * * @since 3.0.0 * @access public * * @return string Control type. */ public function get_type() { return self::CONTROL_TYPE; } /** * Get repeater control default settings. * * Retrieve the default settings of the repeater control. Used to return the * default settings while initializing the repeater control. * * @since 3.0.0 * @access protected * * @return array Control default settings. */ protected function get_default_settings() { $settings = parent::get_default_settings(); $settings['item_actions']['duplicate'] = false; return $settings; } /** * Render repeater control output in the editor. * * Used to generate the control HTML in the editor using Underscore JS * template. The variables for the class are available using `data` JS * object. * * @since 3.0.0 * @access public */ public function content_template() { ?> <div class="elementor-repeater-fields-wrapper"></div> <# if ( itemActions.add ) { #> <div class="elementor-button-wrapper"> <button class="elementor-button elementor-repeater-add" type="button"> <i class="eicon-plus" aria-hidden="true"></i> <span class="elementor-repeater__add-button__text">{{{ addButtonText }}}</span> </button> </div> <# } #> <?php } } kits/documents/kit.php 0000644 00000012613 14717626151 0011034 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents; use Elementor\Core\DocumentTypes\PageBase; use Elementor\Core\Files\CSS\Post as Post_CSS; use Elementor\Core\Kits\Documents\Tabs; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Core\Settings\Page\Manager as PageManager; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Kit extends PageBase { /** * @var Tabs\Tab_Base[] */ private $tabs; public function __construct( array $data = [] ) { parent::__construct( $data ); $this->register_tabs(); } public static function get_properties() { $properties = parent::get_properties(); $properties['has_elements'] = false; $properties['show_in_finder'] = false; $properties['show_on_admin_bar'] = false; $properties['edit_capability'] = 'edit_theme_options'; $properties['support_kit'] = true; return $properties; } public static function get_type() { return 'kit'; } public static function get_title() { return esc_html__( 'Kit', 'elementor' ); } /** * @return Tabs\Tab_Base[] */ public function get_tabs() { return $this->tabs; } /** * Retrieve a tab by ID. * * @param $id * * @return Tabs\Tab_Base */ public function get_tab( $id ) { return self::get_items( $this->get_tabs(), $id ); } protected function get_have_a_look_url() { return ''; } public static function get_editor_panel_config() { $config = parent::get_editor_panel_config(); $config['default_route'] = 'panel/global/menu'; $config['needHelpUrl'] = 'https://go.elementor.com/global-settings/'; return $config; } public function get_css_wrapper_selector() { return '.elementor-kit-' . $this->get_main_id(); } public function save( $data ) { foreach ( $this->tabs as $tab ) { $data = $tab->before_save( $data ); } $saved = parent::save( $data ); if ( ! $saved ) { return false; } // Should set is_saving to true, to avoid infinite loop when updating // settings like: 'site_name" or "site_description". $this->set_is_saving( true ); foreach ( $this->tabs as $tab ) { $tab->on_save( $data ); } $this->set_is_saving( false ); // When deleting a global color or typo, the css variable still exists in the frontend // but without any value and it makes the element to be un styled even if there is a default style for the base element, // for that reason this method removes css files of the entire site. Plugin::instance()->files_manager->clear_cache(); return $saved; } /** * Register a kit settings menu. * * @param $id * @param $class */ public function register_tab( $id, $class ) { $this->tabs[ $id ] = new $class( $this ); } /** * @inheritDoc */ protected function get_initial_config() { $config = parent::get_initial_config(); foreach ( $this->tabs as $id => $tab ) { $config['tabs'][ $id ] = [ 'id' => $id, 'title' => $tab->get_title(), 'icon' => $tab->get_icon(), 'group' => $tab->get_group(), 'helpUrl' => $tab->get_help_url(), 'additionalContent' => $tab->get_additional_tab_content(), ]; } return $config; } /** * @since 3.1.0 * @access protected */ protected function register_controls() { $this->register_document_controls(); foreach ( $this->tabs as $tab ) { $tab->register_controls(); } } protected function get_post_statuses() { return [ 'draft' => sprintf( '%s (%s)', esc_html__( 'Disabled', 'elementor' ), esc_html__( 'Draft', 'elementor' ) ), 'publish' => esc_html__( 'Published', 'elementor' ), ]; } public function add_repeater_row( $control_id, $item ) { $meta_key = PageManager::META_KEY; $document_settings = $this->get_meta( $meta_key ); if ( ! $document_settings ) { $document_settings = []; } if ( ! isset( $document_settings[ $control_id ] ) ) { $document_settings[ $control_id ] = []; } $document_settings[ $control_id ][] = $item; $page_settings_manager = SettingsManager::get_settings_managers( 'page' ); $page_settings_manager->save_settings( $document_settings, $this->get_id() ); /** @var Kit $autosave **/ $autosave = $this->get_autosave(); if ( $autosave ) { $autosave->add_repeater_row( $control_id, $item ); } // Remove Post CSS. $post_css = Post_CSS::create( $this->post->ID ); $post_css->delete(); // Refresh Cache. Plugin::$instance->documents->get( $this->post->ID, false ); $post_css = Post_CSS::create( $this->post->ID ); $post_css->enqueue(); } /** * Register default tabs (menu pages) for site settings. */ private function register_tabs() { $tabs = [ 'global-colors' => Tabs\Global_Colors::class, 'global-typography' => Tabs\Global_Typography::class, 'theme-style-typography' => Tabs\Theme_Style_Typography::class, 'theme-style-buttons' => Tabs\Theme_Style_Buttons::class, 'theme-style-images' => Tabs\Theme_Style_Images::class, 'theme-style-form-fields' => Tabs\Theme_Style_Form_Fields::class, 'settings-site-identity' => Tabs\Settings_Site_Identity::class, 'settings-background' => Tabs\Settings_Background::class, 'settings-layout' => Tabs\Settings_Layout::class, 'settings-lightbox' => Tabs\Settings_Lightbox::class, 'settings-page-transitions' => Tabs\Settings_Page_Transitions::class, 'settings-custom-css' => Tabs\Settings_Custom_CSS::class, ]; foreach ( $tabs as $id => $class ) { $this->register_tab( $id, $class ); } do_action( 'elementor/kit/register_tabs', $this ); } } kits/documents/tabs/global-colors.php 0000644 00000005371 14717626151 0013740 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Controls_Manager; use Elementor\Core\Kits\Controls\Repeater as Global_Style_Repeater; use Elementor\Repeater; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Global_Colors extends Tab_Base { const COLOR_PRIMARY = 'globals/colors?id=primary'; const COLOR_SECONDARY = 'globals/colors?id=secondary'; const COLOR_TEXT = 'globals/colors?id=text'; const COLOR_ACCENT = 'globals/colors?id=accent'; public function get_id() { return 'global-colors'; } public function get_title() { return esc_html__( 'Global Colors', 'elementor' ); } public function get_group() { return 'global'; } public function get_icon() { return 'eicon-global-colors'; } public function get_help_url() { return 'https://go.elementor.com/global-colors/'; } protected function register_tab_controls() { $this->start_controls_section( 'section_global_colors', [ 'label' => esc_html__( 'Global Colors', 'elementor' ), 'tab' => $this->get_id(), ] ); $repeater = new Repeater(); $repeater->add_control( 'title', [ 'type' => Controls_Manager::TEXT, 'label_block' => true, 'required' => true, ] ); // Color Value $repeater->add_control( 'color', [ 'type' => Controls_Manager::COLOR, 'label_block' => true, 'selectors' => [ '{{WRAPPER}}' => '--e-global-color-{{_id.VALUE}}: {{VALUE}}', ], 'global' => [ 'active' => false, ], ] ); $default_colors = [ [ '_id' => 'primary', 'title' => esc_html__( 'Primary', 'elementor' ), 'color' => '#6EC1E4', ], [ '_id' => 'secondary', 'title' => esc_html__( 'Secondary', 'elementor' ), 'color' => '#54595F', ], [ '_id' => 'text', 'title' => esc_html__( 'Text', 'elementor' ), 'color' => '#7A7A7A', ], [ '_id' => 'accent', 'title' => esc_html__( 'Accent', 'elementor' ), 'color' => '#61CE70', ], ]; $this->add_control( 'heading_system_colors', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'System Colors', 'elementor' ), ] ); $this->add_control( 'system_colors', [ 'type' => Global_Style_Repeater::CONTROL_TYPE, 'fields' => $repeater->get_controls(), 'default' => $default_colors, 'item_actions' => [ 'add' => false, 'remove' => false, ], 'separator' => 'after', ] ); $this->add_control( 'heading_custom_colors', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Custom Colors', 'elementor' ), ] ); $this->add_control( 'custom_colors', [ 'type' => Global_Style_Repeater::CONTROL_TYPE, 'fields' => $repeater->get_controls(), ] ); $this->end_controls_section(); } } kits/documents/tabs/theme-style-images.php 0000644 00000011017 14717626151 0014676 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Controls_Manager; use Elementor\Group_Control_Border; use Elementor\Group_Control_Box_Shadow; use Elementor\Group_Control_Css_Filter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Theme_Style_Images extends Tab_Base { public function get_id() { return 'theme-style-images'; } public function get_title() { return esc_html__( 'Images', 'elementor' ); } public function get_group() { return 'theme-style'; } public function get_icon() { return 'eicon-image'; } public function get_help_url() { return 'https://go.elementor.com/global-theme-style-images/'; } protected function register_tab_controls() { $image_selectors = [ '{{WRAPPER}} img', ]; $image_hover_selectors = [ '{{WRAPPER}} img:hover', ]; $image_selectors = implode( ',', $image_selectors ); $image_hover_selectors = implode( ',', $image_hover_selectors ); $this->start_controls_section( 'section_images', [ 'label' => esc_html__( 'Images', 'elementor' ), 'tab' => $this->get_id(), ] ); $this->add_default_globals_notice(); $this->start_controls_tabs( 'tabs_image_style' ); $this->start_controls_tab( 'tab_image_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'image_border', 'selector' => $image_selectors, 'fields_options' => [ 'color' => [ 'dynamic' => [], ], ], ] ); $this->add_responsive_control( 'image_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ $image_selectors => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_control( 'image_opacity', [ 'label' => esc_html__( 'Opacity', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'max' => 1, 'min' => 0.10, 'step' => 0.01, ], ], 'selectors' => [ $image_selectors => 'opacity: {{SIZE}};', ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'image_box_shadow', 'exclude' => [ 'box_shadow_position', ], 'selector' => $image_selectors, ] ); $this->add_group_control( Group_Control_Css_Filter::get_type(), [ 'name' => 'image_css_filters', 'selector' => '{{WRAPPER}} img', ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_image_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'image_hover_border', 'selector' => '{{WRAPPER}} img:hover', 'fields_options' => [ 'color' => [ 'dynamic' => [], ], ], ] ); $this->add_responsive_control( 'image_hover_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ $image_hover_selectors => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_control( 'image_hover_opacity', [ 'label' => esc_html__( 'Opacity', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'max' => 1, 'min' => 0.10, 'step' => 0.01, ], ], 'selectors' => [ $image_hover_selectors => 'opacity: {{SIZE}};', ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'image_hover_box_shadow', 'exclude' => [ 'box_shadow_position', ], 'selector' => $image_hover_selectors, ] ); $this->add_group_control( Group_Control_Css_Filter::get_type(), [ 'name' => 'image_hover_css_filters', 'selector' => $image_hover_selectors, ] ); $this->add_control( 'image_hover_transition', [ 'label' => esc_html__( 'Transition Duration', 'elementor' ) . ' (s)', 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 0, 'max' => 3, 'step' => 0.1, ], ], 'selectors' => [ $image_selectors => 'transition-duration: {{SIZE}}s', ], ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->end_controls_section(); } } kits/documents/tabs/global-typography.php 0000644 00000012343 14717626151 0014642 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Controls_Manager; use Elementor\Core\Kits\Controls\Repeater as Global_Style_Repeater; use Elementor\Group_Control_Typography; use Elementor\Repeater; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Global_Typography extends Tab_Base { const TYPOGRAPHY_PRIMARY = 'globals/typography?id=primary'; const TYPOGRAPHY_SECONDARY = 'globals/typography?id=secondary'; const TYPOGRAPHY_TEXT = 'globals/typography?id=text'; const TYPOGRAPHY_ACCENT = 'globals/typography?id=accent'; const TYPOGRAPHY_NAME = 'typography'; const TYPOGRAPHY_GROUP_PREFIX = self::TYPOGRAPHY_NAME . '_'; public function get_id() { return 'global-typography'; } public function get_title() { return esc_html__( 'Global Fonts', 'elementor' ); } public function get_group() { return 'global'; } public function get_icon() { return 'eicon-t-letter'; } public function get_help_url() { return 'https://go.elementor.com/global-fonts/'; } protected function register_tab_controls() { $this->start_controls_section( 'section_text_style', [ 'label' => esc_html__( 'Global Fonts', 'elementor' ), 'tab' => $this->get_id(), ] ); $repeater = new Repeater(); $repeater->add_control( 'title', [ 'type' => Controls_Manager::TEXT, 'label_block' => true, 'required' => true, ] ); $repeater->add_group_control( Group_Control_Typography::get_type(), [ 'name' => self::TYPOGRAPHY_NAME, 'label' => '', 'global' => [ 'active' => false, ], 'fields_options' => [ 'font_family' => [ 'selectors' => [ '{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-family: "{{VALUE}}"', ], ], 'font_size' => [ 'selectors' => [ '{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-size: {{SIZE}}{{UNIT}}', ], ], 'font_weight' => [ 'selectors' => [ '{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-weight: {{VALUE}}', ], ], 'text_transform' => [ 'selectors' => [ '{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-text-transform: {{VALUE}}', ], ], 'font_style' => [ 'selectors' => [ '{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-style: {{VALUE}}', ], ], 'text_decoration' => [ 'selectors' => [ '{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-text-decoration: {{VALUE}}', ], ], 'line_height' => [ 'selectors' => [ '{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-line-height: {{SIZE}}{{UNIT}}', ], ], 'letter_spacing' => [ 'selectors' => [ '{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-letter-spacing: {{SIZE}}{{UNIT}}', ], ], 'word_spacing' => [ 'selectors' => [ '{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-word-spacing: {{SIZE}}{{UNIT}}', ], ], ], ] ); $typography_key = self::TYPOGRAPHY_GROUP_PREFIX . 'typography'; $font_family_key = self::TYPOGRAPHY_GROUP_PREFIX . 'font_family'; $font_weight_key = self::TYPOGRAPHY_GROUP_PREFIX . 'font_weight'; $default_typography = [ [ '_id' => 'primary', 'title' => esc_html__( 'Primary', 'elementor' ), $typography_key => 'custom', $font_family_key => 'Roboto', $font_weight_key => '600', ], [ '_id' => 'secondary', 'title' => esc_html__( 'Secondary', 'elementor' ), $typography_key => 'custom', $font_family_key => 'Roboto Slab', $font_weight_key => '400', ], [ '_id' => 'text', 'title' => esc_html__( 'Text', 'elementor' ), $typography_key => 'custom', $font_family_key => 'Roboto', $font_weight_key => '400', ], [ '_id' => 'accent', 'title' => esc_html__( 'Accent', 'elementor' ), $typography_key => 'custom', $font_family_key => 'Roboto', $font_weight_key => '500', ], ]; $this->add_control( 'heading_system_typography', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'System Fonts', 'elementor' ), ] ); $this->add_control( 'system_typography', [ 'type' => Global_Style_Repeater::CONTROL_TYPE, 'fields' => $repeater->get_controls(), 'default' => $default_typography, 'item_actions' => [ 'add' => false, 'remove' => false, ], 'separator' => 'after', ] ); $this->add_control( 'heading_custom_typography', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Custom Fonts', 'elementor' ), ] ); $this->add_control( 'custom_typography', [ 'type' => Global_Style_Repeater::CONTROL_TYPE, 'fields' => $repeater->get_controls(), ] ); $this->add_control( 'default_generic_fonts', [ 'label' => esc_html__( 'Fallback Font Family', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => 'Sans-serif', 'description' => esc_html__( 'The list of fonts used if the chosen font is not available.', 'elementor' ), 'label_block' => true, 'separator' => 'before', 'ai' => [ 'active' => false, ], ] ); $this->end_controls_section(); } } kits/documents/tabs/settings-custom-css.php 0000644 00000001300 14717626151 0015123 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Settings_Custom_CSS extends Tab_Base { public function get_id() { return 'settings-custom-css'; } public function get_title() { return esc_html__( 'Custom CSS', 'elementor' ); } public function get_group() { return 'settings'; } public function get_icon() { return 'eicon-custom-css'; } public function get_help_url() { return 'https://go.elementor.com/global-custom-css/'; } protected function register_tab_controls() { Plugin::$instance->controls_manager->add_custom_css_controls( $this->parent, $this->get_id() ); } } kits/documents/tabs/settings-page-transitions.php 0000644 00000001373 14717626151 0016324 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Settings_Page_Transitions extends Tab_Base { const TAB_ID = 'settings-page-transitions'; public function get_id() { return self::TAB_ID; } public function get_title() { return esc_html__( 'Page Transitions', 'elementor' ); } public function get_group() { return 'settings'; } public function get_icon() { return 'eicon-page-transition'; } public function get_help_url() { return 'https://go.elementor.com/page-transitions/'; } protected function register_tab_controls() { Plugin::$instance->controls_manager->add_page_transitions_controls( $this->parent, $this->get_id() ); } } kits/documents/tabs/theme-style-buttons.php 0000644 00000013121 14717626151 0015125 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Controls_Manager; use Elementor\Group_Control_Background; use Elementor\Group_Control_Border; use Elementor\Group_Control_Box_Shadow; use Elementor\Group_Control_Text_Shadow; use Elementor\Group_Control_Typography; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Theme_Style_Buttons extends Tab_Base { public function get_id() { return 'theme-style-buttons'; } public function get_title() { return esc_html__( 'Buttons', 'elementor' ); } public function get_group() { return 'theme-style'; } public function get_icon() { return 'eicon-button'; } public function get_help_url() { return 'https://go.elementor.com/global-theme-style-buttons/'; } protected function register_tab_controls() { $button_selectors = [ '{{WRAPPER}} button', '{{WRAPPER}} input[type="button"]', '{{WRAPPER}} input[type="submit"]', '{{WRAPPER}} .elementor-button', ]; $button_hover_selectors = [ '{{WRAPPER}} button:hover', '{{WRAPPER}} button:focus', '{{WRAPPER}} input[type="button"]:hover', '{{WRAPPER}} input[type="button"]:focus', '{{WRAPPER}} input[type="submit"]:hover', '{{WRAPPER}} input[type="submit"]:focus', '{{WRAPPER}} .elementor-button:hover', '{{WRAPPER}} .elementor-button:focus', ]; $button_selector = implode( ',', $button_selectors ); $button_hover_selector = implode( ',', $button_hover_selectors ); $this->start_controls_section( 'section_buttons', [ 'label' => esc_html__( 'Buttons', 'elementor' ), 'tab' => $this->get_id(), ] ); $this->add_default_globals_notice(); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'button_typography', 'selector' => $button_selector, ] ); $this->add_group_control( Group_Control_Text_Shadow::get_type(), [ 'name' => 'button_text_shadow', 'selector' => $button_selector, ] ); $this->start_controls_tabs( 'tabs_button_style' ); $this->start_controls_tab( 'tab_button_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_control( 'button_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'dynamic' => [], 'selectors' => [ $button_selector => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'button_background', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], 'selector' => $button_selector, 'fields_options' => [ 'background' => [ 'default' => 'classic', ], 'color' => [ 'dynamic' => [], ], 'color_b' => [ 'dynamic' => [], ], ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'button_box_shadow', 'selector' => $button_selector, ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'button_border', 'selector' => $button_selector, 'fields_options' => [ 'color' => [ 'dynamic' => [], ], ], ] ); $this->add_control( 'button_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ $button_selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_button_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_control( 'button_hover_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'dynamic' => [], 'selectors' => [ $button_hover_selector => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'button_hover_background', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], 'selector' => $button_hover_selector, 'fields_options' => [ 'background' => [ 'default' => 'classic', ], 'color' => [ 'dynamic' => [], ], 'color_b' => [ 'dynamic' => [], ], ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'button_hover_box_shadow', 'selector' => $button_hover_selector, ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'button_hover_border', 'selector' => $button_hover_selector, 'fields_options' => [ 'color' => [ 'dynamic' => [], ], ], ] ); $this->add_control( 'button_hover_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ $button_hover_selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_responsive_control( 'button_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ $button_selector => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); $this->end_controls_section(); } } kits/documents/tabs/theme-style-form-fields.php 0000644 00000012470 14717626151 0015644 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Controls_Manager; use Elementor\Group_Control_Border; use Elementor\Group_Control_Box_Shadow; use Elementor\Group_Control_Typography; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Theme_Style_Form_Fields extends Tab_Base { public function get_id() { return 'theme-style-form-fields'; } public function get_title() { return esc_html__( 'Form Fields', 'elementor' ); } public function get_group() { return 'theme-style'; } public function get_icon() { return 'eicon-form-horizontal'; } public function get_help_url() { return 'https://go.elementor.com/global-theme-style-form-fields/'; } protected function register_tab_controls() { $label_selectors = [ '{{WRAPPER}} label', ]; $input_selectors = [ '{{WRAPPER}} input:not([type="button"]):not([type="submit"])', '{{WRAPPER}} textarea', '{{WRAPPER}} .elementor-field-textual', ]; $input_focus_selectors = [ '{{WRAPPER}} input:focus:not([type="button"]):not([type="submit"])', '{{WRAPPER}} textarea:focus', '{{WRAPPER}} .elementor-field-textual:focus', ]; $label_selector = implode( ',', $label_selectors ); $input_selector = implode( ',', $input_selectors ); $input_focus_selector = implode( ',', $input_focus_selectors ); $this->start_controls_section( 'section_form_fields', [ 'label' => esc_html__( 'Form Fields', 'elementor' ), 'tab' => $this->get_id(), ] ); $this->add_default_globals_notice(); $this->add_control( 'form_label_heading', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Label', 'elementor' ), ] ); $this->add_control( 'form_label_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'dynamic' => [], 'selectors' => [ $label_selector => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'form_label_typography', 'selector' => $label_selector, ] ); $this->add_control( 'form_field_heading', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Field', 'elementor' ), 'separator' => 'before', ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'form_field_typography', 'selector' => $input_selector, ] ); $this->start_controls_tabs( 'tabs_form_field_style' ); $this->start_controls_tab( 'tab_form_field_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_form_field_state_tab_controls( 'form_field', $input_selector ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_form_field_focus', [ 'label' => esc_html__( 'Focus', 'elementor' ), ] ); $this->add_form_field_state_tab_controls( 'form_field_focus', $input_focus_selector ); $this->add_control( 'form_field_focus_transition_duration', [ 'label' => esc_html__( 'Transition Duration', 'elementor' ) . ' (ms)', 'type' => Controls_Manager::SLIDER, 'selectors' => [ $input_selector => 'transition: {{SIZE}}ms', ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 3000, 'step' => 100, ], ], ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_responsive_control( 'form_field_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ $input_selector => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); $this->end_controls_section(); } private function add_form_field_state_tab_controls( $prefix, $selector ) { $this->add_control( $prefix . '_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'dynamic' => [], 'selectors' => [ $selector => 'color: {{VALUE}};', ], ] ); $this->add_control( $prefix . '_accent_color', [ 'label' => esc_html__( 'Accent Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'dynamic' => [], 'selectors' => [ $selector => 'accent-color: {{VALUE}};', ], ] ); $this->add_control( $prefix . '_background_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'dynamic' => [], 'selectors' => [ $selector => 'background-color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => $prefix . '_box_shadow', 'selector' => $selector, ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => $prefix . '_border', 'selector' => $selector, 'fields_options' => [ 'color' => [ 'dynamic' => [], ], ], ] ); $this->add_control( $prefix . '_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ $selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); } } kits/documents/tabs/settings-lightbox.php 0000644 00000011400 14717626151 0014645 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Controls_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Settings_Lightbox extends Tab_Base { public function get_id() { return 'settings-lightbox'; } public function get_title() { return esc_html__( 'Lightbox', 'elementor' ); } public function get_group() { return 'settings'; } public function get_icon() { return 'eicon-lightbox-expand'; } public function get_help_url() { return 'https://go.elementor.com/global-lightbox/'; } protected function register_tab_controls() { $this->start_controls_section( 'section_' . $this->get_id(), [ 'label' => $this->get_title(), 'tab' => $this->get_id(), ] ); $this->add_control( 'global_image_lightbox', [ 'label' => esc_html__( 'Image Lightbox', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'description' => esc_html__( 'Open all image links in a lightbox popup window. The lightbox will automatically work on any link that leads to an image file.', 'elementor' ), 'frontend_available' => true, ] ); $this->add_control( 'lightbox_enable_counter', [ 'label' => esc_html__( 'Counter', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'frontend_available' => true, ] ); $this->add_control( 'lightbox_enable_fullscreen', [ 'label' => esc_html__( 'Fullscreen', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'frontend_available' => true, ] ); $this->add_control( 'lightbox_enable_zoom', [ 'label' => esc_html__( 'Zoom', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'frontend_available' => true, ] ); $this->add_control( 'lightbox_enable_share', [ 'label' => esc_html__( 'Share', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'frontend_available' => true, ] ); $this->add_control( 'lightbox_title_src', [ 'label' => esc_html__( 'Title', 'elementor' ), 'type' => Controls_Manager::SELECT, 'options' => [ '' => esc_html__( 'None', 'elementor' ), 'title' => esc_html__( 'Title', 'elementor' ), 'caption' => esc_html__( 'Caption', 'elementor' ), 'alt' => esc_html__( 'Alt', 'elementor' ), 'description' => esc_html__( 'Description', 'elementor' ), ], 'default' => 'title', 'frontend_available' => true, ] ); $this->add_control( 'lightbox_description_src', [ 'label' => esc_html__( 'Description', 'elementor' ), 'type' => Controls_Manager::SELECT, 'options' => [ '' => esc_html__( 'None', 'elementor' ), 'title' => esc_html__( 'Title', 'elementor' ), 'caption' => esc_html__( 'Caption', 'elementor' ), 'alt' => esc_html__( 'Alt', 'elementor' ), 'description' => esc_html__( 'Description', 'elementor' ), ], 'default' => 'description', 'frontend_available' => true, ] ); $this->add_control( 'lightbox_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '.elementor-lightbox' => 'background-color: {{VALUE}}', ], ] ); $this->add_control( 'lightbox_ui_color', [ 'label' => esc_html__( 'UI Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '.elementor-lightbox' => '--lightbox-ui-color: {{VALUE}}', ], ] ); $this->add_control( 'lightbox_ui_color_hover', [ 'label' => esc_html__( 'UI Hover Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '.elementor-lightbox' => '--lightbox-ui-color-hover: {{VALUE}}', ], ] ); $this->add_control( 'lightbox_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '.elementor-lightbox' => '--lightbox-text-color: {{VALUE}}', ], ] ); $this->add_control( 'lightbox_icons_size', [ 'label' => esc_html__( 'Toolbar Icons Size', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '.elementor-lightbox' => '--lightbox-header-icons-size: {{SIZE}}{{UNIT}}', ], 'separator' => 'before', ] ); $this->add_control( 'lightbox_slider_icons_size', [ 'label' => esc_html__( 'Navigation Icons Size', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '.elementor-lightbox' => '--lightbox-navigation-icons-size: {{SIZE}}{{UNIT}}', ], 'separator' => 'before', ] ); $this->end_controls_section(); } } kits/documents/tabs/settings-background.php 0000644 00000004045 14717626151 0015153 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Controls_Manager; use Elementor\Group_Control_Background; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Settings_Background extends Tab_Base { public function get_id() { return 'settings-background'; } public function get_title() { return esc_html__( 'Background', 'elementor' ); } public function get_group() { return 'settings'; } public function get_icon() { return 'eicon-background'; } public function get_help_url() { return 'https://go.elementor.com/global-background/'; } protected function register_tab_controls() { $this->start_controls_section( 'section_background', [ 'label' => $this->get_title(), 'tab' => $this->get_id(), ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'body_background', 'types' => [ 'classic', 'gradient' ], 'selector' => '{{WRAPPER}}', 'fields_options' => [ 'background' => [ 'frontend_available' => true, ], 'color' => [ 'dynamic' => [], ], 'color_b' => [ 'dynamic' => [], ], ], ] ); $this->add_control( 'mobile_browser_background', [ 'label' => esc_html__( 'Mobile Browser Background', 'elementor' ), 'type' => Controls_Manager::COLOR, 'description' => esc_html__( 'The `theme-color` meta tag will only be available in supported browsers and devices.', 'elementor' ), 'separator' => 'before', ] ); $this->add_control( 'body_overscroll_behavior', [ 'label' => esc_html__( 'Overscroll Behavior', 'elementor' ), 'type' => Controls_Manager::SELECT, 'options' => [ '' => esc_html__( 'Default', 'elementor' ), 'none' => esc_html__( 'None', 'elementor' ), 'auto' => esc_html__( 'Auto', 'elementor' ), 'contain' => esc_html__( 'Contain', 'elementor' ), ], 'separator' => 'before', 'selectors' => [ '{{WRAPPER}}' => 'overscroll-behavior: {{VALUE}};', ], ] ); $this->end_controls_section(); } } kits/documents/tabs/settings-layout.php 0000644 00000027561 14717626151 0014361 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Core\Breakpoints\Breakpoint; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Plugin; use Elementor\Controls_Manager; use Elementor\Core\Base\Document; use Elementor\Modules\PageTemplates\Module as PageTemplatesModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Settings_Layout extends Tab_Base { const ACTIVE_BREAKPOINTS_CONTROL_ID = 'active_breakpoints'; public function get_id() { return 'settings-layout'; } public function get_title() { return esc_html__( 'Layout', 'elementor' ); } public function get_group() { return 'settings'; } public function get_icon() { return 'eicon-layout-settings'; } public function get_help_url() { return 'https://go.elementor.com/global-layout/'; } protected function register_tab_controls() { $breakpoints_default_config = Breakpoints_Manager::get_default_config(); $breakpoint_key_mobile = Breakpoints_Manager::BREAKPOINT_KEY_MOBILE; $breakpoint_key_tablet = Breakpoints_Manager::BREAKPOINT_KEY_TABLET; $this->start_controls_section( 'section_' . $this->get_id(), [ 'label' => esc_html__( 'Layout Settings', 'elementor' ), 'tab' => $this->get_id(), ] ); $this->add_responsive_control( 'container_width', [ 'label' => esc_html__( 'Content Width', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'default' => [ 'size' => 1140, ], 'tablet_default' => [ 'size' => $breakpoints_default_config[ $breakpoint_key_tablet ]['default_value'], ], 'mobile_default' => [ 'size' => $breakpoints_default_config[ $breakpoint_key_mobile ]['default_value'], ], 'range' => [ 'px' => [ 'min' => 300, 'max' => 1500, 'step' => 10, ], ], 'description' => esc_html__( 'Sets the default width of the content area (Default: 1140px)', 'elementor' ), 'selectors' => [ '.elementor-section.elementor-section-boxed > .elementor-container' => 'max-width: {{SIZE}}{{UNIT}}', '.e-con' => '--container-max-width: {{SIZE}}{{UNIT}}', ], ] ); $is_container_active = Plugin::instance()->experiments->is_feature_active( 'container' ); if ( $is_container_active ) { $this->add_responsive_control( 'container_padding', [ 'label' => esc_html__( 'Container Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'description' => esc_html__( 'Sets the default space inside the container (Default is 10px)', 'elementor' ), 'selectors' => [ '.e-con' => '--container-default-padding-top: {{TOP}}{{UNIT}}; --container-default-padding-right: {{RIGHT}}{{UNIT}}; --container-default-padding-bottom: {{BOTTOM}}{{UNIT}}; --container-default-padding-left: {{LEFT}}{{UNIT}};', ], ] ); } $widgets_space_label = $is_container_active ? esc_html__( 'Gaps', 'elementor' ) : esc_html__( 'Widgets Space', 'elementor' ); $this->add_control( 'space_between_widgets', [ 'label' => $widgets_space_label, 'type' => Controls_Manager::GAPS, 'default' => [ 'row' => '20', 'column' => '20', 'unit' => 'px', ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'placeholder' => [ 'row' => '20', 'column' => '20', ], 'description' => esc_html__( 'Sets the default space between widgets (Default: 20px)', 'elementor' ), 'selectors' => [ '.elementor-widget:not(:last-child)' => 'margin-block-end: {{ROW}}{{UNIT}}', '.elementor-element' => '--widgets-spacing: {{ROW}}{{UNIT}} {{COLUMN}}{{UNIT}}', ], 'conversion_map' => [ 'old_key' => 'size', 'new_key' => 'column', ], 'upgrade_conversion_map' => [ 'old_key' => 'size', 'new_keys' => [ 'column', 'row' ], ], 'validators' => [ 'Number' => [ 'min' => 0, ], ], ] ); $this->add_control( 'page_title_selector', [ 'label' => esc_html__( 'Page Title Selector', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => 'h1.entry-title', 'placeholder' => 'h1.entry-title', 'description' => esc_html__( 'Elementor lets you hide the page title. This works for themes that have "h1.entry-title" selector. If your theme\'s selector is different, please enter it above.', 'elementor' ), 'label_block' => true, 'ai' => [ 'active' => false, ], 'selectors' => [ // Hack to convert the value into a CSS selector. '' => '}{{VALUE}}{display: var(--page-title-display)', ], ] ); $this->add_control( 'stretched_section_container', [ 'label' => esc_html__( 'Stretched Section Fit To', 'elementor' ), 'type' => Controls_Manager::TEXT, 'placeholder' => 'body', 'description' => esc_html__( 'Enter parent element selector to which stretched sections will fit to (e.g. #primary / .wrapper / main etc). Leave blank to fit to page width.', 'elementor' ), 'label_block' => true, 'frontend_available' => true, 'ai' => [ 'active' => false, ], ] ); /** * @var PageTemplatesModule $page_templates_module */ $page_templates_module = Plugin::$instance->modules_manager->get_modules( 'page-templates' ); $page_templates = $page_templates_module->add_page_templates( [], null, null ); // Removes the Theme option from the templates because 'default' is already handled. unset( $page_templates[ PageTemplatesModule::TEMPLATE_THEME ] ); $page_template_control_options = [ 'label' => esc_html__( 'Default Page Layout', 'elementor' ), 'options' => [ // This is here because the "Theme" string is different than the default option's string. 'default' => esc_html__( 'Theme', 'elementor' ), ] + $page_templates, ]; $page_templates_module->add_template_controls( $this->parent, 'default_page_template', $page_template_control_options ); $this->end_controls_section(); $this->start_controls_section( 'section_breakpoints', [ 'label' => esc_html__( 'Breakpoints', 'elementor' ), 'tab' => $this->get_id(), ] ); $prefix = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX; $options = []; foreach ( $breakpoints_default_config as $breakpoint_key => $breakpoint ) { $options[ $prefix . $breakpoint_key ] = $breakpoint['label']; } if ( Plugin::$instance->experiments->is_feature_active( 'additional_custom_breakpoints' ) ) { $active_breakpoints_control_type = Controls_Manager::SELECT2; } else { $active_breakpoints_control_type = Controls_Manager::HIDDEN; } $this->add_control( self::ACTIVE_BREAKPOINTS_CONTROL_ID, [ 'label' => esc_html__( 'Active Breakpoints', 'elementor' ), 'type' => $active_breakpoints_control_type, 'description' => esc_html__( 'Mobile and Tablet options cannot be deleted.', 'elementor' ), 'options' => $options, 'default' => [ $prefix . $breakpoint_key_mobile, $prefix . $breakpoint_key_tablet, ], 'select2options' => [ 'allowClear' => false, ], 'lockedOptions' => [ $prefix . $breakpoint_key_mobile, $prefix . $breakpoint_key_tablet, ], 'label_block' => true, 'render_type' => 'none', 'frontend_available' => true, 'multiple' => true, ] ); $this->add_breakpoints_controls(); // Include the old mobile and tablet breakpoint controls as hidden for backwards compatibility. $this->add_control( 'viewport_md', [ 'type' => Controls_Manager::HIDDEN ] ); $this->add_control( 'viewport_lg', [ 'type' => Controls_Manager::HIDDEN ] ); $this->end_controls_section(); } /** * Before Save * * Runs Before the Kit document is saved. * * For backwards compatibility, when the mobile and tablet breakpoints are updated, we also update the * old breakpoint settings ('viewport_md', 'viewport_lg' ) with the saved values + 1px. The reason 1px * is added is because the old breakpoints system was min-width based, and the new system introduced in * Elementor v3.2.0 is max-width based. * * @since 3.2.0 * * @param array $data * @return array $data */ public function before_save( array $data ) { // When creating a default kit, $data['settings'] is empty and should remain empty, so settings. if ( empty( $data['settings'] ) ) { return $data; } $prefix = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX; $mobile_breakpoint_key = $prefix . Breakpoints_Manager::BREAKPOINT_KEY_MOBILE; $tablet_breakpoint_key = $prefix . Breakpoints_Manager::BREAKPOINT_KEY_TABLET; $default_breakpoint_config = Breakpoints_Manager::get_default_config(); // Update the old mobile breakpoint. If the setting is empty, use the default value. $data['settings'][ $prefix . 'md' ] = empty( $data['settings'][ $mobile_breakpoint_key ] ) ? $default_breakpoint_config[ Breakpoints_Manager::BREAKPOINT_KEY_MOBILE ]['default_value'] + 1 : $data['settings'][ $mobile_breakpoint_key ] + 1; // Update the old tablet breakpoint. If the setting is empty, use the default value. $data['settings'][ $prefix . 'lg' ] = empty( $data['settings'][ $tablet_breakpoint_key ] ) ? $default_breakpoint_config[ Breakpoints_Manager::BREAKPOINT_KEY_TABLET ]['default_value'] + 1 : $data['settings'][ $tablet_breakpoint_key ] + 1; return $data; } public function on_save( $data ) { if ( ! isset( $data['settings'] ) || ( isset( $data['settings']['post_status'] ) && Document::STATUS_PUBLISH !== $data['settings']['post_status'] ) ) { return; } $should_compile_css = false; $breakpoints_default_config = Breakpoints_Manager::get_default_config(); foreach ( $breakpoints_default_config as $breakpoint_key => $default_config ) { $breakpoint_setting_key = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX . $breakpoint_key; if ( isset( $data['settings'][ $breakpoint_setting_key ] ) ) { $should_compile_css = true; } } if ( $should_compile_css ) { Breakpoints_Manager::compile_stylesheet_templates(); } } private function add_breakpoints_controls() { $default_breakpoints_config = Breakpoints_Manager::get_default_config(); $prefix = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX; // If the ACB experiment is inactive, only add the mobile and tablet controls. if ( ! Plugin::$instance->experiments->is_feature_active( 'additional_custom_breakpoints' ) ) { $default_breakpoints_config = array_intersect_key( $default_breakpoints_config, array_flip( [ Breakpoints_Manager::BREAKPOINT_KEY_MOBILE, Breakpoints_Manager::BREAKPOINT_KEY_TABLET ] ) ); } // Add a control for each of the **default** breakpoints. foreach ( $default_breakpoints_config as $breakpoint_key => $default_breakpoint_config ) { $this->add_control( 'breakpoint_' . $breakpoint_key . '_heading', [ 'label' => $default_breakpoint_config['label'], 'type' => Controls_Manager::HEADING, 'separator' => 'before', 'conditions' => [ 'terms' => [ [ 'name' => 'active_breakpoints', 'operator' => 'contains', 'value' => $prefix . $breakpoint_key, ], ], ], ] ); $control_config = [ 'label' => esc_html__( 'Breakpoint', 'elementor' ) . ' (px)', 'type' => Controls_Manager::NUMBER, 'placeholder' => $default_breakpoint_config['default_value'], 'frontend_available' => true, 'validators' => [ 'Breakpoint' => [ 'breakpointName' => $breakpoint_key, ], ], 'conditions' => [ 'terms' => [ [ 'name' => 'active_breakpoints', 'operator' => 'contains', 'value' => $prefix . $breakpoint_key, ], ], ], ]; if ( Breakpoints_Manager::BREAKPOINT_KEY_WIDESCREEN === $breakpoint_key ) { $control_config['description'] = esc_html__( 'Widescreen breakpoint settings will apply from the selected value and up.', 'elementor' ); } // Add the breakpoint Control itself. $this->add_control( $prefix . $breakpoint_key, $control_config ); } } } kits/documents/tabs/tab-base.php 0000644 00000004224 14717626151 0012653 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Controls_Manager; use Elementor\Core\Kits\Documents\Kit; use Elementor\Core\Kits\Manager; use Elementor\Plugin; use Elementor\Settings; use Elementor\Sub_Controls_Stack; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Tab_Base extends Sub_Controls_Stack { /** * @var Kit */ protected $parent; abstract protected function register_tab_controls(); public function get_group() { return 'settings'; } public function get_icon() { return ''; } public function get_help_url() { return ''; } public function get_additional_tab_content() { return ''; } public function register_controls() { $this->register_tab(); $this->register_tab_controls(); } public function on_save( $data ) {} /** * Before Save * * Allows for modifying the kit data before it is saved to the database. * * @param array $data * @return array */ public function before_save( array $data ) { return $data; } protected function register_tab() { Controls_Manager::add_tab( $this->get_id(), $this->get_title() ); } protected function add_default_globals_notice() { // Get the current section config (array - section id and tab) to use for creating a unique control ID and name $current_section = $this->parent->get_current_section(); /** @var Manager $module */ $kits_manager = Plugin::$instance->kits_manager; if ( $kits_manager->is_custom_colors_enabled() || $kits_manager->is_custom_typography_enabled() ) { $this->add_control( $current_section['section'] . '_schemes_notice', [ 'name' => $current_section['section'] . '_schemes_notice', 'type' => Controls_Manager::ALERT, 'alert_type' => 'warning', 'content' => sprintf( /* translators: 1: Link open tag, 2: Link close tag. */ esc_html__( 'In order for Theme Style to affect all relevant Elementor elements, please disable Default Colors and Fonts from the %1$sSettings Page%2$s.', 'elementor' ), '<a href="' . Settings::get_settings_tab_url( 'general' ) . '" target="_blank">', '</a>' ), 'render_type' => 'ui', ] ); } } } kits/documents/tabs/theme-style-typography.php 0000644 00000010676 14717626151 0015651 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Controls_Manager; use Elementor\Group_Control_Typography; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Theme_Style_Typography extends Tab_Base { public function get_id() { return 'theme-style-typography'; } public function get_title() { return esc_html__( 'Typography', 'elementor' ); } public function get_group() { return 'theme-style'; } public function get_icon() { return 'eicon-typography-1'; } public function get_help_url() { return 'https://go.elementor.com/global-theme-style-typography/'; } public function register_tab_controls() { $this->start_controls_section( 'section_typography', [ 'label' => esc_html__( 'Typography', 'elementor' ), 'tab' => $this->get_id(), ] ); $this->add_default_globals_notice(); $this->add_control( 'body_heading', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Body', 'elementor' ), ] ); $this->add_control( 'body_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'dynamic' => [], 'selectors' => [ '{{WRAPPER}}' => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'body_typography', 'selector' => '{{WRAPPER}}', ] ); $this->add_responsive_control( 'paragraph_spacing', [ 'label' => esc_html__( 'Paragraph Spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'selectors' => [ '{{WRAPPER}} p' => 'margin-bottom: {{SIZE}}{{UNIT}}', ], 'range' => [ 'px' => [ 'max' => 100, ], 'em' => [ 'min' => 0.1, 'max' => 20, ], ], 'size_units' => [ 'px', 'em', 'rem', 'vh', 'custom' ], ] ); //Link Selectors $link_selectors = [ '{{WRAPPER}} a', ]; $link_hover_selectors = [ '{{WRAPPER}} a:hover', ]; $link_selectors = implode( ',', $link_selectors ); $link_hover_selectors = implode( ',', $link_hover_selectors ); $this->add_control( 'link_heading', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Link', 'elementor' ), 'separator' => 'before', ] ); $this->start_controls_tabs( 'tabs_link_style' ); $this->start_controls_tab( 'tab_link_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_control( 'link_normal_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'dynamic' => [], 'selectors' => [ $link_selectors => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'link_normal_typography', 'selector' => $link_selectors, ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_link_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_control( 'link_hover_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'dynamic' => [], 'selectors' => [ $link_hover_selectors => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'link_hover_typography', 'selector' => $link_hover_selectors, ] ); $this->end_controls_tab(); $this->end_controls_tabs(); // Headings. $this->add_element_controls( 'H1', 'h1', '{{WRAPPER}} h1' ); $this->add_element_controls( 'H2', 'h2', '{{WRAPPER}} h2' ); $this->add_element_controls( 'H3', 'h3', '{{WRAPPER}} h3' ); $this->add_element_controls( 'H4', 'h4', '{{WRAPPER}} h4' ); $this->add_element_controls( 'H5', 'h5', '{{WRAPPER}} h5' ); $this->add_element_controls( 'H6', 'h6', '{{WRAPPER}} h6' ); $this->end_controls_section(); } private function add_element_controls( $label, $prefix, $selector ) { $this->add_control( $prefix . '_heading', [ 'type' => Controls_Manager::HEADING, 'label' => $label, 'separator' => 'before', ] ); $this->add_control( $prefix . '_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'dynamic' => [], 'selectors' => [ $selector => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => $prefix . '_typography', 'selector' => $selector, ] ); } } kits/documents/tabs/settings-site-identity.php 0000644 00000010421 14717626151 0015622 0 ustar 00 <?php namespace Elementor\Core\Kits\Documents\Tabs; use Elementor\Controls_Manager; use Elementor\Core\Base\Document; use Elementor\Core\Files\Uploads_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Settings_Site_Identity extends Tab_Base { public function get_id() { return 'settings-site-identity'; } public function get_title() { return esc_html__( 'Site Identity', 'elementor' ); } public function get_group() { return 'settings'; } public function get_icon() { return 'eicon-site-identity'; } public function get_help_url() { return 'https://go.elementor.com/global-site-identity/'; } protected function register_tab_controls() { $custom_logo_id = get_theme_mod( 'custom_logo' ); $custom_logo_src = wp_get_attachment_image_src( $custom_logo_id, 'full' ); $site_icon_id = get_option( 'site_icon' ); $site_icon_src = wp_get_attachment_image_src( $site_icon_id, 'full' ); // If CANNOT upload svg normally, it will add a custom inline option to force svg upload if requested. (in logo and favicon) $should_include_svg_inline_option = ! Uploads_Manager::are_unfiltered_uploads_enabled(); $this->start_controls_section( 'section_' . $this->get_id(), [ 'label' => $this->get_title(), 'tab' => $this->get_id(), ] ); $this->add_control( $this->get_id() . '_refresh_notice', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => sprintf( /* translators: 1: Link open tag, 2: Link open tag, 3: Link close tag. */ esc_html__( 'Changes will be reflected only after %1$s saving %3$s and %2$s reloading %3$s preview.', 'elementor' ), '<a href="javascript: $e.run( \'document/save/default\' )">', '<a href="javascript: $e.run( \'preview/reload\' )">', '</a>' ), ] ); $this->add_control( 'site_name', [ 'label' => esc_html__( 'Site Name', 'elementor' ), 'default' => get_option( 'blogname' ), 'placeholder' => esc_html__( 'Choose name', 'elementor' ), 'label_block' => true, 'export' => false, ] ); $this->add_control( 'site_description', [ 'label' => esc_html__( 'Site Description', 'elementor' ), 'default' => get_option( 'blogdescription' ), 'placeholder' => esc_html__( 'Choose description', 'elementor' ), 'label_block' => true, 'export' => false, ] ); $this->add_control( 'site_logo', [ 'label' => esc_html__( 'Site Logo', 'elementor' ), 'type' => Controls_Manager::MEDIA, 'should_include_svg_inline_option' => $should_include_svg_inline_option, 'default' => [ 'id' => $custom_logo_id, 'url' => $custom_logo_src ? $custom_logo_src[0] : '', ], 'description' => sprintf( /* translators: 1: Width number pixel, 2: Height number pixel. */ esc_html__( 'Suggested image dimensions: %1$s × %2$s pixels.', 'elementor' ), '350', '100' ), 'export' => false, ] ); $this->add_control( 'site_favicon', [ 'label' => esc_html__( 'Site Favicon', 'elementor' ), 'type' => Controls_Manager::MEDIA, 'should_include_svg_inline_option' => $should_include_svg_inline_option, 'default' => [ 'id' => $site_icon_id, 'url' => $site_icon_src ? $site_icon_src[0] : '', ], 'description' => esc_html__( 'Suggested favicon dimensions: 512 × 512 pixels.', 'elementor' ), 'export' => false, ] ); $this->end_controls_section(); } public function on_save( $data ) { if ( ! isset( $data['settings']['post_status'] ) || Document::STATUS_PUBLISH !== $data['settings']['post_status'] || // Should check for the current action to avoid infinite loop // when updating options like: "blogname" and "blogdescription". strpos( current_action(), 'update_option_' ) === 0 ) { return; } if ( isset( $data['settings']['site_name'] ) ) { update_option( 'blogname', $data['settings']['site_name'] ); } if ( isset( $data['settings']['site_description'] ) ) { update_option( 'blogdescription', $data['settings']['site_description'] ); } if ( isset( $data['settings']['site_logo'] ) ) { set_theme_mod( 'custom_logo', $data['settings']['site_logo']['id'] ); } if ( isset( $data['settings']['site_favicon'] ) ) { update_option( 'site_icon', $data['settings']['site_favicon']['id'] ); } } } editor/data/globals/controller.php 0000644 00000002023 14717626151 0013271 0 ustar 00 <?php namespace Elementor\Core\Editor\Data\Globals; use Elementor\Data\V2\Base\Controller as Controller_Base; use Elementor\Data\V2\Base\Endpoint; use Elementor\Plugin; class Controller extends Controller_Base { public function get_name() { return 'globals'; } public function register_endpoints() { $this->register_endpoint( new Endpoints\Colors( $this ) ); $this->register_endpoint( new Endpoints\Typography( $this ) ); } public function get_collection_params() { // Does not have 'get_items' args (OPTIONS). // Maybe TODO: try `$this->get_index_endpoint()->get_collection_params()`. return []; } public function get_permission_callback( $request ) { // Allow internal get global values. (e.g render global.css for a visitor) if ( 'GET' === $request->get_method() && Plugin::$instance->data_manager_v2->is_internal() ) { return true; } return current_user_can( 'edit_posts' ); } protected function register_index_endpoint() { $this->register_endpoint( new Endpoint\Index\AllChildren( $this ) ); } } editor/data/globals/endpoints/base.php 0000644 00000003200 14717626151 0014021 0 ustar 00 <?php namespace Elementor\Core\Editor\Data\Globals\Endpoints; use Elementor\Data\V2\Base\Endpoint; use Elementor\Data\V2\Base\Exceptions\Data_Exception; use Elementor\Data\V2\Base\Exceptions\Error_404; use Elementor\Plugin; abstract class Base extends Endpoint { protected function register() { parent::register(); $args = [ 'id_arg_type_regex' => '[\w]+', ]; $this->register_item_route( \WP_REST_Server::READABLE, $args ); $this->register_item_route( \WP_REST_Server::CREATABLE, $args ); $this->register_item_route( \WP_REST_Server::DELETABLE, $args ); } public function get_items( $request ) { return $this->get_kit_items(); } /** * @inheritDoc * @throws \Elementor\Data\V2\Base\Exceptions\Error_404 */ public function get_item( $id, $request ) { $items = $this->get_kit_items(); if ( ! isset( $items[ $id ] ) ) { throw new Error_404( esc_html__( 'The Global value you are trying to use is not available.', 'elementor' ), 'global_not_found' ); } return $items[ $id ]; } public function create_item( $id, $request ) { $item = $request->get_body_params(); if ( ! isset( $item['title'] ) ) { return new Data_Exception( esc_html__( 'Invalid title', 'elementor' ), 'invalid_title' ); } $kit = Plugin::$instance->kits_manager->get_active_kit(); $item['id'] = $id; $db_item = $this->convert_db_format( $item ); $kit->add_repeater_row( 'custom_' . $this->get_name(), $db_item ); return $item; } abstract protected function get_kit_items(); /** * @param array $item frontend format. * @return array backend format. */ abstract protected function convert_db_format( $item ); } editor/data/globals/endpoints/colors.php 0000644 00000002027 14717626151 0014416 0 ustar 00 <?php namespace Elementor\Core\Editor\Data\Globals\Endpoints; use Elementor\Plugin; class Colors extends Base { public function get_name() { return 'colors'; } public function get_format() { return 'globals/colors/{id}'; } protected function get_kit_items() { $result = []; $kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend(); $system_items = $kit->get_settings_for_display( 'system_colors' ); $custom_items = $kit->get_settings_for_display( 'custom_colors' ); if ( ! $system_items ) { $system_items = []; } if ( ! $custom_items ) { $custom_items = []; } $items = array_merge( $system_items, $custom_items ); foreach ( $items as $index => $item ) { $id = $item['_id']; $result[ $id ] = [ 'id' => $id, 'title' => $item['title'] ?? '', 'value' => $item['color'] ?? '', ]; } return $result; } protected function convert_db_format( $item ) { return [ '_id' => $item['id'], 'title' => $item['title'] ?? '', 'color' => $item['value'] ?? '', ]; } } editor/data/globals/endpoints/typography.php 0000644 00000003137 14717626151 0015326 0 ustar 00 <?php namespace Elementor\Core\Editor\Data\Globals\Endpoints; use Elementor\Plugin; class Typography extends Base { public function get_name() { return 'typography'; } public function get_format() { return 'globals/typography/{id}'; } protected function get_kit_items() { $result = []; $kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend(); // Use raw settings that doesn't have default values. $kit_raw_settings = $kit->get_data( 'settings' ); if ( isset( $kit_raw_settings['system_typography'] ) ) { $system_items = $kit_raw_settings['system_typography']; } else { // Get default items, but without empty defaults. $control = $kit->get_controls( 'system_typography' ); $system_items = $control['default']; } $custom_items = $kit->get_settings( 'custom_typography' ); if ( ! $custom_items ) { $custom_items = []; } $items = array_merge( $system_items, $custom_items ); foreach ( $items as $index => &$item ) { foreach ( $item as $setting => $value ) { $new_setting = str_replace( 'styles_', '', $setting, $count ); if ( $count ) { $item[ $new_setting ] = $value; unset( $item[ $setting ] ); } } $id = $item['_id']; $result[ $id ] = [ 'title' => $item['title'] ?? '', 'id' => $id, ]; unset( $item['_id'], $item['title'] ); $result[ $id ]['value'] = $item; } return $result; } protected function convert_db_format( $item ) { $db_format = [ '_id' => $item['id'], 'title' => $item['title'] ?? '', ]; $db_format = array_merge( $item['value'], $db_format ); return $db_format; } } editor/loader/editor-loader-interface.php 0000644 00000002162 14717626151 0014514 0 ustar 00 <?php namespace Elementor\Core\Editor\Loader; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Editor_Loader_Interface { /** * Init function purpose is to prepare some stuff that should be available for other methods * and register some hooks * * @return void */ public function init(); /** * Register all the scripts for the editor. * * @return void */ public function register_scripts(); /** * Enqueue all the scripts for the editor. * * @return void */ public function enqueue_scripts(); /** * Register all the styles for the editor. * * @return void */ public function register_styles(); /** * Enqueue all the styles for the editor. * * @return void */ public function enqueue_styles(); /** * Print the actual initial html for the editor, later on, the scripts takeover and renders the JS apps. * * @return void */ public function print_root_template(); /** * Register additional templates that are required for the marionette part of the application * * @return void */ public function register_additional_templates(); } editor/loader/v1/editor-v1-loader.php 0000644 00000004073 14717626151 0013433 0 ustar 00 <?php namespace Elementor\Core\Editor\Loader\V1; use Elementor\Core\Editor\Loader\Common\Editor_Common_Scripts_Settings; use Elementor\Core\Editor\Loader\Editor_Base_Loader; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Editor_V1_Loader extends Editor_Base_Loader { /** * @return void */ public function init() { // Loading UI and Icons v2 scrips for the use of new features that should live in V1. $packages_to_register = [ 'ui', 'icons', 'query' ]; foreach ( $packages_to_register as $package ) { $this->assets_config_provider->load( $package ); } } /** * @return void */ public function register_scripts() { parent::register_scripts(); $assets_url = $this->config->get( 'assets_url' ); $min_suffix = $this->config->get( 'min_suffix' ); foreach ( $this->assets_config_provider->all() as $package => $config ) { wp_register_script( $config['handle'], "{$assets_url}js/packages/{$package}/{$package}{$min_suffix}.js", $config['deps'], ELEMENTOR_VERSION, true ); } wp_register_script( 'elementor-editor-loader-v1', "{$assets_url}js/editor-loader-v1{$min_suffix}.js", [ 'elementor-editor' ], ELEMENTOR_VERSION, true ); } /** * @return void */ public function enqueue_scripts() { parent::enqueue_scripts(); // Must be last. wp_enqueue_script( 'elementor-editor-loader-v1' ); Utils::print_js_config( 'elementor-editor', 'ElementorConfig', Editor_Common_Scripts_Settings::get() ); } /** * @return void */ public function print_root_template() { // Exposing the path for the view part to render the body of the editor template. $body_file_path = __DIR__ . '/templates/editor-body-v1.view.php'; include ELEMENTOR_PATH . 'includes/editor-templates/editor-wrapper.php'; } /** * @return void */ public function register_additional_templates() { parent::register_additional_templates(); Plugin::$instance->common->add_template( ELEMENTOR_PATH . 'includes/editor-templates/responsive-bar.php' ); } } editor/loader/v1/templates/editor-body-v1.view.php 0000644 00000003102 14717626151 0016061 0 ustar 00 <?php namespace Elementor\Core\Editor\Loader\V1\Templates; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } $notice = Plugin::$instance->editor->notice_bar->get_notice(); ?> <div id="elementor-loading"> <div class="elementor-loader-wrapper"> <div class="elementor-loader" aria-hidden="true"> <div class="elementor-loader-boxes"> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> </div> </div> <div class="elementor-loading-title"><?php echo esc_html__( 'Loading', 'elementor' ); ?></div> </div> </div> <h1 class="elementor-screen-only"><?php echo sprintf( esc_html__( 'Edit "%s" with Elementor', 'elementor' ), esc_html( get_the_title() ) ); ?></h1> <div id="elementor-editor-wrapper"> <aside id="elementor-panel" class="elementor-panel" aria-labelledby="elementor-panel-header-title"></aside> <main id="elementor-preview" aria-label="<?php echo esc_attr__( 'Preview', 'elementor' ); ?>"> <div id="elementor-responsive-bar"></div> <div id="elementor-preview-responsive-wrapper" class="elementor-device-desktop elementor-device-rotate-portrait"> <div id="elementor-preview-loading"> <i class="eicon-loading eicon-animation-spin" aria-hidden="true"></i> </div> <?php if ( $notice ) { $notice->render(); } // IFrame will be created here by the Javascript later. ?> </div> </main> <aside id="elementor-navigator" aria-labelledby="elementor-navigator__header__title"></aside> </div> editor/loader/v1/js/editor-loader-v1.js 0000644 00000000032 14717626151 0013663 0 ustar 00 window.elementor.start(); editor/loader/editor-base-loader.php 0000644 00000013200 14717626151 0013461 0 ustar 00 <?php namespace Elementor\Core\Editor\Loader; use Elementor\Core\Utils\Assets_Config_Provider; use Elementor\Core\Utils\Collection; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Editor_Base_Loader implements Editor_Loader_Interface { /** * @var Collection */ protected $config; /** * @var Assets_Config_Provider */ protected $assets_config_provider; /** * @param Collection $config * @param Assets_Config_Provider $assets_config_provider\ */ public function __construct( Collection $config, Assets_Config_Provider $assets_config_provider ) { $this->config = $config; $this->assets_config_provider = $assets_config_provider; } /** * @return void */ public function register_scripts() { $assets_url = $this->config->get( 'assets_url' ); $min_suffix = $this->config->get( 'min_suffix' ); wp_register_script( 'elementor-editor-modules', "{$assets_url}js/editor-modules{$min_suffix}.js", [ 'elementor-common-modules' ], ELEMENTOR_VERSION, true ); wp_register_script( 'elementor-editor-document', "{$assets_url}js/editor-document{$min_suffix}.js", [ 'elementor-common-modules' ], ELEMENTOR_VERSION, true ); wp_register_script( 'perfect-scrollbar', "{$assets_url}lib/perfect-scrollbar/js/perfect-scrollbar{$min_suffix}.js", [], '1.4.0', true ); wp_register_script( 'jquery-easing', "{$assets_url}lib/jquery-easing/jquery-easing{$min_suffix}.js", [ 'jquery' ], '1.3.2', true ); wp_register_script( 'nprogress', "{$assets_url}lib/nprogress/nprogress{$min_suffix}.js", [], '0.2.0', true ); wp_register_script( 'tipsy', "{$assets_url}lib/tipsy/tipsy{$min_suffix}.js", [ 'jquery' ], '1.0.0', true ); wp_register_script( 'jquery-elementor-select2', "{$assets_url}lib/e-select2/js/e-select2.full{$min_suffix}.js", [ 'jquery' ], '4.0.6-rc.1', true ); wp_register_script( 'flatpickr', "{$assets_url}lib/flatpickr/flatpickr{$min_suffix}.js", [ 'jquery' ], '4.6.13', true ); wp_register_script( 'ace', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.5/ace.js', [], '1.2.5', true ); wp_register_script( 'ace-language-tools', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.5/ext-language_tools.js', [ 'ace' ], '1.2.5', true ); wp_register_script( 'jquery-hover-intent', "{$assets_url}lib/jquery-hover-intent/jquery-hover-intent{$min_suffix}.js", [], '1.0.0', true ); wp_register_script( 'nouislider', "{$assets_url}lib/nouislider/nouislider{$min_suffix}.js", [], '13.0.0', true ); wp_register_script( 'pickr', "{$assets_url}lib/pickr/pickr.min.js", [], '1.8.2', true ); wp_register_script( 'elementor-editor', "{$assets_url}js/editor{$min_suffix}.js", [ 'elementor-common', 'elementor-editor-modules', 'elementor-editor-document', 'wp-auth-check', 'jquery-ui-sortable', 'jquery-ui-resizable', 'perfect-scrollbar', 'nprogress', 'tipsy', 'imagesloaded', 'heartbeat', 'jquery-elementor-select2', 'flatpickr', 'ace', 'ace-language-tools', 'jquery-hover-intent', 'nouislider', 'pickr', 'react', 'react-dom', ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-editor', 'elementor' ); wp_register_script( 'elementor-responsive-bar', "{$assets_url}js/responsive-bar{$min_suffix}.js", [ 'elementor-editor' ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-responsive-bar', 'elementor' ); } /** * @return void */ public function enqueue_scripts() { wp_enqueue_script( 'elementor-responsive-bar' ); } /** * @return void */ public function register_styles() { $assets_url = $this->config->get( 'assets_url' ); $min_suffix = $this->config->get( 'min_suffix' ); $direction_suffix = $this->config->get( 'direction_suffix' ); wp_register_style( 'font-awesome', "{$assets_url}lib/font-awesome/css/font-awesome{$min_suffix}.css", [], '4.7.0' ); wp_register_style( 'elementor-select2', "{$assets_url}lib/e-select2/css/e-select2{$min_suffix}.css", [], '4.0.6-rc.1' ); wp_register_style( 'google-font-roboto', 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700', [], ELEMENTOR_VERSION ); wp_register_style( 'flatpickr', "{$assets_url}lib/flatpickr/flatpickr{$min_suffix}.css", [], '4.6.13' ); wp_register_style( 'pickr', "{$assets_url}lib/pickr/themes/monolith.min.css", [], '1.8.2' ); wp_register_style( 'elementor-editor', "{$assets_url}css/editor{$direction_suffix}{$min_suffix}.css", [ 'elementor-common', 'elementor-select2', 'elementor-icons', 'wp-auth-check', 'google-font-roboto', 'flatpickr', 'pickr', ], ELEMENTOR_VERSION ); wp_register_style( 'elementor-responsive-bar', "{$assets_url}css/responsive-bar{$min_suffix}.css", [], ELEMENTOR_VERSION ); } /** * @return void */ public function enqueue_styles() { wp_enqueue_style( 'elementor-editor' ); wp_enqueue_style( 'elementor-responsive-bar' ); } /** * @return void */ public function register_additional_templates() { $templates = [ 'global', 'panel', 'panel-elements', 'repeater', 'templates', 'navigator', 'hotkeys', 'responsive-bar', ]; $templates = apply_filters( 'elementor/editor/templates', $templates ); foreach ( $templates as $template ) { Plugin::$instance->common->add_template( ELEMENTOR_PATH . "includes/editor-templates/{$template}.php" ); } } } editor/loader/editor-loader-factory.php 0000644 00000002450 14717626151 0014223 0 ustar 00 <?php namespace Elementor\Core\Editor\Loader; use Elementor\Core\Editor\Editor; use Elementor\Core\Editor\Loader\V1\Editor_V1_Loader; use Elementor\Core\Editor\Loader\V2\Editor_V2_Loader; use Elementor\Core\Utils\Assets_Config_Provider; use Elementor\Core\Utils\Collection; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Editor_Loader_Factory { /** * @return Editor_Loader_Interface */ public static function create() { $config = new Collection( [ 'assets_url' => ELEMENTOR_ASSETS_URL, 'min_suffix' => ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min', 'direction_suffix' => is_rtl() ? '-rtl' : '', ] ); $assets_config_provider = ( new Assets_Config_Provider() ) ->set_path_resolver( function ( $name ) { return ELEMENTOR_ASSETS_PATH . "js/packages/{$name}/{$name}.asset.php"; } ); if ( static::should_use_v2_loader() ) { return new Editor_V2_Loader( $config, $assets_config_provider ); } return new Editor_V1_Loader( $config, $assets_config_provider ); } /** * If there are v2 packages enqueued, we should use the V2 loader. * * @return bool */ private static function should_use_v2_loader() { return ! empty( Editor_V2_Loader::get_packages_to_enqueue() ); } } editor/loader/v2/scss/editor-v2-app-bar-overrides.scss 0000644 00000002142 14717626151 0016643 0 ustar 00 /** * Here should be only styles that related to the Editor v1, and should be overridden when using the Editor v2. */ body { --editor-v2-top-bar-height: 48px; } #elementor-editor-wrapper { height: calc(100vh - var(--editor-v2-top-bar-height)); } body.elementor-navigator-docked #elementor-navigator { height: calc(100% - var(--editor-v2-top-bar-height)); top: var(--editor-v2-top-bar-height); } .elementor-panel #elementor-panel-header-menu-button, .elementor-panel #elementor-panel-header-add-button, .elementor-panel #elementor-panel-footer { display: none; } .elementor-panel #elementor-panel-header { font-weight: 700; background-color: var( --e-a-bg-default ); color: var( --e-a-color-txt-accent ); border-block-end: var( --e-a-border ); height: 48px; } // Make the MCE full-screen work properly with the top bar. .elementor-control-type-wysiwyg .mce-fullscreen { inset: var(--editor-v2-top-bar-height) 0 0 0; & > .mce-container-body { display: flex; flex-direction: column; height: 100%; & > .mce-edit-area { flex-grow: 1; & > iframe { height: 100% !important; } } } } editor/loader/v2/templates/editor-body-v2.view.php 0000644 00000003160 14717626151 0016067 0 ustar 00 <?php namespace Elementor\Core\Editor\Loader\V2\Templates; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } $notice = Plugin::$instance->editor->notice_bar->get_notice(); ?> <div id="elementor-loading"> <div class="elementor-loader-wrapper"> <div class="elementor-loader" aria-hidden="true"> <div class="elementor-loader-boxes"> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> </div> </div> <div class="elementor-loading-title"><?php echo esc_html__( 'Loading', 'elementor' ); ?></div> </div> </div> <h1 class="elementor-screen-only"><?php echo sprintf( esc_html__( 'Edit "%s" with Elementor', 'elementor' ), esc_html( get_the_title() ) ); ?></h1> <div id="elementor-editor-wrapper-v2"></div> <div id="elementor-editor-wrapper"> <aside id="elementor-panel" class="elementor-panel" aria-labelledby="elementor-panel-header-title"></aside> <main id="elementor-preview" aria-label="<?php echo esc_attr__( 'Preview', 'elementor' ); ?>"> <div id="elementor-responsive-bar"></div> <div id="elementor-preview-responsive-wrapper" class="elementor-device-desktop elementor-device-rotate-portrait"> <div id="elementor-preview-loading"> <i class="eicon-loading eicon-animation-spin" aria-hidden="true"></i> </div> <?php if ( $notice ) { $notice->render(); } // IFrame will be created here by the Javascript later. ?> </div> </main> <aside id="elementor-navigator" aria-labelledby="elementor-navigator__header__title"></aside> </div> editor/loader/v2/js/editor-environment-v2.js 0000644 00000000251 14717626151 0014766 0 ustar 00 if ( ! window.elementorV2?.env ) { throw new Error( 'The "@elementor/env" package was not loaded.' ); } window.elementorV2.env.initEnv( window.elementorEditorV2Env ); editor/loader/v2/js/editor-loader-v2.js 0000644 00000000625 14717626151 0013675 0 ustar 00 window.__elementorEditorV1LoadingPromise = new Promise( ( resolve ) => { window.addEventListener( 'elementor/init', () => { resolve(); }, { once: true } ); } ); window.elementor.start(); if ( ! window.elementorV2?.editor ) { throw new Error( 'The "@elementor/editor" package was not loaded.' ); } window.elementorV2 .editor .init( document.getElementById( 'elementor-editor-wrapper-v2' ), ); editor/loader/v2/editor-v2-loader.php 0000644 00000011661 14717626151 0013436 0 ustar 00 <?php namespace Elementor\Core\Editor\Loader\V2; use Elementor\Core\Editor\Loader\Common\Editor_Common_Scripts_Settings; use Elementor\Core\Editor\Loader\Editor_Base_Loader; use Elementor\Core\Utils\Assets_Translation_Loader; use Elementor\Core\Utils\Collection; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Editor_V2_Loader extends Editor_Base_Loader { const APP_PACKAGE = 'editor'; const ENV_PACKAGE = 'env'; /** * Packages that should only be registered, unless some other asset depends on them. */ const LIBS = [ 'editor-responsive', 'editor-v1-adapters', self::ENV_PACKAGE, 'icons', 'locations', 'menus', 'query', 'schema', 'store', 'ui', 'utils', 'wp-media', ]; /** * Additional dependencies for packages that rely on global variables, rather than * an explicit npm dependency (e.g. `window.elementor`, `window.wp`, etc.). */ const ADDITIONAL_DEPS = [ 'editor-v1-adapters' => [ 'elementor-web-cli', ], 'wp-media' => [ 'media-models', ], ]; /** * @return void */ public function init() { $packages = array_merge( $this->get_packages_to_enqueue(), self::LIBS ); $packages_with_app = array_merge( $packages, [ self::APP_PACKAGE ] ); foreach ( $packages_with_app as $package ) { $this->assets_config_provider->load( $package ); } do_action( 'elementor/editor/v2/init' ); } /** * @return void */ public function register_scripts() { parent::register_scripts(); $assets_url = $this->config->get( 'assets_url' ); $min_suffix = $this->config->get( 'min_suffix' ); foreach ( $this->assets_config_provider->all() as $package => $config ) { if ( self::ENV_PACKAGE === $package ) { wp_register_script( 'elementor-editor-environment-v2', "{$assets_url}js/editor-environment-v2{$min_suffix}.js", [ $config['handle'] ], ELEMENTOR_VERSION, true ); } if ( static::APP_PACKAGE === $package ) { wp_register_script( 'elementor-editor-loader-v2', "{$assets_url}js/editor-loader-v2{$min_suffix}.js", [ 'elementor-editor', $config['handle'] ], ELEMENTOR_VERSION, true ); } $additional_deps = self::ADDITIONAL_DEPS[ $package ] ?? []; $deps = array_merge( $config['deps'], $additional_deps ); wp_register_script( $config['handle'], "{$assets_url}js/packages/{$package}/{$package}{$min_suffix}.js", $deps, ELEMENTOR_VERSION, true ); } $packages_handles = $this->assets_config_provider->pluck( 'handle' )->all(); Assets_Translation_Loader::for_handles( $packages_handles, 'elementor' ); do_action( 'elementor/editor/v2/scripts/register' ); } /** * @return void */ public function enqueue_scripts() { do_action( 'elementor/editor/v2/scripts/enqueue/before' ); parent::enqueue_scripts(); wp_enqueue_script( 'elementor-editor-environment-v2' ); $env_config = $this->assets_config_provider->get( self::ENV_PACKAGE ); if ( $env_config ) { $client_env = apply_filters( 'elementor/editor/v2/scripts/env', [] ); Utils::print_js_config( $env_config['handle'], 'elementorEditorV2Env', $client_env ); } $packages_with_app = array_merge( $this->get_packages_to_enqueue(), [ self::APP_PACKAGE ] ); foreach ( $this->assets_config_provider->only( $packages_with_app ) as $config ) { wp_enqueue_script( $config['handle'] ); } do_action( 'elementor/editor/v2/scripts/enqueue' ); Utils::print_js_config( 'elementor-editor', 'ElementorConfig', Editor_Common_Scripts_Settings::get() ); // Must be last. wp_enqueue_script( 'elementor-editor-loader-v2' ); do_action( 'elementor/editor/v2/scripts/enqueue/after' ); } /** * @return void */ public function register_styles() { parent::register_styles(); $assets_url = $this->config->get( 'assets_url' ); $min_suffix = $this->config->get( 'min_suffix' ); foreach ( $this->get_styles() as $style ) { wp_register_style( "elementor-{$style}", "{$assets_url}css/{$style}{$min_suffix}.css", [ 'elementor-editor' ], ELEMENTOR_VERSION ); } do_action( 'elementor/editor/v2/styles/register' ); } /** * @return void */ public function enqueue_styles() { parent::enqueue_styles(); foreach ( $this->get_styles() as $style ) { wp_enqueue_style( "elementor-{$style}" ); } do_action( 'elementor/editor/v2/styles/enqueue' ); } /** * @return void */ public function print_root_template() { // Exposing the path for the view part to render the body of the editor template. $body_file_path = __DIR__ . '/templates/editor-body-v2.view.php'; include ELEMENTOR_PATH . 'includes/editor-templates/editor-wrapper.php'; } public static function get_packages_to_enqueue() : array { return apply_filters( 'elementor/editor/v2/packages', [] ); } private function get_styles() : array { $styles = apply_filters( 'elementor/editor/v2/styles', [] ); return Collection::make( $styles ) ->unique() ->all(); } } editor/loader/common/editor-common-scripts-settings.php 0000644 00000016545 14717626151 0017425 0 ustar 00 <?php namespace Elementor\Core\Editor\Loader\Common; use Elementor\Api; use Elementor\Core\Debug\Loading_Inspection_Manager; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Group_Control_Typography; use Elementor\Icons_Manager; use Elementor\Modules\Apps\Module as AppsModule; use Elementor\Modules\EditorEvents\Module as EditorEventsModule; use Elementor\Modules\Home\Module as Home_Module; use Elementor\Plugin; use Elementor\Settings; use Elementor\Shapes; use Elementor\Tools; use Elementor\User; use Elementor\Utils; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Editor_Common_Scripts_Settings { public static function get() { $settings = SettingsManager::get_settings_managers_config(); // Moved to document since 2.9.0. unset( $settings['page'] ); $document = Plugin::$instance->documents->get_doc_or_auto_save( Plugin::$instance->editor->get_post_id() ); $kits_manager = Plugin::$instance->kits_manager; $page_title_selector = $kits_manager->get_current_settings( 'page_title_selector' ); $page_title_selector .= ', .elementor-page-title .elementor-heading-title'; $client_env = [ 'initial_document' => $document->get_config(), 'version' => ELEMENTOR_VERSION, 'home_url' => home_url(), 'admin_settings_url' => admin_url( 'admin.php?page=' . Home_Module::get_elementor_settings_page_id() ), 'admin_tools_url' => admin_url( 'admin.php?page=' . Tools::PAGE_ID ), 'admin_apps_url' => admin_url( 'admin.php?page=' . AppsModule::PAGE_ID ), 'autosave_interval' => AUTOSAVE_INTERVAL, 'tabs' => Plugin::$instance->controls_manager->get_tabs(), 'controls' => Plugin::$instance->controls_manager->get_controls_data(), 'elements' => Plugin::$instance->elements_manager->get_element_types_config(), 'globals' => [ 'defaults_enabled' => [ 'colors' => $kits_manager->is_custom_colors_enabled(), 'typography' => $kits_manager->is_custom_typography_enabled(), ], ], 'icons' => [ 'libraries' => Icons_Manager::get_icon_manager_tabs_config(), 'goProURL' => 'https://go.elementor.com/go-pro-icon-library/', ], 'fa4_to_fa5_mapping_url' => ELEMENTOR_ASSETS_URL . 'lib/font-awesome/migration/mapping.js', 'settings' => $settings, 'wp_editor' => static::get_wp_editor_config(), 'settings_page_link' => Settings::get_url(), 'tools_page_link' => Tools::get_url(), 'tools_page_nonce' => wp_create_nonce( 'tools-page-from-editor' ), 'elementor_site' => 'https://go.elementor.com/about-elementor/', 'docs_elementor_site' => 'https://go.elementor.com/docs/', 'help_the_content_url' => 'https://go.elementor.com/the-content-missing/', 'help_flexbox_bc_url' => 'https://go.elementor.com/flexbox-layout-bc/', 'elementPromotionURL' => 'https://go.elementor.com/go-pro-%s', 'dynamicPromotionURL' => 'https://go.elementor.com/go-pro-dynamic-tag', 'additional_shapes' => Shapes::get_additional_shapes_for_config(), 'user' => [ 'restrictions' => Plugin::$instance->role_manager->get_user_restrictions_array(), 'is_administrator' => current_user_can( 'manage_options' ), 'introduction' => User::get_introduction_meta(), 'dismissed_editor_notices' => User::get_dismissed_editor_notices(), 'locale' => get_user_locale(), ], 'preview' => [ 'help_preview_error_url' => 'https://go.elementor.com/preview-not-loaded/', 'help_preview_http_error_url' => 'https://go.elementor.com/preview-not-loaded/#permissions', 'help_preview_http_error_500_url' => 'https://go.elementor.com/500-error/', 'debug_data' => Loading_Inspection_Manager::instance()->run_inspections(), ], 'locale' => get_locale(), 'rich_editing_enabled' => filter_var( get_user_meta( get_current_user_id(), 'rich_editing', true ), FILTER_VALIDATE_BOOLEAN ), 'page_title_selector' => $page_title_selector, 'tinymceHasCustomConfig' => class_exists( 'Tinymce_Advanced' ) || class_exists( 'Advanced_Editor_Tools' ), 'inlineEditing' => Plugin::$instance->widgets_manager->get_inline_editing_config(), 'dynamicTags' => Plugin::$instance->dynamic_tags->get_config(), 'ui' => [ 'defaultGenericFonts' => $kits_manager->get_current_settings( 'default_generic_fonts' ), ], // Empty array for BC to avoid errors. 'i18n' => [], // 'responsive' contains the custom breakpoints config introduced in Elementor v3.2.0 'responsive' => [ 'breakpoints' => Plugin::$instance->breakpoints->get_breakpoints_config(), 'icons_map' => Plugin::$instance->breakpoints->get_responsive_icons_classes_map(), ], 'promotion' => [ 'elements' => Plugin::$instance->editor->promotion->get_elements_promotion(), ], 'editor_events' => EditorEventsModule::get_editor_events_config(), 'promotions' => [ 'notes' => Filtered_Promotions_Manager::get_filtered_promotion_data( [ 'upgrade_url' => 'https://go.elementor.com/go-pro-notes/' ], 'elementor/panel/notes/custom_promotion', 'upgrade_url' ), ], 'fontVariableRanges' => Group_Control_Typography::get_font_variable_ranges(), ]; if ( ! Utils::has_pro() && current_user_can( 'manage_options' ) ) { $client_env['promotionWidgets'] = Api::get_promotion_widgets(); } if ( Plugin::$instance->experiments->is_feature_active( 'container' ) ) { $client_env['elementsPresets'] = Plugin::$instance->editor->get_elements_presets(); } static::bc_move_document_filters(); /** * Localize editor settings. * * Filters the editor localized settings. * * @since 1.0.0 * * @param array $client_env Editor configuration. * @param int $post_id The ID of the current post being edited. */ return apply_filters( 'elementor/editor/localize_settings', $client_env ); } private static function bc_move_document_filters() { global $wp_filter; $old_tag = 'elementor/editor/localize_settings'; $new_tag = 'elementor/document/config'; if ( ! has_filter( $old_tag ) ) { return; } foreach ( $wp_filter[ $old_tag ] as $priority => $filters ) { foreach ( $filters as $filter_id => $filter_args ) { if ( 2 === $filter_args['accepted_args'] ) { remove_filter( $old_tag, $filter_id, $priority ); add_filter( $new_tag, $filter_args['function'], $priority, 2 ); } } } } /** * Get WordPress editor config. * * Config the default WordPress editor with custom settings for Elementor use. * * @since 1.9.0 * @access private */ private static function get_wp_editor_config() { // Remove all TinyMCE plugins. remove_all_filters( 'mce_buttons', 10 ); remove_all_filters( 'mce_external_plugins', 10 ); if ( ! class_exists( '\_WP_Editors', false ) ) { require ABSPATH . WPINC . '/class-wp-editor.php'; } // WordPress 4.8 and higher if ( method_exists( '\_WP_Editors', 'print_tinymce_scripts' ) ) { \_WP_Editors::print_default_editor_scripts(); \_WP_Editors::print_tinymce_scripts(); } ob_start(); wp_editor( '%%EDITORCONTENT%%', 'elementorwpeditor', [ 'editor_class' => 'elementor-wp-editor', 'editor_height' => 250, 'drag_drop_upload' => true, ] ); $config = ob_get_clean(); // Don't call \_WP_Editors methods again remove_action( 'admin_print_footer_scripts', [ '_WP_Editors', 'editor_js' ], 50 ); remove_action( 'admin_print_footer_scripts', [ '_WP_Editors', 'print_default_editor_scripts' ], 45 ); \_WP_Editors::editor_js(); return $config; } } editor/editor.php 0000644 00000037267 14717626151 0010062 0 ustar 00 <?php namespace Elementor\Core\Editor; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Core\Common\Modules\Ajax\Module; use Elementor\Core\Debug\Loading_Inspection_Manager; use Elementor\Core\Editor\Loader\Editor_Loader_Factory; use Elementor\Core\Editor\Loader\Editor_Loader_Interface; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; use Elementor\Core\Editor\Data; use Elementor\Modules\EditorAppBar\Module as App_Bar_Module; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor editor. * * Elementor editor handler class is responsible for initializing Elementor * editor and register all the actions needed to display the editor. * * @since 1.0.0 */ class Editor { /** * User capability required to access Elementor editor. */ const EDITING_CAPABILITY = 'edit_posts'; /** * The const is deprecated, it remains here for backward compatibility. * * @deprecated Use App_Bar_Module::EXPERIMENT_NAME instead */ const EDITOR_V2_EXPERIMENT_NAME = App_Bar_Module::EXPERIMENT_NAME; /** * Post ID. * * Holds the ID of the current post being edited. * * @since 1.0.0 * @access private * * @var int Post ID. */ private $post_id; /** * Whether the edit mode is active. * * Used to determine whether we are in edit mode. * * @since 1.0.0 * @access private * * @var bool Whether the edit mode is active. */ private $is_edit_mode; /** * @var Notice_Bar */ public $notice_bar; /** * @var Promotion */ public $promotion; /** * @var Editor_Loader_Interface */ private $loader; /** * Init. * * Initialize Elementor editor. Registers all needed actions to run Elementor, * removes conflicting actions etc. * * Fired by `admin_action_elementor` action. * * @since 1.0.0 * @access public * * @param bool $die Optional. Whether to die at the end. Default is `true`. */ public function init( $die = true ) { if ( empty( $_REQUEST['post'] ) ) { return; } $this->set_post_id( absint( $_REQUEST['post'] ) ); if ( ! $this->is_edit_mode( $this->post_id ) ) { return; } // BC: From 2.9.0, the editor shouldn't handle the global post / current document. // Use requested id and not the global in order to avoid conflicts with plugins that changes the global post. query_posts( [ 'p' => $this->post_id, 'post_type' => get_post_type( $this->post_id ), ] ); Plugin::$instance->db->switch_to_post( $this->post_id ); $document = Plugin::$instance->documents->get( $this->post_id ); Plugin::$instance->documents->switch_to_document( $document ); // Change mode to Builder $document->set_is_built_with_elementor( true ); // End BC. Loading_Inspection_Manager::instance()->register_inspections(); // Send MIME Type header like WP admin-header. @header( 'Content-Type: ' . get_option( 'html_type' ) . '; charset=' . get_option( 'blog_charset' ) ); add_filter( 'show_admin_bar', '__return_false' ); // Remove all WordPress actions remove_all_actions( 'wp_head' ); remove_all_actions( 'wp_print_styles' ); remove_all_actions( 'wp_print_head_scripts' ); remove_all_actions( 'wp_footer' ); // Handle `wp_head` add_action( 'wp_head', 'wp_enqueue_scripts', 1 ); add_action( 'wp_head', 'wp_print_styles', 8 ); add_action( 'wp_head', 'wp_print_head_scripts', 9 ); add_action( 'wp_head', 'wp_site_icon' ); add_action( 'wp_head', [ $this, 'editor_head_trigger' ], 30 ); // Handle `wp_footer` add_action( 'wp_footer', 'wp_print_footer_scripts', 20 ); add_action( 'wp_footer', 'wp_auth_check_html', 30 ); add_action( 'wp_footer', [ $this, 'wp_footer' ] ); // Handle `wp_enqueue_scripts` remove_all_actions( 'wp_enqueue_scripts' ); // Also remove all scripts hooked into after_wp_tiny_mce. remove_all_actions( 'after_wp_tiny_mce' ); add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ], 999999 ); add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_styles' ], 999999 ); // Setup default heartbeat options add_filter( 'heartbeat_settings', function( $settings ) { $settings['interval'] = 15; return $settings; } ); // Tell to WP Cache plugins do not cache this request. Utils::do_not_cache(); do_action( 'elementor/editor/init' ); $this->get_loader()->print_root_template(); // From the action it's an empty string, from tests its `false` if ( false !== $die ) { die; } } /** * Retrieve post ID. * * Get the ID of the current post. * * @since 1.8.0 * @access public * * @return int Post ID. */ public function get_post_id() { return $this->post_id; } /** * Redirect to new URL. * * Used as a fallback function for the old URL structure of Elementor page * edit URL. * * Fired by `template_redirect` action. * * @since 1.6.0 * @access public */ public function redirect_to_new_url() { if ( ! isset( $_GET['elementor'] ) ) { return; } $document = Plugin::$instance->documents->get( get_the_ID() ); if ( ! $document ) { wp_die( esc_html__( 'Document not found.', 'elementor' ) ); } if ( ! $document->is_editable_by_current_user() || ! $document->is_built_with_elementor() ) { return; } wp_safe_redirect( $document->get_edit_url() ); die; } /** * Whether the edit mode is active. * * Used to determine whether we are in the edit mode. * * @since 1.0.0 * @access public * * @param int $post_id Optional. Post ID. Default is `null`, the current * post ID. * * @return bool Whether the edit mode is active. */ public function is_edit_mode( $post_id = null ) { if ( null !== $this->is_edit_mode ) { return $this->is_edit_mode; } if ( empty( $post_id ) ) { $post_id = $this->post_id; } $document = Plugin::$instance->documents->get( $post_id ); if ( ! $document || ! $document->is_editable_by_current_user() ) { return false; } /** @var Module ajax */ $ajax_data = Plugin::$instance->common->get_component( 'ajax' )->get_current_action_data(); if ( ! empty( $ajax_data ) && 'get_document_config' === $ajax_data['action'] ) { return true; } // Ajax request as Editor mode $actions = [ 'elementor', // Templates 'elementor_get_templates', 'elementor_save_template', 'elementor_get_template', 'elementor_delete_template', 'elementor_import_template', 'elementor_library_direct_actions', ]; if ( isset( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], $actions ) ) { return true; } return false; } /** * Lock post. * * Mark the post as currently being edited by the current user. * * @since 1.0.0 * @access public * * @param int $post_id The ID of the post being edited. */ public function lock_post( $post_id ) { if ( ! function_exists( 'wp_set_post_lock' ) ) { require_once ABSPATH . 'wp-admin/includes/post.php'; } wp_set_post_lock( $post_id ); } /** * Get locked user. * * Check what user is currently editing the post. * * @since 1.0.0 * @access public * * @param int $post_id The ID of the post being edited. * * @return \WP_User|false User information or false if the post is not locked. */ public function get_locked_user( $post_id ) { if ( ! function_exists( 'wp_check_post_lock' ) ) { require_once ABSPATH . 'wp-admin/includes/post.php'; } $locked_user = wp_check_post_lock( $post_id ); if ( ! $locked_user ) { return false; } return get_user_by( 'id', $locked_user ); } /** * NOTICE: This method not in use, it's here for backward compatibility. * * Print Editor Template. * * Include the wrapper template of the editor. * * @since 2.2.0 * @access public */ public function print_editor_template() { include ELEMENTOR_PATH . 'includes/editor-templates/editor-wrapper.php'; } /** * Enqueue scripts. * * Registers all the editor scripts and enqueues them. * * @since 1.0.0 * @access public */ public function enqueue_scripts() { remove_action( 'wp_enqueue_scripts', [ $this, __FUNCTION__ ], 999999 ); global $wp_styles, $wp_scripts; // Reset global variable $wp_styles = new \WP_Styles(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $wp_scripts = new \WP_Scripts(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $this->get_loader()->register_scripts(); /** * Before editor enqueue scripts. * * Fires before Elementor editor scripts are enqueued. * * @since 1.0.0 */ do_action( 'elementor/editor/before_enqueue_scripts' ); // Tweak for WP Admin menu icons wp_print_styles( 'editor-buttons' ); $this->get_loader()->enqueue_scripts(); Plugin::$instance->controls_manager->enqueue_control_scripts(); /** * After editor enqueue scripts. * * Fires after Elementor editor scripts are enqueued. * * @since 1.0.0 */ do_action( 'elementor/editor/after_enqueue_scripts' ); } /** * Enqueue styles. * * Registers all the editor styles and enqueues them. * * @since 1.0.0 * @access public */ public function enqueue_styles() { /** * Before editor enqueue styles. * * Fires before Elementor editor styles are enqueued. * * @since 1.0.0 */ do_action( 'elementor/editor/before_enqueue_styles' ); $this->get_loader()->register_styles(); $this->get_loader()->enqueue_styles(); $this->enqueue_theme_ui_styles(); $breakpoints = Plugin::$instance->breakpoints->get_breakpoints(); // The two breakpoints under 'tablet' need to be checked for values. if ( $breakpoints[ Breakpoints_Manager::BREAKPOINT_KEY_MOBILE ]->is_custom() || $breakpoints[ Breakpoints_Manager::BREAKPOINT_KEY_MOBILE_EXTRA ]->is_enabled() ) { wp_add_inline_style( 'elementor-editor', '.elementor-device-tablet #elementor-preview-responsive-wrapper { width: ' . Plugin::$instance->breakpoints->get_device_min_breakpoint( Breakpoints_Manager::BREAKPOINT_KEY_TABLET ) . 'px; }' ); } /** * After editor enqueue styles. * * Fires after Elementor editor styles are enqueued. * * @since 1.0.0 */ do_action( 'elementor/editor/after_enqueue_styles' ); } private function enqueue_theme_ui_styles() { $ui_theme_selected = SettingsManager::get_settings_managers( 'editorPreferences' )->get_model()->get_settings( 'ui_theme' ); $ui_themes = [ 'light', 'dark', ]; if ( 'auto' === $ui_theme_selected || ! in_array( $ui_theme_selected, $ui_themes, true ) ) { $ui_light_theme_media_queries = '(prefers-color-scheme: light)'; $ui_dark_theme_media_queries = '(prefers-color-scheme: dark)'; } else { $ui_light_theme_media_queries = 'none'; $ui_dark_theme_media_queries = 'none'; if ( 'light' === $ui_theme_selected ) { $ui_light_theme_media_queries = 'all'; } elseif ( 'dark' === $ui_theme_selected ) { $ui_dark_theme_media_queries = 'all'; } } $this->enqueue_theme_ui( 'light', $ui_light_theme_media_queries ); $this->enqueue_theme_ui( 'dark', $ui_dark_theme_media_queries ); } private function enqueue_theme_ui( $ui_theme, $ui_theme_media_queries = 'all' ) { $suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_style( 'e-theme-ui-' . $ui_theme, ELEMENTOR_ASSETS_URL . 'css/theme-' . $ui_theme . $suffix . '.css', [], ELEMENTOR_VERSION, $ui_theme_media_queries ); } /** * Editor head trigger. * * Fires the 'elementor/editor/wp_head' action in the head tag in Elementor * editor. * * @since 1.0.0 * @access public */ public function editor_head_trigger() { /** * Elementor editor head. * * Fires on Elementor editor head tag. * * Used to prints scripts or any other data in the head tag. * * @since 1.0.0 */ do_action( 'elementor/editor/wp_head' ); } /** * WP footer. * * Prints Elementor editor with all the editor templates, and render controls, * widgets and content elements. * * Fired by `wp_footer` action. * * @since 1.0.0 * @access public */ public function wp_footer() { $plugin = Plugin::$instance; $plugin->controls_manager->render_controls(); $plugin->widgets_manager->render_widgets_content(); $plugin->elements_manager->render_elements_content(); $plugin->dynamic_tags->print_templates(); $this->get_loader()->register_additional_templates(); /** * Elementor editor footer. * * Fires on Elementor editor before closing the body tag. * * Used to prints scripts or any other HTML before closing the body tag. * * @since 1.0.0 */ do_action( 'elementor/editor/footer' ); } /** * Set edit mode. * * Used to update the edit mode. * * @since 1.0.0 * @access public * * @param bool $edit_mode Whether the edit mode is active. */ public function set_edit_mode( $edit_mode ) { $this->is_edit_mode = $edit_mode; } /** * Editor constructor. * * Initializing Elementor editor and redirect from old URL structure of * Elementor editor. * * @since 1.0.0 * @access public */ public function __construct() { Plugin::$instance->data_manager_v2->register_controller( new Data\Globals\Controller() ); $this->notice_bar = new Notice_Bar(); $this->promotion = new Promotion(); add_action( 'admin_action_elementor', [ $this, 'init' ] ); add_action( 'template_redirect', [ $this, 'redirect_to_new_url' ] ); // Handle autocomplete feature for URL control. add_filter( 'wp_link_query_args', [ $this, 'filter_wp_link_query_args' ] ); add_filter( 'wp_link_query', [ $this, 'filter_wp_link_query' ] ); } /** * @since 2.2.0 * @access public */ public function filter_wp_link_query_args( $query ) { $library_cpt_key = array_search( Source_Local::CPT, $query['post_type'], true ); if ( false !== $library_cpt_key ) { unset( $query['post_type'][ $library_cpt_key ] ); } return $query; } /** * @since 2.2.0 * @access public */ public function filter_wp_link_query( $results ) { // PHPCS - The user data is not used. if ( isset( $_POST['editor'] ) && 'elementor' === $_POST['editor'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $post_type_object = get_post_type_object( 'post' ); $post_label = $post_type_object->labels->singular_name; foreach ( $results as & $result ) { if ( 'post' === get_post_type( $result['ID'] ) ) { $result['info'] = $post_label; } } } return $results; } public function set_post_id( $post_id ) { $this->post_id = $post_id; } /** * Get loader. * * @return Editor_Loader_Interface */ private function get_loader() { if ( ! $this->loader ) { $this->loader = Editor_Loader_Factory::create(); $this->loader->init(); } return $this->loader; } /** * Get elements presets. * * @return array */ public function get_elements_presets() { $element_types = Plugin::$instance->elements_manager->get_element_types(); $presets = []; foreach ( $element_types as $el_type => $element ) { $this->check_element_for_presets( $element, $el_type, $presets ); } return $presets; } /** * @return void */ private function check_element_for_presets( $element, $el_type, &$presets ) { $element_presets = $element->get_panel_presets(); if ( empty( $element_presets ) ) { return; } foreach ( $element_presets as $key => $preset ) { $this->maybe_add_preset( $el_type, $preset, $key, $presets ); } } /** * @return void */ private function maybe_add_preset( $el_type, $preset, $key, &$presets ) { if ( $this->is_valid_preset( $el_type, $preset ) ) { $presets[ $key ] = $preset; } } /** * @return boolean */ private function is_valid_preset( $el_type, $preset ) { return isset( $preset['replacements']['custom']['originalWidget'] ) && $el_type === $preset['replacements']['custom']['originalWidget']; } } editor/promotion.php 0000644 00000002711 14717626151 0010604 0 ustar 00 <?php namespace Elementor\Core\Editor; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Promotion { /** * @return array */ public function get_elements_promotion() { return Filtered_Promotions_Manager::get_filtered_promotion_data( $this->get_promotion_data(), 'elementor/editor/promotion/get_elements_promotion', 'action_button', 'url' ); } /** * @return array */ private function get_action_button_content(): array { $has_pro = Utils::has_pro(); return $has_pro ? [ 'text' => __( 'Connect & Activate', 'elementor' ), 'url' => admin_url( 'admin.php?page=elementor-license' ), ] : [ 'text' => __( 'Upgrade Now', 'elementor' ), 'url' => 'https://go.elementor.com/go-pro-%s', ]; } /** * @return string */ private function get_promotion_url(): string { return Utils::has_pro() ? admin_url( 'admin.php?page=elementor-license' ) : 'https://go.elementor.com/go-pro-%s'; } /** * @return array */ private function get_promotion_data(): array { return [ /* translators: %s: Widget title. */ 'title' => __( '%s Widget', 'elementor' ), /* translators: %s: Widget title. */ 'content' => __( 'Use %s widget and dozens more pro features to extend your toolbox and build sites faster and better.', 'elementor' ), 'action_button' => $this->get_action_button_content(), ]; } } editor/notice-bar.php 0000644 00000010052 14717626151 0010576 0 ustar 00 <?php namespace Elementor\Core\Editor; use Elementor\Core\Base\Base_Object; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Notice_Bar extends Base_Object { protected function get_init_settings() { if ( Plugin::$instance->get_install_time() > strtotime( '-1 days' ) ) { return []; } $upgrade_url = 'https://go.elementor.com/go-pro-editor-notice-bar/'; $config = [ 'description' => $this->get_description(), 'upgrade_text' => $this->get_upgrade_text(), 'upgrade_url' => $upgrade_url, ]; $config = Filtered_Promotions_Manager::get_filtered_promotion_data( $config, 'elementor/notice-bar/custom_promotion', 'upgrade_url' ); return [ 'muted_period' => 14, 'option_key' => '_elementor_editor_upgrade_notice_dismissed', 'message' => $config['description'] ?? $this->get_description(), 'action_title' => $config['upgrade_text'] ?? $this->get_upgrade_text(), 'action_url' => $config['upgrade_url'] ?? $upgrade_url, ]; } public function get_upgrade_text() { return esc_html__( 'Upgrade Now', 'elementor' ); } public function get_description() { return esc_html__( 'Unleash the full power of Elementor\'s features and web creation tools.', 'elementor' ); } final public function get_notice() { if ( ! $this->has_access_to_notice() ) { return null; } $settings = $this->get_settings(); if ( empty( $settings['option_key'] ) ) { return null; } $dismissed_time = get_option( $settings['option_key'] ); if ( $dismissed_time ) { if ( $dismissed_time > strtotime( '-' . $settings['muted_period'] . ' days' ) ) { return null; } $this->set_notice_dismissed(); } return $this; } protected function render_action( $type ) { $settings = $this->get_settings(); // TODO: Make the API better. The bad naming is because of BC. $prefix_map = [ 'primary' => '', 'secondary' => 'secondary_', ]; $prefix = $prefix_map[ $type ]; $action_title = "{$prefix}action_title"; $action_url = "{$prefix}action_url"; $action_message = "{$prefix}message"; $action_target = "{$prefix}action_target"; if ( empty( $settings[ $action_title ] ) || empty( $settings[ $action_url ] ) || empty( $settings[ $action_message ] ) ) { return; } ?> <div class="e-notice-bar__message <?php echo esc_attr( "e-notice-bar__{$type}_message" ); ?>"> <?php Utils::print_unescaped_internal_string( sprintf( $settings[ $action_message ], $settings[ $action_url ] ) ); ?> </div> <div class="e-notice-bar__action <?php echo esc_attr( "e-notice-bar__{$type}_action" ); ?>"> <a href="<?php Utils::print_unescaped_internal_string( $settings[ $action_url ] ); ?>" target="<?php echo empty( $settings[ $action_target ] ) ? '_blank' : esc_attr( $settings[ $action_target ] ); ?>" > <?php Utils::print_unescaped_internal_string( $settings[ $action_title ] ); ?> </a> </div> <?php } public function render() { $settings = $this->get_settings(); $icon = empty( $settings['icon'] ) ? 'eicon-elementor-square' : esc_attr( $settings['icon'] ); ?> <div id="e-notice-bar" class="e-notice-bar"> <i class="e-notice-bar__icon <?php echo esc_attr( $icon ); ?>"></i> <?php $this->render_action( 'primary' ); $this->render_action( 'secondary' ); ?> <i id="e-notice-bar__close" class="e-notice-bar__close eicon-close"></i> </div> <?php } public function __construct() { add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); } public function set_notice_dismissed() { if ( ! $this->has_access_to_notice() ) { throw new \Exception( 'Access denied' ); } update_option( $this->get_settings( 'option_key' ), time() ); } public function register_ajax_actions( Ajax $ajax ) { $ajax->register_ajax_action( 'notice_bar_dismiss', [ $this, 'set_notice_dismissed' ] ); } private function has_access_to_notice() { return current_user_can( 'manage_options' ); } } experiments/wrap-core-dependency.php 0000644 00000001102 14717626151 0013637 0 ustar 00 <?php namespace Elementor\Core\Experiments; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Wrap_Core_Dependency { private $feature_data; public function __construct( $feature_data ) { $this->feature_data = $feature_data; } public function get_name() { return $this->feature_data['name']; } public function get_title() { return $this->feature_data['title']; } public function is_hidden() { return $this->feature_data['hidden']; } public static function instance( $feature_data ) { return new static( $feature_data ); } } experiments/non-existing-dependency.php 0000644 00000001014 14717626151 0014364 0 ustar 00 <?php namespace Elementor\Core\Experiments; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Non_Existing_Dependency { private $feature_id; public function __construct( $feature_id ) { $this->feature_id = $feature_id; } public function get_name() { return $this->feature_id; } public function get_title() { return $this->feature_id; } public function is_hidden() { return false; } public static function instance( $feature_id ) { return new static( $feature_id ); } } experiments/manager.php 0000644 00000075734 14717626151 0011264 0 ustar 00 <?php namespace Elementor\Core\Experiments; use Elementor\Core\Base\Base_Object; use Elementor\Core\Upgrade\Manager as Upgrade_Manager; use Elementor\Core\Utils\Collection; use Elementor\Modules\System_Info\Module as System_Info; use Elementor\Plugin; use Elementor\Settings; use Elementor\Tracker; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Manager extends Base_Object { const RELEASE_STATUS_DEV = 'dev'; const RELEASE_STATUS_ALPHA = 'alpha'; const RELEASE_STATUS_BETA = 'beta'; const RELEASE_STATUS_RC = 'rc'; const RELEASE_STATUS_STABLE = 'stable'; const STATE_DEFAULT = 'default'; const STATE_ACTIVE = 'active'; const STATE_INACTIVE = 'inactive'; const TYPE_HIDDEN = 'hidden'; const OPTION_PREFIX = 'elementor_experiment-'; private $states; private $release_statuses; private $features; /** * Add Feature * * @since 3.1.0 * @access public * * @param array $options { * @type string $name * @type string $title * @type string $tag * @type array $tags * @type string $description * @type string $release_status * @type string $default * @type callable $on_state_change * } * * @return array|null * @throws \Exception */ public function add_feature( array $options ) { if ( isset( $this->features[ $options['name'] ] ) ) { return null; } $default_experimental_data = [ 'tag' => '', // Deprecated, use 'tags' instead. 'tags' => [], 'description' => '', 'release_status' => self::RELEASE_STATUS_ALPHA, 'default' => self::STATE_INACTIVE, 'mutable' => true, static::TYPE_HIDDEN => false, 'new_site' => [ 'always_active' => false, 'default_active' => false, 'default_inactive' => false, 'minimum_installation_version' => null, ], 'on_state_change' => null, 'generator_tag' => false, ]; $allowed_options = [ 'name', 'title', 'tag', 'tags', 'description', 'release_status', 'default', 'mutable', static::TYPE_HIDDEN, 'new_site', 'on_state_change', 'dependencies', 'generator_tag', 'messages' ]; $experimental_data = $this->merge_properties( $default_experimental_data, $options, $allowed_options ); $experimental_data = $this->unify_feature_tags( $experimental_data ); $new_site = $experimental_data['new_site']; if ( $new_site['default_active'] || $new_site['always_active'] || $new_site['default_inactive'] ) { $is_new_installation = $this->install_compare( $new_site['minimum_installation_version'] ); if ( $is_new_installation ) { if ( $new_site['always_active'] ) { $experimental_data['state'] = self::STATE_ACTIVE; $experimental_data['mutable'] = false; } elseif ( $new_site['default_active'] ) { $experimental_data['default'] = self::STATE_ACTIVE; } elseif ( $new_site['default_inactive'] ) { $experimental_data['default'] = self::STATE_INACTIVE; } } } if ( $experimental_data['mutable'] ) { $experimental_data['state'] = $this->get_saved_feature_state( $options['name'] ); } if ( empty( $experimental_data['state'] ) ) { $experimental_data['state'] = self::STATE_DEFAULT; } if ( ! empty( $experimental_data['dependencies'] ) ) { foreach ( $experimental_data['dependencies'] as $key => $dependency ) { $feature = $this->get_features( $dependency ); if ( ! empty( $feature[ static::TYPE_HIDDEN ] ) ) { throw new Exceptions\Dependency_Exception( 'Depending on a hidden experiment is not allowed.' ); } $experimental_data['dependencies'][ $key ] = $this->create_dependency_class( $dependency, $feature ); } } $this->features[ $options['name'] ] = $experimental_data; if ( $experimental_data['mutable'] && is_admin() ) { $feature_option_key = $this->get_feature_option_key( $options['name'] ); $on_state_change_callback = function( $old_state, $new_state ) use ( $experimental_data, $feature_option_key ) { try { $this->on_feature_state_change( $experimental_data, $new_state, $old_state ); } catch ( Exceptions\Dependency_Exception $e ) { $message = sprintf( '<p>%s</p><p><a href="#" onclick="location.href=\'%s\'">%s</a></p>', esc_html( $e->getMessage() ), Settings::get_settings_tab_url( 'experiments' ), esc_html__( 'Back', 'elementor' ) ); wp_die( $message ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } }; add_action( 'add_option_' . $feature_option_key, $on_state_change_callback, 10, 2 ); add_action( 'update_option_' . $feature_option_key, $on_state_change_callback, 10, 2 ); } do_action( 'elementor/experiments/feature-registered', $this, $experimental_data ); return $experimental_data; } private function install_compare( $version ) { $installs_history = Upgrade_Manager::get_installs_history(); if ( empty( $installs_history ) ) { return false; } $cleaned_version = preg_replace( '/-(beta|cloud|dev)\d*$/', '', key( $installs_history ) ); return version_compare( $cleaned_version, $version, '>=' ); } /** * Combine 'tag' and 'tags' into one property. * * @param array $experimental_data * * @return array */ private function unify_feature_tags( array $experimental_data ) : array { foreach ( [ 'tag', 'tags' ] as $key ) { if ( empty( $experimental_data[ $key ] ) ) { continue; } $experimental_data[ $key ] = $this->format_feature_tags( $experimental_data[ $key ] ); } if ( is_array( $experimental_data['tag'] ) ) { $experimental_data['tags'] = array_merge( $experimental_data['tag'], $experimental_data['tags'] ); } return $experimental_data; } /** * Format feature tags into the right format. * * @param string|array[ * [ * 'type' => string, * 'label' => string * ] * ] $tag * * @return array */ private function format_feature_tags( $tags ) : array { if ( ! is_string( $tags ) && ! is_array( $tags ) ) { return []; } $default_tag = [ 'type' => 'default', 'label' => '', ]; $allowed_tag_properties = [ 'type', 'label' ]; // If $tags is string, explode by commas and convert to array. if ( is_string( $tags ) ) { $tags = array_filter( explode( ',', $tags ) ); foreach ( $tags as $i => $tag ) { $tags[ $i ] = [ 'label' => trim( $tag ) ]; } } foreach ( $tags as $i => $tag ) { if ( empty( $tag['label'] ) ) { unset( $tags[ $i ] ); continue; } $tags[ $i ] = $this->merge_properties( $default_tag, $tag, $allowed_tag_properties ); } return $tags; } /** * Remove Feature * * @since 3.1.0 * @access public * * @param string $feature_name */ public function remove_feature( $feature_name ) { unset( $this->features[ $feature_name ] ); } /** * Get Features * * @since 3.1.0 * @access public * * @param string $feature_name Optional. Default is null * * @return array|null */ public function get_features( $feature_name = null ) { return self::get_items( $this->features, $feature_name ); } /** * Get Active Features * * @since 3.1.0 * @access public * * @return array */ public function get_active_features() { return array_filter( $this->features, [ $this, 'is_feature_active' ], ARRAY_FILTER_USE_KEY ); } /** * Is Feature Active * * @since 3.1.0 * @access public * * @param string $feature_name * * @return bool */ public function is_feature_active( $feature_name, $check_dependencies = false ) { $feature = $this->get_features( $feature_name ); if ( ! $feature || self::STATE_ACTIVE !== $this->get_feature_actual_state( $feature ) ) { return false; } if ( $check_dependencies && isset( $feature['dependencies'] ) && is_array( $feature['dependencies'] ) ) { foreach ( $feature['dependencies'] as $dependency ) { $dependent_feature = $this->get_features( $dependency->get_name() ); $feature_state = self::STATE_ACTIVE === $this->get_feature_actual_state( $dependent_feature ); if ( ! $feature_state ) { return false; } } } return true; } /** * Set Feature Default State * * @since 3.1.0 * @access public * * @param string $feature_name * @param string $default_state */ public function set_feature_default_state( $feature_name, $default_state ) { $feature = $this->get_features( $feature_name ); if ( ! $feature ) { return; } $this->features[ $feature_name ]['default'] = $default_state; } /** * Get Feature Option Key * * @since 3.1.0 * @access public * * @param string $feature_name * * @return string */ public function get_feature_option_key( $feature_name ) { return static::OPTION_PREFIX . $feature_name; } private function add_default_features() { $this->add_feature( [ 'name' => 'e_optimized_css_loading', 'title' => esc_html__( 'Improved CSS Loading', 'elementor' ), 'tag' => esc_html__( 'Performance', 'elementor' ), 'description' => sprintf( '%1$s <a href="https://go.elementor.com/wp-dash-improved-css-loading/" target="_blank">%2$s</a>', esc_html__( 'Please Note! The “Improved CSS Loading” mode reduces the amount of CSS code that is loaded on the page by default. When activated, the CSS code will be loaded, rather inline or in a dedicated file, only when needed. Activating this experiment may cause conflicts with incompatible plugins.', 'elementor' ), esc_html__( 'Learn more', 'elementor' ) ), 'release_status' => self::RELEASE_STATUS_STABLE, 'default' => self::STATE_INACTIVE, static::TYPE_HIDDEN => true, 'mutable' => false, 'generator_tag' => true, ] ); $this->add_feature( [ 'name' => 'e_font_icon_svg', 'title' => esc_html__( 'Inline Font Icons', 'elementor' ), 'tag' => esc_html__( 'Performance', 'elementor' ), 'description' => sprintf( '%1$s <a href="https://go.elementor.com/wp-dash-inline-font-awesome/" target="_blank">%2$s</a>', esc_html__( 'The “Inline Font Icons” will render the icons as inline SVG without loading the Font-Awesome and the eicons libraries and its related CSS files and fonts.', 'elementor' ), esc_html__( 'Learn more', 'elementor' ) ), 'release_status' => self::RELEASE_STATUS_STABLE, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '3.17.0', ], 'generator_tag' => true, ] ); $this->add_feature( [ 'name' => 'additional_custom_breakpoints', 'title' => esc_html__( 'Additional Custom Breakpoints', 'elementor' ), 'description' => sprintf( '%1$s <a href="https://go.elementor.com/wp-dash-additional-custom-breakpoints/" target="_blank">%2$s</a>', esc_html__( 'Get pixel-perfect design for every screen size. You can now add up to 6 customizable breakpoints beyond the default desktop setting: mobile, mobile extra, tablet, tablet extra, laptop, and widescreen.', 'elementor' ), esc_html__( 'Learn more', 'elementor' ) ), 'release_status' => self::RELEASE_STATUS_STABLE, 'default' => self::STATE_ACTIVE, 'generator_tag' => true, ] ); $this->add_feature( [ 'name' => 'container', 'title' => esc_html__( 'Container', 'elementor' ), 'description' => sprintf( esc_html__( 'Create advanced layouts and responsive designs with %1$sFlexbox%2$s and %3$sGrid%4$s container elements. Give it a try using the %5$sContainer playground%6$s.', 'elementor' ), '<a target="_blank" href="https://go.elementor.com/wp-dash-flex-container/">', '</a>', '<a target="_blank" href="https://go.elementor.com/wp-dash-grid-container/">', '</a>', '<a target="_blank" href="https://go.elementor.com/wp-dash-flex-container-playground/">', '</a>' ), 'release_status' => self::RELEASE_STATUS_STABLE, 'default' => self::STATE_INACTIVE, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '3.16.0', ], 'messages' => [ 'on_deactivate' => sprintf( '%1$s <a target="_blank" href="https://go.elementor.com/wp-dash-deactivate-container/">%2$s</a>', esc_html__( 'Container-based content will be hidden from your site and may not be recoverable in all cases.', 'elementor' ), esc_html__( 'Learn more', 'elementor' ), ), ], ] ); $this->add_feature( [ 'name' => 'e_swiper_latest', 'title' => esc_html__( 'Upgrade Swiper Library', 'elementor' ), 'description' => esc_html__( 'Prepare your website for future improvements to carousel features by upgrading the Swiper library integrated into your site from v5.36 to v8.45. This experiment includes markup changes so it might require updating custom code and cause compatibility issues with third party plugins.', 'elementor' ), 'release_status' => self::RELEASE_STATUS_STABLE, 'default' => self::STATE_ACTIVE, ] ); $this->add_feature( [ 'name' => 'e_nested_atomic_repeaters', 'title' => esc_html__( 'Nested Elements Performance', 'elementor' ), 'tag' => esc_html__( 'Performance', 'elementor' ), 'description' => esc_html__( 'Improve the performance of the Nested widgets.', 'elementor' ), static::TYPE_HIDDEN => true, 'release_status' => self::RELEASE_STATUS_DEV, 'default' => self::STATE_ACTIVE, ] ); $this->add_feature( [ 'name' => 'e_optimized_control_loading', 'title' => esc_html__( 'Optimized Control Loading', 'elementor' ), 'tag' => esc_html__( 'Performance', 'elementor' ), 'description' => esc_html__( 'Use this experiment to improve control loading. This experiment improves site performance by loading controls only when needed.', 'elementor' ), 'release_status' => self::RELEASE_STATUS_STABLE, 'default' => self::STATE_ACTIVE, 'generator_tag' => true, ] ); $this->add_feature( [ 'name' => 'e_optimized_markup', 'title' => esc_html__( 'Optimized Markup', 'elementor' ), 'tag' => esc_html__( 'Performance', 'elementor' ), 'description' => esc_html__( 'Reduce the DOM size by eliminating HTML tags in various elements and widgets. This experiment includes markup changes so it might require updating custom CSS/JS code and cause compatibility issues with third party plugins.', 'elementor' ), 'release_status' => self::RELEASE_STATUS_ALPHA, 'default' => self::STATE_INACTIVE, ] ); $this->add_feature( [ 'name' => 'e_swiper_css_conditional_loading', 'title' => esc_html__( 'Conditionally load Swiper CSS files', 'elementor' ), static::TYPE_HIDDEN => true, 'default' => self::STATE_INACTIVE, ] ); $this->add_feature( [ 'name' => 'e_onboarding', 'title' => esc_html__( 'Plugin Onboarding', 'elementor' ), 'description' => esc_html__( 'New plugin onboarding.', 'elementor' ), static::TYPE_HIDDEN => true, 'release_status' => self::RELEASE_STATUS_ALPHA, 'default' => self::STATE_ACTIVE, ] ); // TODO: Possibly remove experiment in v3.27.0 [ED-15717]. // Check this reference in Pro: 'sticky_anchor_link_offset'. $this->add_feature( [ 'name' => 'e_css_smooth_scroll', 'title' => esc_html__( 'CSS Smooth Scroll', 'elementor' ), 'tag' => esc_html__( 'Performance', 'elementor' ), 'description' => esc_html__( 'Use CSS Smooth Scroll to improve the user experience on your site. This experiment replaces the default JavaScript-based smooth scroll with a CSS-based solution.', 'elementor' ), 'release_status' => self::RELEASE_STATUS_DEV, static::TYPE_HIDDEN => true, 'default' => self::STATE_ACTIVE, 'mutable' => false, ] ); } /** * Init States * * @since 3.1.0 * @access private */ private function init_states() { $this->states = [ self::STATE_DEFAULT => esc_html__( 'Default', 'elementor' ), self::STATE_ACTIVE => esc_html__( 'Active', 'elementor' ), self::STATE_INACTIVE => esc_html__( 'Inactive', 'elementor' ), ]; } /** * Init Statuses * * @since 3.1.0 * @access private */ private function init_release_statuses() { $this->release_statuses = [ self::RELEASE_STATUS_DEV => esc_html__( 'Development', 'elementor' ), self::RELEASE_STATUS_ALPHA => esc_html__( 'Alpha', 'elementor' ), self::RELEASE_STATUS_BETA => esc_html__( 'Beta', 'elementor' ), self::RELEASE_STATUS_RC => esc_html__( 'Release Candidate', 'elementor' ), self::RELEASE_STATUS_STABLE => esc_html__( 'Stable', 'elementor' ), ]; } /** * Init Features * * @since 3.1.0 * @access private */ private function init_features() { $this->features = []; $this->add_default_features(); do_action( 'elementor/experiments/default-features-registered', $this ); } /** * Register Settings Fields * * @param Settings $settings * * @since 3.1.0 * @access private * */ private function register_settings_fields( Settings $settings ) { $features = $this->get_features(); $fields = []; foreach ( $features as $feature_name => $feature ) { $is_hidden = $feature[ static::TYPE_HIDDEN ]; $is_mutable = $feature['mutable']; $should_hide_experiment = ! $is_mutable || ( $is_hidden && ! $this->should_show_hidden() ) || $this->has_non_existing_dependency( $feature ); if ( $should_hide_experiment ) { unset( $features[ $feature_name ] ); continue; } $feature_key = 'experiment-' . $feature_name; $section = 'stable' === $feature['release_status'] ? 'stable' : 'ongoing'; $fields[ $section ][ $feature_key ]['label'] = $this->get_feature_settings_label_html( $feature ); $fields[ $section ][ $feature_key ]['field_args'] = $feature; $fields[ $section ][ $feature_key ]['render'] = function( $feature ) { $this->render_feature_settings_field( $feature ); }; } foreach ( [ 'stable', 'ongoing' ] as $section ) { if ( ! isset( $fields[ $section ] ) ) { $fields[ $section ]['no_features'] = [ 'label' => esc_html__( 'No available experiments', 'elementor' ), 'field_args' => [ 'type' => 'raw_html', 'html' => esc_html__( 'The current version of Elementor doesn\'t have any experimental features . if you\'re feeling curious make sure to come back in future versions.', 'elementor' ), ], ]; } if ( ! Tracker::is_allow_track() && 'stable' === $section ) { $fields[ $section ] += $settings->get_usage_fields(); } } $settings->add_tab( 'experiments', [ 'label' => esc_html__( 'Features', 'elementor' ), 'sections' => [ 'ongoing_experiments' => [ 'callback' => function() { $this->render_settings_intro(); }, 'fields' => $fields['ongoing'], ], 'stable_experiments' => [ 'callback' => function() { $this->render_stable_section_title(); }, 'fields' => $fields['stable'], ], ], ] ); } private function render_stable_section_title() { ?> <hr> <h2> <?php echo esc_html__( 'Stable Features', 'elementor' ); ?> </h2> <?php } /** * Render Settings Intro * * @since 3.1.0 * @access private */ private function render_settings_intro() { ?> <h2> <?php echo esc_html__( 'Experiments and Features', 'elementor' ); ?> </h2> <p class="e-experiment__description"> <?php echo sprintf( /* translators: %1$s Link open tag, %2$s: Link close tag. */ esc_html__( 'Personalize your Elementor experience by controlling which features and experiments are active on your site. Help make Elementor better by %1$ssharing your experience and feedback with us%2$s.', 'elementor' ), '<a href="https://go.elementor.com/wp-dash-experiments-report-an-issue/" target="_blank">', '</a>' ); ?> </p> <p class="e-experiment__description"> <?php echo sprintf( '%1$s <a href="https://go.elementor.com/wp-dash-experiments/" target="_blank">%2$s</a>', esc_html__( 'To use an experiment or feature on your site, simply click on the dropdown next to it and switch to Active. You can always deactivate them at any time.', 'elementor' ), esc_html__( 'Learn more', 'elementor' ), ); ?> </p> <?php if ( $this->get_features() ) { ?> <button type="button" class="button e-experiment__button" value="active"><?php echo esc_html__( 'Activate All', 'elementor' ); ?></button> <button type="button" class="button e-experiment__button" value="inactive"><?php echo esc_html__( 'Deactivate All', 'elementor' ); ?></button> <button type="button" class="button e-experiment__button" value="default"><?php echo esc_html__( 'Back to default', 'elementor' ); ?></button> <?php } ?> <hr> <h2 class="e-experiment__table-title"> <?php echo esc_html__( 'Ongoing Experiments', 'elementor' ); ?> </h2> <?php } /** * Render Feature Settings Field * * @since 3.1.0 * @access private * * @param array $feature */ private function render_feature_settings_field( array $feature ) { $control_id = 'e-experiment-' . $feature['name']; $control_name = $this->get_feature_option_key( $feature['name'] ); $status = sprintf( /* translators: %s Release status. */ esc_html__( 'Status: %s', 'elementor' ), $this->release_statuses[ $feature['release_status'] ] ); ?> <div class="e-experiment__content"> <select class="e-experiment__select" id="<?php echo esc_attr( $control_id ); ?>" name="<?php echo esc_attr( $control_name ); ?>" data-experiment-id="<?php echo esc_attr( $feature['name'] ); ?>" > <?php foreach ( $this->states as $state_key => $state_title ) { ?> <option value="<?php echo esc_attr( $state_key ); ?>" <?php selected( $state_key, $feature['state'] ); ?> > <?php echo esc_html( $state_title ); ?> </option> <?php } ?> </select> <p class="description"> <?php Utils::print_unescaped_internal_string( $feature['description'] ); ?> </p> <?php $this->render_feature_dependency( $feature ); ?> <?php if ( 'stable' !== $feature['release_status'] ) { ?> <div class="e-experiment__status"> <?php echo esc_html( $status ); ?> </div> <?php } ?> </div> <?php } private function render_feature_dependency( $feature ) { $dependencies = ( new Collection( $feature['dependencies'] ?? [] ) ) ->map( function ( $dependency ) { return $dependency->get_title(); } ) ->implode( ', ' ); if ( empty( $dependencies ) ) { return; } ?> <div class="e-experiment__dependency"> <strong class="e-experiment__dependency__title"><?php echo esc_html__( 'Requires', 'elementor' ); ?>:</strong> <span><?php echo esc_html( $dependencies ); ?></span> </div> <?php } private function has_non_existing_dependency( $feature ) { $non_existing_dep = ( new Collection( $feature['dependencies'] ?? [] ) ) ->find( function ( $dependency ) { return $dependency instanceof Non_Existing_Dependency; } ); return ! ! $non_existing_dep; } /** * Get Feature Settings Label HTML * * @since 3.1.0 * @access private * * @param array $feature * * @return string */ private function get_feature_settings_label_html( array $feature ) { ob_start(); $is_feature_active = $this->is_feature_active( $feature['name'] ); $indicator_classes = 'e-experiment__title__indicator'; if ( $is_feature_active ) { $indicator_classes .= ' e-experiment__title__indicator--active'; } $indicator_tooltip = $this->get_feature_state_label( $feature ); ?> <div class="e-experiment__title"> <div class="<?php echo $indicator_classes; ?>" data-tooltip="<?php echo $indicator_tooltip; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"></div> <label class="e-experiment__title__label" for="e-experiment-<?php echo $feature['name']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"><?php echo $feature['title']; ?></label> <?php foreach ( $feature['tags'] as $tag ) { ?> <span class="e-experiment__title__tag e-experiment__title__tag__<?php echo $tag['type']; ?>"><?php echo $tag['label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span> <?php } ?> </div> <?php return ob_get_clean(); } /** * Get Feature State Label * * @param array $feature * * @return string */ public function get_feature_state_label( array $feature ) { $is_feature_active = $this->is_feature_active( $feature['name'] ); if ( self::STATE_DEFAULT === $feature['state'] ) { $label = $is_feature_active ? esc_html__( 'Active by default', 'elementor' ) : esc_html__( 'Inactive by default', 'elementor' ); } else { $label = self::STATE_ACTIVE === $feature['state'] ? esc_html__( 'Active', 'elementor' ) : esc_html__( 'Inactive', 'elementor' ); } return $label; } /** * Get Feature Settings Label HTML * * @since 3.1.0 * @access private * * @param string $feature_name * * @return int */ private function get_saved_feature_state( $feature_name ) { return get_option( $this->get_feature_option_key( $feature_name ) ); } /** * Get Feature Actual State * * @since 3.1.0 * @access private * * @param array $feature * * @return string */ private function get_feature_actual_state( array $feature ) { if ( ! empty( $feature['state'] ) && self::STATE_DEFAULT !== $feature['state'] ) { return $feature['state']; } return $feature['default']; } /** * On Feature State Change * * @since 3.1.0 * @access private * * @param array $old_feature_data * @param string $new_state * * @throws \Elementor\Core\Experiments\Exceptions\Dependency_Exception */ private function on_feature_state_change( array $old_feature_data, $new_state, $old_state ) { $new_feature_data = $this->get_features( $old_feature_data['name'] ); $this->validate_dependency( $new_feature_data, $new_state ); $this->features[ $old_feature_data['name'] ]['state'] = $new_state; if ( $old_state === $new_state ) { return; } Plugin::$instance->files_manager->clear_cache(); if ( $new_feature_data['on_state_change'] ) { $new_feature_data['on_state_change']( $old_state, $new_state ); } do_action( 'elementor/experiments/feature-state-change/' . $old_feature_data['name'], $old_state, $new_state ); } /** * @throws \Elementor\Core\Experiments\Exceptions\Dependency_Exception */ private function validate_dependency( array $feature, $new_state ) { $rollback = function ( $feature_option_key, $state ) { remove_all_actions( 'add_option_' . $feature_option_key ); remove_all_actions( 'update_option_' . $feature_option_key ); update_option( $feature_option_key, $state ); }; if ( self::STATE_DEFAULT === $new_state ) { $new_state = $this->get_feature_actual_state( $feature ); } $feature_option_key = $this->get_feature_option_key( $feature['name'] ); if ( self::STATE_ACTIVE === $new_state ) { if ( empty( $feature['dependencies'] ) ) { return; } // Validate if the current feature dependency is available. foreach ( $feature['dependencies'] as $dependency ) { $dependency_feature = $this->get_features( $dependency->get_name() ); if ( ! $dependency_feature ) { $rollback( $feature_option_key, self::STATE_INACTIVE ); throw new Exceptions\Dependency_Exception( sprintf( 'The feature `%s` has a dependency `%s` that is not available.', $feature['name'], $dependency->get_name() ) ); } $dependency_state = $this->get_feature_actual_state( $dependency_feature ); // If dependency is not active. if ( self::STATE_INACTIVE === $dependency_state ) { $rollback( $feature_option_key, self::STATE_INACTIVE ); throw new Exceptions\Dependency_Exception( sprintf( 'To turn on `%1$s`, Experiment: `%2$s` activity is required!', $feature['name'], $dependency_feature['name'] ) ); } } } elseif ( self::STATE_INACTIVE === $new_state ) { // Make sure to deactivate a dependant experiment of the current feature when it's deactivated. foreach ( $this->get_features() as $current_feature ) { if ( empty( $current_feature['dependencies'] ) ) { continue; } $current_feature_state = $this->get_feature_actual_state( $current_feature ); foreach ( $current_feature['dependencies'] as $dependency ) { if ( self::STATE_ACTIVE === $current_feature_state && $feature['name'] === $dependency->get_name() ) { update_option( $this->get_feature_option_key( $current_feature['name'] ), static::STATE_INACTIVE ); } } } } } private function should_show_hidden() { return defined( 'ELEMENTOR_SHOW_HIDDEN_EXPERIMENTS' ) && ELEMENTOR_SHOW_HIDDEN_EXPERIMENTS; } private function create_dependency_class( $dependency_name, $dependency_args ) { if ( class_exists( $dependency_name ) ) { return $dependency_name::instance(); } if ( ! empty( $dependency_args ) ) { return new Wrap_Core_Dependency( $dependency_args ); } return new Non_Existing_Dependency( $dependency_name ); } /** * The experiments page is a WordPress options page, which means all the experiments are registered via WordPress' register_settings(), * and their states are being sent in the POST request when saving. * The options are being updated in a chronological order based on the POST data. * This behavior interferes with the experiments dependency mechanism because the data that's being sent can be in any order, * while the dependencies mechanism expects it to be in a specific order (dependencies should be activated before their dependents can). * In order to solve this issue, we sort the experiments in the POST data based on their dependencies tree. * * @param $allowed_options * * @return mixed */ private function sort_allowed_options_by_dependencies( $allowed_options ) { if ( ! isset( $allowed_options['elementor'] ) ) { return $allowed_options; } $sorted = Collection::make(); $visited = Collection::make(); $sort = function ( $item ) use ( &$sort, $sorted, $visited ) { if ( $visited->contains( $item ) ) { return; } $visited->push( $item ); $feature = $this->get_features( $item ); if ( ! $feature ) { return; } foreach ( $feature['dependencies'] ?? [] as $dep ) { $name = is_string( $dep ) ? $dep : $dep->get_name(); $sort( $name ); } $sorted->push( $item ); }; foreach ( $allowed_options['elementor'] as $option ) { $is_experiment_option = strpos( $option, static::OPTION_PREFIX ) === 0; if ( ! $is_experiment_option ) { continue; } $sort( str_replace( static::OPTION_PREFIX, '', $option ) ); } $allowed_options['elementor'] = Collection::make( $allowed_options['elementor'] ) ->filter( function ( $option ) { return 0 !== strpos( $option, static::OPTION_PREFIX ); } ) ->merge( $sorted->map( function ( $item ) { return static::OPTION_PREFIX . $item; } ) ) ->values(); return $allowed_options; } public function __construct() { $this->init_states(); $this->init_release_statuses(); $this->init_features(); add_action( 'admin_init', function () { System_Info::add_report( 'experiments', [ 'file_name' => __DIR__ . '/experiments-reporter.php', 'class_name' => __NAMESPACE__ . '\Experiments_Reporter', ] ); }, 79 /* Before log */ ); if ( is_admin() ) { $page_id = Settings::PAGE_ID; add_action( "elementor/admin/after_create_settings/{$page_id}", function( Settings $settings ) { $this->register_settings_fields( $settings ); }, 11 ); add_filter( 'allowed_options', function ( $allowed_options ) { return $this->sort_allowed_options_by_dependencies( $allowed_options ); }, 11 ); } // Register CLI commands. if ( Utils::is_wp_cli() ) { \WP_CLI::add_command( 'elementor experiments', WP_CLI::class ); } } } experiments/wp-cli.php 0000644 00000012576 14717626151 0011040 0 ustar 00 <?php namespace Elementor\Core\Experiments; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Wp_Cli extends \WP_CLI_Command { /** * Activate an Experiment * * ## EXAMPLES * * 1. wp elementor experiments activate container * - This will activate the Container experiment. * * @param array $args * @param array|null $assoc_args - Arguments from WP CLI command. */ public function activate( $args, $assoc_args ) { if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please specify an experiment.' ); } $is_network = $this->is_network( $assoc_args ); $experiments = $this->parse_experiments( $args[0] ); $plural = $this->get_plural( $experiments ); $success = 'Experiment' . $plural . ' activated successfully'; $error = 'Cannot activate experiment' . $plural; if ( $is_network ) { $success .= " for site {$site}"; $error .= " for site {$site}"; } $experiments_manager = Plugin::instance()->experiments; if ( ! $this->check_experiments_exist( $experiments_manager, $experiments ) ) { \WP_CLI::error( 'Experiments do not exist' . $args[0] ); } if ( $is_network ) { $this->foreach_sites( $this->update_experiment_state, $experiments, Experiments_Manager::STATE_ACTIVE, $is_network, $success, $error ); } else { $this->update_experiment_state( $experiments, Experiments_Manager::STATE_ACTIVE, $is_network, $success, $error ); } } /** * Deactivate an Experiment * * ## EXAMPLES * * 1. wp elementor experiments deactivate container * - This will deactivate the Container experiment. * * @param array $args * @param array|null $assoc_args - Arguments from WP CLI command. */ public function deactivate( $args, $assoc_args ) { if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please specify an experiment.' ); } $is_network = $this->is_network( $assoc_args ); $experiments = $this->parse_experiments( $args[0] ); $plural = $this->get_plural( $experiments ); $success = 'Experiment' . $plural . ' deactivated successfully'; $error = 'Cannot deactivate experiment' . $plural; $experiments_manager = Plugin::instance()->experiments; if ( ! $this->check_experiments_exist( $experiments_manager, $experiments ) ) { \WP_CLI::error( 'Experiments do not exist' ); } if ( $is_network ) { $this->foreach_sites( $this->update_experiment_state, $experiments, Experiments_Manager::STATE_INACTIVE, $is_network, $success, $error ); } else { $this->update_experiment_state( $experiments, Experiments_Manager::STATE_INACTIVE, $is_network, $success, $error ); } } /** * Experiment Status * * ## EXAMPLES * * 1. wp elementor experiments status container * - This will return the status of Container experiment. (active/inactive) * * @param array $args */ public function status( $args ) { if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please specify an experiment.' ); } $experiments_manager = Plugin::$instance->experiments; $experiments_status = $experiments_manager->is_feature_active( $args[0] ) ? 'active' : 'inactive'; \WP_CLI::line( $experiments_status ); } /** * Determine if the current website is a multisite. * * @param array|null $assoc_args - Arguments from WP CLI command. * * @return bool */ private function is_network( $assoc_args ) { return ! empty( $assoc_args['network'] ) && is_multisite(); } /** * Iterate over network sites and execute a callback. * * @param callable $callback - Callback to execute. Gets the site name & id as parameters. * * @return void */ private function foreach_sites( callable $callback, $experiments, $state, $is_network, $success, $error ) { $blog_ids = get_sites( [ 'fields' => 'ids', 'number' => 0, ] ); foreach ( $blog_ids as $blog_id ) { switch_to_blog( $blog_id ); $callback( get_option( 'home' ), $experiments, $state, $is_network, $success, $error ); restore_current_blog(); } } /** * @param string $experiments_str comma delimited string of experiments * * @return array array of experiments */ private function parse_experiments( $experiments_str ) { return explode( ',', $experiments_str ); } /** * @param array $experiments experiments * * @return string plural */ private function get_plural( $experiments ) { return count( $experiments ) > 0 ? 's' : ''; } /** * @param Experiments_Manager $experiments_manager manager * @param array $experiments experiments * * @return bool true when all experiments exist, otherwise false */ private function check_experiments_exist( $experiments_manager, $experiments ) { foreach ( $experiments as $experiment ) { $feature = $experiments_manager->get_features( $experiment ); if ( ! $feature ) { return false; } } return true; } private function update_experiment_state( $experiments, $state, $is_network, $success_message, $error_message, $site_id = '' ) { if ( $is_network ) { $success_message .= " for site {$site}"; $error_message .= " for site {$site}"; } $experiments_manager = Plugin::instance()->experiments; foreach ( $experiments as $experiment ) { $option = $experiments_manager->get_feature_option_key( $experiment ); update_option( $option, $state ); } try { \WP_CLI::success( $success_message ); } catch ( \Exception $e ) { \WP_CLI::error( $error_message ); } } } experiments/experiments-reporter.php 0000644 00000006235 14717626151 0014043 0 ustar 00 <?php namespace Elementor\Core\Experiments; use Elementor\Modules\System_Info\Reporters\Base; use Elementor\Plugin; /** * Elementor experiments report. * * Elementor experiment report handler class responsible for generating a report for * the experiments included in Elementor and their status. */ class Experiments_Reporter extends Base { /** * Get experiments reporter title. * * @return string Reporter title. */ public function get_title() { return esc_html__( 'Elementor Experiments', 'elementor' ); } /** * Get experiments report fields. * * @return array Required report fields with field ID and field label. */ public function get_fields() { return [ 'experiments' => '', ]; } /** * Get Experiments. */ public function get_experiments() { $result = []; $experiments_manager = Plugin::$instance->experiments; // TODO: Those keys should be at `$experiments_manager`. $tracking_keys = [ 'default', 'state', 'tags', ]; foreach ( $experiments_manager->get_features() as $feature_name => $feature_data ) { $data_to_collect = []; // Extract only tracking keys. foreach ( $tracking_keys as $tracking_key ) { if ( empty( $feature_data[ $tracking_key ] ) ) { continue; } $data_to_collect[ $tracking_key ] = $feature_data[ $tracking_key ]; } $result[ $feature_name ] = $data_to_collect; } return [ 'value' => $result, ]; } /** * Get Raw Experiments. * * Retrieve a string containing the list of Elementor experiments and each experiment's status (active/inactive). * The string is formatted in a non-table structure, and it is meant for export/download of the system info reports. * * @return array */ public function get_raw_experiments() { $experiments = Plugin::$instance->experiments->get_features(); $output = ''; $is_first_item = true; foreach ( $experiments as $experiment ) { // If the state is default, add the default state to the string. $state = Plugin::$instance->experiments->get_feature_state_label( $experiment ); // The first item automatically has a tab character before it. Add tabs only to the rest of the items. if ( ! $is_first_item ) { $output .= "\t"; } $title = isset( $experiment['title'] ) ? $experiment['title'] : $experiment['name']; $output .= $title . ': ' . $state . PHP_EOL; $is_first_item = false; } return [ 'value' => $output, ]; } /** * Get HTML Experiments. * * Retrieve the list of Elementor experiments and each experiment's status (active/inactive), in HTML table format. * * @return array */ public function get_html_experiments() { $experiments = Plugin::$instance->experiments->get_features(); $output = ''; foreach ( $experiments as $experiment ) { // If the state is default, add the default state to the string. $state = Plugin::$instance->experiments->get_feature_state_label( $experiment ); $title = isset( $experiment['title'] ) ? $experiment['title'] : $experiment['name']; $output .= '<tr><td>' . esc_html( $title ) . ': </td>'; $output .= '<td>' . esc_html( $state ) . '</td>'; $output .= '</tr>'; } return [ 'value' => $output, ]; } } experiments/exceptions/dependency-exception.php 0000644 00000000261 14717626151 0016124 0 ustar 00 <?php namespace Elementor\Core\Experiments\Exceptions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Dependency_Exception extends \Exception { } app/app.php 0000644 00000000543 14717626151 0006631 0 ustar 00 <?php namespace Elementor\Core\App; use Elementor\Core\Base\App as BaseApp; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * This App class was introduced for backwards compatibility with 3rd parties. */ class App extends BaseApp { const PAGE_ID = 'elementor-app'; public function get_name() { return 'app-bc'; } } app/modules/import-export/module.php 0000644 00000001111 14717626151 0013627 0 ustar 00 <?php namespace Elementor\App\Modules\ImportExport; use Elementor\App\Modules\ImportExport\Module as Import_Export_Module; use Elementor\Core\Base\Module as BaseModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * This App class exists for backwards compatibility with 3rd parties. * * @deprecated 3.8.0 */ class Module extends BaseModule { /** * @deprecated 3.8.0 */ const VERSION = '1.0.0'; /** * @deprecated 3.8.0 */ public $import; /** * @deprecated 3.8.0 */ public function get_name() { return 'import-export-bc'; } } app/modules/kit-library/module.php 0000644 00000000721 14717626151 0013235 0 ustar 00 <?php namespace Elementor\Core\App\Modules\KitLibrary; use Elementor\Core\Base\Module as BaseModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * This App class exists for backwards compatibility with 3rd parties. * * @deprecated 3.8.0 */ class Module extends BaseModule { /** * @deprecated 3.8.0 */ const VERSION = '1.0.0'; /** * @deprecated 3.8.0 */ public function get_name() { return 'kit-library-bc'; } } app/modules/kit-library/connect/kit-library.php 0000644 00000001303 14717626151 0015627 0 ustar 00 <?php namespace Elementor\Core\App\Modules\KitLibrary\Connect; use Elementor\App\Modules\KitLibrary\Connect\Kit_Library as Kit_Library_Connect; use Elementor\Core\Common\Modules\Connect\Apps\Library; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * This App class exists for backwards compatibility with 3rd parties. * * @deprecated 3.8.0 */ class Kit_Library extends Library { /** * @deprecated 3.8.0 */ public function is_connected() { /** @var Kit_Library_Connect $kit_library */ $kit_library = Plugin::$instance->common->get_component( 'connect' )->get_app( 'kit-library' ); return $kit_library && $kit_library->is_connected(); } } app/modules/onboarding/module.php 0000644 00000000720 14717626151 0013125 0 ustar 00 <?php namespace Elementor\Core\App\Modules\Onboarding; use Elementor\Core\Base\Module as BaseModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * This App class exists for backwards compatibility with 3rd parties. * * @deprecated 3.8.0 */ class Module extends BaseModule { /** * @deprecated 3.8.0 */ const VERSION = '1.0.0'; /** * @deprecated 3.8.0 */ public function get_name() { return 'onboarding-bc'; } } admin/ui/components/button.php 0000644 00000004565 14717626151 0012506 0 ustar 00 <?php namespace Elementor\Core\Admin\UI\Components; use Elementor\Core\Base\Base_Object; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Button extends Base_Object { private $options; /** * @inheritDoc */ public function get_name() { return 'admin-button'; } public function print_button() { $options = $this->get_options(); if ( empty( $options['text'] ) ) { return; } $html_tag = ! empty( $options['url'] ) ? 'a' : 'button'; $before = ''; $icon = ''; $attributes = []; if ( ! empty( $options['icon'] ) ) { $icon = '<i class="' . esc_attr( $options['icon'] ) . '"></i>'; } $classes = $options['classes']; $default_classes = $this->get_default_options( 'classes' ); $classes = array_merge( $classes, $default_classes ); if ( ! empty( $options['type'] ) ) { $classes[] = 'e-button--' . $options['type']; } if ( ! empty( $options['variant'] ) ) { $classes[] = 'e-button--' . $options['variant']; } if ( ! empty( $options['before'] ) ) { $before = '<span>' . wp_kses_post( $options['before'] ) . '</span>'; } if ( ! empty( $options['url'] ) ) { $attributes['href'] = $options['url']; if ( $options['new_tab'] ) { $attributes['target'] = '_blank'; } } $attributes['class'] = $classes; $html = $before . '<' . $html_tag . ' ' . Utils::render_html_attributes( $attributes ) . '>'; $html .= $icon; $html .= '<span>' . sanitize_text_field( $options['text'] ) . '</span>'; $html .= '</' . $html_tag . '>'; echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * @param string $option Optional default is null * @return array|mixed */ private function get_options( $option = null ) { return $this->get_items( $this->options, $option ); } /** * @param null $option * @return array */ private function get_default_options( $option = null ) { $default_options = [ 'classes' => [ 'e-button' ], 'icon' => '', 'new_tab' => false, 'text' => '', 'type' => '', 'url' => '', 'variant' => '', 'before' => '', ]; if ( null !== $option && -1 !== in_array( $option, $default_options ) ) { return $default_options[ $option ]; } return $default_options; } public function __construct( array $options ) { $this->options = $this->merge_properties( $this->get_default_options(), $options ); } } admin/notices/elementor-dev-notice.php 0000644 00000007237 14717626151 0014061 0 ustar 00 <?php namespace Elementor\Core\Admin\Notices; use Elementor\User; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Elementor_Dev_Notice extends Base_Notice { /** * Notice ID. */ const ID = 'elementor_dev_promote'; /** * Plugin slug to install. */ const PLUGIN_SLUG = 'elementor-beta'; /** * Plugin name. */ const PLUGIN_NAME = 'elementor-beta/elementor-beta.php'; /** * Holds the plugins names. * * @var array */ private $plugins = []; /** * If one of those plugin is installed it will show the notice. * * @var string[] */ private $promotion_plugins = [ 'woocommerce-beta-tester/woocommerce-beta-tester.php', 'wp-jquery-update-test/wp-jquery-update-test.php', 'wordpress-beta-tester/wp-beta-tester.php', 'gutenberg/gutenberg.php', ]; /** * If one of those options is enabled it will show the notice. * * @var string[] */ private $promotion_options = [ 'elementor_beta', ]; /** * @inheritDoc */ public function should_print() { return current_user_can( 'install_plugins' ) && ! User::is_user_notice_viewed( static::ID ) && ! $this->is_elementor_dev_installed() && ! $this->is_install_screen() && ( $this->is_promotion_plugins_installed() || $this->is_promotion_options_enabled() ); } /** * @inheritDoc */ public function get_config() { return [ 'id' => static::ID, 'title' => esc_html__( 'Elementor Developer Edition', 'elementor' ), 'description' => __( 'Get a sneak peek at our in progress development versions, and help us improve Elementor to perfection. Developer Edition releases contain experimental functionality for testing purposes.', 'elementor' ), 'button' => [ 'text' => esc_html__( 'Install & Activate', 'elementor' ), 'url' => wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . static::PLUGIN_SLUG ), 'install-plugin_' . static::PLUGIN_SLUG ), 'type' => 'cta', ], ]; } /** * Return all the plugins names. * * This method is protected so it can be mocked in tests. * * @return array */ protected function get_plugins() { if ( ! $this->plugins ) { $this->plugins = array_keys( get_plugins() ); } return $this->plugins; } /** * Checks if elementor dev is installed * * @return bool */ private function is_elementor_dev_installed() { return in_array( static::PLUGIN_NAME, $this->get_plugins(), true ); } /** * Checks if the admin screen is install screen. * * @return bool */ private function is_install_screen() { $screen = get_current_screen(); if ( ! $screen ) { return false; } return 'update' === $screen->id; } /** * Checks if is one of the promotion plugins is installed * * @return bool */ private function is_promotion_plugins_installed() { return array_reduce( $this->promotion_plugins, function ( $should_show_notice, $plugin_name ) { if ( $should_show_notice ) { return true; } return in_array( $plugin_name, $this->get_plugins(), true ); }, false ); } /** * Checks if is one of the promotion options is enabled. * * @return bool */ private function is_promotion_options_enabled() { return array_reduce( $this->promotion_options, function ( $should_show_notice, $option ) { if ( $should_show_notice ) { return true; } return 'yes' === get_option( $option, 'no' ); }, false ); } /** * Checks if the current page is elementor settings page * * @return bool */ private function is_elementor_setting_page() { $current_screen = get_current_screen(); return $current_screen && 'toplevel_page_' . Settings::PAGE_ID === $current_screen->id; } } admin/notices/base-notice.php 0000644 00000001003 14717626151 0012206 0 ustar 00 <?php namespace Elementor\Core\Admin\Notices; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Notice { /** * Determine if the notice should be printed or not. * * @return boolean */ abstract public function should_print(); /** * Returns the config of the notice itself. * based on that config the notice will be printed. * * @see \Elementor\Core\Admin\Admin_Notices::admin_notices * * @return array */ abstract public function get_config(); } admin/menu/base.php 0000644 00000007040 14717626151 0010236 0 ustar 00 <?php namespace Elementor\Core\Admin\Menu; use Elementor\Core\Base\Base_Object; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Base extends Base_Object { private $args; private $options; private $submenus = []; abstract protected function get_init_args(); public function __construct() { $this->init_args(); $this->init_options(); add_action( 'admin_menu', function() { $this->register(); } ); if ( $this->options['separator'] ) { add_action( 'admin_menu', function() { $this->add_menu_separator(); } ); add_filter( 'custom_menu_order', '__return_true' ); add_filter( 'menu_order', function( $menu_order ) { return $this->rearrange_menu_separator( $menu_order ); } ); } } public function get_args( $arg = null ) { return self::get_items( $this->args, $arg ); } public function add_submenu( $submenu_args ) { $default_submenu_args = [ 'page_title' => '', 'capability' => $this->args['capability'], 'function' => null, 'index' => null, ]; $this->submenus[] = array_merge( $default_submenu_args, $submenu_args ); } protected function get_init_options() { return []; } protected function register_default_submenus() {} protected function register() { $args = $this->args; add_menu_page( $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'], $args['icon_url'], $args['position'] ); $this->register_default_submenus(); do_action( 'elementor/admin/menu_registered/' . $args['menu_slug'], $this ); usort( $this->submenus, function( $a, $b ) { return $a['index'] - $b['index']; } ); foreach ( $this->submenus as $index => $submenu_item ) { $submenu_args = [ $args['menu_slug'], $submenu_item['page_title'], $submenu_item['menu_title'], $submenu_item['capability'], $submenu_item['menu_slug'], $submenu_item['function'], ]; if ( 0 === $submenu_item['index'] ) { $submenu_args[] = 0; } add_submenu_page( ...$submenu_args ); if ( ! empty( $submenu_item['class'] ) ) { global $submenu; $submenu[ $args['menu_slug'] ][ $index + 1 ][4] = $submenu_item['class']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } } } private function init_args() { $default_args = [ 'function' => null, 'icon_url' => null, 'position' => null, ]; $this->args = array_merge( $default_args, $this->get_init_args() ); } private function init_options() { $default_options = [ 'separator' => false, ]; $this->options = array_merge( $default_options, $this->get_init_options() ); } private function add_menu_separator() { global $menu; $slug = $this->args['menu_slug']; $menu[] = [ '', 'read', 'separator-' . $slug, '', 'wp-menu-separator ' . $slug ]; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } private function rearrange_menu_separator( $menu_order ) { // Initialize our custom order array. $custom_menu_order = []; $slug = $this->args['menu_slug']; $separator_name = 'separator-' . $slug; // Get the index of our custom separator. $custom_separator = array_search( $separator_name, $menu_order, true ); // Loop through menu order and do some rearranging. foreach ( $menu_order as $item ) { if ( $slug === $item ) { $custom_menu_order[] = $separator_name; $custom_menu_order[] = $item; unset( $menu_order[ $custom_separator ] ); } elseif ( $separator_name !== $item ) { $custom_menu_order[] = $item; } } // Return order. return $custom_menu_order; } } admin/menu/admin-menu-manager.php 0000644 00000005367 14717626151 0013000 0 ustar 00 <?php namespace Elementor\Core\Admin\Menu; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_Has_Position; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Admin_Menu_Manager { /** * @var Admin_Menu_Item[] */ private $items = []; public function register( $item_slug, Admin_Menu_Item $item ) { $this->items[ $item_slug ] = $item; } public function unregister( $item_slug ) { unset( $this->items[ $item_slug ] ); } public function get( $item_slug ) { if ( empty( $this->items[ $item_slug ] ) ) { return null; } return $this->items[ $item_slug ]; } public function get_all() { return $this->items; } public function register_actions() { add_action( 'admin_menu', function () { $this->register_wp_menus(); }, 20 ); add_action( 'admin_head', function () { $this->hide_invisible_menus(); } ); } private function register_wp_menus() { do_action( 'elementor/admin/menu/register', $this ); $hooks = []; foreach ( $this->get_all() as $item_slug => $item ) { $is_top_level = empty( $item->get_parent_slug() ); if ( $is_top_level ) { $hooks[ $item_slug ] = $this->register_top_level_menu( $item_slug, $item ); } else { $hooks[ $item_slug ] = $this->register_sub_menu( $item_slug, $item ); } } do_action( 'elementor/admin/menu/after_register', $this, $hooks ); } private function register_top_level_menu( $item_slug, Admin_Menu_Item $item ) { $has_page = ( $item instanceof Admin_Menu_Item_With_Page ); $has_position = ( $item instanceof Admin_Menu_Item_Has_Position ); $page_title = $has_page ? $item->get_page_title() : ''; $callback = $has_page ? [ $item, 'render' ] : ''; $position = $has_position ? $item->get_position() : null; return add_menu_page( $page_title, $item->get_label(), $item->get_capability(), $item_slug, $callback, '', $position ); } private function register_sub_menu( $item_slug, Admin_Menu_Item $item ) { $has_page = ( $item instanceof Admin_Menu_Item_With_Page ); $page_title = $has_page ? $item->get_page_title() : ''; $callback = $has_page ? [ $item, 'render' ] : ''; return add_submenu_page( $item->get_parent_slug(), $page_title, $item->get_label(), $item->get_capability(), $item_slug, $callback ); } private function hide_invisible_menus() { foreach ( $this->get_all() as $item_slug => $item ) { if ( $item->is_visible() ) { continue; } $is_top_level = empty( $item->get_parent_slug() ); if ( $is_top_level ) { remove_menu_page( $item_slug ); } else { remove_submenu_page( $item->get_parent_slug(), $item_slug ); } } } } admin/menu/main.php 0000644 00000004011 14717626151 0010243 0 ustar 00 <?php namespace Elementor\Core\Admin\Menu; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Tools; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Main extends Base { protected function get_init_args() { return [ 'page_title' => esc_html__( 'Elementor', 'elementor' ), 'menu_title' => esc_html__( 'Elementor', 'elementor' ), 'capability' => 'manage_options', 'menu_slug' => 'elementor', 'function' => [ Plugin::$instance->settings, 'display_settings_page' ], 'position' => 58.5, ]; } protected function get_init_options() { return [ 'separator' => true, ]; } protected function register_default_submenus() { $this->add_submenu( [ 'page_title' => esc_html_x( 'Templates', 'Template Library', 'elementor' ), 'menu_title' => esc_html_x( 'Templates', 'Template Library', 'elementor' ), 'menu_slug' => Source_Local::ADMIN_MENU_SLUG, 'index' => 0, ] ); $this->add_submenu( [ 'menu_title' => esc_html__( 'Help', 'elementor' ), 'menu_slug' => 'go_knowledge_base_site', 'function' => [ Plugin::$instance->settings, 'handle_external_redirects' ], 'index' => 150, ] ); } protected function register() { parent::register(); $this->rearrange_elementor_submenu(); } private function rearrange_elementor_submenu() { global $submenu; $elementor_menu_slug = $this->get_args( 'menu_slug' ); $elementor_submenu_old_index = null; $tools_submenu_index = null; foreach ( $submenu[ $elementor_menu_slug ] as $index => $submenu_item ) { if ( $elementor_menu_slug === $submenu_item[2] ) { $elementor_submenu_old_index = $index; } elseif ( Tools::PAGE_ID === $submenu_item[2] ) { $tools_submenu_index = $index; break; } } $elementor_submenu = array_splice( $submenu[ $elementor_menu_slug ], $elementor_submenu_old_index, 1 ); $elementor_submenu[0][0] = esc_html__( 'Settings', 'elementor' ); array_splice( $submenu[ $elementor_menu_slug ], $tools_submenu_index, 0, $elementor_submenu ); } } admin/menu/interfaces/admin-menu-item-with-page.php 0000644 00000000376 14717626151 0016325 0 ustar 00 <?php namespace Elementor\Core\Admin\Menu\Interfaces; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } interface Admin_Menu_Item_With_Page extends Admin_Menu_Item { public function get_page_title(); public function render(); } admin/menu/interfaces/admin-menu-item-has-position.php 0000644 00000000313 14717626151 0017044 0 ustar 00 <?php namespace Elementor\Core\Admin\Menu\Interfaces; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } interface Admin_Menu_Item_Has_Position { public function get_position(); } admin/menu/interfaces/admin-menu-item.php 0000644 00000000444 14717626151 0014436 0 ustar 00 <?php namespace Elementor\Core\Admin\Menu\Interfaces; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } interface Admin_Menu_Item { public function get_capability(); public function get_label(); public function get_parent_slug(); public function is_visible(); } admin/canary-deployment.php 0000644 00000011756 14717626151 0012024 0 ustar 00 <?php namespace Elementor\Core\Admin; use Elementor\Api; use Elementor\Core\Base\Module; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } // TODO: Move this class to pro version for better architecture. class Canary_Deployment extends Module { const CURRENT_VERSION = ELEMENTOR_VERSION; const PLUGIN_BASE = ELEMENTOR_PLUGIN_BASE; private $canary_deployment_info = null; /** * Get module name. * * Retrieve the module name. * * @since 2.6.0 * @access public * * @return string Module name. */ public function get_name() { return 'canary-deployment'; } /** * Check version. * * @since 2.6.0 * @access public * * @param object $transient Plugin updates data. * * @return object Plugin updates data. */ public function check_version( $transient ) { // First transient before the real check. if ( ! isset( $transient->response ) ) { return $transient; } // Placeholder $stable_version = '0.0.0'; if ( ! empty( $transient->response[ static::PLUGIN_BASE ]->new_version ) ) { $stable_version = $transient->response[ static::PLUGIN_BASE ]->new_version; } if ( null === $this->canary_deployment_info ) { $this->canary_deployment_info = $this->get_canary_deployment_info(); } // Can be false - if canary version is not available. if ( empty( $this->canary_deployment_info ) ) { return $transient; } if ( ! version_compare( $this->canary_deployment_info['new_version'], $stable_version, '>' ) ) { return $transient; } $canary_deployment_info = $this->canary_deployment_info; // Most of plugin info comes from the $transient but on first check - the response is empty. if ( ! empty( $transient->response[ static::PLUGIN_BASE ] ) ) { $canary_deployment_info = array_merge( (array) $transient->response[ static::PLUGIN_BASE ], $canary_deployment_info ); } $transient->response[ static::PLUGIN_BASE ] = (object) $canary_deployment_info; return $transient; } protected function get_canary_deployment_remote_info( $force ) { return Api::get_canary_deployment_info( $force ); } private function get_canary_deployment_info() { global $pagenow; $force = 'update-core.php' === $pagenow && isset( $_GET['force-check'] ); $canary_deployment = $this->get_canary_deployment_remote_info( $force ); if ( empty( $canary_deployment['plugin_info']['new_version'] ) ) { return false; } $canary_version = $canary_deployment['plugin_info']['new_version']; if ( version_compare( $canary_version, static::CURRENT_VERSION, '<=' ) ) { return false; } if ( ! empty( $canary_deployment['conditions'] ) && ! $this->check_conditions( $canary_deployment['conditions'] ) ) { return false; } return $canary_deployment['plugin_info']; } private function check_conditions( $groups ) { foreach ( $groups as $group ) { if ( $this->check_group( $group ) ) { return true; } } return false; } private function check_group( $group ) { $is_or_relation = ! empty( $group['relation'] ) && 'OR' === $group['relation']; unset( $group['relation'] ); $result = false; foreach ( $group as $condition ) { // Reset results for each condition. $result = false; switch ( $condition['type'] ) { case 'wordpress': // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled // include an unmodified $wp_version include ABSPATH . WPINC . '/version.php'; $result = version_compare( $wp_version, $condition['version'], $condition['operator'] ); break; case 'multisite': $result = is_multisite() === $condition['multisite']; break; case 'language': $in_array = in_array( get_locale(), $condition['languages'], true ); $result = 'in' === $condition['operator'] ? $in_array : ! $in_array; break; case 'plugin': if ( ! empty( $condition['plugin_file'] ) ) { $plugin_file = $condition['plugin_file']; // For PHP Unit tests. } else { $plugin_file = WP_PLUGIN_DIR . '/' . $condition['plugin']; // Default. } $version = ''; if ( is_plugin_active( $condition['plugin'] ) && file_exists( $plugin_file ) ) { $plugin_data = get_plugin_data( $plugin_file ); if ( isset( $plugin_data['Version'] ) ) { $version = $plugin_data['Version']; } } $result = version_compare( $version, $condition['version'], $condition['operator'] ); break; case 'theme': $theme = wp_get_theme(); if ( wp_get_theme()->parent() ) { $theme = wp_get_theme()->parent(); } if ( $theme->get_template() === $condition['theme'] ) { $version = $theme->version; } else { $version = ''; } $result = version_compare( $version, $condition['version'], $condition['operator'] ); break; } if ( ( $is_or_relation && $result ) || ( ! $is_or_relation && ! $result ) ) { return $result; } } return $result; } /** * @since 2.6.0 * @access public */ public function __construct() { add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_version' ] ); } } admin/admin-notices.php 0000644 00000047366 14717626151 0011131 0 ustar 00 <?php namespace Elementor\Core\Admin; use Elementor\Api; use Elementor\Core\Admin\UI\Components\Button; use Elementor\Core\Base\Module; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Plugin; use Elementor\Settings; use Elementor\Tracker; use Elementor\User; use Elementor\Utils; use Elementor\Core\Admin\Notices\Base_Notice; use Elementor\Core\Admin\Notices\Elementor_Dev_Notice; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin_Notices extends Module { const DEFAULT_EXCLUDED_PAGES = [ 'plugins.php', 'plugin-install.php', 'plugin-editor.php' ]; private $plain_notices = [ 'api_notice', 'api_upgrade_plugin', 'tracker', 'rate_us_feedback', 'role_manager_promote', 'experiment_promotion', 'site_mailer_promotion', 'design_not_appearing', 'plugin_image_optimization', ]; private $elementor_pages_count = null; private $install_time = null; private $current_screen_id = null; private function get_notices() { $notices = [ new Elementor_Dev_Notice(), ]; /** * Admin notices. * * Filters Elementor admin notices. * * This hook can be used by external developers to manage existing * admin notice or to add new notices for Elementor add-ons. * * @param array $notices A list of notice classes. */ $notices = apply_filters( 'elementor/core/admin/notices', $notices ); return $notices; } private function get_install_time() { if ( null === $this->install_time ) { $this->install_time = Plugin::$instance->get_install_time(); } return $this->install_time; } private function get_elementor_pages_count() { if ( null === $this->elementor_pages_count ) { $elementor_pages = new \WP_Query( [ 'no_found_rows' => true, 'post_type' => 'any', 'post_status' => 'publish', 'fields' => 'ids', 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'meta_key' => '_elementor_edit_mode', 'meta_value' => 'builder', ] ); $this->elementor_pages_count = $elementor_pages->post_count; } return $this->elementor_pages_count; } private function notice_api_upgrade_plugin() { $upgrade_notice = Api::get_upgrade_notice(); if ( empty( $upgrade_notice ) ) { return false; } if ( ! current_user_can( 'update_plugins' ) ) { return false; } if ( ! in_array( $this->current_screen_id, [ 'toplevel_page_elementor', 'edit-elementor_library', 'elementor_page_elementor-system-info', 'dashboard' ], true ) ) { return false; } // Check for upgrades. $update_plugins = get_site_transient( 'update_plugins' ); $has_remote_update_package = ! ( empty( $update_plugins ) || empty( $update_plugins->response[ ELEMENTOR_PLUGIN_BASE ] ) || empty( $update_plugins->response[ ELEMENTOR_PLUGIN_BASE ]->package ) ); if ( ! $has_remote_update_package && empty( $upgrade_notice['update_link'] ) ) { return false; } if ( $has_remote_update_package ) { $product = $update_plugins->response[ ELEMENTOR_PLUGIN_BASE ]; $details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $product->slug . '§ion=changelog&TB_iframe=true&width=600&height=800' ); $upgrade_url = wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . ELEMENTOR_PLUGIN_BASE ), 'upgrade-plugin_' . ELEMENTOR_PLUGIN_BASE ); $new_version = $product->new_version; } else { $upgrade_url = $upgrade_notice['update_link']; $details_url = $upgrade_url; $new_version = $upgrade_notice['version']; } // Check if upgrade messages should be shown. if ( version_compare( ELEMENTOR_VERSION, $upgrade_notice['version'], '>=' ) ) { return false; } $notice_id = 'upgrade_notice_' . $upgrade_notice['version']; if ( User::is_user_notice_viewed( $notice_id ) ) { return false; } $message = sprintf( /* translators: 1: Details URL, 2: Accessibility text, 3: Version number, 4: Update URL, 5: Accessibility text. */ __( 'There is a new version of Elementor Page Builder available. <a href="%1$s" class="thickbox open-plugin-details-modal" aria-label="%2$s">View version %3$s details</a> or <a href="%4$s" class="update-link" aria-label="%5$s">update now</a>.', 'elementor' ), esc_url( $details_url ), esc_attr( sprintf( /* translators: %s: Elementor version. */ __( 'View Elementor version %s details', 'elementor' ), $new_version ) ), $new_version, esc_url( $upgrade_url ), esc_attr( esc_html__( 'Update Now', 'elementor' ) ) ); $options = [ 'title' => esc_html__( 'Update Notification', 'elementor' ), 'description' => $message, 'button' => [ 'icon_classes' => 'dashicons dashicons-update', 'text' => esc_html__( 'Update Now', 'elementor' ), 'url' => $upgrade_url, ], 'id' => $notice_id, ]; $this->print_admin_notice( $options ); return true; } private function notice_api_notice() { $admin_notice = Api::get_admin_notice(); if ( empty( $admin_notice ) ) { return false; } if ( ! current_user_can( 'manage_options' ) ) { return false; } if ( ! in_array( $this->current_screen_id, [ 'toplevel_page_elementor', 'edit-elementor_library', 'elementor_page_elementor-system-info', 'dashboard' ], true ) ) { return false; } $notice_id = 'admin_notice_api_' . $admin_notice['notice_id']; if ( User::is_user_notice_viewed( $notice_id ) ) { return false; } $options = [ 'title' => esc_html__( 'Update Notification', 'elementor' ), 'description' => $admin_notice['notice_text'], 'id' => $notice_id, ]; $this->print_admin_notice( $options ); return true; } private function notice_tracker() { if ( ! current_user_can( 'manage_options' ) ) { return false; } // Show tracker notice after 24 hours from installed time. if ( strtotime( '+24 hours', $this->get_install_time() ) > time() ) { return false; } if ( '1' === get_option( 'elementor_tracker_notice' ) ) { return false; } if ( Tracker::is_allow_track() ) { return false; } if ( 2 > $this->get_elementor_pages_count() ) { return false; } // TODO: Skip for development env. $optin_url = wp_nonce_url( add_query_arg( 'elementor_tracker', 'opt_into' ), 'opt_into' ); $optout_url = wp_nonce_url( add_query_arg( 'elementor_tracker', 'opt_out' ), 'opt_out' ); $tracker_description_text = esc_html__( 'Become a super contributor by opting in to share non-sensitive plugin data and to receive periodic email updates from us.', 'elementor' ); /** * Tracker admin description text. * * Filters the admin notice text for non-sensitive data collection. * * @since 1.0.0 * * @param string $tracker_description_text Description text displayed in admin notice. */ $tracker_description_text = apply_filters( 'elementor/tracker/admin_description_text', $tracker_description_text ); $message = esc_html( $tracker_description_text ) . ' <a href="https://go.elementor.com/usage-data-tracking/" target="_blank">' . esc_html__( 'Learn more.', 'elementor' ) . '</a>'; $options = [ 'title' => esc_html__( 'Love using Elementor?', 'elementor' ), 'description' => $message, 'button' => [ 'text' => esc_html__( 'Sure! I\'d love to help', 'elementor' ), 'url' => $optin_url, 'type' => 'cta', ], 'button_secondary' => [ 'text' => esc_html__( 'No thanks', 'elementor' ), 'url' => $optout_url, 'variant' => 'outline', 'type' => 'cta', ], ]; $this->print_admin_notice( $options ); return true; } private function notice_rate_us_feedback() { $notice_id = 'rate_us_feedback'; if ( ! current_user_can( 'manage_options' ) ) { return false; } if ( 'dashboard' !== $this->current_screen_id || User::is_user_notice_viewed( $notice_id ) ) { return false; } if ( 10 >= $this->get_elementor_pages_count() ) { return false; } $dismiss_url = add_query_arg( [ 'action' => 'elementor_set_admin_notice_viewed', 'notice_id' => esc_attr( $notice_id ), ], admin_url( 'admin-post.php' ) ); $options = [ 'title' => esc_html__( 'Congrats!', 'elementor' ), 'description' => esc_html__( 'You created over 10 pages with Elementor. Great job! If you can spare a minute, please help us by leaving a five star review on WordPress.org.', 'elementor' ), 'id' => $notice_id, 'button' => [ 'text' => esc_html__( 'Happy To Help', 'elementor' ), 'url' => 'https://go.elementor.com/admin-review/', 'new_tab' => true, 'type' => 'cta', ], 'button_secondary' => [ 'text' => esc_html__( 'Hide Notification', 'elementor' ), 'classes' => [ 'e-notice-dismiss' ], 'url' => esc_url_raw( $dismiss_url ), 'new_tab' => true, 'type' => 'cta', ], ]; $this->print_admin_notice( $options ); return true; } private function notice_role_manager_promote() { $notice_id = 'role_manager_promote'; if ( Utils::has_pro() ) { return false; } if ( ! current_user_can( 'manage_options' ) ) { return false; } if ( 'elementor_page_elementor-role-manager' !== $this->current_screen_id || User::is_user_notice_viewed( $notice_id ) ) { return false; } $users = new \WP_User_Query( [ 'fields' => 'ID', 'number' => 10, ] ); if ( 5 > $users->get_total() ) { return false; } $options = [ 'title' => esc_html__( 'Managing a multi-user site?', 'elementor' ), 'description' => esc_html__( 'With Elementor Pro, you can control user access and make sure no one messes up your design.', 'elementor' ), 'id' => $notice_id, 'button' => [ 'text' => esc_html__( 'Learn More', 'elementor' ), 'url' => 'https://go.elementor.com/plugin-promotion-role-manager/', 'new_tab' => true, 'type' => 'cta', ], ]; $options = Filtered_Promotions_Manager::get_filtered_promotion_data( $options, 'core/admin/notice_role_manager_promote', 'button', 'url' ); $this->print_admin_notice( $options ); return true; } private function notice_experiment_promotion() { $notice_id = 'experiment_promotion'; if ( ! current_user_can( 'manage_options' ) || User::is_user_notice_viewed( $notice_id ) ) { return false; } $experiments = Plugin::$instance->experiments; $is_all_performance_features_active = ( $experiments->is_feature_active( 'e_element_cache' ) && $experiments->is_feature_active( 'e_font_icon_svg' ) ); if ( $is_all_performance_features_active ) { return false; } $options = [ 'title' => esc_html__( 'Improve your site’s performance score.', 'elementor' ), 'description' => esc_html__( 'With our experimental speed boosting features you can go faster than ever before. Look for the Performance label on our Experiments page and activate those experiments to improve your site loading speed.', 'elementor' ), 'id' => $notice_id, 'button' => [ 'text' => esc_html__( 'Try it out', 'elementor' ), 'url' => Settings::get_settings_tab_url( 'experiments' ), 'type' => 'cta', ], 'button_secondary' => [ 'text' => esc_html__( 'Learn more', 'elementor' ), 'url' => 'https://go.elementor.com/wp-dash-experiment-promotion/', 'new_tab' => true, 'type' => 'cta', ], ]; $this->print_admin_notice( $options ); return true; } private function notice_site_mailer_promotion() { $notice_id = 'site_mailer_promotion'; if ( ! defined( 'WPFORMS_VERSION' ) && ! defined( 'WPCF7_VERSION' ) && ! defined( 'FLUENTFORM_VERSION' ) && ! class_exists( '\GFCommon' ) && ! class_exists( '\Ninja_Forms' ) && ! function_exists( 'load_formidable_forms' ) ) { return false; } if ( ! $this->is_elementor_page() && ! in_array( $this->current_screen_id, [ 'toplevel_page_elementor', 'edit-elementor_library', 'dashboard' ], true ) ) { return false; } if ( Utils::has_pro() || ! current_user_can( 'install_plugins' ) || User::is_user_notice_viewed( $notice_id ) ) { return false; } $plugin_file_path = 'site-mailer/site-mailer.php'; $plugin_slug = 'site-mailer'; $cta_data = $this->get_plugin_cta_data( $plugin_slug, $plugin_file_path ); if ( empty( $cta_data ) ) { return false; } $options = [ 'title' => esc_html__( 'Ensure your form emails avoid the spam folder!', 'elementor' ), 'description' => esc_html__( 'Use Site Mailer for improved email deliverability, detailed email logs, and an easy setup.', 'elementor' ), 'id' => $notice_id, 'type' => 'cta', 'button' => [ 'text' => $cta_data['text'], 'url' => $cta_data['url'], 'type' => 'cta', ], 'button_secondary' => [ 'text' => esc_html__( 'Learn more', 'elementor' ), 'url' => 'https://go.elementor.com/sm-core-form/', 'new_tab' => true, 'type' => 'cta', ], ]; $this->print_admin_notice( $options ); return true; } private function is_elementor_page(): bool { return 0 === strpos( $this->current_screen_id, 'elementor_page' ); } private function get_plugin_cta_data( $plugin_slug, $plugin_file_path ) { if ( is_plugin_active( $plugin_file_path ) ) { return false; } if ( $this->is_plugin_installed( $plugin_file_path ) ) { $url = wp_nonce_url( 'plugins.php?action=activate&plugin=' . $plugin_file_path . '&plugin_status=all&paged=1&s', 'activate-plugin_' . $plugin_file_path ); $cta_text = esc_html__( 'Activate Plugin', 'elementor' ); } else { $url = wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $plugin_slug ), 'install-plugin_' . $plugin_slug ); $cta_text = esc_html__( 'Install Plugin', 'elementor' ); } return [ 'url' => $url, 'text' => $cta_text, ]; } private function notice_design_not_appearing() { $installs_history = get_option( 'elementor_install_history', [] ); $is_first_install = 1 === count( $installs_history ); if ( $is_first_install || ! current_user_can( 'update_plugins' ) ) { return false; } $notice_id = 'design_not_appearing'; $notice = User::get_user_notices()[ $notice_id ] ?? []; $notice_version = $notice['meta']['version'] ?? null; $is_version_changed = $this->get_elementor_version() !== $notice_version; if ( $is_version_changed ) { User::set_user_notice( $notice_id, false, [ 'version' => $this->get_elementor_version() ] ); } if ( ! in_array( $this->current_screen_id, [ 'toplevel_page_elementor', 'edit-elementor_library', 'elementor_page_elementor-system-info', 'dashboard', 'update-core', 'plugins' ], true ) ) { return false; } if ( User::is_user_notice_viewed( $notice_id ) ) { return false; } $options = [ 'title' => esc_html__( 'The version was updated successfully!', 'elementor' ), 'description' => sprintf( esc_html__( 'Encountering issues after updating the version? Don’t worry - we’ve collected all the fixes for troubleshooting common issues. %1$sFind a solution%2$s', 'elementor' ), '<a href="https://go.elementor.com/wp-dash-changes-do-not-appear-online/" target="_blank">', '</a>' ), 'id' => $notice_id, ]; $excluded_pages = []; $this->print_admin_notice( $options, $excluded_pages ); return true; } // For testing purposes public function get_elementor_version() { return ELEMENTOR_VERSION; } private function notice_plugin_image_optimization() { $notice_id = 'plugin_image_optimization'; if ( 'upload' !== $this->current_screen_id ) { return false; } if ( ! current_user_can( 'manage_options' ) || User::is_user_notice_viewed( $notice_id ) ) { return false; } $attachments = new \WP_Query( [ 'post_type' => 'attachment', 'post_status' => 'any', 'fields' => 'ids', ] ); if ( 1 > $attachments->found_posts ) { return false; } $plugin_file_path = 'image-optimization/image-optimization.php'; $plugin_slug = 'image-optimization'; $cta_data = $this->get_plugin_cta_data( $plugin_slug, $plugin_file_path ); if ( empty( $cta_data ) ) { return false; } $options = [ 'title' => esc_html__( 'Speed up your website with Image Optimizer by Elementor', 'elementor' ), 'description' => esc_html__( 'Automatically compress and optimize images, resize larger files, or convert to WebP. Optimize images individually, in bulk, or on upload.', 'elementor' ), 'id' => $notice_id, 'type' => 'cta', 'button_secondary' => [ 'text' => $cta_data['text'], 'url' => $cta_data['url'], 'type' => 'cta', ], ]; $this->print_admin_notice( $options ); return true; } private function is_plugin_installed( $file_path ): bool { $installed_plugins = get_plugins(); return isset( $installed_plugins[ $file_path ] ); } public function print_admin_notice( array $options, $exclude_pages = self::DEFAULT_EXCLUDED_PAGES ) { global $pagenow; if ( in_array( $pagenow, $exclude_pages, true ) ) { return; } $default_options = [ 'id' => null, 'title' => '', 'description' => '', 'classes' => [ 'notice', 'e-notice' ], // We include WP's default notice class so it will be properly handled by WP's js handler 'type' => '', 'dismissible' => true, 'icon' => 'eicon-elementor', 'button' => [], 'button_secondary' => [], ]; $options = array_replace_recursive( $default_options, $options ); $notice_classes = $options['classes']; $dismiss_button = ''; $icon = ''; if ( $options['type'] ) { $notice_classes[] = 'e-notice--' . $options['type']; } if ( $options['dismissible'] ) { $label = esc_html__( 'Dismiss this notice.', 'elementor' ); $notice_classes[] = 'e-notice--dismissible'; $dismiss_button = '<i class="e-notice__dismiss" role="button" aria-label="' . $label . '" tabindex="0"></i>'; } if ( $options['icon'] ) { $notice_classes[] = 'e-notice--extended'; $icon = '<div class="e-notice__icon-wrapper"><i class="' . esc_attr( $options['icon'] ) . '" aria-hidden="true"></i></div>'; } $wrapper_attributes = [ 'class' => $notice_classes, ]; if ( $options['id'] ) { $wrapper_attributes['data-notice_id'] = $options['id']; } ?> <div <?php Utils::print_html_attributes( $wrapper_attributes ); ?>> <?php echo $dismiss_button; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> <div class="e-notice__aside"> <?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </div> <div class="e-notice__content"> <?php if ( $options['title'] ) { ?> <h3><?php echo wp_kses_post( $options['title'] ); ?></h3> <?php } ?> <?php if ( $options['description'] ) { ?> <p><?php echo wp_kses_post( $options['description'] ); ?></p> <?php } ?> <?php if ( ! empty( $options['button']['text'] ) || ! empty( $options['button_secondary']['text'] ) ) { ?> <div class="e-notice__actions"> <?php foreach ( [ $options['button'], $options['button_secondary'] ] as $index => $button_settings ) { if ( empty( $button_settings['variant'] ) && $index ) { $button_settings['variant'] = 'outline'; } if ( empty( $button_settings['text'] ) ) { continue; } $button = new Button( $button_settings ); $button->print_button(); } ?> </div> <?php } ?> </div> </div> <?php } public function admin_notices() { $this->install_time = Plugin::$instance->get_install_time(); $this->current_screen_id = get_current_screen()->id; foreach ( $this->plain_notices as $notice ) { $method_callback = "notice_{$notice}"; if ( $this->$method_callback() ) { return; } } /** @var Base_Notice $notice_instance */ foreach ( $this->get_notices() as $notice_instance ) { if ( ! $notice_instance->should_print() ) { continue; } $this->print_admin_notice( $notice_instance->get_config() ); // It exits the method to make sure it prints only one notice. return; } } /** * @since 2.9.0 * @access public */ public function __construct() { add_action( 'admin_notices', [ $this, 'admin_notices' ], 20 ); } /** * Get module name. * * Retrieve the module name. * * @since 2.9.0 * @access public * * @return string Module name. */ public function get_name() { return 'admin-notices'; } } admin/admin.php 0000644 00000100240 14717626151 0007444 0 ustar 00 <?php namespace Elementor\Core\Admin; use Elementor\Api; use Elementor\Beta_Testers; use Elementor\Core\Admin\Menu\Main as MainMenu; use Elementor\App\Modules\Onboarding\Module as Onboarding_Module; use Elementor\Core\Base\App; use Elementor\Core\Upgrade\Manager as Upgrade_Manager; use Elementor\Core\Utils\Assets_Config_Provider; use Elementor\Core\Utils\Collection; use Elementor\Plugin; use Elementor\Settings; use Elementor\User; use Elementor\Utils; use Elementor\Core\Utils\Hints; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin extends App { private $menus = []; /** * Get module name. * * Retrieve the module name. * * @since 2.3.0 * @access public * * @return string Module name. */ public function get_name() { return 'admin'; } /** * @since 2.2.0 * @access public */ public function maybe_redirect_to_getting_started() { if ( ! get_transient( 'elementor_activation_redirect' ) ) { return; } if ( wp_doing_ajax() ) { return; } delete_transient( 'elementor_activation_redirect' ); if ( is_network_admin() || isset( $_GET['activate-multi'] ) ) { return; } $already_had_onboarding = get_option( Onboarding_Module::ONBOARDING_OPTION ); // Get the latest installation from Elementor's Install log in the DB. $latest_install = key( Upgrade_Manager::get_installs_history() ); if ( ! empty( $latest_install ) ) { $is_new_install = version_compare( $latest_install, '3.6.0-beta', '>=' ); } else { // If `$latest_install` is not set, Elementor was never installed on this site. $is_new_install = true; } if ( $already_had_onboarding || ! $is_new_install ) { return; } wp_safe_redirect( admin_url( 'admin.php?page=elementor-app#onboarding' ) ); exit; } private function register_packages() { $assets_config_provider = ( new Assets_Config_Provider() ) ->set_path_resolver( function ( $name ) { return ELEMENTOR_ASSETS_PATH . "js/packages/{$name}/{$name}.asset.php"; } ); Collection::make( [ 'ui', 'icons', 'query' ] ) ->each( function( $package ) use ( $assets_config_provider ) { $suffix = Utils::is_script_debug() ? '' : '.min'; $config = $assets_config_provider->load( $package )->get( $package ); if ( ! $config ) { return; } wp_register_script( $config['handle'], ELEMENTOR_ASSETS_URL . "js/packages/{$package}/{$package}{$suffix}.js", $config['deps'], ELEMENTOR_VERSION, true ); } ); } /** * Enqueue admin scripts. * * Registers all the admin scripts and enqueues them. * * Fired by `admin_enqueue_scripts` action. * * @since 1.0.0 * @access public */ public function enqueue_scripts() { wp_register_script( 'elementor-admin-modules', $this->get_js_assets_url( 'admin-modules' ), [], ELEMENTOR_VERSION, true ); $this->register_packages(); // Temporary solution for the admin. wp_register_script( 'elementor-ai-admin', $this->get_js_assets_url( 'ai-admin' ), [ 'elementor-common', 'elementor-v2-ui', 'elementor-v2-icons', ], ELEMENTOR_VERSION, true ); wp_register_script( 'elementor-admin', $this->get_js_assets_url( 'admin' ), [ 'elementor-common', 'elementor-admin-modules', ], ELEMENTOR_VERSION, true ); wp_enqueue_script( 'elementor-admin' ); wp_set_script_translations( 'elementor-admin', 'elementor' ); $this->maybe_enqueue_hints(); $this->print_config(); } /** * Enqueue admin styles. * * Registers all the admin styles and enqueues them. * * Fired by `admin_enqueue_scripts` action. * * @since 1.0.0 * @access public */ public function enqueue_styles() { $direction_suffix = is_rtl() ? '-rtl' : ''; wp_register_style( 'elementor-admin', $this->get_css_assets_url( 'admin' . $direction_suffix ), [ 'elementor-common', ], ELEMENTOR_VERSION ); wp_enqueue_style( 'elementor-admin' ); // It's for upgrade notice. // TODO: enqueue this just if needed. add_thickbox(); } /** * Print switch mode button. * * Adds a switch button in post edit screen (which has cpt support). To allow * the user to switch from the native WordPress editor to Elementor builder. * * Fired by `edit_form_after_title` action. * * @since 1.0.0 * @access public * * @param \WP_Post $post The current post object. */ public function print_switch_mode_button( $post ) { // Exit if Gutenberg are active. if ( did_action( 'enqueue_block_editor_assets' ) ) { return; } $document = Plugin::$instance->documents->get( $post->ID ); if ( ! $document || ! $document->is_editable_by_current_user() ) { return; } wp_nonce_field( basename( __FILE__ ), '_elementor_edit_mode_nonce' ); ?> <div id="elementor-switch-mode"> <input id="elementor-switch-mode-input" type="hidden" name="_elementor_post_mode" value="<?php echo esc_attr( $document->is_built_with_elementor() ); ?>" /> <button id="elementor-switch-mode-button" type="button" class="button button-primary button-hero"> <span class="elementor-switch-mode-on"> <i class="eicon-arrow-<?php echo ( is_rtl() ) ? 'right' : 'left'; ?>" aria-hidden="true"></i> <?php echo esc_html__( 'Back to WordPress Editor', 'elementor' ); ?> </span> <span class="elementor-switch-mode-off"> <i class="eicon-elementor-square" aria-hidden="true"></i> <?php echo esc_html__( 'Edit with Elementor', 'elementor' ); ?> </span> </button> </div> <div id="elementor-editor"> <a id="elementor-go-to-edit-page-link" href="<?php echo esc_url( $document->get_edit_url() ); ?>"> <div id="elementor-editor-button" class="button button-primary button-hero"> <i class="eicon-elementor-square" aria-hidden="true"></i> <?php echo esc_html__( 'Edit with Elementor', 'elementor' ); ?> </div> <div class="elementor-loader-wrapper"> <div class="elementor-loader"> <div class="elementor-loader-boxes"> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> </div> </div> <div class="elementor-loading-title"><?php echo esc_html__( 'Loading', 'elementor' ); ?></div> </div> </a> </div> <?php } /** * Save post. * * Flag the post mode when the post is saved. * * Fired by `save_post` action. * * @since 1.0.0 * @access public * * @param int $post_id Post ID. */ public function save_post( $post_id ) { if ( ! wp_verify_nonce( Utils::get_super_global_value( $_POST, '_elementor_edit_mode_nonce' ), basename( __FILE__ ) ) ) { return; } if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } Plugin::$instance->documents->get( $post_id )->set_is_built_with_elementor( ! empty( $_POST['_elementor_post_mode'] ) ); } /** * Add Elementor post state. * * Adds a new "Elementor" post state to the post table. * * Fired by `display_post_states` filter. * * @since 1.8.0 * @access public * * @param array $post_states An array of post display states. * @param \WP_Post $post The current post object. * * @return array A filtered array of post display states. */ public function add_elementor_post_state( $post_states, $post ) { $document = Plugin::$instance->documents->get( $post->ID ); if ( $document && $document->is_built_with_elementor() && $document->is_editable_by_current_user() ) { $post_states['elementor'] = esc_html__( 'Elementor', 'elementor' ); } return $post_states; } /** * Body status classes. * * Adds CSS classes to the admin body tag. * * Fired by `admin_body_class` filter. * * @since 1.0.0 * @access public * * @param string $classes Space-separated list of CSS classes. * * @return string Space-separated list of CSS classes. */ public function body_status_classes( $classes ) { global $pagenow; if ( in_array( $pagenow, [ 'post.php', 'post-new.php' ], true ) && Utils::is_post_support() ) { $post = get_post(); $document = Plugin::$instance->documents->get( $post->ID ); $mode_class = $document && $document->is_built_with_elementor() ? 'elementor-editor-active' : 'elementor-editor-inactive'; $classes .= ' ' . $mode_class; } return $classes; } /** * Plugin action links. * * Adds action links to the plugin list table * * Fired by `plugin_action_links` filter. * * @since 1.0.0 * @access public * * @param array $links An array of plugin action links. * * @return array An array of plugin action links. */ public function plugin_action_links( $links ) { $settings_link = sprintf( '<a href="%1$s">%2$s</a>', admin_url( 'admin.php?page=' . Settings::PAGE_ID ), esc_html__( 'Settings', 'elementor' ) ); array_unshift( $links, $settings_link ); $go_pro_text = esc_html__( 'Get Elementor Pro', 'elementor' ); if ( Utils::is_sale_time() ) { $go_pro_text = esc_html__( 'Discounted Upgrades Now!', 'elementor' ); } $links['go_pro'] = sprintf( '<a href="%1$s" target="_blank" class="elementor-plugins-gopro">%2$s</a>', 'https://go.elementor.com/go-pro-wp-plugins/', $go_pro_text ); return $links; } /** * Plugin row meta. * * Adds row meta links to the plugin list table * * Fired by `plugin_row_meta` filter. * * @since 1.1.4 * @access public * * @param array $plugin_meta An array of the plugin's metadata, including * the version, author, author URI, and plugin URI. * @param string $plugin_file Path to the plugin file, relative to the plugins * directory. * * @return array An array of plugin row meta links. */ public function plugin_row_meta( $plugin_meta, $plugin_file ) { if ( ELEMENTOR_PLUGIN_BASE === $plugin_file ) { $row_meta = [ 'docs' => '<a href="https://go.elementor.com/docs-admin-plugins/" aria-label="' . esc_attr( esc_html__( 'View Elementor Documentation', 'elementor' ) ) . '" target="_blank">' . esc_html__( 'Docs & FAQs', 'elementor' ) . '</a>', 'ideo' => '<a href="https://go.elementor.com/yt-admin-plugins/" aria-label="' . esc_attr( esc_html__( 'View Elementor Video Tutorials', 'elementor' ) ) . '" target="_blank">' . esc_html__( 'Video Tutorials', 'elementor' ) . '</a>', ]; $plugin_meta = array_merge( $plugin_meta, $row_meta ); } return $plugin_meta; } /** * Admin footer text. * * Modifies the "Thank you" text displayed in the admin footer. * * Fired by `admin_footer_text` filter. * * @since 1.0.0 * @access public * * @param string $footer_text The content that will be printed. * * @return string The content that will be printed. */ public function admin_footer_text( $footer_text ) { $current_screen = get_current_screen(); $is_elementor_screen = ( $current_screen && false !== strpos( $current_screen->id, 'elementor' ) ); if ( $is_elementor_screen ) { $footer_text = sprintf( /* translators: 1: Elementor, 2: Link to plugin review */ __( 'Enjoyed %1$s? Please leave us a %2$s rating. We really appreciate your support!', 'elementor' ), '<strong>' . esc_html__( 'Elementor', 'elementor' ) . '</strong>', '<a href="https://go.elementor.com/admin-review/" target="_blank">★★★★★</a>' ); } return $footer_text; } /** * Register dashboard widgets. * * Adds a new Elementor widgets to WordPress dashboard. * * Fired by `wp_dashboard_setup` action. * * @since 1.9.0 * @access public */ public function register_dashboard_widgets() { wp_add_dashboard_widget( 'e-dashboard-overview', esc_html__( 'Elementor Overview', 'elementor' ), [ $this, 'elementor_dashboard_overview_widget' ] ); // Move our widget to top. global $wp_meta_boxes; $dashboard = $wp_meta_boxes['dashboard']['normal']['core']; $ours = [ 'e-dashboard-overview' => $dashboard['e-dashboard-overview'], ]; $wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } /** * Displays the Elementor dashboard widget. * * Fired by `wp_add_dashboard_widget` function. * * @since 1.9.0 * @access public */ public function elementor_dashboard_overview_widget() { ?> <div class="e-dashboard-overview e-dashboard-widget"> <?php self::elementor_dashboard_overview_header(); self::elementor_dashboard_overview_recently_edited(); self::elementor_dashboard_overview_news_updates(); self::elementor_dashboard_overview_footer(); ?> </div> <?php } /** * Displays the Elementor dashboard widget - header section. * Fired by `elementor_dashboard_overview_widget` function. * * @param bool $show_versions * @param bool $is_create_post_enabled * * @return void * @since 3.12.0 */ public static function elementor_dashboard_overview_header( bool $show_versions = true, bool $is_create_post_enabled = true ) { if ( User::is_current_user_can_edit_post_type( 'page' ) ) { $create_new_label = esc_html__( 'Create New Page', 'elementor' ); $create_new_post_type = 'page'; } elseif ( User::is_current_user_can_edit_post_type( 'post' ) ) { $create_new_label = esc_html__( 'Create New Post', 'elementor' ); $create_new_post_type = 'post'; } ?> <div class="e-overview__header"> <?php if ( $show_versions ) { ?> <div class="e-overview__logo"> <div class="e-logo-wrapper"><i class="eicon-elementor"></i></div> </div> <div class="e-overview__versions"> <span class="e-overview__version"><?php echo esc_html__( 'Elementor', 'elementor' ); ?> v<?php echo ELEMENTOR_VERSION; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span> <?php /** * Elementor dashboard widget after the version. * Fires after Elementor version display in the dashboard widget. * * @since 1.9.0 */ do_action( 'elementor/admin/dashboard_overview_widget/after_version' ); ?> </div> <?php } ?> <?php if ( ! empty( $create_new_post_type ) && $is_create_post_enabled ) { ?> <div class="e-overview__create"> <a href="<?php echo esc_url( Plugin::$instance->documents->get_create_new_post_url( $create_new_post_type ) ); ?>" class="button"><span aria-hidden="true" class="dashicons dashicons-plus"></span> <?php echo esc_html( $create_new_label ); ?></a> </div> <?php } ?> </div> <?php } /** * Displays the Elementor dashboard widget - recently edited section. * Fired by `elementor_dashboard_overview_widget` function. * * @param array $args * @param bool $show_heading * * @return void * @since 3.12.0 */ public static function elementor_dashboard_overview_recently_edited( array $args = [], bool $show_heading = true ) { $recently_edited_query = Utils::get_recently_edited_posts_query( $args ); if ( $recently_edited_query->have_posts() ) { ?> <div class="e-overview__recently-edited"> <?php if ( $show_heading ) { ?> <h3 class="e-heading e-divider_bottom"><?php echo esc_html__( 'Recently Edited', 'elementor' ); ?></h3> <?php } ?> <ul class="e-overview__posts"> <?php while ( $recently_edited_query->have_posts() ) { $recently_edited_query->the_post(); $document = Plugin::$instance->documents->get( get_the_ID() ); $date = date_i18n( _x( 'M jS', 'Dashboard Overview Widget Recently Date', 'elementor' ), get_the_modified_time( 'U' ) ); ?> <li class="e-overview__post"> <a href="<?php echo esc_url( $document->get_edit_url() ); ?>" class="e-overview__post-link"><?php echo esc_html( get_the_title() ); ?> <span class="dashicons dashicons-edit"></span></a> <span><?php echo $date; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>, <?php the_modified_time(); ?></span> </li> <?php } ?> </ul> </div> <?php } } /** * Displays the Elementor dashboard widget - news and updates section. * Fired by `elementor_dashboard_overview_widget` function. * * @param int $limit_feed * @param bool $show_heading * * @return void * @since 3.12.0 * @access public */ public static function elementor_dashboard_overview_news_updates( int $limit_feed = 0, bool $show_heading = true ) { $elementor_feed = Api::get_feed_data(); if ( $limit_feed > 0 ) { $elementor_feed = array_slice( $elementor_feed, 0, $limit_feed ); } if ( ! empty( $elementor_feed ) ) { ?> <div class="e-overview__feed"> <?php if ( $show_heading ) { ?> <h3 class="e-heading e-divider_bottom"><?php echo esc_html__( 'News & Updates', 'elementor' ); ?></h3> <?php } ?> <ul class="e-overview__posts"> <?php foreach ( $elementor_feed as $feed_item ) { ?> <li class="e-overview__post"> <a href="<?php echo esc_url( $feed_item['url'] ); ?>" class="e-overview__post-link" target="_blank"> <?php if ( ! empty( $feed_item['badge'] ) ) { ?> <span class="e-overview__badge"><?php echo esc_html( $feed_item['badge'] ); ?></span> <?php } ?> <?php echo esc_html( $feed_item['title'] ); ?> </a> <p class="e-overview__post-description"><?php echo esc_html( $feed_item['excerpt'] ); ?></p> </li> <?php } ?> </ul> </div> <?php } } /** * Displays the Elementor dashboard widget - footer section. * Fired by `elementor_dashboard_overview_widget` function. * * @since 3.12.0 */ public static function elementor_dashboard_overview_footer() { ?> <div class="e-overview__footer e-divider_top"> <ul> <?php foreach ( self::static_get_dashboard_overview_widget_footer_actions() as $action_id => $action ) { ?> <li class="e-overview__<?php echo esc_attr( $action_id ); ?>"> <a href="<?php echo esc_url( $action['link'] ); ?>" target="_blank"><?php echo esc_html( $action['title'] ); ?> <span class="screen-reader-text"><?php echo esc_html__( '(opens in a new window)', 'elementor' ); ?></span> <span aria-hidden="true" class="dashicons dashicons-external"></span></a> </li> <?php } ?> </ul> </div> <?php } /** * Get elementor dashboard overview widget footer actions. * * Retrieves the footer action links displayed in elementor dashboard widget. * * @since 3.12.0 * @access public */ public static function static_get_dashboard_overview_widget_footer_actions() { $base_actions = [ 'blog' => [ 'title' => esc_html__( 'Blog', 'elementor' ), 'link' => 'https://go.elementor.com/overview-widget-blog/', ], 'help' => [ 'title' => esc_html__( 'Help', 'elementor' ), 'link' => 'https://go.elementor.com/overview-widget-docs/', ], ]; $additions_actions = []; if ( User::get_introduction_meta( 'ai_get_started' ) ) { $additions_actions['ai-library'] = [ 'title' => esc_html__( 'AI Prompts Library', 'elementor' ), 'link' => 'https://go.elementor.com/overview-ai-prompts-library/', ]; } else { $additions_actions['ai'] = [ 'title' => esc_html__( 'Build Smart with AI', 'elementor' ), 'link' => 'https://go.elementor.com/overview-widget-ai/', ]; } $additions_actions['go-pro'] = [ 'title' => esc_html__( 'Upgrade', 'elementor' ), 'link' => 'https://go.elementor.com/go-pro-wp-overview-widget/', ]; /** * Dashboard widget footer actions. * * Filters the additions actions displayed in Elementor dashboard widget. * * Developers can add new action links to Elementor dashboard widget * footer using this filter. * * @since 1.9.0 * * @param array $additions_actions Elementor dashboard widget footer actions. */ $additions_actions = apply_filters( 'elementor/admin/dashboard_overview_widget/footer_actions', $additions_actions ); $actions = $base_actions + $additions_actions; return $actions; } /** * Get elementor dashboard overview widget footer actions. * * Retrieves the footer action links displayed in elementor dashboard widget. * * @since 1.9.0 * @access private */ private function get_dashboard_overview_widget_footer_actions() { return self::static_get_dashboard_overview_widget_footer_actions(); } /** * Admin action new post. * * When a new post action is fired the title is set to 'Elementor' and the post ID. * * Fired by `admin_action_elementor_new_post` action. * * @since 1.9.0 * @access public */ public function admin_action_new_post() { check_admin_referer( 'elementor_action_new_post' ); $post_type = Utils::get_super_global_value( $_GET, 'post_type' ) ?? 'post'; if ( ! User::is_current_user_can_edit_post_type( $post_type ) ) { return; } if ( empty( $_GET['template_type'] ) ) { $type = 'post'; } else { $type = sanitize_text_field( wp_unslash( $_GET['template_type'] ) ); } $post_data = Utils::get_super_global_value( $_GET, 'post_data' ) ?? []; $post_data = $this->filter_post_data( $post_data ); /** * Create new post meta data. * * Filters the meta data of any new post created. * * @since 2.0.0 * * @param array $meta Post meta data. */ $meta = []; if ( isset( $_GET['meta'] ) && is_array( $_GET['meta'] ) ) { $meta = array_map( 'sanitize_text_field', wp_unslash( $_GET['meta'] ) ); } $meta = apply_filters( 'elementor/admin/create_new_post/meta', $meta ); $post_data['post_type'] = $post_type; $document = Plugin::$instance->documents->create( $type, $post_data, $meta ); if ( is_wp_error( $document ) ) { wp_die( $document ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } wp_redirect( $document->get_edit_url() ); die; } private function get_allowed_fields_for_role() { $allowed_fields = array( 'post_title', 'post_content', 'post_excerpt', 'post_category', 'post_type', 'tags_input', ); if ( current_user_can( 'publish_posts' ) ) { $allowed_fields[] = 'post_status'; } if ( current_user_can( 'edit_others_posts' ) ) { $allowed_fields[] = 'post_author'; } return $allowed_fields; } private function filter_post_data( $post_data ) { $allowed_fields = $this->get_allowed_fields_for_role(); return array_filter( $post_data, function( $key ) use ( $allowed_fields ) { return in_array( $key, $allowed_fields, true ); }, ARRAY_FILTER_USE_KEY ); } /** * @since 2.3.0 * @access public */ public function add_new_template_template() { Plugin::$instance->common->add_template( ELEMENTOR_PATH . 'includes/admin-templates/new-template.php' ); } public function add_new_floating_elements_template() { Plugin::$instance->common->add_template( ELEMENTOR_PATH . 'includes/admin-templates/new-floating-elements.php' ); } public function enqueue_new_floating_elements_scripts() { $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; wp_enqueue_script( 'elementor-floating-elements-modal', ELEMENTOR_ASSETS_URL . 'js/floating-elements-modal' . $suffix . '.js', [], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-floating-elements-modal', 'elementor' ); } /** * @access public */ public function enqueue_new_template_scripts() { $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; wp_enqueue_script( 'elementor-new-template', ELEMENTOR_ASSETS_URL . 'js/new-template' . $suffix . '.js', [], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-new-template', 'elementor' ); } /** * @since 2.6.0 * @access public */ public function add_beta_tester_template() { Plugin::$instance->common->add_template( ELEMENTOR_PATH . 'includes/admin-templates/beta-tester.php' ); } /** * @access public */ public function enqueue_beta_tester_scripts() { $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; wp_enqueue_script( 'elementor-beta-tester', ELEMENTOR_ASSETS_URL . 'js/beta-tester' . $suffix . '.js', [], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-beta-tester', 'elementor' ); } public function init_floating_elements() { $screens = [ 'elementor_library_page_e-floating-buttons' => true, 'edit-e-floating-buttons' => true, ]; if ( ! isset( $screens[ get_current_screen()->id ] ) ) { return; } add_action( 'admin_head', [ $this, 'add_new_floating_elements_template' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_new_floating_elements_scripts' ] ); } /** * @access public */ public function init_new_template() { if ( 'edit-elementor_library' !== get_current_screen()->id ) { return; } // Allow plugins to add their templates on admin_head. add_action( 'admin_head', [ $this, 'add_new_template_template' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_new_template_scripts' ] ); } public function version_update_warning( $current_version, $new_version ) { $current_version_minor_part = explode( '.', $current_version )[1]; $new_version_minor_part = explode( '.', $new_version )[1]; if ( $current_version_minor_part === $new_version_minor_part ) { return; } ?> <hr class="e-major-update-warning__separator" /> <div class="e-major-update-warning"> <div class="e-major-update-warning__icon"> <i class="eicon-info-circle"></i> </div> <div> <div class="e-major-update-warning__title"> <?php echo esc_html__( 'Heads up, Please backup before upgrade!', 'elementor' ); ?> </div> <div class="e-major-update-warning__message"> <?php printf( /* translators: %1$s Link open tag, %2$s: Link close tag. */ esc_html__( 'The latest update includes some substantial changes across different areas of the plugin. We highly recommend you %1$sbackup your site before upgrading%2$s, and make sure you first update in a staging environment', 'elementor' ), '<a href="https://go.elementor.com/wp-dash-update-backup/">', '</a>' ); ?> </div> </div> </div> <?php } /** * @access public */ public function init_beta_tester( $current_screen ) { if ( ( 'toplevel_page_elementor' === $current_screen->base ) || 'elementor_page_elementor-tools' === $current_screen->id ) { add_action( 'admin_head', [ $this, 'add_beta_tester_template' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_beta_tester_scripts' ] ); } } /** * Admin constructor. * * Initializing Elementor in WordPress admin. * * @since 1.0.0 * @access public */ public function __construct() { Plugin::$instance->init_common(); $this->add_component( 'feedback', new Feedback() ); $this->add_component( 'admin-notices', new Admin_Notices() ); add_action( 'admin_init', [ $this, 'maybe_redirect_to_getting_started' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] ); add_action( 'edit_form_after_title', [ $this, 'print_switch_mode_button' ] ); add_action( 'save_post', [ $this, 'save_post' ] ); add_filter( 'display_post_states', [ $this, 'add_elementor_post_state' ], 10, 2 ); add_filter( 'plugin_action_links_' . ELEMENTOR_PLUGIN_BASE, [ $this, 'plugin_action_links' ] ); add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 2 ); add_filter( 'admin_body_class', [ $this, 'body_status_classes' ] ); add_filter( 'admin_footer_text', [ $this, 'admin_footer_text' ] ); // Register Dashboard Widgets. add_action( 'wp_dashboard_setup', [ $this, 'register_dashboard_widgets' ] ); // Admin Actions add_action( 'admin_action_elementor_new_post', [ $this, 'admin_action_new_post' ] ); add_action( 'current_screen', [ $this, 'init_new_template' ] ); add_action( 'current_screen', [ $this, 'init_floating_elements' ] ); add_action( 'current_screen', [ $this, 'init_beta_tester' ] ); add_action( 'in_plugin_update_message-' . ELEMENTOR_PLUGIN_BASE, function( $plugin_data ) { $this->version_update_warning( ELEMENTOR_VERSION, $plugin_data['new_version'] ); } ); add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_hints' ] ); } /** * @since 2.3.0 * @access protected */ protected function get_init_settings() { $beta_tester_email = get_user_meta( get_current_user_id(), User::BETA_TESTER_META_KEY, true ); $elementor_beta = get_option( 'elementor_beta', 'no' ); $all_introductions = User::get_introduction_meta(); $beta_tester_signup_dismissed = array_key_exists( Beta_Testers::BETA_TESTER_SIGNUP, $all_introductions ); $settings = [ 'home_url' => home_url(), 'settings_url' => Settings::get_url(), 'user' => [ 'introduction' => User::get_introduction_meta(), 'restrictions' => Plugin::$instance->role_manager->get_user_restrictions_array(), 'is_administrator' => current_user_can( 'manage_options' ), ], 'beta_tester' => [ 'beta_tester_signup' => Beta_Testers::BETA_TESTER_SIGNUP, 'has_email' => $beta_tester_email, 'option_enabled' => 'no' !== $elementor_beta, 'signup_dismissed' => $beta_tester_signup_dismissed, ], 'experiments' => $this->get_experiments(), ]; /** * Localize settings. * * Filters the initial localize settings in the admin. * * WordPress has it's own way to pass localized data from PHP (backend) to * JS (frontend). Elementor uses this method to pass localize data in the * admin. This hook can be used to add more localized settings in addition * to the initial Elementor settings. * * @since 2.3.0 * * @param array $settings Initial localize settings. */ $settings = apply_filters( 'elementor/admin/localize_settings', $settings ); return $settings; } private function get_experiments() { return ( new Collection( Plugin::$instance->experiments->get_features() ) ) ->map( function ( $experiment_data ) { $dependencies = $experiment_data['dependencies'] ?? []; $dependencies = ( new Collection( $dependencies ) ) ->map( function ( $dependency ) { return $dependency->get_name(); } )->all(); return [ 'name' => $experiment_data['name'], 'title' => $experiment_data['title'] ?? $experiment_data['name'], 'state' => $experiment_data['state'], 'default' => $experiment_data['default'], 'dependencies' => $dependencies, 'messages' => $experiment_data['messages'] ?? [], ]; } )->all(); } private function maybe_enqueue_hints() { if ( ! Hints::should_display_hint( 'image-optimization' ) ) { return; } wp_register_script( 'media-hints', $this->get_js_assets_url( 'media-hints' ), [], ELEMENTOR_VERSION, true ); $content = sprintf("%1\$s <a class='e-btn-1' href='%2\$s' target='_blank'>%3\$s</a>!", __( 'Optimize your images to enhance site performance by using Image Optimizer.', 'elementor' ), Hints::get_plugin_action_url( 'image-optimization' ), ( Hints::is_plugin_installed( 'image-optimization' ) ? __( 'Activate', 'elementor' ) : __( 'Install', 'elementor' ) ) . ' ' . __( 'Image Optimizer', 'elementor' ) ); $dismissible = 'image_optimizer_hint'; wp_localize_script( 'media-hints', 'elementorAdminHints', [ 'mediaHint' => [ 'display' => true, 'type' => 'info', 'content' => $content, 'icon' => true, 'dismissible' => $dismissible, 'dismiss' => __( 'Dismiss this notice.', 'elementor' ), 'button_event' => $dismissible, 'button_data' => base64_encode( json_encode( [ 'action_url' => Hints::get_plugin_action_url( 'image-optimization' ), ] ), ), ], ] ); wp_enqueue_script( 'media-hints' ); } public function register_ajax_hints( $ajax_manager ) { $ajax_manager->register_ajax_action( 'elementor_image_optimization_campaign', [ $this, 'ajax_set_image_optimization_campaign' ] ); $ajax_manager->register_ajax_action( 'elementor_core_site_mailer_campaign', [ $this, 'ajax_site_mailer_campaign' ] ); } public function ajax_set_image_optimization_campaign( $request ) { if ( ! current_user_can( 'install_plugins' ) ) { return; } if ( empty( $request['source'] ) ) { return; } $campaign_data = [ 'source' => sanitize_key( $request['source'] ), 'campaign' => 'io-plg', 'medium' => 'wp-dash', ]; set_transient( 'elementor_image_optimization_campaign', $campaign_data, 30 * DAY_IN_SECONDS ); } public function ajax_site_mailer_campaign( $request ) { if ( ! current_user_can( 'install_plugins' ) ) { return; } if ( empty( $request['source'] ) ) { return; } $campaign_data = [ 'source' => sanitize_key( $request['source'] ), 'campaign' => 'sm-plg', 'medium' => 'wp-dash', ]; set_transient( 'elementor_site_mailer_campaign', $campaign_data, 30 * DAY_IN_SECONDS ); } } admin/feedback.php 0000644 00000013325 14717626151 0010107 0 ustar 00 <?php namespace Elementor\Core\Admin; use Elementor\Api; use Elementor\Core\Base\Module; use Elementor\Plugin; use Elementor\Tracker; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Feedback extends Module { /** * @since 2.2.0 * @access public */ public function __construct() { add_action( 'current_screen', function () { if ( ! $this->is_plugins_screen() ) { return; } add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_feedback_dialog_scripts' ] ); } ); // Ajax. add_action( 'wp_ajax_elementor_deactivate_feedback', [ $this, 'ajax_elementor_deactivate_feedback' ] ); } /** * Get module name. * * Retrieve the module name. * * @since 1.7.0 * @access public * * @return string Module name. */ public function get_name() { return 'feedback'; } /** * Enqueue feedback dialog scripts. * * Registers the feedback dialog scripts and enqueues them. * * @since 1.0.0 * @access public */ public function enqueue_feedback_dialog_scripts() { add_action( 'admin_footer', [ $this, 'print_deactivate_feedback_dialog' ] ); $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; wp_register_script( 'elementor-admin-feedback', ELEMENTOR_ASSETS_URL . 'js/admin-feedback' . $suffix . '.js', [ 'elementor-common', 'wp-i18n', ], ELEMENTOR_VERSION, true ); wp_enqueue_script( 'elementor-admin-feedback' ); wp_set_script_translations( 'elementor-admin-feedback', 'elementor' ); } /** * Print deactivate feedback dialog. * * Display a dialog box to ask the user why he deactivated Elementor. * * Fired by `admin_footer` filter. * * @since 1.0.0 * @access public */ public function print_deactivate_feedback_dialog() { $deactivate_reasons = [ 'no_longer_needed' => [ 'title' => esc_html__( 'I no longer need the plugin', 'elementor' ), 'input_placeholder' => '', ], 'found_a_better_plugin' => [ 'title' => esc_html__( 'I found a better plugin', 'elementor' ), 'input_placeholder' => esc_html__( 'Please share which plugin', 'elementor' ), ], 'couldnt_get_the_plugin_to_work' => [ 'title' => esc_html__( 'I couldn\'t get the plugin to work', 'elementor' ), 'input_placeholder' => '', ], 'temporary_deactivation' => [ 'title' => esc_html__( 'It\'s a temporary deactivation', 'elementor' ), 'input_placeholder' => '', ], 'elementor_pro' => [ 'title' => esc_html__( 'I have Elementor Pro', 'elementor' ), 'input_placeholder' => '', 'alert' => esc_html__( 'Wait! Don\'t deactivate Elementor. You have to activate both Elementor and Elementor Pro in order for the plugin to work.', 'elementor' ), ], 'other' => [ 'title' => esc_html__( 'Other', 'elementor' ), 'input_placeholder' => esc_html__( 'Please share the reason', 'elementor' ), ], ]; ?> <div id="elementor-deactivate-feedback-dialog-wrapper"> <div id="elementor-deactivate-feedback-dialog-header"> <i class="eicon-elementor-square" aria-hidden="true"></i> <span id="elementor-deactivate-feedback-dialog-header-title"><?php echo esc_html__( 'Quick Feedback', 'elementor' ); ?></span> </div> <form id="elementor-deactivate-feedback-dialog-form" method="post"> <?php wp_nonce_field( '_elementor_deactivate_feedback_nonce' ); ?> <input type="hidden" name="action" value="elementor_deactivate_feedback" /> <div id="elementor-deactivate-feedback-dialog-form-caption"><?php echo esc_html__( 'If you have a moment, please share why you are deactivating Elementor:', 'elementor' ); ?></div> <div id="elementor-deactivate-feedback-dialog-form-body"> <?php foreach ( $deactivate_reasons as $reason_key => $reason ) : ?> <div class="elementor-deactivate-feedback-dialog-input-wrapper"> <input id="elementor-deactivate-feedback-<?php echo esc_attr( $reason_key ); ?>" class="elementor-deactivate-feedback-dialog-input" type="radio" name="reason_key" value="<?php echo esc_attr( $reason_key ); ?>" /> <label for="elementor-deactivate-feedback-<?php echo esc_attr( $reason_key ); ?>" class="elementor-deactivate-feedback-dialog-label"><?php echo esc_html( $reason['title'] ); ?></label> <?php if ( ! empty( $reason['input_placeholder'] ) ) : ?> <input class="elementor-feedback-text" type="text" name="reason_<?php echo esc_attr( $reason_key ); ?>" placeholder="<?php echo esc_attr( $reason['input_placeholder'] ); ?>" /> <?php endif; ?> <?php if ( ! empty( $reason['alert'] ) ) : ?> <div class="elementor-feedback-text"><?php echo esc_html( $reason['alert'] ); ?></div> <?php endif; ?> </div> <?php endforeach; ?> </div> </form> </div> <?php } /** * Ajax elementor deactivate feedback. * * Send the user feedback when Elementor is deactivated. * * Fired by `wp_ajax_elementor_deactivate_feedback` action. * * @since 1.0.0 * @access public */ public function ajax_elementor_deactivate_feedback() { $wpnonce = Utils::get_super_global_value( $_POST, '_wpnonce' ); // phpcs:ignore -- Nonce verification is made in `wp_verify_nonce()`. if ( ! wp_verify_nonce( $wpnonce, '_elementor_deactivate_feedback_nonce' ) ) { wp_send_json_error(); } if ( ! current_user_can( 'activate_plugins' ) ) { wp_send_json_error( 'Permission denied' ); } $reason_key = Utils::get_super_global_value( $_POST, 'reason_key' ) ?? ''; $reason_text = Utils::get_super_global_value( $_POST, "reason_{$reason_key}" ) ?? ''; Api::send_feedback( $reason_key, $reason_text ); wp_send_json_success(); } /** * @since 2.3.0 * @access private */ private function is_plugins_screen() { return in_array( get_current_screen()->id, [ 'plugins', 'plugins-network' ] ); } } base/db-upgrades-manager.php 0000644 00000013751 14717626151 0012015 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Core\Admin\Admin_Notices; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class DB_Upgrades_Manager extends Background_Task_Manager { protected $current_version = null; protected $query_limit = 100; abstract public function get_new_version(); abstract public function get_version_option_name(); abstract public function get_upgrades_class(); abstract public function get_updater_label(); public function get_task_runner_class() { return 'Elementor\Core\Upgrade\Updater'; } public function get_query_limit() { return $this->query_limit; } public function set_query_limit( $limit ) { $this->query_limit = $limit; } public function get_current_version() { if ( null === $this->current_version ) { $this->current_version = get_option( $this->get_version_option_name() ); } return $this->current_version; } public function should_upgrade() { $current_version = $this->get_current_version(); // It's a new install. if ( ! $current_version ) { $this->update_db_version(); return false; } return version_compare( $this->get_new_version(), $current_version, '>' ); } public function on_runner_start() { parent::on_runner_start(); if ( ! defined( 'IS_ELEMENTOR_UPGRADE' ) ) { define( 'IS_ELEMENTOR_UPGRADE', true ); } } public function on_runner_complete( $did_tasks = false ) { $logger = Plugin::$instance->logger->get_logger(); $logger->info( 'Elementor data updater process has been completed.', [ 'meta' => [ 'plugin' => $this->get_plugin_label(), 'from' => $this->current_version, 'to' => $this->get_new_version(), ], ] ); $this->clear_cache(); $this->update_db_version(); if ( $did_tasks ) { $this->add_flag( 'completed' ); } } protected function clear_cache() { Plugin::$instance->files_manager->clear_cache(); } public function admin_notice_start_upgrade() { /** * @var Admin_Notices $admin_notices */ $admin_notices = Plugin::$instance->admin->get_component( 'admin-notices' ); $options = [ 'title' => $this->get_updater_label(), 'description' => esc_html__( 'Your site database needs to be updated to the latest version.', 'elementor' ), 'type' => 'error', 'icon' => false, 'button' => [ 'text' => esc_html__( 'Update Now', 'elementor' ), 'url' => $this->get_start_action_url(), 'class' => 'e-button e-button--cta', ], ]; $admin_notices->print_admin_notice( $options ); } public function admin_notice_upgrade_is_running() { /** * @var Admin_Notices $admin_notices */ $admin_notices = Plugin::$instance->admin->get_component( 'admin-notices' ); $options = [ 'title' => $this->get_updater_label(), 'description' => esc_html__( 'Database update process is running in the background. Taking a while?', 'elementor' ), 'type' => 'warning', 'icon' => false, 'button' => [ 'text' => esc_html__( 'Click here to run it now', 'elementor' ), 'url' => $this->get_continue_action_url(), 'class' => 'e-button e-button--primary', ], ]; $admin_notices->print_admin_notice( $options ); } public function admin_notice_upgrade_is_completed() { $this->delete_flag( 'completed' ); $message = esc_html__( 'The database update process is now complete. Thank you for updating to the latest version!', 'elementor' ); /** * @var Admin_Notices $admin_notices */ $admin_notices = Plugin::$instance->admin->get_component( 'admin-notices' ); $options = [ 'description' => '<b>' . $this->get_updater_label() . '</b> - ' . $message, 'type' => 'success', 'icon' => false, ]; $admin_notices->print_admin_notice( $options ); } /** * @access protected */ protected function start_run() { $updater = $this->get_task_runner(); if ( $updater->is_running() ) { return; } $upgrade_callbacks = $this->get_upgrade_callbacks(); if ( empty( $upgrade_callbacks ) ) { $this->on_runner_complete(); return; } $this->clear_cache(); foreach ( $upgrade_callbacks as $callback ) { $updater->push_to_queue( [ 'callback' => $callback, ] ); } $updater->save()->dispatch(); Plugin::$instance->logger->get_logger()->info( 'Elementor data updater process has been queued.', [ 'meta' => [ 'plugin' => $this->get_plugin_label(), 'from' => $this->current_version, 'to' => $this->get_new_version(), ], ] ); } protected function update_db_version() { update_option( $this->get_version_option_name(), $this->get_new_version() ); } public function get_upgrade_callbacks() { $prefix = '_v_'; $upgrades_class = $this->get_upgrades_class(); $upgrades_reflection = new \ReflectionClass( $upgrades_class ); $callbacks = []; foreach ( $upgrades_reflection->getMethods() as $method ) { $method_name = $method->getName(); if ( '_on_each_version' === $method_name ) { $callbacks[] = [ $upgrades_class, $method_name ]; continue; } if ( false === strpos( $method_name, $prefix ) ) { continue; } if ( ! preg_match_all( "/$prefix(\d+_\d+_\d+)/", $method_name, $matches ) ) { continue; } $method_version = str_replace( '_', '.', $matches[1][0] ); if ( ! version_compare( $method_version, $this->current_version, '>' ) ) { continue; } $callbacks[] = [ $upgrades_class, $method_name ]; } return $callbacks; } public function __construct() { // If upgrade is completed - show the notice only for admins. // Note: in this case `should_upgrade` returns false, because it's already upgraded. if ( is_admin() && current_user_can( 'update_plugins' ) && $this->get_flag( 'completed' ) ) { add_action( 'admin_notices', [ $this, 'admin_notice_upgrade_is_completed' ] ); } if ( ! $this->should_upgrade() ) { return; } $updater = $this->get_task_runner(); $this->start_run(); if ( $updater->is_running() && current_user_can( 'update_plugins' ) ) { add_action( 'admin_notices', [ $this, 'admin_notice_upgrade_is_running' ] ); } parent::__construct(); } } base/traits/shared-widget-controls-trait.php 0000644 00000015766 14717626151 0015237 0 ustar 00 <?php namespace Elementor\Core\Base\Traits; use Elementor\Controls_Manager; use Elementor\Modules\FloatingButtons\Control\Hover_Animation_Floating_Buttons; use Elementor\Plugin; use Elementor\Shapes; use Elementor\Utils; trait Shared_Widget_Controls_Trait { protected $border_width_range = [ 'min' => 0, 'max' => 10, 'step' => 1, ]; protected function add_html_tag_control( string $name, string $default = 'h2' ): void { $this->add_control( $name, [ 'label' => esc_html__( 'HTML Tag', 'elementor' ), 'type' => Controls_Manager::SELECT, 'options' => [ 'h1' => 'H1', 'h2' => 'H2', 'h3' => 'H3', 'h4' => 'H4', 'h5' => 'H5', 'h6' => 'H6', 'div' => 'div', 'span' => 'span', 'p' => 'p', ], 'default' => $default, ] ); } /** * Remove any child arrays where all properties are empty */ protected function clean_array( $input_array = [] ) { $output_array = array_filter( $input_array, function( $sub_array ) { // Use array_filter on the sub array $filtered_sub_array = array_filter( $sub_array, function( $val ) { // Filter out empty or null values return ! is_null( $val ) && '' !== $val; } ); // A non-empty result means the sub array contains some non-empty value(s) return ! empty( $filtered_sub_array ); } ); return $output_array; } protected function get_link_attributes( $link = [], $other_attributes = [] ) { $url_attrs = []; $rel_string = ''; if ( ! empty( $link['url'] ) ) { $url_attrs['href'] = esc_url( $link['url'] ); } if ( ! empty( $link['is_external'] ) ) { $url_attrs['target'] = '_blank'; $rel_string .= 'noopener '; } if ( ! empty( $link['nofollow'] ) ) { $rel_string .= 'nofollow '; } if ( ! empty( $rel_string ) ) { $url_attrs['rel'] = $rel_string; } /** * Note - we deliberately merge $other_attributes second * to allow overriding default attributes values such as a more formatted href */ $url_combined_attrs = array_merge( $url_attrs, $other_attributes, Utils::parse_custom_attributes( $link['custom_attributes'] ?? '' ), ); return $url_combined_attrs; } protected function add_icons_per_row_control( string $name = 'icons_per_row', $options = [ '2' => '2', '3' => '3', ], string $default = '3', $label = '', $selector_custom_property = '--e-link-in-bio-icon-columns' ): void { if ( ! $label ) { $label = esc_html__( 'Icons Per Row', 'elementor' ); } $this->add_control( $name, [ 'label' => $label, 'type' => Controls_Manager::SELECT, 'options' => $options, 'default' => $default, 'render_type' => 'template', 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => $selector_custom_property . ': {{VALUE}};', ], ] ); } protected function add_slider_control( string $name, array $args = [] ): void { $default_args = [ 'type' => Controls_Manager::SLIDER, 'default' => [ 'unit' => 'px', ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 100, 'step' => 1, ], ], ]; $this->add_control( $name, array_merge_recursive( $default_args, $args ) ); } protected function add_borders_control( string $prefix, array $show_border_args = [], array $border_width_args = [], array $border_color_args = [] ): void { $show_border = [ 'label' => esc_html__( 'Border', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'return_value' => 'yes', 'default' => '', ]; $this->add_control( $prefix . '_show_border', array_merge( $show_border, $show_border_args ) ); $condition = [ $prefix . '_show_border' => 'yes', ]; if ( isset( $border_width_args['condition'] ) ) { $condition = array_merge( $condition, $border_width_args['condition'] ); unset( $border_width_args['condition'] ); } $border_width = [ 'label' => esc_html__( 'Border Width', 'elementor' ) . ' (px)', 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px' ], 'range' => [ 'px' => $this->border_width_range, ], 'condition' => $condition, 'default' => [ 'unit' => 'px', 'size' => 1, ], ]; $this->add_responsive_control( $prefix . '_border_width', array_merge( $border_width, $border_width_args ), ); $condition = [ $prefix . '_show_border' => 'yes', ]; if ( isset( $border_color_args['condition'] ) ) { $condition = array_merge( $condition, $border_color_args['condition'] ); unset( $border_color_args['condition'] ); } $border_color = [ 'label' => esc_html__( 'Border Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'condition' => $condition, 'default' => '#000000', ]; $this->add_control( $prefix . '_border_color', array_merge( $border_color, $border_color_args ) ); } protected function get_shape_divider( $side = 'bottom' ) { $settings = $this->settings; $base_setting_key = "identity_section_style_cover_divider_$side"; $file_name = $settings[ $base_setting_key ]; if ( empty( $file_name ) ) { return []; } $negative = ! empty( $settings[ $base_setting_key . '_negative' ] ); $shape_path = Shapes::get_shape_path( $file_name, $negative ); if ( ! is_file( $shape_path ) || ! is_readable( $shape_path ) ) { return []; } return [ 'negative' => $negative, 'svg' => Utils::file_get_contents( $shape_path ), ]; } protected function print_shape_divider( $side = 'bottom' ) { $shape_divider = $this->get_shape_divider( $side ); if ( empty( $shape_divider ) ) { return; } ?> <div class="elementor-shape elementor-shape-<?php echo esc_attr( $side ); ?>" data-negative="<?php echo esc_attr( $shape_divider['negative'] ? 'true' : 'false' ); ?>" > <?php // PHPCS - The file content is being read from a strict file path structure. echo $shape_divider['svg']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </div> <?php } protected function get_configured_breakpoints( $add_desktop = 'true' ) { $active_devices = Plugin::$instance->breakpoints->get_active_devices_list( [ 'reverse' => true ] ); $active_breakpoint_instances = Plugin::$instance->breakpoints->get_active_breakpoints(); $devices_options = []; foreach ( $active_devices as $device_key ) { $device_label = 'desktop' === $device_key ? esc_html__( 'Desktop', 'elementor' ) : $active_breakpoint_instances[ $device_key ]->get_label(); $devices_options[ $device_key ] = $device_label; } return [ 'active_devices' => $active_devices, 'devices_options' => $devices_options, ]; } protected function add_hover_animation_control( string $name, array $args = [] ): void { $this->add_control( $name, array_merge( [ 'label' => esc_html__( 'Hover Animation', 'elementor' ), 'type' => Hover_Animation_Floating_Buttons::TYPE, 'frontend_available' => true, 'default' => 'grow', ], $args ) ); } } base/background-task.php 0000644 00000021572 14717626151 0011267 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Plugin; use Elementor\Core\Base\BackgroundProcess\WP_Background_Process; /** * Based on https://github.com/woocommerce/woocommerce/blob/master/includes/abstracts/class-wc-background-process.php * & https://github.com/woocommerce/woocommerce/blob/master/includes/class-wc-background-updater.php */ defined( 'ABSPATH' ) || exit; /** * WC_Background_Process class. */ abstract class Background_Task extends WP_Background_Process { protected $current_item; /** * Dispatch updater. * * Updater will still run via cron job if this fails for any reason. */ public function dispatch() { $dispatched = parent::dispatch(); if ( is_wp_error( $dispatched ) ) { wp_die( esc_html( $dispatched ) ); } } public function query_col( $sql ) { global $wpdb; // Add Calc. $item = $this->get_current_item(); if ( empty( $item['total'] ) ) { $sql = preg_replace( '/^SELECT/', 'SELECT SQL_CALC_FOUND_ROWS', $sql ); } // Add offset & limit. $sql = preg_replace( '/;$/', '', $sql ); $sql .= ' LIMIT %d, %d;'; $results = $wpdb->get_col( $wpdb->prepare( $sql, $this->get_current_offset(), $this->get_limit() ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if ( ! empty( $results ) ) { $this->set_total(); } return $results; } public function should_run_again( $updated_rows ) { return count( $updated_rows ) === $this->get_limit(); } public function get_current_offset() { $limit = $this->get_limit(); return ( $this->current_item['iterate_num'] - 1 ) * $limit; } public function get_limit() { return $this->manager->get_query_limit(); } public function set_total() { global $wpdb; if ( empty( $this->current_item['total'] ) ) { $total_rows = $wpdb->get_var( 'SELECT FOUND_ROWS();' ); $total_iterates = ceil( $total_rows / $this->get_limit() ); $this->current_item['total'] = $total_iterates; } } /** * Complete * * Override if applicable, but ensure that the below actions are * performed, or, call parent::complete(). */ protected function complete() { $this->manager->on_runner_complete( true ); parent::complete(); } public function continue_run() { // Used to fire an action added in WP_Background_Process::_construct() that calls WP_Background_Process::handle_cron_healthcheck(). // This method will make sure the database updates are executed even if cron is disabled. Nothing will happen if the updates are already running. do_action( $this->cron_hook_identifier ); } /** * @return mixed */ public function get_current_item() { return $this->current_item; } /** * Get batch. * * @return \stdClass Return the first batch from the queue. */ protected function get_batch() { $batch = parent::get_batch(); $batch->data = array_filter( (array) $batch->data ); return $batch; } /** * Handle cron healthcheck * * Restart the background process if not already running * and data exists in the queue. */ public function handle_cron_healthcheck() { if ( $this->is_process_running() ) { // Background process already running. return; } if ( $this->is_queue_empty() ) { // No data to process. $this->clear_scheduled_event(); return; } $this->handle(); } /** * Schedule fallback event. */ protected function schedule_event() { if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { wp_schedule_event( time() + 10, $this->cron_interval_identifier, $this->cron_hook_identifier ); } } /** * Is the updater running? * * @return boolean */ public function is_running() { return false === $this->is_queue_empty(); } /** * See if the batch limit has been exceeded. * * @return bool */ protected function batch_limit_exceeded() { return $this->time_exceeded() || $this->memory_exceeded(); } /** * Handle. * * Pass each queue item to the task handler, while remaining * within server memory and time limit constraints. */ protected function handle() { $this->manager->on_runner_start(); $this->lock_process(); do { $batch = $this->get_batch(); foreach ( $batch->data as $key => $value ) { $task = $this->task( $value ); if ( false !== $task ) { $batch->data[ $key ] = $task; } else { unset( $batch->data[ $key ] ); } if ( $this->batch_limit_exceeded() ) { // Batch limits reached. break; } } // Update or delete current batch. if ( ! empty( $batch->data ) ) { $this->update( $batch->key, $batch->data ); } else { $this->delete( $batch->key ); } } while ( ! $this->batch_limit_exceeded() && ! $this->is_queue_empty() ); $this->unlock_process(); // Start next batch or complete process. if ( ! $this->is_queue_empty() ) { $this->dispatch(); } else { $this->complete(); } } /** * Use the protected `is_process_running` method as a public method. * @return bool */ public function is_process_locked() { return $this->is_process_running(); } public function handle_immediately( $callbacks ) { $this->manager->on_runner_start(); $this->lock_process(); foreach ( $callbacks as $callback ) { $item = [ 'callback' => $callback, ]; do { $item = $this->task( $item ); } while ( $item ); } $this->unlock_process(); } /** * Task * * Override this method to perform any actions required on each * queue item. Return the modified item for further processing * in the next pass through. Or, return false to remove the * item from the queue. * * @param array $item * * @return array|bool */ protected function task( $item ) { $result = false; if ( ! isset( $item['iterate_num'] ) ) { $item['iterate_num'] = 1; } $logger = Plugin::$instance->logger->get_logger(); $callback = $this->format_callback_log( $item ); if ( is_callable( $item['callback'] ) ) { $progress = ''; if ( 1 < $item['iterate_num'] ) { if ( empty( $item['total'] ) ) { $progress = sprintf( '(x%s)', $item['iterate_num'] ); } else { $percent = ceil( $item['iterate_num'] / ( $item['total'] / 100 ) ); $progress = sprintf( '(%s of %s, %s%%)', $item['iterate_num'], $item['total'], $percent ); } } $logger->info( sprintf( '%s Start %s', $callback, $progress ) ); $this->current_item = $item; $result = (bool) call_user_func( $item['callback'], $this ); // get back the updated item. $item = $this->current_item; $this->current_item = null; if ( $result ) { if ( empty( $item['total'] ) ) { $logger->info( sprintf( '%s callback needs to run again', $callback ) ); } elseif ( 1 === $item['iterate_num'] ) { $logger->info( sprintf( '%s callback needs to run more %d times', $callback, $item['total'] - $item['iterate_num'] ) ); } $item['iterate_num']++; } else { $logger->info( sprintf( '%s Finished', $callback ) ); } } else { $logger->notice( sprintf( 'Could not find %s callback', $callback ) ); } return $result ? $item : false; } /** * Schedule cron healthcheck. * * @param array $schedules Schedules. * @return array */ public function schedule_cron_healthcheck( $schedules ) { $interval = apply_filters( $this->identifier . '_cron_interval', 5 ); // Adds every 5 minutes to the existing schedules. $schedules[ $this->identifier . '_cron_interval' ] = array( 'interval' => MINUTE_IN_SECONDS * $interval, 'display' => sprintf( /* translators: %d: Interval in minutes. */ esc_html__( 'Every %d minutes', 'elementor' ), $interval ), ); return $schedules; } /** * See if the batch limit has been exceeded. * * @return bool */ public function is_memory_exceeded() { return $this->memory_exceeded(); } /** * Delete all batches. * * @return self */ public function delete_all_batches() { global $wpdb; $table = $wpdb->options; $column = 'option_name'; if ( is_multisite() ) { $table = $wpdb->sitemeta; $column = 'meta_key'; } $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; $wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine. return $this; } /** * Kill process. * * Stop processing queue items, clear cronjob and delete all batches. */ public function kill_process() { if ( ! $this->is_queue_empty() ) { $this->delete_all_batches(); wp_clear_scheduled_hook( $this->cron_hook_identifier ); } } public function set_current_item( $item ) { $this->current_item = $item; } protected function format_callback_log( $item ) { return implode( '::', (array) $item['callback'] ); } /** * @var \Elementor\Core\Base\Background_Task_Manager */ protected $manager; public function __construct( $manager ) { $this->manager = $manager; // Uses unique prefix per blog so each blog has separate queue. $this->prefix = 'elementor_' . get_current_blog_id(); $this->action = $this->manager->get_action(); parent::__construct(); } } base/providers/social-network-provider.php 0000644 00000016233 14717626151 0015014 0 ustar 00 <?php namespace Elementor\Core\Base\Providers; class Social_Network_Provider { private static array $social_networks = []; public const FACEBOOK = 'Facebook'; public const TWITTER = 'X (Twitter)'; public const INSTAGRAM = 'Instagram'; public const LINKEDIN = 'LinkedIn'; public const PINTEREST = 'Pinterest'; public const YOUTUBE = 'YouTube'; public const TIKTOK = 'TikTok'; public const WHATSAPP = 'WhatsApp'; public const APPLEMUSIC = 'Apple Music'; public const SPOTIFY = 'Spotify'; public const SOUNDCLOUD = 'SoundCloud'; public const BEHANCE = 'Behance'; public const DRIBBBLE = 'Dribbble'; public const VIMEO = 'Vimeo'; public const WAZE = 'Waze'; public const MESSENGER = 'Messenger'; public const TELEPHONE = 'Telephone'; public const EMAIL = 'Email'; public const URL = 'Url'; public const FILE_DOWNLOAD = 'File Download'; public const SMS = 'SMS'; public const VIBER = 'VIBER'; public const SKYPE = 'Skype'; public const VCF = 'Save contact (vCard)'; public static function get_social_networks_icons(): array { static::init_social_networks_array_if_empty(); static $icons = []; if ( empty( $icons ) ) { foreach ( static::$social_networks as $network => $data ) { $icons[ $network ] = $data['icon']; } } return $icons; } public static function get_icon_mapping( string $platform ): string { static::init_social_networks_array_if_empty(); if ( isset( self::$social_networks[ $platform ]['icon'] ) ) { return self::$social_networks[ $platform ]['icon']; } return ''; } public static function get_name_mapping( string $platform ): string { static::init_social_networks_array_if_empty(); if ( isset( self::$social_networks[ $platform ]['name'] ) ) { return self::$social_networks[ $platform ]['name']; } return ''; } public static function get_text_mapping( string $platform ): string { static::init_social_networks_array_if_empty(); if ( isset( self::$social_networks[ $platform ]['text'] ) ) { return self::$social_networks[ $platform ]['text']; } return ''; } public static function get_social_networks_text( $providers = [] ): array { static::init_social_networks_array_if_empty(); static $texts = []; if ( empty( $texts ) ) { foreach ( static::$social_networks as $network => $data ) { $texts[ $network ] = $data['text']; } } if ( $providers ) { return array_intersect_key( $texts, array_flip( $providers ) ); } return $texts; } private static function init_social_networks_array_if_empty(): void { if ( ! empty( static::$social_networks ) ) { return; } static::$social_networks[ static::VCF ] = [ 'text' => esc_html__( 'Save contact (vCard)', 'elementor' ), 'icon' => 'fab fa-outlook', 'name' => 'vcf', ]; static::$social_networks[ static::FACEBOOK ] = [ 'text' => esc_html__( 'Facebook', 'elementor' ), 'icon' => 'fab fa-facebook', 'name' => 'facebook', ]; static::$social_networks[ static::TWITTER ] = [ 'text' => esc_html__( 'X (Twitter)', 'elementor' ), 'icon' => 'fab fa-x-twitter', 'name' => 'x-twitter', ]; static::$social_networks[ static::INSTAGRAM ] = [ 'text' => esc_html__( 'Instagram', 'elementor' ), 'icon' => 'fab fa-instagram', 'name' => 'instagram', ]; static::$social_networks[ static::LINKEDIN ] = [ 'text' => esc_html__( 'LinkedIn', 'elementor' ), 'icon' => 'fab fa-linkedin-in', 'name' => 'linkedin', ]; static::$social_networks[ static::PINTEREST ] = [ 'text' => esc_html__( 'Pinterest', 'elementor' ), 'icon' => 'fab fa-pinterest', 'name' => 'pinterest', ]; static::$social_networks[ static::YOUTUBE ] = [ 'text' => esc_html__( 'YouTube', 'elementor' ), 'icon' => 'fab fa-youtube', 'name' => 'youtube', ]; static::$social_networks[ static::TIKTOK ] = [ 'text' => esc_html__( 'TikTok', 'elementor' ), 'icon' => 'fab fa-tiktok', 'name' => 'tiktok', ]; static::$social_networks[ static::WHATSAPP ] = [ 'text' => esc_html__( 'WhatsApp', 'elementor' ), 'icon' => 'fab fa-whatsapp', 'name' => 'whatsapp', ]; static::$social_networks[ static::APPLEMUSIC ] = [ 'text' => esc_html__( 'Apple Music', 'elementor' ), 'icon' => 'fa fa-music', 'name' => 'apple-music', ]; static::$social_networks[ static::SPOTIFY ] = [ 'text' => esc_html__( 'Spotify', 'elementor' ), 'icon' => 'fab fa-spotify', 'name' => 'spotify', ]; static::$social_networks[ static::SOUNDCLOUD ] = [ 'text' => esc_html__( 'SoundCloud', 'elementor' ), 'icon' => 'fab fa-soundcloud', 'name' => 'soundcloud', ]; static::$social_networks[ static::BEHANCE ] = [ 'text' => esc_html__( 'Behance', 'elementor' ), 'icon' => 'fab fa-behance', 'name' => 'behance', ]; static::$social_networks[ static::DRIBBBLE ] = [ 'text' => esc_html__( 'Dribbble', 'elementor' ), 'icon' => 'fab fa-dribbble', 'name' => 'dribble', ]; static::$social_networks[ static::VIMEO ] = [ 'text' => esc_html__( 'Vimeo', 'elementor' ), 'icon' => 'fab fa-vimeo-v', 'name' => 'vimeo', ]; static::$social_networks[ static::WAZE ] = [ 'text' => esc_html__( 'Waze', 'elementor' ), 'icon' => 'fab fa-waze', 'name' => 'waze', ]; static::$social_networks[ static::MESSENGER ] = [ 'text' => esc_html__( 'Messenger', 'elementor' ), 'icon' => 'fab fa-facebook-messenger', 'name' => 'messenger', ]; static::$social_networks[ static::TELEPHONE ] = [ 'text' => esc_html__( 'Telephone', 'elementor' ), 'icon' => 'fas fa-phone-alt', 'name' => 'phone', ]; static::$social_networks[ static::EMAIL ] = [ 'text' => esc_html__( 'Email', 'elementor' ), 'icon' => 'fas fa-envelope', 'name' => 'email', ]; static::$social_networks[ static::URL ] = [ 'text' => esc_html__( 'URL', 'elementor' ), 'icon' => 'fas fa-globe', 'name' => 'url', ]; static::$social_networks[ static::FILE_DOWNLOAD ] = [ 'text' => esc_html__( 'File Download', 'elementor' ), 'icon' => 'fas fa-download', 'name' => 'download', ]; static::$social_networks[ static::SMS ] = [ 'text' => esc_html__( 'SMS', 'elementor' ), 'icon' => 'fas fa-sms', 'name' => 'sms', ]; static::$social_networks[ static::VIBER ] = [ 'text' => esc_html__( 'Viber', 'elementor' ), 'icon' => 'fab fa-viber', 'name' => 'viber', ]; static::$social_networks[ static::SKYPE ] = [ 'text' => esc_html__( 'Skype', 'elementor' ), 'icon' => 'fab fa-skype', 'name' => 'skype', ]; } public static function build_messenger_link( string $username ) { return 'https://m.me/' . $username; } public static function build_email_link( array $data, string $prefix ) { $email = $data[ $prefix . '_mail' ] ?? ''; $subject = $data[ $prefix . '_mail_subject' ] ?? ''; $body = $data[ $prefix . '_mail_body' ] ?? ''; if ( ! $email ) { return ''; } $link = 'mailto:' . $email; if ( $subject ) { $link .= '?subject=' . $subject; } if ( $body ) { $link .= $subject ? '&' : '?'; $link .= 'body=' . $body; } return $link; } public static function build_viber_link( string $action, string $number ) { if ( empty( $number ) ) { return ''; } return add_query_arg( [ 'number' => urlencode( $number ), ], 'viber://' . $action ); } } base/background-process/wp-background-process.php 0000644 00000025510 14717626151 0016216 0 ustar 00 <?php namespace Elementor\Core\Base\BackgroundProcess; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * https://github.com/A5hleyRich/wp-background-processing GPL v2.0 * * WP Background Process * * @package WP-Background-Processing */ /** * Abstract WP_Background_Process class. * * @abstract * @extends WP_Async_Request */ abstract class WP_Background_Process extends WP_Async_Request { /** * Action * * (default value: 'background_process') * * @var string * @access protected */ protected $action = 'background_process'; /** * Start time of current process. * * (default value: 0) * * @var int * @access protected */ protected $start_time = 0; /** * Cron_hook_identifier * * @var mixed * @access protected */ protected $cron_hook_identifier; /** * Cron_interval_identifier * * @var mixed * @access protected */ protected $cron_interval_identifier; /** * Initiate new background process */ public function __construct() { parent::__construct(); $this->cron_hook_identifier = $this->identifier . '_cron'; $this->cron_interval_identifier = $this->identifier . '_cron_interval'; add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) ); add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) ); } /** * Dispatch * * @access public * @return array|\WP_Error */ public function dispatch() { // Schedule the cron healthcheck. $this->schedule_event(); // Perform remote post. return parent::dispatch(); } /** * Push to queue * * @param mixed $data Data. * * @return $this */ public function push_to_queue( $data ) { $this->data[] = $data; return $this; } /** * Save queue * * @return $this */ public function save() { $key = $this->generate_key(); if ( ! empty( $this->data ) ) { update_site_option( $key, $this->data ); } return $this; } /** * Update queue * * @param string $key Key. * @param array $data Data. * * @return $this */ public function update( $key, $data ) { if ( ! empty( $data ) ) { update_site_option( $key, $data ); } return $this; } /** * Delete queue * * @param string $key Key. * * @return $this */ public function delete( $key ) { delete_site_option( $key ); return $this; } /** * Generate key * * Generates a unique key based on microtime. Queue items are * given a unique key so that they can be merged upon save. * * @param int $length Length. * * @return string */ protected function generate_key( $length = 64 ) { $unique = md5( microtime() . rand() ); $prepend = $this->identifier . '_batch_'; return substr( $prepend . $unique, 0, $length ); } /** * Maybe process queue * * Checks whether data exists within the queue and that * the process is not already running. */ public function maybe_handle() { // Don't lock up other requests while processing session_write_close(); if ( $this->is_process_running() ) { // Background process already running. wp_die(); } if ( $this->is_queue_empty() ) { // No data to process. wp_die(); } check_ajax_referer( $this->identifier, 'nonce' ); $this->handle(); wp_die(); } /** * Is queue empty * * @return bool */ protected function is_queue_empty() { global $wpdb; $table = $wpdb->options; $column = 'option_name'; if ( is_multisite() ) { $table = $wpdb->sitemeta; $column = 'meta_key'; } $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // Can't use placeholders for table/column names, it will be wrapped by a single quote (') instead of a backquote (`). $count = $wpdb->get_var( $wpdb->prepare( " SELECT COUNT(*) FROM {$table} WHERE {$column} LIKE %s ", $key ) ); // phpcs:enable return ( $count > 0 ) ? false : true; } /** * Is process running * * Check whether the current process is already running * in a background process. */ protected function is_process_running() { if ( get_site_transient( $this->identifier . '_process_lock' ) ) { // Process already running. return true; } return false; } /** * Lock process * * Lock the process so that multiple instances can't run simultaneously. * Override if applicable, but the duration should be greater than that * defined in the time_exceeded() method. */ protected function lock_process() { $this->start_time = time(); // Set start time of current process. $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration ); set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration ); } /** * Unlock process * * Unlock the process so that other instances can spawn. * * @return $this */ protected function unlock_process() { delete_site_transient( $this->identifier . '_process_lock' ); return $this; } /** * Get batch * * @return \stdClass Return the first batch from the queue */ protected function get_batch() { global $wpdb; $table = $wpdb->options; $column = 'option_name'; $key_column = 'option_id'; $value_column = 'option_value'; if ( is_multisite() ) { $table = $wpdb->sitemeta; $column = 'meta_key'; $key_column = 'meta_id'; $value_column = 'meta_value'; } $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // Can't use placeholders for table/column names, it will be wrapped by a single quote (') instead of a backquote (`). $query = $wpdb->get_row( $wpdb->prepare( " SELECT * FROM {$table} WHERE {$column} LIKE %s ORDER BY {$key_column} ASC LIMIT 1 ", $key ) ); // phpcs:enable $batch = new \stdClass(); $batch->key = $query->$column; $batch->data = maybe_unserialize( $query->$value_column ); return $batch; } /** * Handle * * Pass each queue item to the task handler, while remaining * within server memory and time limit constraints. */ protected function handle() { $this->lock_process(); do { $batch = $this->get_batch(); foreach ( $batch->data as $key => $value ) { $task = $this->task( $value ); if ( false !== $task ) { $batch->data[ $key ] = $task; } else { unset( $batch->data[ $key ] ); } if ( $this->time_exceeded() || $this->memory_exceeded() ) { // Batch limits reached. break; } } // Update or delete current batch. if ( ! empty( $batch->data ) ) { $this->update( $batch->key, $batch->data ); } else { $this->delete( $batch->key ); } } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() ); $this->unlock_process(); // Start next batch or complete process. if ( ! $this->is_queue_empty() ) { $this->dispatch(); } else { $this->complete(); } wp_die(); } /** * Memory exceeded * * Ensures the batch process never exceeds 90% * of the maximum WordPress memory. * * @return bool */ protected function memory_exceeded() { $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory $current_memory = memory_get_usage( true ); $return = false; if ( $current_memory >= $memory_limit ) { $return = true; } return apply_filters( $this->identifier . '_memory_exceeded', $return ); } /** * Get memory limit * * @return int */ protected function get_memory_limit() { if ( function_exists( 'ini_get' ) ) { $memory_limit = ini_get( 'memory_limit' ); } else { // Sensible default. $memory_limit = '128M'; } if ( ! $memory_limit || -1 === intval( $memory_limit ) ) { // Unlimited, set to 32GB. $memory_limit = '32000M'; } return intval( $memory_limit ) * 1024 * 1024; } /** * Time exceeded. * * Ensures the batch never exceeds a sensible time limit. * A timeout limit of 30s is common on shared hosting. * * @return bool */ protected function time_exceeded() { $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds $return = false; if ( time() >= $finish ) { $return = true; } return apply_filters( $this->identifier . '_time_exceeded', $return ); } /** * Complete. * * Override if applicable, but ensure that the below actions are * performed, or, call parent::complete(). */ protected function complete() { // Unschedule the cron healthcheck. $this->clear_scheduled_event(); } /** * Schedule cron healthcheck * * @access public * @param mixed $schedules Schedules. * @return mixed */ public function schedule_cron_healthcheck( $schedules ) { $interval = apply_filters( $this->identifier . '_cron_interval', 5 ); if ( property_exists( $this, 'cron_interval' ) ) { $interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval ); } // Adds every 5 minutes to the existing schedules. $schedules[ $this->identifier . '_cron_interval' ] = array( 'interval' => MINUTE_IN_SECONDS * $interval, 'display' => sprintf( /* translators: %d: Interval in minutes. */ esc_html__( 'Every %d minutes', 'elementor' ), $interval, ), ); return $schedules; } /** * Handle cron healthcheck * * Restart the background process if not already running * and data exists in the queue. */ public function handle_cron_healthcheck() { if ( $this->is_process_running() ) { // Background process already running. exit; } if ( $this->is_queue_empty() ) { // No data to process. $this->clear_scheduled_event(); exit; } $this->handle(); exit; } /** * Schedule event */ protected function schedule_event() { if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier ); } } /** * Clear scheduled event */ protected function clear_scheduled_event() { $timestamp = wp_next_scheduled( $this->cron_hook_identifier ); if ( $timestamp ) { wp_unschedule_event( $timestamp, $this->cron_hook_identifier ); } } /** * Cancel Process * * Stop processing queue items, clear cronjob and delete batch. * */ public function cancel_process() { if ( ! $this->is_queue_empty() ) { $batch = $this->get_batch(); $this->delete( $batch->key ); wp_clear_scheduled_hook( $this->cron_hook_identifier ); } } /** * Task * * Override this method to perform any actions required on each * queue item. Return the modified item for further processing * in the next pass through. Or, return false to remove the * item from the queue. * * @param mixed $item Queue item to iterate over. * * @return mixed */ abstract protected function task( $item ); } base/background-process/wp-async-request.php 0000644 00000005605 14717626151 0015231 0 ustar 00 <?php namespace Elementor\Core\Base\BackgroundProcess; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * https://github.com/A5hleyRich/wp-background-processing GPL v2.0 * * WP Async Request * * @package WP-Background-Processing */ /** * Abstract WP_Async_Request class. * * @abstract */ abstract class WP_Async_Request { /** * Prefix * * (default value: 'wp') * * @var string * @access protected */ protected $prefix = 'wp'; /** * Action * * (default value: 'async_request') * * @var string * @access protected */ protected $action = 'async_request'; /** * Identifier * * @var mixed * @access protected */ protected $identifier; /** * Data * * (default value: array()) * * @var array * @access protected */ protected $data = array(); /** * Initiate new async request */ public function __construct() { $this->identifier = $this->prefix . '_' . $this->action; add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) ); add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) ); } /** * Set data used during the request * * @param array $data Data. * * @return $this */ public function data( $data ) { $this->data = $data; return $this; } /** * Dispatch the async request * * @return array|\WP_Error */ public function dispatch() { $url = add_query_arg( $this->get_query_args(), $this->get_query_url() ); $args = $this->get_post_args(); return wp_remote_post( esc_url_raw( $url ), $args ); } /** * Get query args * * @return array */ protected function get_query_args() { if ( property_exists( $this, 'query_args' ) ) { return $this->query_args; } return array( 'action' => $this->identifier, 'nonce' => wp_create_nonce( $this->identifier ), ); } /** * Get query URL * * @return string */ protected function get_query_url() { if ( property_exists( $this, 'query_url' ) ) { return $this->query_url; } return admin_url( 'admin-ajax.php' ); } /** * Get post args * * @return array */ protected function get_post_args() { if ( property_exists( $this, 'post_args' ) ) { return $this->post_args; } return array( 'timeout' => 0.01, 'blocking' => false, 'body' => $this->data, 'cookies' => $_COOKIE, /** This filter is documented in wp-includes/class-wp-http-streams.php */ 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), ); } /** * Maybe handle * * Check for correct nonce and pass to handler. */ public function maybe_handle() { // Don't lock up other requests while processing session_write_close(); check_ajax_referer( $this->identifier, 'nonce' ); $this->handle(); wp_die(); } /** * Handle * * Override this method to perform any actions required * during the async request. */ abstract protected function handle(); } base/module.php 0000644 00000016610 14717626151 0007472 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor module. * * An abstract class that provides the needed properties and methods to * manage and handle modules in inheriting classes. * * @since 1.7.0 * @abstract */ abstract class Module extends Base_Object { /** * Module class reflection. * * Holds the information about a class. * * @since 1.7.0 * @access private * * @var \ReflectionClass */ private $reflection; /** * Module components. * * Holds the module components. * * @since 1.7.0 * @access private * * @var array */ private $components = []; /** * Module instance. * * Holds the module instance. * * @since 1.7.0 * @access protected * * @var Module */ protected static $_instances = []; /** * Get module name. * * Retrieve the module name. * * @since 1.7.0 * @access public * @abstract * * @return string Module name. */ abstract public function get_name(); /** * Instance. * * Ensures only one instance of the module class is loaded or can be loaded. * * @since 1.7.0 * @access public * @static * * @return $this An instance of the class. */ public static function instance() { $class_name = static::class_name(); if ( empty( static::$_instances[ $class_name ] ) ) { static::$_instances[ $class_name ] = new static(); } return static::$_instances[ $class_name ]; } /** * @since 2.0.0 * @access public * @static */ public static function is_active() { return true; } /** * Class name. * * Retrieve the name of the class. * * @since 1.7.0 * @access public * @static */ public static function class_name() { return get_called_class(); } public static function get_experimental_data() { return []; } /** * Clone. * * Disable class cloning and throw an error on object clone. * * The whole idea of the singleton design pattern is that there is a single * object. Therefore, we don't want the object to be cloned. * * @since 1.7.0 * @access public */ public function __clone() { _doing_it_wrong( __FUNCTION__, sprintf( 'Cloning instances of the singleton "%s" class is forbidden.', get_class( $this ) ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped '1.0.0' ); } /** * Wakeup. * * Disable unserializing of the class. * * @since 1.7.0 * @access public */ public function __wakeup() { _doing_it_wrong( __FUNCTION__, sprintf( 'Unserializing instances of the singleton "%s" class is forbidden.', get_class( $this ) ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped '1.0.0' ); } /** * @since 2.0.0 * @access public */ public function get_reflection() { if ( null === $this->reflection ) { $this->reflection = new \ReflectionClass( $this ); } return $this->reflection; } /** * Add module component. * * Add new component to the current module. * * @since 1.7.0 * @access public * * @param string $id Component ID. * @param mixed $instance An instance of the component. */ public function add_component( $id, $instance ) { $this->components[ $id ] = $instance; } /** * @since 2.3.0 * @access public * @return Module[] */ public function get_components() { return $this->components; } /** * Get module component. * * Retrieve the module component. * * @since 1.7.0 * @access public * * @param string $id Component ID. * * @return mixed An instance of the component, or `false` if the component * doesn't exist. */ public function get_component( $id ) { if ( isset( $this->components[ $id ] ) ) { return $this->components[ $id ]; } return false; } /** * Get assets url. * * @since 2.3.0 * @access protected * * @param string $file_name * @param string $file_extension * @param string $relative_url Optional. Default is null. * @param string $add_min_suffix Optional. Default is 'default'. * * @return string */ final protected function get_assets_url( $file_name, $file_extension, $relative_url = null, $add_min_suffix = 'default' ) { static $is_test_mode = null; if ( null === $is_test_mode ) { $is_test_mode = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || defined( 'ELEMENTOR_TESTS' ) && ELEMENTOR_TESTS; } if ( ! $relative_url ) { $relative_url = $this->get_assets_relative_url() . $file_extension . '/'; } $url = $this->get_assets_base_url() . $relative_url . $file_name; if ( 'default' === $add_min_suffix ) { $add_min_suffix = ! $is_test_mode; } if ( $add_min_suffix ) { $url .= '.min'; } return $url . '.' . $file_extension; } /** * Get js assets url * * @since 2.3.0 * @access protected * * @param string $file_name * @param string $relative_url Optional. Default is null. * @param string $add_min_suffix Optional. Default is 'default'. * * @return string */ final protected function get_js_assets_url( $file_name, $relative_url = null, $add_min_suffix = 'default' ) { return $this->get_assets_url( $file_name, 'js', $relative_url, $add_min_suffix ); } /** * Get css assets url * * @since 2.3.0 * @access protected * * @param string $file_name * @param string $relative_url Optional. Default is null. * @param string $add_min_suffix Optional. Default is 'default'. * @param bool $add_direction_suffix Optional. Default is `false` * * @return string */ final protected function get_css_assets_url( $file_name, $relative_url = null, $add_min_suffix = 'default', $add_direction_suffix = false ) { static $direction_suffix = null; if ( ! $direction_suffix ) { $direction_suffix = is_rtl() ? '-rtl' : ''; } if ( $add_direction_suffix ) { $file_name .= $direction_suffix; } return $this->get_assets_url( $file_name, 'css', $relative_url, $add_min_suffix ); } /** * Get Frontend File URL * * Returns the URL for the CSS file to be loaded in the front end. If requested via the second parameter, a custom * file is generated based on a passed template file name. Otherwise, the URL for the default CSS file is returned. * * @since 3.24.0 * * @access public * * @param string $file_name * @param boolean $has_custom_breakpoints * * @return string frontend file URL */ public function get_frontend_file_url( $file_name, $has_custom_breakpoints ) { return Plugin::$instance->frontend->get_frontend_file_url( $file_name, $has_custom_breakpoints ); } /** * Get assets base url * * @since 2.6.0 * @access protected * * @return string */ protected function get_assets_base_url() { return ELEMENTOR_URL; } /** * Get assets relative url * * @since 2.3.0 * @access protected * * @return string */ protected function get_assets_relative_url() { return 'assets/'; } /** * Get the module's associated widgets. * * @return string[] */ protected function get_widgets() { return []; } /** * Initialize the module related widgets. */ public function init_widgets() { $widget_manager = Plugin::instance()->widgets_manager; foreach ( $this->get_widgets() as $widget ) { $class_name = $this->get_reflection()->getNamespaceName() . '\Widgets\\' . $widget; $widget_manager->register( new $class_name() ); } } public function __construct() { add_action( 'elementor/widgets/register', [ $this, 'init_widgets' ] ); } } base/document.php 0000644 00000144530 14717626151 0010026 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Core\Base\Elements_Iteration_Actions\Assets as Assets_Iteration_Action; use Elementor\Core\Base\Elements_Iteration_Actions\Base as Elements_Iteration_Action; use Elementor\Core\Behaviors\Interfaces\Lock_Behavior; use Elementor\Core\Files\CSS\Post as Post_CSS; use Elementor\Core\Settings\Page\Model as Page_Model; use Elementor\Core\Utils\Exceptions; use Elementor\Includes\Elements\Container; use Elementor\Plugin; use Elementor\Controls_Manager; use Elementor\Controls_Stack; use Elementor\TemplateLibrary\Source_Local; use Elementor\User; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Utils; use Elementor\Widget_Base; use Elementor\Core\Settings\Page\Manager as PageManager; use ElementorPro\Modules\Library\Widgets\Template; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Elementor document. * * An abstract class that provides the needed properties and methods to * manage and handle documents in inheriting classes. * * @since 2.0.0 * @abstract */ abstract class Document extends Controls_Stack { /** * Document type meta key. */ const TYPE_META_KEY = '_elementor_template_type'; const PAGE_META_KEY = '_elementor_page_settings'; const BUILT_WITH_ELEMENTOR_META_KEY = '_elementor_edit_mode'; const CACHE_META_KEY = '_elementor_element_cache'; /** * Document publish status. */ const STATUS_PUBLISH = 'publish'; /** * Document draft status. */ const STATUS_DRAFT = 'draft'; /** * Document private status. */ const STATUS_PRIVATE = 'private'; /** * Document autosave status. */ const STATUS_AUTOSAVE = 'autosave'; /** * Document pending status. */ const STATUS_PENDING = 'pending'; /** * @var int */ private $main_id; /** * @var bool */ private $is_saving = false; private static $properties = []; /** * @var Elements_Iteration_Action[] */ private $elements_iteration_actions = []; /** * Document post data. * * Holds the document post data. * * @since 2.0.0 * @access protected * * @var \WP_Post WordPress post data. */ protected $post; /** * @param array $internal_elements * * @return array[] */ private function get_container_elements_data( array $internal_elements ): array { return [ [ 'id' => Utils::generate_random_string(), 'elType' => 'container', 'elements' => $internal_elements, ], ]; } /** * @param array $internal_elements * * @return array[] */ private function get_sections_elements_data( array $internal_elements ): array { return [ [ 'id' => Utils::generate_random_string(), 'elType' => 'section', 'elements' => [ [ 'id' => Utils::generate_random_string(), 'elType' => 'column', 'elements' => $internal_elements, ], ], ], ]; } /** * @since 2.1.0 * @access protected * @static */ protected static function get_editor_panel_categories() { return Plugin::$instance->elements_manager->get_categories(); } /** * Get properties. * * Retrieve the document properties. * * @since 2.0.0 * @access public * @static * * @return array Document properties. */ public static function get_properties() { return [ 'has_elements' => true, 'is_editable' => true, 'edit_capability' => '', 'show_in_finder' => true, 'show_on_admin_bar' => true, 'support_kit' => false, 'show_navigator' => true, 'allow_adding_widgets' => true, 'support_page_layout' => true, 'show_copy_and_share' => false, 'library_close_title' => esc_html__( 'Close', 'elementor' ), 'publish_button_title' => esc_html__( 'Publish', 'elementor' ), 'allow_closing_remote_library' => true, ]; } /** * @since 2.1.0 * @access public * @static */ public static function get_editor_panel_config() { $default_route = 'panel/elements/categories'; if ( ! Plugin::instance()->role_manager->user_can( 'design' ) ) { $default_route = 'panel/page-settings/settings'; } return [ 'title' => static::get_title(), // JS Container title. 'widgets_settings' => [], 'elements_categories' => self::get_filtered_editor_panel_categories(), 'default_route' => $default_route, 'has_elements' => static::get_property( 'has_elements' ), 'support_kit' => static::get_property( 'support_kit' ), 'messages' => [ 'publish_notification' => sprintf( /* translators: %s: Document title. */ esc_html__( 'Hurray! Your %s is live.', 'elementor' ), static::get_title() ), ], 'show_navigator' => static::get_property( 'show_navigator' ), 'allow_adding_widgets' => static::get_property( 'allow_adding_widgets' ), 'show_copy_and_share' => static::get_property( 'show_copy_and_share' ), 'library_close_title' => static::get_property( 'library_close_title' ), 'publish_button_title' => static::get_property( 'publish_button_title' ), 'allow_closing_remote_library' => static::get_property( 'allow_closing_remote_library' ), ]; } public static function get_filtered_editor_panel_categories(): array { $categories = static::get_editor_panel_categories(); $has_pro = Utils::has_pro(); foreach ( $categories as $index => $category ) { if ( isset( $category['promotion'] ) ) { $categories = self::get_panel_category_item( $category['promotion'], $index, $categories, $has_pro ); } } return $categories; } /** * @param $promotion * @param $index * @param array $categories * * @return array */ private static function get_panel_category_item( $promotion, $index, array $categories, bool $has_pro ): array { if ( ! $has_pro ) { $categories[ $index ]['promotion'] = Filtered_Promotions_Manager::get_filtered_promotion_data( $promotion, 'elementor/panel/' . $index . '/custom_promotion', 'url' ); } else { unset( $categories[ $index ]['promotion'] ); } return $categories; } /** * Get element title. * * Retrieve the element title. * * @since 2.0.0 * @access public * @static * * @return string Element title. */ public static function get_title() { return esc_html__( 'Document', 'elementor' ); } public static function get_plural_title() { return static::get_title(); } public static function get_add_new_title() { return sprintf( /* translators: %s: Document title. */ esc_html__( 'Add New %s', 'elementor' ), static::get_title() ); } /** * Get property. * * Retrieve the document property. * * @since 2.0.0 * @access public * @static * * @param string $key The property key. * * @return mixed The property value. */ public static function get_property( $key ) { $id = static::get_class_full_name(); if ( ! isset( self::$properties[ $id ] ) ) { self::$properties[ $id ] = static::get_properties(); } return self::get_items( self::$properties[ $id ], $key ); } /** * @since 2.0.0 * @access public * @static */ public static function get_class_full_name() { return get_called_class(); } public static function get_create_url() { $properties = static::get_properties(); // BC Support - Each document should define it own CPT this code is for BC support. $cpt = Source_Local::CPT; if ( isset( $properties['cpt'][0] ) ) { $cpt = $properties['cpt'][0]; } return Plugin::$instance->documents->get_create_new_post_url( $cpt, static::get_type() ); } public function get_name() { return static::get_type(); } /** * @since 2.0.0 * @access public */ public function get_unique_name() { return static::get_type() . '-' . $this->post->ID; } /** * @since 2.3.0 * @access public */ public function get_post_type_title() { $post_type_object = get_post_type_object( $this->post->post_type ); return $post_type_object->labels->singular_name; } /** * @since 2.0.0 * @access public */ public function get_main_id() { if ( ! $this->main_id ) { $post_id = $this->post->ID; $parent_post_id = wp_is_post_revision( $post_id ); if ( $parent_post_id ) { $post_id = $parent_post_id; } $this->main_id = $post_id; } return $this->main_id; } /** * @return null|Lock_Behavior */ public static function get_lock_behavior_v2() { return null; } /** * @since 2.0.0 * @access public * * @param $data * * @throws \Exception If the widget was not found. * * @return string */ public function render_element( $data ) { // Start buffering ob_start(); /** @var Widget_Base $widget */ $widget = Plugin::$instance->elements_manager->create_element_instance( $data ); if ( ! $widget ) { throw new \Exception( 'Widget not found.' ); } $widget->render_content(); $render_html = ob_get_clean(); return $render_html; } /** * @since 2.0.0 * @access public */ public function get_main_post() { return get_post( $this->get_main_id() ); } public function get_container_attributes() { $id = $this->get_main_id(); $attributes = [ 'data-elementor-type' => $this->get_name(), 'data-elementor-id' => $id, 'class' => 'elementor elementor-' . $id, ]; $version_meta = $this->get_main_meta( '_elementor_version' ); if ( version_compare( $version_meta, '2.5.0', '<' ) ) { $attributes['class'] .= ' elementor-bc-flex-widget'; } if ( Plugin::$instance->preview->is_preview() ) { $attributes['data-elementor-title'] = static::get_title(); } else { $elementor_settings = $this->get_frontend_settings(); if ( ! empty( $elementor_settings ) ) { $attributes['data-elementor-settings'] = wp_json_encode( $elementor_settings ); } } // apply this filter to allow the attributes to be modified by different sources return apply_filters( 'elementor/document/wrapper_attributes', $attributes, $this ); } /** * @since 2.0.0 * @access public */ public function get_wp_preview_url() { $main_post_id = $this->get_main_id(); $document = $this; // Ajax request from editor. $initial_document_id = Utils::get_super_global_value( $_POST, 'initial_document_id' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! empty( $initial_document_id ) ) { $document = Plugin::$instance->documents->get( $initial_document_id ); // phpcs:ignore WordPress.Security.NonceVerification.Missing } $url = get_preview_post_link( $document->get_main_id(), [ 'preview_id' => $main_post_id, 'preview_nonce' => wp_create_nonce( 'post_preview_' . $main_post_id ), ] ); /** * Document "WordPress preview" URL. * * Filters the WordPress preview URL. * * @since 2.0.0 * * @param string $url WordPress preview URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/wp_preview', $url, $this ); return $url; } /** * @since 2.0.0 * @access public */ public function get_exit_to_dashboard_url() { $url = get_edit_post_link( $this->get_main_id(), 'raw' ); /** * Document "exit to dashboard" URL. * * Filters the "Exit To Dashboard" URL. * * @since 2.0.0 * * @param string $url The exit URL * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/exit_to_dashboard', $url, $this ); return $url; } /** * Get All Post Type URL * * Get url of the page which display all the posts of the current active document's post type. * * @since 3.7.0 * * @return string $url */ public function get_all_post_type_url() { $post_type = get_post_type( $this->get_main_id() ); $url = get_admin_url() . 'edit.php'; if ( 'post' !== $post_type ) { $url .= '?post_type=' . $post_type; } /** * Document "display all post type" URL. * * @since 3.7.0 * * @param string $url The URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/all_post_type', $url, $this ); return $url; } /** * Get Main WP dashboard URL. * * @since 3.7.0 * * @return string $url */ protected function get_main_dashboard_url() { $url = get_dashboard_url(); /** * Document "Main Dashboard" URL. * * @since 3.7.0 * * @param string $url The URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/main_dashboard', $url, $this ); return $url; } /** * Get auto-saved post revision. * * Retrieve the auto-saved post revision that is newer than current post. * * @since 2.0.0 * @access public * * * @return bool|Document */ public function get_newer_autosave() { $autosave = $this->get_autosave(); // Detect if there exists an autosave newer than the post. if ( $autosave && mysql2date( 'U', $autosave->get_post()->post_modified_gmt, false ) > mysql2date( 'U', $this->post->post_modified_gmt, false ) ) { return $autosave; } return false; } /** * @since 2.0.0 * @access public */ public function is_autosave() { return wp_is_post_autosave( $this->post->ID ); } /** * Check if the current document is a 'revision' * * @return bool */ public function is_revision() { return 'revision' === $this->post->post_type; } /** * Checks if the current document status is 'trash'. * * @return bool */ public function is_trash() { return 'trash' === $this->post->post_status; } /** * @since 2.0.0 * @access public * * @param int $user_id * @param bool $create * * @return bool|Document */ public function get_autosave( $user_id = 0, $create = false ) { if ( ! $user_id ) { $user_id = get_current_user_id(); } $autosave_id = $this->get_autosave_id( $user_id ); if ( $autosave_id ) { $document = Plugin::$instance->documents->get( $autosave_id ); } elseif ( $create ) { $autosave_id = wp_create_post_autosave( [ 'post_ID' => $this->post->ID, 'post_type' => $this->post->post_type, 'post_title' => $this->post->post_title, 'post_excerpt' => $this->post->post_excerpt, // Hack to cause $autosave_is_different=true in `wp_create_post_autosave`. 'post_content' => '<!-- Created With Elementor -->', 'post_modified' => current_time( 'mysql' ), ] ); Plugin::$instance->db->copy_elementor_meta( $this->post->ID, $autosave_id ); $document = Plugin::$instance->documents->get( $autosave_id ); $document->save_template_type(); } else { $document = false; } return $document; } /** * Add/Remove edit link in dashboard. * * Add or remove an edit link to the post/page action links on the post/pages list table. * * Fired by `post_row_actions` and `page_row_actions` filters. * * @access public * * @param array $actions An array of row action links. * * @return array An updated array of row action links. */ public function filter_admin_row_actions( $actions ) { if ( $this->is_built_with_elementor() && $this->is_editable_by_current_user() ) { $actions['edit_with_elementor'] = sprintf( '<a href="%1$s">%2$s</a>', $this->get_edit_url(), __( 'Edit with Elementor', 'elementor' ) ); } return $actions; } /** * @since 2.0.0 * @access public */ public function is_editable_by_current_user() { $edit_capability = static::get_property( 'edit_capability' ); if ( $edit_capability && ! current_user_can( $edit_capability ) ) { return false; } return self::get_property( 'is_editable' ) && User::is_current_user_can_edit( $this->get_main_id() ); } /** * @since 2.9.0 * @access protected */ protected function get_initial_config() { // Get document data *after* the scripts hook - so plugins can run compatibility before get data, but *before* enqueue the editor script - so elements can enqueue their own scripts that depended in editor script. $locked_user = Plugin::$instance->editor->get_locked_user( $this->get_main_id() ); if ( $locked_user ) { $locked_user = $locked_user->display_name; } $post = $this->get_main_post(); $post_type_object = get_post_type_object( $post->post_type ); $settings = SettingsManager::get_settings_managers_config(); $config = [ 'id' => $this->get_main_id(), 'type' => $this->get_name(), 'version' => $this->get_main_meta( '_elementor_version' ), 'settings' => $settings['page'], 'remoteLibrary' => $this->get_remote_library_config(), 'last_edited' => $this->get_last_edited(), 'panel' => static::get_editor_panel_config(), 'container' => 'body', 'post_type_title' => $this->get_post_type_title(), 'user' => [ 'can_publish' => current_user_can( $post_type_object->cap->publish_posts ), // Deprecated config since 2.9.0. 'locked' => $locked_user, ], 'urls' => [ 'exit_to_dashboard' => $this->get_exit_to_dashboard_url(), // WP post type edit page 'all_post_type' => $this->get_all_post_type_url(), 'preview' => $this->get_preview_url(), 'wp_preview' => $this->get_wp_preview_url(), 'permalink' => $this->get_permalink(), 'have_a_look' => $this->get_have_a_look_url(), 'main_dashboard' => $this->get_main_dashboard_url(), ], ]; $post_status_object = get_post_status_object( $post->post_status ); if ( $post_status_object ) { $config['status'] = [ 'value' => $post_status_object->name, 'label' => $post_status_object->label, ]; } do_action( 'elementor/document/before_get_config', $this ); if ( static::get_property( 'has_elements' ) ) { $container_config = []; if ( Plugin::$instance->experiments->is_feature_active( 'container' ) ) { $container_config = [ 'container' => Plugin::$instance->elements_manager->get_element_types( 'container' )->get_config(), ]; } $config['elements'] = $this->get_elements_raw_data( null, true ); $config['widgets'] = $container_config + Plugin::$instance->widgets_manager->get_widget_types_config(); } $additional_config = []; /** * Additional document configuration. * * Filters the document configuration by adding additional configuration. * External developers can use this hook to add custom configuration in * addition to Elementor's initial configuration. * * Use the $post_id to add custom configuration for different pages. * * @param array $additional_config The additional document configuration. * @param int $post_id The post ID of the document. */ $additional_config = apply_filters( 'elementor/document/config', $additional_config, $this->get_main_id() ); if ( ! empty( $additional_config ) ) { $config = array_replace_recursive( $config, $additional_config ); } return $config; } /** * @since 3.1.0 * @access protected */ protected function register_controls() { $this->register_document_controls(); /** * Register document controls. * * Fires after Elementor registers the document controls. * * External developers can use this hook to add new controls to the document. * * @since 2.0.0 * * @param Document $this The document instance. */ do_action( 'elementor/documents/register_controls', $this ); } /** * @since 2.0.0 * @access public * * @param $data * * @return bool */ public function save( $data ) { /** * Set locale to "C" to avoid issues with comma as decimal separator. * * @see https://github.com/elementor/elementor/issues/10992 */ $original_lc = setlocale( LC_NUMERIC, 0 ); setlocale( LC_NUMERIC, 'C' ); /** * Document save data. * * Filter the document data before saving process starts. * * External developers can use this hook to change the data before * saving it to the database. * * @since 3.3.0 * * @param array $data The document data. * @param \Elementor\Core\Base\Document $this The document instance. */ $data = apply_filters( 'elementor/document/save/data', $data, $this ); $this->add_handle_revisions_changed_filter(); if ( ! $this->is_editable_by_current_user() ) { return false; } $this->set_is_saving( true ); /** * Before document save. * * Fires when document save starts on Elementor. * * @since 2.5.12 * * @param \Elementor\Core\Base\Document $this The current document. * @param $data. */ do_action( 'elementor/document/before_save', $this, $data ); if ( ! current_user_can( 'unfiltered_html' ) ) { $data = wp_kses_post_deep( $data ); } if ( ! empty( $data['settings'] ) ) { if ( isset( $data['settings']['post_status'] ) && self::STATUS_AUTOSAVE === $data['settings']['post_status'] ) { if ( ! defined( 'DOING_AUTOSAVE' ) ) { define( 'DOING_AUTOSAVE', true ); } } $this->save_settings( $data['settings'] ); $this->refresh_post(); } // Don't check is_empty, because an empty array should be saved. if ( isset( $data['elements'] ) && is_array( $data['elements'] ) ) { $this->save_elements( $data['elements'] ); } $this->save_template_type(); $this->save_version(); // Remove Post CSS $post_css = Post_CSS::create( $this->post->ID ); $post_css->delete(); // Remove Document Cache $this->delete_cache(); /** * After document save. * * Fires when document save is complete. * * @since 2.5.12 * * @param \Elementor\Core\Base\Document $this The current document. * @param $data. */ do_action( 'elementor/document/after_save', $this, $data ); $this->set_is_saving( false ); $this->remove_handle_revisions_changed_filter(); setlocale( LC_NUMERIC, $original_lc ); return true; } public function refresh_post() { $this->post = get_post( $this->post->ID ); } /** * @param array $new_settings * * @return static */ public function update_settings( array $new_settings ) { $document_settings = $this->get_meta( PageManager::META_KEY ); if ( ! $document_settings ) { $document_settings = []; } $this->save_settings( array_replace_recursive( $document_settings, $new_settings ) ); return $this; } /** * Is built with Elementor. * * Check whether the post was built with Elementor. * * @since 2.0.0 * @access public * * @return bool Whether the post was built with Elementor. */ public function is_built_with_elementor() { return ! ! $this->get_meta( self::BUILT_WITH_ELEMENTOR_META_KEY ); } /** * Mark the post as "built with elementor" or not. * * @param bool $is_built_with_elementor * * @return $this */ public function set_is_built_with_elementor( $is_built_with_elementor ) { if ( $is_built_with_elementor ) { // Use the string `builder` and not a boolean for rollback compatibility $this->update_meta( self::BUILT_WITH_ELEMENTOR_META_KEY, 'builder' ); } else { $this->delete_meta( self::BUILT_WITH_ELEMENTOR_META_KEY ); } return $this; } /** * @since 2.0.0 * @access public * @static * * @return mixed */ public function get_edit_url() { $url = add_query_arg( [ 'post' => $this->get_main_id(), 'action' => 'elementor', ], admin_url( 'post.php' ) ); /** * Document edit url. * * Filters the document edit url. * * @since 2.0.0 * * @param string $url The edit url. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/edit', $url, $this ); return $url; } /** * @since 2.0.0 * @access public */ public function get_preview_url() { /** * Use a static var - to avoid change the `ver` parameter on every call. */ static $url; if ( empty( $url ) ) { add_filter( 'pre_option_permalink_structure', '__return_empty_string' ); $url = set_url_scheme( add_query_arg( [ 'elementor-preview' => $this->get_main_id(), 'ver' => time(), ], $this->get_permalink() ) ); remove_filter( 'pre_option_permalink_structure', '__return_empty_string' ); /** * Document preview URL. * * Filters the document preview URL. * * @since 2.0.0 * * @param string $url The preview URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/preview', $url, $this ); } return $url; } /** * @since 2.0.0 * @access public * * @param string $key * * @return array */ public function get_json_meta( $key ) { $meta = get_post_meta( $this->post->ID, $key, true ); if ( is_string( $meta ) && ! empty( $meta ) ) { $meta = json_decode( $meta, true ); } if ( empty( $meta ) ) { $meta = []; } return $meta; } public function update_json_meta( $key, $value ) { $this->update_meta( $key, // `wp_slash` in order to avoid the unslashing during the `update_post_meta` wp_slash( wp_json_encode( $value ) ) ); } /** * @since 2.0.0 * @access public * * @param null $data * @param bool $with_html_content * * @return array */ public function get_elements_raw_data( $data = null, $with_html_content = false ) { if ( ! static::get_property( 'has_elements' ) ) { return []; } if ( is_null( $data ) ) { $data = $this->get_elements_data(); } // Change the current documents, so widgets can use `documents->get_current` and other post data Plugin::$instance->documents->switch_to_document( $this ); $editor_data = []; foreach ( $data as $element_data ) { if ( ! is_array( $element_data ) ) { throw new \Exception( 'Invalid data: ' . wp_json_encode( [ 'data' => $data, 'element' => $element_data, ] ) ); } $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( ! $element ) { continue; } if ( $this->is_saving ) { $element_data = $element->get_data_for_save(); } else { $element_data = $element->get_raw_data( $with_html_content ); } $editor_data[] = $element_data; } // End foreach(). Plugin::$instance->documents->restore_document(); return $editor_data; } /** * @since 2.0.0 * @access public * * @param string $status * * @return array */ public function get_elements_data( $status = self::STATUS_PUBLISH ) { $elements = $this->get_json_meta( '_elementor_data' ); if ( self::STATUS_DRAFT === $status ) { $autosave = $this->get_newer_autosave(); if ( is_object( $autosave ) ) { $autosave_elements = Plugin::$instance->documents ->get( $autosave->get_post()->ID ) ->get_json_meta( '_elementor_data' ); } } if ( Plugin::$instance->editor->is_edit_mode() ) { if ( empty( $elements ) && empty( $autosave_elements ) ) { // Convert to Elementor. $elements = $this->convert_to_elementor(); if ( $this->is_autosave() ) { Plugin::$instance->db->copy_elementor_meta( $this->post->post_parent, $this->post->ID ); } } } if ( ! empty( $autosave_elements ) ) { $elements = $autosave_elements; } return $elements; } /** * Get document setting from DB. * * @return array */ public function get_db_document_settings() { return $this->get_meta( static::PAGE_META_KEY ); } /** * @since 2.3.0 * @access public */ public function convert_to_elementor() { $this->save( [] ); if ( empty( $this->post->post_content ) ) { return []; } // Check if it's only a shortcode. preg_match_all( '/' . get_shortcode_regex() . '/', $this->post->post_content, $matches, PREG_SET_ORDER ); if ( ! empty( $matches ) ) { foreach ( $matches as $shortcode ) { if ( trim( $this->post->post_content ) === $shortcode[0] ) { $widget_type = Plugin::$instance->widgets_manager->get_widget_types( 'shortcode' ); $settings = [ 'shortcode' => $this->post->post_content, ]; break; } } } if ( empty( $widget_type ) ) { $widget_type = Plugin::$instance->widgets_manager->get_widget_types( 'text-editor' ); $settings = [ 'editor' => $this->post->post_content, ]; } // TODO: Better coding to start template for editor $converted_blocks = [ [ 'id' => Utils::generate_random_string(), 'elType' => $widget_type::get_type(), 'widgetType' => $widget_type->get_name(), 'settings' => $settings, ], ]; return Plugin::$instance->experiments->is_feature_active( 'container' ) ? $this->get_container_elements_data( $converted_blocks ) : $this->get_sections_elements_data( $converted_blocks ); } /** * @since 2.1.3 * @access public */ public function print_elements_with_wrapper( $elements_data = null ) { if ( ! $elements_data ) { $elements_data = $this->get_elements_data(); } ?> <div <?php Utils::print_html_attributes( $this->get_container_attributes() ); ?>> <?php $this->print_elements( $elements_data ); ?> </div> <?php } /** * @since 2.0.0 * @access public */ public function get_css_wrapper_selector() { return ''; } /** * @since 2.0.0 * @access public */ public function get_panel_page_settings() { return [ 'title' => sprintf( /* translators: %s: Document title. */ esc_html__( '%s Settings', 'elementor' ), static::get_title() ), ]; } /** * @since 2.0.0 * @access public */ public function get_post() { return $this->post; } /** * @since 2.0.0 * @access public */ public function get_permalink() { return get_permalink( $this->get_main_id() ); } /** * @since 2.0.8 * @access public */ public function get_content( $with_css = false ) { return Plugin::$instance->frontend->get_builder_content( $this->post->ID, $with_css ); } /** * @since 2.0.0 * @access public */ public function delete() { if ( 'revision' === $this->post->post_type ) { $deleted = wp_delete_post_revision( $this->post ); } else { $deleted = wp_delete_post( $this->post->ID ); } return $deleted && ! is_wp_error( $deleted ); } public function force_delete() { $deleted = wp_delete_post( $this->post->ID, true ); return $deleted && ! is_wp_error( $deleted ); } /** * On import update dynamic content (e.g. post and term IDs). * * @since 3.8.0 * * @param array $config The config of the passed element. * @param array $data The data that requires updating/replacement when imported. * @param array|null $controls The available controls. * * @return array Element data. */ public static function on_import_update_dynamic_content( array $config, array $data, $controls = null ) : array { foreach ( $config as &$element_config ) { $element_instance = Plugin::$instance->elements_manager->create_element_instance( $element_config ); if ( is_null( $element_instance ) ) { continue; } if ( $element_instance->has_own_method( 'on_import_replace_dynamic_content' ) ) { // TODO: Remove this check in the future. $element_config = $element_instance::on_import_replace_dynamic_content( $element_config, $data['post_ids'] ); } else { $element_config = $element_instance::on_import_update_dynamic_content( $element_config, $data, $element_instance->get_controls() ); } $element_config['elements'] = static::on_import_update_dynamic_content( $element_config['elements'], $data ); } return $config; } /** * Update dynamic settings in the document for import. * * @param array $settings The settings of the document. * @param array $config Import config to update the settings. * * @return array */ public function on_import_update_settings( array $settings, array $config ): array { $controls = $this->get_controls(); $controls_manager = Plugin::$instance->controls_manager; foreach ( $settings as $key => $value ) { if ( ! isset( $controls[ $key ] ) ) { continue; } $control = $controls[ $key ]; $control_instance = $controls_manager->get_control( $control['type'] ); if ( ! $control_instance ) { continue; } $settings[ $key ] = $control_instance->on_import_update_settings( $value, $control, $config ); } return $settings; } /** * Save editor elements. * * Save data from the editor to the database. * * @since 2.0.0 * @access protected * * @param array $elements */ protected function save_elements( $elements ) { $editor_data = $this->get_elements_raw_data( $elements ); // We need the `wp_slash` in order to avoid the unslashing during the `update_post_meta` $json_value = wp_slash( wp_json_encode( $editor_data ) ); // Don't use `update_post_meta` that can't handle `revision` post type $is_meta_updated = update_metadata( 'post', $this->post->ID, '_elementor_data', $json_value ); /** * Before saving data. * * Fires before Elementor saves data to the database. * * @since 1.0.0 * * @param string $status Post status. * @param int|bool $is_meta_updated Meta ID if the key didn't exist, true on successful update, false on failure. */ do_action( 'elementor/db/before_save', $this->post->post_status, $is_meta_updated ); Plugin::$instance->db->save_plain_text( $this->post->ID ); $elements_iteration_actions = $this->get_elements_iteration_actions(); if ( $elements_iteration_actions ) { $this->iterate_elements( $elements, $elements_iteration_actions, 'save' ); } /** * After saving data. * * Fires after Elementor saves data to the database. * * @since 1.0.0 * * @param int $post_id The ID of the post. * @param array $editor_data Sanitize posted data. */ do_action( 'elementor/editor/after_save', $this->post->ID, $editor_data ); } /** * @since 2.0.0 * @access public * * @param int $user_id Optional. User ID. Default value is `0`. * * @return bool|int */ public function get_autosave_id( $user_id = 0 ) { if ( ! $user_id ) { $user_id = get_current_user_id(); } $autosave = Utils::get_post_autosave( $this->post->ID, $user_id ); if ( $autosave ) { return $autosave->ID; } return false; } public function save_version() { if ( ! defined( 'IS_ELEMENTOR_UPGRADE' ) ) { // Save per revision. $this->update_meta( '_elementor_version', ELEMENTOR_VERSION ); /** * Document version save. * * Fires when document version is saved on Elementor. * Will not fire during Elementor Upgrade. * * @since 2.5.12 * * @param \Elementor\Core\Base\Document $this The current document. * */ do_action( 'elementor/document/save_version', $this ); } } /** * @since 2.3.0 * @access public */ public function save_template_type() { return $this->update_main_meta( self::TYPE_META_KEY, $this->get_name() ); } /** * @since 2.3.0 * @access public */ public function get_template_type() { return $this->get_main_meta( self::TYPE_META_KEY ); } /** * @since 2.0.0 * @access public * * @param string $key Meta data key. * * @return mixed */ public function get_main_meta( $key ) { return get_post_meta( $this->get_main_id(), $key, true ); } /** * @since 2.0.4 * @access public * * @param string $key Meta data key. * @param mixed $value Meta data value. * * @return bool|int */ public function update_main_meta( $key, $value ) { return update_post_meta( $this->get_main_id(), $key, $value ); } /** * @since 2.0.4 * @access public * * @param string $key Meta data key. * @param string $value Optional. Meta data value. Default is an empty string. * * @return bool */ public function delete_main_meta( $key, $value = '' ) { return delete_post_meta( $this->get_main_id(), $key, $value ); } /** * @since 2.0.0 * @access public * * @param string $key Meta data key. * * @return mixed */ public function get_meta( $key ) { return get_post_meta( $this->post->ID, $key, true ); } /** * @since 2.0.0 * @access public * * @param string $key Meta data key. * @param mixed $value Meta data value. * * @return bool|int */ public function update_meta( $key, $value ) { // Use `update_metadata` in order to work also with revisions. return update_metadata( 'post', $this->post->ID, $key, $value ); } /** * @since 2.0.3 * @access public * * @param string $key Meta data key. * @param string $value Meta data value. * * @return bool */ public function delete_meta( $key, $value = '' ) { // Use `delete_metadata` in order to work also with revisions. return delete_metadata( 'post', $this->post->ID, $key, $value ); } /** * @since 2.0.0 * @access public */ public function get_last_edited() { $post = $this->post; $autosave_post = $this->get_autosave(); if ( $autosave_post ) { $post = $autosave_post->get_post(); } $date = date_i18n( _x( 'M j, H:i', 'revision date format', 'elementor' ), strtotime( $post->post_modified ) ); $display_name = get_the_author_meta( 'display_name', $post->post_author ); if ( $autosave_post || 'revision' === $post->post_type ) { $last_edited = sprintf( /* translators: 1: Saving date, 2: Author display name. */ esc_html__( 'Draft saved on %1$s by %2$s', 'elementor' ), '<time>' . $date . '</time>', $display_name ); } else { $last_edited = sprintf( /* translators: 1: Editing date, 2: Author display name. */ esc_html__( 'Last edited on %1$s by %2$s', 'elementor' ), '<time>' . $date . '</time>', $display_name ); } return $last_edited; } /** * @return bool */ public function is_saving() { return $this->is_saving; } /** * @param $is_saving * * @return $this */ public function set_is_saving( $is_saving ) { $this->is_saving = $is_saving; return $this; } /** * @since 2.0.0 * @access public * * @param array $data * * @throws \Exception If the post does not exist. */ public function __construct( array $data = [] ) { if ( $data ) { if ( empty( $data['post_id'] ) ) { $this->post = new \WP_Post( (object) [] ); } else { $this->post = get_post( $data['post_id'] ); if ( ! $this->post ) { throw new \Exception( sprintf( 'Post ID #%s does not exist.', $data['post_id'] ), Exceptions::NOT_FOUND ); } } // Each Control_Stack is based on a unique ID. $data['id'] = $data['post_id']; if ( ! isset( $data['settings'] ) ) { $data['settings'] = []; } $saved_settings = get_post_meta( $this->post->ID, '_elementor_page_settings', true ); if ( ! empty( $saved_settings ) && is_array( $saved_settings ) ) { $data['settings'] += $saved_settings; } } parent::__construct( $data ); } /* * Get Export Data * * Filters a document's data on export * * @since 3.2.0 * @access public * * @return array The data to export */ public function get_export_data() { $content = Plugin::$instance->db->iterate_data( $this->get_elements_data(), function( $element_data ) { $element_data['id'] = Utils::generate_random_string(); $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); // If the widget/element does not exist, like a plugin that creates a widget but deactivated. if ( ! $element ) { return null; } return $this->process_element_import_export( $element, 'on_export' ); } ); return [ 'content' => $content, 'settings' => $this->get_data( 'settings' ), 'metadata' => $this->get_export_metadata(), ]; } public function get_export_summary() { return [ 'title' => $this->post->post_title, 'doc_type' => $this->get_name(), 'thumbnail' => get_the_post_thumbnail_url( $this->post ), ]; } /* * Get Import Data * * Filters a document's data on import * * @since 3.2.0 * @access public * * @return array The data to import */ public function get_import_data( array $data ) { $data['content'] = Plugin::$instance->db->iterate_data( $data['content'], function( $element_data ) { $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); // If the widget/element isn't exist, like a plugin that creates a widget but deactivated if ( ! $element ) { return null; } return $this->process_element_import_export( $element, 'on_import' ); } ); if ( ! empty( $data['settings'] ) ) { $template_model = new Page_Model( [ 'id' => 0, 'settings' => $data['settings'], ] ); $page_data = $this->process_element_import_export( $template_model, 'on_import' ); $data['settings'] = $page_data['settings']; } return $data; } /** * Import * * Allows to import an external data to a document * * @since 3.2.0 * @access public * * @param array $data */ public function import( array $data ) { $data = $this->get_import_data( $data ); $this->save( [ 'elements' => $data['content'], 'settings' => $data['settings'], ] ); if ( $data['import_settings']['thumbnail'] ) { $attachment = Plugin::$instance->templates_manager->get_import_images_instance()->import( [ 'url' => $data['import_settings']['thumbnail'] ] ); set_post_thumbnail( $this->get_main_post(), $attachment['id'] ); } if ( ! empty( $data['metadata'] ) ) { foreach ( $data['metadata'] as $key => $value ) { $this->update_meta( $key, $value ); } } } public function process_element_import_export( Controls_Stack $element, $method, $element_data = null ) { if ( null === $element_data ) { $element_data = $element->get_data(); } if ( method_exists( $element, $method ) ) { // TODO: Use the internal element data without parameters. $element_data = $element->{$method}( $element_data ); } foreach ( $element->get_controls() as $control ) { $control_class = Plugin::$instance->controls_manager->get_control( $control['type'] ); // If the control isn't exist, like a plugin that creates the control but deactivated. if ( ! $control_class ) { return $element_data; } // Do not add default value to the final settings, if there is no value at the // data before the methods `on_import` or `on_export` called. $has_value = isset( $element_data['settings'][ $control['name'] ] ); if ( $has_value && method_exists( $control_class, $method ) ) { $element_data['settings'][ $control['name'] ] = $control_class->{$method}( $element_data['settings'][ $control['name'] ], $control ); } // On Export, check if the control has an argument 'export' => false. if ( 'on_export' === $method && isset( $control['export'] ) && false === $control['export'] ) { unset( $element_data['settings'][ $control['name'] ] ); } } return $element_data; } protected function get_export_metadata() { $metadata = get_post_meta( $this->get_main_id() ); foreach ( $metadata as $meta_key => $meta_value ) { if ( is_protected_meta( $meta_key, 'post' ) ) { unset( $metadata[ $meta_key ] ); continue; } $metadata[ $meta_key ] = $meta_value[0]; } return $metadata; } protected function get_remote_library_config() { $config = [ 'type' => 'block', 'default_route' => 'templates/blocks', 'category' => $this->get_name(), 'autoImportSettings' => false, ]; return $config; } /** * @since 2.0.4 * @access protected * * @param $settings */ protected function save_settings( $settings ) { $page_settings_manager = SettingsManager::get_settings_managers( 'page' ); $page_settings_manager->ajax_before_save_settings( $settings, $this->post->ID ); $page_settings_manager->save_settings( $settings, $this->post->ID ); } /** * @since 2.1.3 * @access protected */ protected function print_elements( $elements_data ) { if ( ! Plugin::$instance->experiments->is_feature_active( 'e_element_cache' ) ) { $this->do_print_elements( $elements_data ); return; } $cached_data = $this->get_document_cache(); if ( false === $cached_data ) { add_filter( 'elementor/element/should_render_shortcode', '__return_true' ); $scripts_to_queue = []; $styles_to_queue = []; global $wp_scripts, $wp_styles; $should_store_scripts = $wp_scripts instanceof \WP_Scripts && $wp_styles instanceof \WP_Styles; if ( $should_store_scripts ) { $scripts_ignored = $wp_scripts->queue; $styles_ignored = $wp_styles->queue; } ob_start(); $this->do_print_elements( $elements_data ); if ( $should_store_scripts ) { $scripts_to_queue = array_values( array_diff( $wp_scripts->queue, $scripts_ignored ) ); $styles_to_queue = array_values( array_diff( $wp_styles->queue, $styles_ignored ) ); } $cached_data = [ 'content' => ob_get_clean(), 'scripts' => $scripts_to_queue, 'styles' => $styles_to_queue, ]; if ( $this->should_store_cache_elements() ) { $this->set_document_cache( $cached_data ); } remove_filter( 'elementor/element/should_render_shortcode', '__return_true' ); } else { if ( ! empty( $cached_data['scripts'] ) ) { foreach ( $cached_data['scripts'] as $script_handle ) { wp_enqueue_script( $script_handle ); } } if ( ! empty( $cached_data['styles'] ) ) { foreach ( $cached_data['styles'] as $style_handle ) { wp_enqueue_style( $style_handle ); } } } if ( ! empty( $cached_data['content'] ) ) { echo do_shortcode( $cached_data['content'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } protected function do_print_elements( $elements_data ) { // Collect all data updaters that should be updated on runtime. $runtime_elements_iteration_actions = $this->get_runtime_elements_iteration_actions(); if ( $runtime_elements_iteration_actions ) { $this->iterate_elements( $elements_data, $runtime_elements_iteration_actions, 'render' ); } foreach ( $elements_data as $element_data ) { $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( ! $element ) { continue; } $element->print_element(); } } public function set_document_cache( $value ) { $expiration_hours = get_option( 'elementor_element_cache_ttl', '' ); if ( empty( $expiration_hours ) || ! is_numeric( $expiration_hours ) ) { $expiration_hours = '24'; } $expiration_hours = absint( $expiration_hours ); $expiration = '+' . $expiration_hours . ' hours'; $data = [ 'timeout' => strtotime( $expiration, current_time( 'timestamp' ) ), 'value' => $value, ]; $this->update_json_meta( static::CACHE_META_KEY, $data ); } private function get_document_cache() { $cache = $this->get_json_meta( static::CACHE_META_KEY ); if ( empty( $cache['timeout'] ) ) { return false; } if ( current_time( 'timestamp' ) > $cache['timeout'] ) { return false; } if ( ! is_array( $cache['value'] ) ) { return false; } return $cache['value']; } protected function delete_cache() { $this->delete_meta( static::CACHE_META_KEY ); } private function should_store_cache_elements() { static $should_store_cache_elements = null; if ( null === $should_store_cache_elements ) { $should_store_cache_elements = ( ! is_admin() && ! Plugin::$instance->preview->is_preview_mode() ); } return $should_store_cache_elements; } protected function register_document_controls() { $this->start_controls_section( 'document_settings', [ 'label' => esc_html__( 'General Settings', 'elementor' ), 'tab' => Controls_Manager::TAB_SETTINGS, ] ); $this->add_control( 'post_title', [ 'label' => esc_html__( 'Title', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => $this->post->post_title, 'label_block' => true, ] ); $post_type_object = get_post_type_object( $this->post->post_type ); $can_publish = $post_type_object && current_user_can( $post_type_object->cap->publish_posts ); $is_published = self::STATUS_PUBLISH === $this->post->post_status || self::STATUS_PRIVATE === $this->post->post_status; if ( $is_published || $can_publish || ! Plugin::$instance->editor->is_edit_mode() ) { $statuses = $this->get_post_statuses(); if ( 'future' === $this->get_main_post()->post_status ) { $statuses['future'] = esc_html__( 'Future', 'elementor' ); } $this->add_control( 'post_status', [ 'label' => esc_html__( 'Status', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => $this->get_main_post()->post_status, 'options' => $statuses, ] ); } $this->end_controls_section(); } protected function get_post_statuses() { return get_post_statuses(); } protected function get_have_a_look_url() { return $this->get_permalink(); } public function handle_revisions_changed( $post_has_changed, $last_revision, $post ) { // In case default, didn't determine the changes. if ( ! $post_has_changed ) { $last_revision_id = $last_revision->ID; $last_revision_document = Plugin::instance()->documents->get( $last_revision_id ); $post_document = Plugin::instance()->documents->get( $post->ID ); $last_revision_settings = $last_revision_document->get_settings(); $post_settings = $post_document->get_settings(); // TODO: Its better to add crc32 signature for each revision and then only compare one part of the checksum. $post_has_changed = $last_revision_settings !== $post_settings; } return $post_has_changed; } private function add_handle_revisions_changed_filter() { add_filter( 'wp_save_post_revision_post_has_changed', [ $this, 'handle_revisions_changed' ], 10, 3 ); } private function remove_handle_revisions_changed_filter() { remove_filter( 'wp_save_post_revision_post_has_changed', [ $this, 'handle_revisions_changed' ] ); } private function get_runtime_elements_iteration_actions() { $runtime_elements_iteration_actions = []; $elements_iteration_actions = $this->get_elements_iteration_actions(); foreach ( $elements_iteration_actions as $elements_iteration_action ) { if ( $elements_iteration_action->is_action_needed() ) { $runtime_elements_iteration_actions[] = $elements_iteration_action; } } return $runtime_elements_iteration_actions; } private function iterate_elements( $elements, $elements_iteration_actions, $mode ) { $unique_page_elements = []; foreach ( $elements_iteration_actions as $elements_iteration_action ) { $elements_iteration_action->set_mode( $mode ); } Plugin::$instance->db->iterate_data( $elements, function( array $element_data ) use ( &$unique_page_elements, $elements_iteration_actions ) { $element_type = 'widget' === $element_data['elType'] ? $element_data['widgetType'] : $element_data['elType']; $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( $element ) { if ( ! in_array( $element_type, $unique_page_elements, true ) ) { $unique_page_elements[] = $element_type; foreach ( $elements_iteration_actions as $elements_iteration_action ) { $elements_iteration_action->unique_element_action( $element ); } } foreach ( $elements_iteration_actions as $elements_iteration_action ) { $elements_iteration_action->element_action( $element ); } } return $element_data; } ); foreach ( $elements_iteration_actions as $elements_iteration_action ) { $elements_iteration_action->after_elements_iteration(); } } private function get_elements_iteration_actions() { if ( ! $this->elements_iteration_actions ) { $this->elements_iteration_actions[] = new Assets_Iteration_Action( $this ); } return $this->elements_iteration_actions; } } base/app.php 0000644 00000002344 14717626151 0006764 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Base App * * Base app utility class that provides shared functionality of apps. * * @since 2.3.0 */ abstract class App extends Module { /** * Print config. * * Used to print the app and its components settings as a JavaScript object. * * @param string $handle Optional * * @since 2.3.0 * @since 2.6.0 added the `$handle` parameter * @access protected */ final protected function print_config( $handle = null ) { $name = $this->get_name(); $js_var = 'elementor' . str_replace( ' ', '', ucwords( str_replace( '-', ' ', $name ) ) ) . 'Config'; $config = $this->get_settings() + $this->get_components_config(); if ( ! $handle ) { $handle = 'elementor-' . $name; } Utils::print_js_config( $handle, $js_var, $config ); } /** * Get components config. * * Retrieves the app components settings. * * @since 2.3.0 * @access private * * @return array */ private function get_components_config() { $settings = []; foreach ( $this->get_components() as $id => $instance ) { $settings[ $id ] = $instance->get_settings(); } return $settings; } } base/background-task-manager.php 0000644 00000004744 14717626151 0012701 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Background_Task_Manager extends BaseModule { /** * @var Background_Task */ protected $task_runner; abstract public function get_action(); abstract public function get_plugin_name(); abstract public function get_plugin_label(); abstract public function get_task_runner_class(); abstract public function get_query_limit(); abstract protected function start_run(); public function on_runner_start() { $logger = Plugin::$instance->logger->get_logger(); $logger->info( $this->get_plugin_name() . '::' . $this->get_action() . ' Started' ); } public function on_runner_complete( $did_tasks = false ) { $logger = Plugin::$instance->logger->get_logger(); $logger->info( $this->get_plugin_name() . '::' . $this->get_action() . ' Completed' ); } public function get_task_runner() { if ( empty( $this->task_runner ) ) { $class_name = $this->get_task_runner_class(); $this->task_runner = new $class_name( $this ); } return $this->task_runner; } // TODO: Replace with a db settings system. protected function add_flag( $flag ) { add_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag, 1 ); } protected function get_flag( $flag ) { return get_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag ); } protected function delete_flag( $flag ) { delete_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag ); } protected function get_start_action_url() { return wp_nonce_url( add_query_arg( $this->get_action(), 'run' ), $this->get_action() . 'run' ); } protected function get_continue_action_url() { return wp_nonce_url( add_query_arg( $this->get_action(), 'continue' ), $this->get_action() . 'continue' ); } private function continue_run() { $runner = $this->get_task_runner(); $runner->continue_run(); } public function __construct() { if ( empty( $_GET[ $this->get_action() ] ) ) { return; } Plugin::$instance->init_common(); if ( 'run' === $_GET[ $this->get_action() ] && check_admin_referer( $this->get_action() . 'run' ) ) { $this->start_run(); } if ( 'continue' === $_GET[ $this->get_action() ] && check_admin_referer( $this->get_action() . 'continue' ) ) { $this->continue_run(); } wp_safe_redirect( remove_query_arg( [ $this->get_action(), '_wpnonce' ] ) ); die; } } base/base-object.php 0000644 00000013064 14717626151 0010363 0 ustar 00 <?php namespace Elementor\Core\Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Base Object * * Base class that provides basic settings handling functionality. * * @since 2.3.0 */ class Base_Object { /** * Settings. * * Holds the object settings. * * @access private * * @var array */ private $settings; /** * Get Settings. * * @since 2.3.0 * @access public * * @param string $setting Optional. The key of the requested setting. Default is null. * * @return mixed An array of all settings, or a single value if `$setting` was specified. */ final public function get_settings( $setting = null ) { $this->ensure_settings(); return self::get_items( $this->settings, $setting ); } /** * Set settings. * * @since 2.3.0 * @access public * * @param array|string $key If key is an array, the settings are overwritten by that array. Otherwise, the * settings of the key will be set to the given `$value` param. * * @param mixed $value Optional. Default is null. */ final public function set_settings( $key, $value = null ) { $this->ensure_settings(); if ( is_array( $key ) ) { $this->settings = $key; } else { $this->settings[ $key ] = $value; } } /** * Delete setting. * * Deletes the settings array or a specific key of the settings array if `$key` is specified. * @since 2.3.0 * @access public * * @param string $key Optional. Default is null. */ public function delete_setting( $key = null ) { if ( $key ) { unset( $this->settings[ $key ] ); } else { $this->settings = []; } } final public function merge_properties( array $default_props, array $custom_props, array $allowed_props_keys = [] ) { $props = array_replace_recursive( $default_props, $custom_props ); if ( $allowed_props_keys ) { $props = array_intersect_key( $props, array_flip( $allowed_props_keys ) ); } return $props; } /** * Get items. * * Utility method that receives an array with a needle and returns all the * items that match the needle. If needle is not defined the entire haystack * will be returned. * * @since 2.3.0 * @access protected * @static * * @param array $haystack An array of items. * @param string $needle Optional. Needle. Default is null. * * @return mixed The whole haystack or the needle from the haystack when requested. */ final protected static function get_items( array $haystack, $needle = null ) { if ( $needle ) { return isset( $haystack[ $needle ] ) ? $haystack[ $needle ] : null; } return $haystack; } /** * Get init settings. * * Used to define the default/initial settings of the object. Inheriting classes may implement this method to define * their own default/initial settings. * * @since 2.3.0 * @access protected * * @return array */ protected function get_init_settings() { return []; } /** * Ensure settings. * * Ensures that the `$settings` member is initialized * * @since 2.3.0 * @access private */ private function ensure_settings() { if ( null === $this->settings ) { $this->settings = $this->get_init_settings(); } } /** * Has Own Method * * Used for check whether the method passed as a parameter was declared in the current instance or inherited. * If a base_class_name is passed, it checks whether the method was declared in that class. If the method's * declaring class is the class passed as $base_class_name, it returns false. Otherwise (method was NOT declared * in $base_class_name), it returns true. * * Example #1 - only $method_name is passed: * The initial declaration of `register_controls()` happens in the `Controls_Stack` class. However, all * widgets which have their own controls declare this function as well, overriding the original * declaration. If `has_own_method()` would be called by a Widget's class which implements `register_controls()`, * with 'register_controls' passed as the first parameter - `has_own_method()` will return true. If the Widget * does not declare `register_controls()`, `has_own_method()` will return false. * * Example #2 - both $method_name and $base_class_name are passed * In this example, the widget class inherits from a base class `Widget_Base`, and the base implements * `register_controls()` to add certain controls to all widgets inheriting from it. `has_own_method()` is called by * the widget, with the string 'register_controls' passed as the first parameter, and 'Elementor\Widget_Base' (its full name * including the namespace) passed as the second parameter. If the widget class implements `register_controls()`, * `has_own_method` will return true. If the widget class DOESN'T implement `register_controls()`, it will return * false (because `Widget_Base` is the declaring class for `register_controls()`, and not the class that called * `has_own_method()`). * * @since 3.1.0 * * @param string $method_name * @param string $base_class_name * * @return bool True if the method was declared by the current instance, False if it was inherited. */ public function has_own_method( $method_name, $base_class_name = null ) { try { $reflection_method = new \ReflectionMethod( $this, $method_name ); // If a ReflectionMethod is successfully created, get its declaring class. $declaring_class = $reflection_method->getDeclaringClass(); } catch ( \Exception $e ) { return false; } if ( $base_class_name ) { return $base_class_name !== $declaring_class->name; } return get_called_class() === $declaring_class->name; } } base/elements-iteration-actions/base.php 0000644 00000003307 14717626151 0014364 0 ustar 00 <?php namespace Elementor\Core\Base\Elements_Iteration_Actions; use Elementor\Element_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Base { /** * The current document that the Base class instance was created from. */ protected $document; /** * Indicates if the methods are being triggered on page save or at render time (value will be either 'save' or 'render'). * * @var string */ protected $mode = ''; /** * Is Action Needed. * * Runs only at runtime and used as a flag to determine if all methods should run on page render. * If returns false, all methods will run only on page save. * If returns true, all methods will run on both page render and on save. * * @since 3.3.0 * @access public * * @return bool */ abstract public function is_action_needed(); /** * Unique Element Action. * * Will be triggered for each unique page element - section / column / widget unique type (heading, icon etc.). * * @since 3.3.0 * @access public * * @return void */ public function unique_element_action( Element_Base $element_data ) {} /** * Element Action. * * Will be triggered for each page element - section / column / widget. * * @since 3.3.0 * @access public * * @return void */ public function element_action( Element_Base $element_data ) {} /** * After Elements Iteration. * * Will be triggered after all page elements iteration has ended. * * @since 3.3.0 * @access public * * @return void */ public function after_elements_iteration() {} public function set_mode( $mode ) { $this->mode = $mode; } public function __construct( $document ) { $this->document = $document; } } base/elements-iteration-actions/assets.php 0000644 00000012637 14717626151 0014762 0 ustar 00 <?php namespace Elementor\Core\Base\Elements_Iteration_Actions; use Elementor\Conditions; use Elementor\Element_Base; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Assets extends Base { const ASSETS_META_KEY = '_elementor_page_assets'; // Default value must be empty. private $page_assets; // Default value must be empty. private $saved_page_assets; public function element_action( Element_Base $element_data ) { $settings = $element_data->get_active_settings(); $controls = $element_data->get_controls(); $element_assets = $this->get_assets( $settings, $controls ); $element_assets_depend = [ 'styles' => $element_data->get_style_depends(), 'scripts' => $element_data->get_script_depends(), ]; if ( $element_assets_depend ) { foreach ( $element_assets_depend as $assets_type => $assets ) { if ( empty( $assets ) ) { continue; } if ( ! isset( $element_assets[ $assets_type ] ) ) { $element_assets[ $assets_type ] = []; } foreach ( $assets as $asset_name ) { if ( ! in_array( $asset_name, $element_assets[ $assets_type ], true ) ) { $element_assets[ $assets_type ][] = $asset_name; } } } } if ( $element_assets ) { $this->update_page_assets( $element_assets ); } } public function is_action_needed() { // No need to evaluate in preview mode, will be made in the saving process. if ( Plugin::$instance->preview->is_preview_mode() ) { return false; } $page_assets = $this->get_saved_page_assets(); // When $page_assets is array it means that the assets registration has already been made at least once. if ( is_array( $page_assets ) ) { return false; } return true; } public function after_elements_iteration() { // In case that the page assets value is empty, it should still be saved as an empty array as an indication that at lease one iteration has occurred. if ( ! is_array( $this->page_assets ) ) { $this->page_assets = []; } $this->get_document_assets(); // Saving the page assets data. $this->document->update_meta( self::ASSETS_META_KEY, $this->page_assets ); if ( 'render' === $this->mode && $this->page_assets ) { Plugin::$instance->assets_loader->enable_assets( $this->page_assets ); } } private function get_saved_page_assets( $force_meta_fetch = false ) { if ( ! is_array( $this->saved_page_assets ) || $force_meta_fetch ) { $this->saved_page_assets = $this->document->get_meta( self::ASSETS_META_KEY ); } return $this->saved_page_assets; } private function update_page_assets( $new_assets ) { if ( ! is_array( $this->page_assets ) ) { $this->page_assets = []; } foreach ( $new_assets as $assets_type => $assets_type_data ) { if ( ! isset( $this->page_assets[ $assets_type ] ) ) { $this->page_assets[ $assets_type ] = []; } foreach ( $assets_type_data as $asset_name ) { if ( ! in_array( $asset_name, $this->page_assets[ $assets_type ], true ) ) { $this->page_assets[ $assets_type ][] = $asset_name; } } } } private function get_assets( $settings, $controls ) { $assets = []; foreach ( $settings as $setting_key => $setting ) { if ( ! isset( $controls[ $setting_key ] ) ) { continue; } $control = $controls[ $setting_key ]; // Enabling assets loading from the registered control fields. if ( ! empty( $control['assets'] ) ) { foreach ( $control['assets'] as $assets_type => $dependencies ) { foreach ( $dependencies as $dependency ) { if ( ! empty( $dependency['conditions'] ) ) { $is_condition_fulfilled = Conditions::check( $dependency['conditions'], $settings ); if ( ! $is_condition_fulfilled ) { continue; } } if ( ! isset( $assets[ $assets_type ] ) ) { $assets[ $assets_type ] = []; } $assets[ $assets_type ][] = $dependency['name']; } } } // Enabling assets loading from the control object. $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] ); $control_conditional_assets = $control_obj::get_assets( $setting ); if ( $control_conditional_assets ) { foreach ( $control_conditional_assets as $assets_type => $dependencies ) { foreach ( $dependencies as $dependency ) { if ( ! isset( $assets[ $assets_type ] ) ) { $assets[ $assets_type ] = []; } $assets[ $assets_type ][] = $dependency; } } } } return $assets; } private function get_document_assets() { $document_id = $this->document->get_post()->ID; // Getting the document instance in order to get the most updated settings. $updated_document = Plugin::$instance->documents->get( $document_id, false ); $document_settings = $updated_document->get_settings(); $document_controls = $this->document->get_controls(); $document_assets = $this->get_assets( $document_settings, $document_controls ); if ( $document_assets ) { $this->update_page_assets( $document_assets ); } } public function __construct( $document ) { parent::__construct( $document ); // No need to enable assets in preview mode, all assets will be loaded by default by the assets loader. if ( Plugin::$instance->preview->is_preview_mode() ) { return; } $page_assets = $this->get_saved_page_assets(); // If $page_assets is not empty then enabling the assets for loading. if ( $page_assets ) { Plugin::$instance->assets_loader->enable_assets( $page_assets ); } } } utils/str.php 0000644 00000001750 14717626151 0007242 0 ustar 00 <?php namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Str { /** * Convert a non-latin URL to an IDN one. * Note: Max length is 64 chars. * * @param string $url - A URL to encode. * * @return string - IDN encoded URL ( e.g. `http://é.com` will be encoded to `http://xn--9ca.com` ). */ public static function encode_idn_url( $url ) { return preg_replace_callback( '/(https?:\/\/)(.+)/', function ( $matches ) { // WP >= 6.2-alpha if ( class_exists( '\WpOrg\Requests\IdnaEncoder' ) ) { $class = \WpOrg\Requests\IdnaEncoder::class; } else { $class = \Requests_IDNAEncoder::class; } return $matches[1] . $class::encode( $matches[2] ); }, $url ); } /** * Checks if a string ends with a given substring * * @param $haystack * @param $needle * @return bool */ public static function ends_with( $haystack, $needle ) { return substr( $haystack, -strlen( $needle ) ) === $needle; } } utils/exceptions.php 0000644 00000001304 14717626151 0010606 0 ustar 00 <?php namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Elementor exceptions. * * Elementor exceptions handler class is responsible for handling exceptions. * * @since 2.0.0 */ class Exceptions { /** * HTTP status code for bad request error. */ const BAD_REQUEST = 400; /** * HTTP status code for unauthorized access error. */ const UNAUTHORIZED = 401; /** * HTTP status code for forbidden access error. */ const FORBIDDEN = 403; /** * HTTP status code for resource that could not be found. */ const NOT_FOUND = 404; /** * HTTP status code for internal server error. */ const INTERNAL_SERVER_ERROR = 500; } utils/force-locale.php 0000644 00000007070 14717626151 0010766 0 ustar 00 <?php namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Force translation to use a specific locale. * * A hacky class to force any translation functions in the call-stack between the * `force()` & `reset()` methods to use a specific locale. */ class Force_Locale { /** * @var string Locale to force (e.g. `he_IL`). */ private $new_locale; /** * @var string Original locale before forcing. */ private $original_locale; /** * @var \WP_Textdomain_Registry */ private $original_textdomain_registry; /** * @var \Closure Filter reference `pre_determine_locale`. */ private $filter; public function __construct( $new_locale, $original_locale = null ) { $this->new_locale = $new_locale; $this->original_locale = $original_locale ? $original_locale : determine_locale(); $this->filter = function() use ( $new_locale ) { return $new_locale; }; } /** * Force the translations to use a specific locale. * * @return void */ public function force() { switch_to_locale( $this->new_locale ); /** * Reset the \WP_Textdomain_Registry instance to clear its cache. * * @see https://github.com/WordPress/wordpress-develop/blob/799d7dc86f5b07b17f7a418948fc851bd2fc334b/src/wp-includes/class-wp-textdomain-registry.php#L179-L187 * @see https://github.com/WordPress/wordpress-develop/blob/799d7dc86f5b07b17f7a418948fc851bd2fc334b/tests/phpunit/tests/l10n/wpLocaleSwitcher.php#L19-L31 */ $this->reset_textdomain_registry(); /** * Reset l10n in order to clear the translations cache. * * @see https://github.com/WordPress/wordpress-develop/blob/2437ef5130f10153bc4fffa412d4f37e65e3d66b/src/wp-includes/l10n.php#L1324 * @see https://github.com/WordPress/wordpress-develop/blob/2437ef5130f10153bc4fffa412d4f37e65e3d66b/src/wp-includes/l10n.php#L1222 * @see https://github.com/WordPress/wordpress-develop/blob/2437ef5130f10153bc4fffa412d4f37e65e3d66b/src/wp-includes/l10n.php#L821 */ $this->reset_l10n(); /** * Force the translations of `$new_locale` to be loaded. * * @see https://github.com/WordPress/wordpress-develop/blob/2437ef5130f10153bc4fffa412d4f37e65e3d66b/src/wp-includes/l10n.php#L1294 */ add_filter( 'pre_determine_locale', $this->filter ); } /** * Restore the original locale and cleanup filters, etc. * * @return void */ public function restore() { $this->restore_textdomain_registry(); $this->reset_l10n(); switch_to_locale( $this->original_locale ); remove_filter( 'pre_determine_locale', $this->filter ); } private function reset_textdomain_registry() { if ( ! class_exists( '\WP_Textdomain_Registry' ) ) { return; } /** @var \WP_Textdomain_Registry $wp_textdomain_registry */ global $wp_textdomain_registry; $this->original_textdomain_registry = $wp_textdomain_registry; $wp_textdomain_registry = new \WP_Textdomain_Registry(); } private function restore_textdomain_registry() { if ( ! $this->original_textdomain_registry ) { return; } /** @var \WP_Textdomain_Registry $wp_textdomain_registry */ global $wp_textdomain_registry; $wp_textdomain_registry = $this->original_textdomain_registry; } /** * Reset the l10n global variables. * * @return void */ private function reset_l10n() { global $l10n, $l10n_unloaded; if ( is_array( $l10n ) ) { foreach ( $l10n as $domain => $l10n_data ) { unset( $l10n[ $domain ] ); } } if ( is_array( $l10n_unloaded ) ) { foreach ( $l10n_unloaded as $domain => $l10n_unloaded_data ) { unset( $l10n_unloaded[ $domain ] ); } } } } utils/http.php 0000644 00000001725 14717626151 0007413 0 ustar 00 <?php namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Http extends \WP_Http { /** * Pass multiple urls to implements a fallback machine when one of the urls * is sending an error or not exists anymore. * * @param array $urls * @param array $args * * @return array|\WP_Error|null */ public function request_with_fallback( array $urls, $args = [] ) { $response = null; foreach ( $urls as $url ) { $response = $this->request( $url, $args ); if ( $this->is_successful_response( $response ) ) { return $response; } } return $response; } /** * @param $response * * @return bool */ private function is_successful_response( $response ) { if ( is_wp_error( $response ) ) { return false; } $response_code = (int) wp_remote_retrieve_response_code( $response ); if ( in_array( $response_code, [ 0, 404, 500 ], true ) ) { return false; } return true; } } utils/version.php 0000644 00000007211 14717626151 0010115 0 ustar 00 <?php namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Version { const PART_MAJOR_1 = 'major1'; const PART_MAJOR_2 = 'major2'; const PART_PATCH = 'patch'; const PART_STAGE = 'stage'; /** * First number of a version 0.x.x * * @var string */ public $major1; /** * Second number of a version x.0.x * * @var string */ public $major2; /** * Third number of a version x.x.0 * * @var string */ public $patch; /** * The stage of a version x.x.x-stage. * e.g: x.x.x-dev1, x.x.x-beta3, x.x.x-rc * * @var string|null */ public $stage; /** * Version constructor. * * @param $major1 * @param $major2 * @param $patch * @param $stage */ public function __construct( $major1, $major2, $patch, $stage = null ) { $this->major1 = $major1; $this->major2 = $major2; $this->patch = $patch; $this->stage = $stage; } /** * Create Version instance. * * @param string $major1 * @param string $major2 * @param string $patch * @param null $stage * * @return static */ public static function create( $major1 = '0', $major2 = '0', $patch = '0', $stage = null ) { return new static( $major1, $major2, $patch, $stage ); } /** * Checks if the current version string is valid. * * @param $version * * @return bool */ public static function is_valid_version( $version ) { return ! ! preg_match( '/^(\d+\.)?(\d+\.)?(\*|\d+)(-.+)?$/', $version ); } /** * Creates a Version instance from a string. * * @param $version * @param bool $should_validate * * @return static * @throws \Exception */ public static function create_from_string( $version, $should_validate = true ) { if ( $should_validate && ! static::is_valid_version( $version ) ) { throw new \Exception( "{$version} is an invalid version." ); } $parts = explode( '.', $version ); $patch_parts = []; $major1 = '0'; $major2 = '0'; $patch = '0'; $stage = null; if ( isset( $parts[0] ) ) { $major1 = $parts[0]; } if ( isset( $parts[1] ) ) { $major2 = $parts[1]; } if ( isset( $parts[2] ) ) { $patch_parts = explode( '-', $parts[2] ); $patch = $patch_parts[0]; } if ( isset( $patch_parts[1] ) ) { $stage = $patch_parts[1]; } return static::create( $major1, $major2, $patch, $stage ); } /** * Compare the current version instance with another version. * * @param $operator * @param $version * @param string $part * * @return bool * @throws \Exception */ public function compare( $operator, $version, $part = self::PART_STAGE ) { if ( ! ( $version instanceof Version ) ) { if ( ! static::is_valid_version( $version ) ) { $version = '0.0.0'; } $version = static::create_from_string( $version, false ); } $current_version = clone $this; $compare_version = clone $version; if ( in_array( $part, [ self::PART_PATCH, self::PART_MAJOR_2, self::PART_MAJOR_1 ], true ) ) { $current_version->stage = null; $compare_version->stage = null; } if ( in_array( $part, [ self::PART_MAJOR_2, self::PART_MAJOR_1 ], true ) ) { $current_version->patch = '0'; $compare_version->patch = '0'; } if ( self::PART_MAJOR_1 === $part ) { $current_version->major2 = '0'; $compare_version->major2 = '0'; } return version_compare( $current_version, $compare_version, $operator ); } /** * Implode the version and return it as string. * * @return string */ public function __toString() { $version = implode( '.', [ $this->major1, $this->major2, $this->patch ] ); if ( $this->stage ) { $version .= '-' . $this->stage; } return $version; } } utils/hints.php 0000644 00000023266 14717626151 0007565 0 ustar 00 <?php namespace elementor\core\utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use Elementor\User; use Elementor\Utils; class Hints { const INFO = 'info'; const SUCCESS = 'success'; const WARNING = 'warning'; const DANGER = 'danger'; const DEFINED = 'defined'; const DISMISSED = 'dismissed'; const CAPABILITY = 'capability'; const PLUGIN_INSTALLED = 'plugin_installed'; const PLUGIN_ACTIVE = 'plugin_active'; /** * get_notice_types * @return string[] */ public static function get_notice_types(): array { return [ self::INFO, self::SUCCESS, self::WARNING, self::DANGER, ]; } /** * get_hints * * @param $hint_key * * @return array|string[]|\string[][] */ public static function get_hints( $hint_key = null ): array { $hints = [ 'image-optimization-once' => [ self::DISMISSED => 'image-optimization-once', self::CAPABILITY => 'install_plugins', self::DEFINED => 'IMAGE_OPTIMIZATION_VERSION', ], 'image-optimization-once-media-modal' => [ self::DISMISSED => 'image-optimization-once-media-modal', self::CAPABILITY => 'install_plugins', self::DEFINED => 'IMAGE_OPTIMIZATION_VERSION', ], 'image-optimization' => [ self::DISMISSED => 'image_optimizer_hint', self::CAPABILITY => 'install_plugins', self::DEFINED => 'IMAGE_OPTIMIZATION_VERSION', ], 'image-optimization-media-modal' => [ self::DISMISSED => 'image-optimization-media-modal', self::CAPABILITY => 'install_plugins', self::DEFINED => 'IMAGE_OPTIMIZATION_VERSION', ], ]; if ( ! $hint_key ) { return $hints; } return $hints[ $hint_key ] ?? []; } /** * get_notice_icon * @return string */ public static function get_notice_icon(): string { return '<div class="elementor-control-notice-icon"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M2.25 9H3M9 2.25V3M15 9H15.75M4.2 4.2L4.725 4.725M13.8 4.2L13.275 4.725M7.27496 12.75H10.725M6.75 12C6.12035 11.5278 5.65525 10.8694 5.42057 10.1181C5.1859 9.36687 5.19355 8.56082 5.44244 7.81415C5.69133 7.06748 6.16884 6.41804 6.80734 5.95784C7.44583 5.49764 8.21294 5.25 9 5.25C9.78706 5.25 10.5542 5.49764 11.1927 5.95784C11.8312 6.41804 12.3087 7.06748 12.5576 7.81415C12.8065 8.56082 12.8141 9.36687 12.5794 10.1181C12.3448 10.8694 11.8796 11.5278 11.25 12C10.9572 12.2899 10.7367 12.6446 10.6064 13.0355C10.4761 13.4264 10.4397 13.8424 10.5 14.25C10.5 14.6478 10.342 15.0294 10.0607 15.3107C9.77936 15.592 9.39782 15.75 9 15.75C8.60218 15.75 8.22064 15.592 7.93934 15.3107C7.65804 15.0294 7.5 14.6478 7.5 14.25C7.56034 13.8424 7.52389 13.4264 7.3936 13.0355C7.2633 12.6446 7.04282 12.2899 6.75 12Z" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </div>'; } /** * get_notice_template * * Print or Retrieve the notice template. * @param array $notice * @param bool $return * * @return string|void */ public static function get_notice_template( array $notice, bool $return = false ) { $default_settings = [ 'type' => 'info', 'icon' => false, 'heading' => '', 'content' => '', 'dismissible' => false, 'button_text' => '', 'button_event' => '', 'button_data' => [], 'display' => false, ]; $notice_settings = array_merge( $default_settings, $notice ); if ( empty( $notice_settings['heading'] ) && empty( $notice_settings['content'] ) ) { return ''; } if ( ! in_array( $notice_settings['type'], self::get_notice_types(), true ) ) { $notice_settings['type'] = 'info'; } $icon = ''; $heading = ''; $content = ''; $dismissible = ''; $button = ''; if ( $notice_settings['icon'] ) { $icon = self::get_notice_icon(); } if ( ! empty( $notice_settings['heading'] ) ) { $heading = '<div class="elementor-control-notice-main-heading">' . $notice_settings['heading'] . '</div>'; } if ( ! empty( $notice_settings['content'] ) ) { $content = '<div class="elementor-control-notice-main-content">' . $notice_settings['content'] . '</div>'; } if ( ! empty( $notice_settings['button_text'] ) ) { $button_settings = ( ! empty( $notice_settings['button_data'] ) ) ? ' data-settings="' . esc_attr( json_encode( $notice_settings['button_data'] ) ) . '"' : ''; $button = '<div class="elementor-control-notice-main-actions"> <button type="button" class="e-btn e-' . $notice_settings['type'] . ' e-btn-1" data-event="' . $notice_settings['button_event'] . '"' . $button_settings . '> ' . $notice_settings['button_text'] . ' </button> </div>'; } if ( $notice_settings['dismissible'] ) { $dismissible = '<button class="elementor-control-notice-dismiss tooltip-target" data-event="' . $notice_settings['dismissible'] . '" data-tooltip="' . esc_attr__( 'Don’t show again.', 'elementor' ) . '"> <i class="eicon eicon-close" aria-hidden="true"></i> <span class="elementor-screen-only">' . esc_html__( 'Don’t show again.', 'elementor' ) . '</span> </button>'; } $notice_template = sprintf( '<div class="elementor-control-notice elementor-control-notice-type-%1$s" data-display="%7$s"> %2$s <div class="elementor-control-notice-main"> %3$s %4$s %5$s </div> %6$s </div>', $notice_settings['type'], $icon, $heading, $content, $button, $dismissible, $notice_settings['display'] ); if ( $return ) { return $notice_template; } echo wp_kses( $notice_template, self::get_notice_allowed_html() ); } /** * get_plugin_install_url * @param $plugin_slug * * @return string */ public static function get_plugin_install_url( $plugin_slug ): string { $action = 'install-plugin'; return wp_nonce_url( add_query_arg( [ 'action' => $action, 'plugin' => $plugin_slug, ], admin_url( 'update.php' ) ), $action . '_' . $plugin_slug ); } /** * get_plugin_activate_url * @param $plugin_slug * * @return string */ public static function get_plugin_activate_url( $plugin_slug ): string { $path = "$plugin_slug/$plugin_slug.php"; return wp_nonce_url( admin_url( 'plugins.php?action=activate&plugin=' . $path ), 'activate-plugin_' . $path ); } /** * is_dismissed * @param $key * * @return bool */ public static function is_dismissed( $key ): bool { $dismissed = User::get_dismissed_editor_notices(); return in_array( $key, $dismissed, true ); } /** * should_display_hint * @param $hint_key * * @return bool */ public static function should_display_hint( $hint_key ): bool { $hint = self::get_hints( $hint_key ); if ( empty( $hint ) ) { return false; } foreach ( $hint as $key => $value ) { switch ( $key ) { case self::DISMISSED: if ( self::is_dismissed( $value ) ) { return false; } break; case self::CAPABILITY: if ( ! current_user_can( $value ) ) { return false; } break; case self::DEFINED: if ( defined( $value ) ) { return false; } break; case self::PLUGIN_INSTALLED: if ( ! self::is_plugin_installed( $value ) ) { return false; } break; case self::PLUGIN_ACTIVE: if ( ! self::is_plugin_active( $value ) ) { return false; } break; } } return true; } private static function is_conflict_plugin_installed(): bool { if ( ! Utils::has_pro() ) { return false; } $conflicting_plugins = [ 'imagify/imagify.php', 'optimole-wp/optimole-wp.php', 'ewww-image-optimizer/ewww-image-optimizer.php', 'ewww-image-optimizer-cloud/ewww-image-optimizer-cloud.php', 'kraken-image-optimizer/kraken.php', 'shortpixel-image-optimiser/wp-shortpixel.php', 'wp-smushit/wp-smush.php', 'wp-smush-pro/wp-smush.php', 'tiny-compress-images/tiny-compress-images.php', ]; foreach ( $conflicting_plugins as $plugin ) { if ( self::is_plugin_active( $plugin ) ) { return true; } } return false; } /** * is_plugin_installed * @param $plugin * * @return bool */ public static function is_plugin_installed( $plugin ) : bool { $plugins = get_plugins(); $plugin = self::ensure_plugin_folder( $plugin ); return ! empty( $plugins[ $plugin ] ); } /** * is_plugin_active * @param $plugin * * @return bool */ public static function is_plugin_active( $plugin ): bool { $plugin = self::ensure_plugin_folder( $plugin ); return is_plugin_active( $plugin ); } /** * get_plugin_action_url * @param $plugin * * @return string */ public static function get_plugin_action_url( $plugin ): string { if ( ! self::is_plugin_installed( $plugin ) ) { return self::get_plugin_install_url( $plugin ); } if ( ! self::is_plugin_active( $plugin ) ) { return self::get_plugin_activate_url( $plugin ); } return ''; } /** * ensure_plugin_folder * @param $plugin * * @return string */ private static function ensure_plugin_folder( $plugin ): string { if ( false === strpos( $plugin, '/' ) ) { $plugin = $plugin . '/' . $plugin . '.php'; } return $plugin; } /** * get_notice_allowed_html * @return array[] */ public static function get_notice_allowed_html(): array { return [ 'div' => [ 'class' => [], 'data-display' => [], ], 'svg' => [ 'width' => [], 'height' => [], 'viewbox' => [], 'fill' => [], 'xmlns' => [], ], 'path' => [ 'd' => [], 'stroke' => [], 'stroke-width' => [], 'stroke-linecap' => [], 'stroke-linejoin' => [], ], 'button' => [ 'class' => [], 'data-event' => [], 'data-settings' => [], 'data-tooltip' => [], ], 'i' => [ 'class' => [], 'aria-hidden' => [], ], 'span' => [ 'class' => [], ], 'a' => [ 'href' => [], 'style' => [], 'target' => [], ], ]; } } utils/import-export/wp-import.php 0000644 00000125714 14717626151 0013230 0 ustar 00 <?php namespace Elementor\Core\Utils\ImportExport; use Elementor\Core\Utils\ImportExport\Parsers\WXR_Parser; use WP_Error; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Originally made by WordPress part of WordPress/Importer. * https://plugins.trac.wordpress.org/browser/wordpress-importer/trunk/class-wp-import.php * * What was done: * Reformat of the code. * Changed text domain. * Changed methods visibility. * Changed method from `get_authors_from_import` to `set_authors_from_import`. * Changed method from `get_author_mapping` to `set_author_mapping`. * Removed use of '$_POST' the input 'options' will be passed via constructor args. * Removed echos, UI and print methods, all echos replaced with `$this->output` append. * Removed `die` ( exit(s) ). */ if ( ! class_exists( 'WP_Importer' ) ) { $class_wp_importer = ABSPATH . 'wp-admin/includes/class-wp-importer.php'; if ( file_exists( $class_wp_importer ) ) { require $class_wp_importer; } } class WP_Import extends \WP_Importer { const DEFAULT_BUMP_REQUEST_TIMEOUT = 60; const DEFAULT_ALLOW_CREATE_USERS = true; const DEFAULT_IMPORT_ATTACHMENT_SIZE_LIMIT = 0; // 0 = unlimited. /** * @var string */ private $requested_file_path; /** * @var array */ private $args; /** * @var array */ private $output = [ 'status' => 'failed', 'errors' => [], ]; /* * WXR attachment ID */ private $id; // Information to import from WXR file. private $version; private $authors = []; private $posts = []; private $terms = []; private $base_url = ''; private $page_on_front; private $base_blog_url = ''; // Mappings from old information to new. private $processed_taxonomies; private $processed_terms = []; private $processed_posts = []; private $processed_authors = []; private $author_mapping = []; private $processed_menu_items = []; private $post_orphans = []; private $menu_item_orphans = []; private $mapped_terms_slug = []; private $fetch_attachments = false; private $url_remap = []; private $featured_images = []; /** * @var array[] [meta_key => meta_value] Meta value that should be set for every imported post. */ private $posts_meta = []; /** * @var array[] [meta_key => meta_value] Meta value that should be set for every imported term. */ private $terms_meta = []; /** * Parses filename from a Content-Disposition header value. * * As per RFC6266: * * content-disposition = "Content-Disposition" ":" * disposition-type *( ";" disposition-parm ) * * disposition-type = "inline" | "attachment" | disp-ext-type * ; case-insensitive * disp-ext-type = token * * disposition-parm = filename-parm | disp-ext-parm * * filename-parm = "filename" "=" value * | "filename*" "=" ext-value * * disp-ext-parm = token "=" value * | ext-token "=" ext-value * ext-token = <the characters in token, followed by "*"> * * @param string[] $disposition_header List of Content-Disposition header values. * * @return string|null Filename if available, or null if not found. * @link http://tools.ietf.org/html/rfc2388 * @link http://tools.ietf.org/html/rfc6266 * * @see WP_REST_Attachments_Controller::get_filename_from_disposition() * */ protected static function get_filename_from_disposition( $disposition_header ) { // Get the filename. $filename = null; foreach ( $disposition_header as $value ) { $value = trim( $value ); if ( strpos( $value, ';' ) === false ) { continue; } list( $type, $attr_parts ) = explode( ';', $value, 2 ); $attr_parts = explode( ';', $attr_parts ); $attributes = []; foreach ( $attr_parts as $part ) { if ( strpos( $part, '=' ) === false ) { continue; } list( $key, $value ) = explode( '=', $part, 2 ); $attributes[ trim( $key ) ] = trim( $value ); } if ( empty( $attributes['filename'] ) ) { continue; } $filename = trim( $attributes['filename'] ); // Unquote quoted filename, but after trimming. if ( substr( $filename, 0, 1 ) === '"' && substr( $filename, -1, 1 ) === '"' ) { $filename = substr( $filename, 1, -1 ); } } return $filename; } /** * Retrieves file extension by mime type. * * @param string $mime_type Mime type to search extension for. * * @return string|null File extension if available, or null if not found. */ protected static function get_file_extension_by_mime_type( $mime_type ) { static $map = null; if ( is_array( $map ) ) { return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null; } $mime_types = wp_get_mime_types(); $map = array_flip( $mime_types ); // Some types have multiple extensions, use only the first one. foreach ( $map as $type => $extensions ) { $map[ $type ] = strtok( $extensions, '|' ); } return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null; } /** * The main controller for the actual import stage. * * @param string $file Path to the WXR file for importing */ private function import( $file ) { add_filter( 'import_post_meta_key', function ( $key ) { return $this->is_valid_meta_key( $key ); } ); add_filter( 'http_request_timeout', function () { return self::DEFAULT_BUMP_REQUEST_TIMEOUT; } ); if ( ! $this->import_start( $file ) ) { return; } $this->set_author_mapping(); wp_suspend_cache_invalidation( true ); $imported_summary = [ 'terms' => $this->process_terms(), 'posts' => $this->process_posts(), ]; wp_suspend_cache_invalidation( false ); // Update incorrect/missing information in the DB. $this->backfill_parents(); $this->backfill_attachment_urls(); $this->remap_featured_images(); $this->import_end(); $is_some_succeed = false; foreach ( $imported_summary as $item ) { if ( $item > 0 ) { $is_some_succeed = true; break; } } if ( $is_some_succeed ) { $this->output['status'] = 'success'; $this->output['summary'] = $imported_summary; } } /** * Parses the WXR file and prepares us for the task of processing parsed data. * * @param string $file Path to the WXR file for importing */ private function import_start( $file ) { if ( ! is_file( $file ) ) { $this->output['errors'] = [ esc_html__( 'The file does not exist, please try again.', 'elementor' ) ]; return false; } $import_data = $this->parse( $file ); if ( is_wp_error( $import_data ) ) { $this->output['errors'] = [ $import_data->get_error_message() ]; return false; } $this->version = $import_data['version']; $this->set_authors_from_import( $import_data ); $this->posts = $import_data['posts']; $this->terms = $import_data['terms']; $this->base_url = esc_url( $import_data['base_url'] ); $this->base_blog_url = esc_url( $import_data['base_blog_url'] ); $this->page_on_front = $import_data['page_on_front']; wp_defer_term_counting( true ); wp_defer_comment_counting( true ); do_action( 'import_start' ); return true; } /** * Performs post-import cleanup of files and the cache */ private function import_end() { wp_import_cleanup( $this->id ); wp_cache_flush(); foreach ( get_taxonomies() as $tax ) { delete_option( "{$tax}_children" ); _get_term_hierarchy( $tax ); } wp_defer_term_counting( false ); wp_defer_comment_counting( false ); do_action( 'import_end' ); } /** * Retrieve authors from parsed WXR data and set it to `$this->>authors`. * * Uses the provided author information from WXR 1.1 files * or extracts info from each post for WXR 1.0 files * * @param array $import_data Data returned by a WXR parser */ private function set_authors_from_import( $import_data ) { if ( ! empty( $import_data['authors'] ) ) { $this->authors = $import_data['authors']; // No author information, grab it from the posts. } else { foreach ( $import_data['posts'] as $post ) { $login = sanitize_user( $post['post_author'], true ); if ( empty( $login ) ) { $this->output['errors'][] = sprintf( /* translators: %s: Post author. */ esc_html__( 'Failed to import author %s. Their posts will be attributed to the current user.', 'elementor' ), $post['post_author'] ); continue; } if ( ! isset( $this->authors[ $login ] ) ) { $this->authors[ $login ] = [ 'author_login' => $login, 'author_display_name' => $post['post_author'], ]; } } } } /** * Map old author logins to local user IDs based on decisions made * in import options form. Can map to an existing user, create a new user * or falls back to the current user in case of error with either of the previous */ private function set_author_mapping() { if ( ! isset( $this->args['imported_authors'] ) ) { return; } $create_users = apply_filters( 'import_allow_create_users', self::DEFAULT_ALLOW_CREATE_USERS ); foreach ( (array) $this->args['imported_authors'] as $i => $old_login ) { // Multisite adds strtolower to sanitize_user. Need to sanitize here to stop breakage in process_posts. $sanitized_old_login = sanitize_user( $old_login, true ); $old_id = isset( $this->authors[ $old_login ]['author_id'] ) ? (int) $this->authors[ $old_login ]['author_id'] : false; if ( ! empty( $this->args['user_map'][ $i ] ) ) { $user = get_userdata( (int) $this->args['user_map'][ $i ] ); if ( isset( $user->ID ) ) { if ( $old_id ) { $this->processed_authors[ $old_id ] = $user->ID; } $this->author_mapping[ $sanitized_old_login ] = $user->ID; } } elseif ( $create_users ) { $user_id = 0; if ( ! empty( $this->args['user_new'][ $i ] ) ) { $user_id = wp_create_user( $this->args['user_new'][ $i ], wp_generate_password() ); } elseif ( '1.0' !== $this->version ) { $user_data = [ 'user_login' => $old_login, 'user_pass' => wp_generate_password(), 'user_email' => isset( $this->authors[ $old_login ]['author_email'] ) ? $this->authors[ $old_login ]['author_email'] : '', 'display_name' => $this->authors[ $old_login ]['author_display_name'], 'first_name' => isset( $this->authors[ $old_login ]['author_first_name'] ) ? $this->authors[ $old_login ]['author_first_name'] : '', 'last_name' => isset( $this->authors[ $old_login ]['author_last_name'] ) ? $this->authors[ $old_login ]['author_last_name'] : '', ]; $user_id = wp_insert_user( $user_data ); } if ( ! is_wp_error( $user_id ) ) { if ( $old_id ) { $this->processed_authors[ $old_id ] = $user_id; } $this->author_mapping[ $sanitized_old_login ] = $user_id; } else { $error = sprintf( /* translators: %s: Author display name. */ esc_html__( 'Failed to create new user for %s. Their posts will be attributed to the current user.', 'elementor' ), $this->authors[ $old_login ]['author_display_name'] ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { $error .= PHP_EOL . $user_id->get_error_message(); } $this->output['errors'][] = $error; } } // Failsafe: if the user_id was invalid, default to the current user. if ( ! isset( $this->author_mapping[ $sanitized_old_login ] ) ) { if ( $old_id ) { $this->processed_authors[ $old_id ] = (int) get_current_user_id(); } $this->author_mapping[ $sanitized_old_login ] = (int) get_current_user_id(); } } } /** * Create new terms based on import information * * Doesn't create a term its slug already exists * * @return array|array[] the ids of succeed/failed imported terms. */ private function process_terms() { $result = [ 'succeed' => [], 'failed' => [], ]; $this->terms = apply_filters( 'wp_import_terms', $this->terms ); if ( empty( $this->terms ) ) { return $result; } foreach ( $this->terms as $term ) { // if the term already exists in the correct taxonomy leave it alone $term_id = term_exists( $term['slug'], $term['term_taxonomy'] ); if ( $term_id ) { if ( is_array( $term_id ) ) { $term_id = $term_id['term_id']; } if ( isset( $term['term_id'] ) ) { if ( 'nav_menu' === $term['term_taxonomy'] ) { // BC - support old kits that the menu terms are part of the 'nav_menu_item' post type // and not part of the taxonomies. if ( ! empty( $this->processed_taxonomies[ $term['term_taxonomy'] ] ) ) { foreach ( $this->processed_taxonomies[ $term['term_taxonomy'] ] as $processed_term ) { $old_slug = $processed_term['old_slug']; $new_slug = $processed_term['new_slug']; $this->mapped_terms_slug[ $old_slug ] = $new_slug; $result['succeed'][ $old_slug ] = $new_slug; } continue; } else { $term = $this->handle_duplicated_nav_menu_term( $term ); } } else { $this->processed_terms[ (int) $term['term_id'] ] = (int) $term_id; $result['succeed'][ (int) $term['term_id'] ] = (int) $term_id; continue; } } } if ( empty( $term['term_parent'] ) ) { $parent = 0; } else { $parent = term_exists( $term['term_parent'], $term['term_taxonomy'] ); if ( is_array( $parent ) ) { $parent = $parent['term_id']; } } $description = isset( $term['term_description'] ) ? $term['term_description'] : ''; $args = [ 'slug' => $term['slug'], 'description' => wp_slash( $description ), 'parent' => (int) $parent, ]; $id = wp_insert_term( wp_slash( $term['term_name'] ), $term['term_taxonomy'], $args ); if ( ! is_wp_error( $id ) ) { if ( isset( $term['term_id'] ) ) { $this->processed_terms[ (int) $term['term_id'] ] = $id['term_id']; $result['succeed'][ (int) $term['term_id'] ] = $id['term_id']; $this->update_term_meta( $id['term_id'] ); } } else { /* translators: 1: Term taxonomy, 2: Term name. */ $error = sprintf( esc_html__( 'Failed to import %1$s %2$s', 'elementor' ), $term['term_taxonomy'], $term['term_name'] ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { $error .= PHP_EOL . $id->get_error_message(); } $result['failed'][] = $id; $this->output['errors'][] = $error; continue; } $this->process_termmeta( $term, $id['term_id'] ); } unset( $this->terms ); return $result; } /** * Add metadata to imported term. * * @param array $term Term data from WXR import. * @param int $term_id ID of the newly created term. */ private function process_termmeta( $term, $term_id ) { if ( ! function_exists( 'add_term_meta' ) ) { return; } if ( ! isset( $term['termmeta'] ) ) { $term['termmeta'] = []; } /** * Filters the metadata attached to an imported term. * * @param array $termmeta Array of term meta. * @param int $term_id ID of the newly created term. * @param array $term Term data from the WXR import. */ $term['termmeta'] = apply_filters( 'wp_import_term_meta', $term['termmeta'], $term_id, $term ); if ( empty( $term['termmeta'] ) ) { return; } foreach ( $term['termmeta'] as $meta ) { /** * Filters the meta key for an imported piece of term meta. * * @param string $meta_key Meta key. * @param int $term_id ID of the newly created term. * @param array $term Term data from the WXR import. */ $key = apply_filters( 'import_term_meta_key', $meta['key'], $term_id, $term ); if ( ! $key ) { continue; } // Export gets meta straight from the DB so could have a serialized string $value = maybe_unserialize( $meta['value'] ); add_term_meta( $term_id, wp_slash( $key ), wp_slash_strings_only( $value ) ); /** * Fires after term meta is imported. * * @param int $term_id ID of the newly created term. * @param string $key Meta key. * @param mixed $value Meta value. */ do_action( 'import_term_meta', $term_id, $key, $value ); } } /** * Create new posts based on import information * * Posts marked as having a parent which doesn't exist will become top level items. * Doesn't create a new post if: the post type doesn't exist, the given post ID * is already noted as imported or a post with the same title and date already exists. * Note that new/updated terms, comments and meta are imported for the last of the above. * * @return array the ids of succeed/failed imported posts. */ private function process_posts() { $result = [ 'succeed' => [], 'failed' => [], ]; $this->posts = apply_filters( 'wp_import_posts', $this->posts ); foreach ( $this->posts as $post ) { $post = apply_filters( 'wp_import_post_data_raw', $post ); if ( ! post_type_exists( $post['post_type'] ) ) { /* translators: 1: Post title, 2: Post type. */ $this->output['errors'][] = sprintf( esc_html__( 'Failed to import %1$s: Invalid post type %2$s', 'elementor' ), $post['post_title'], $post['post_type'] ); do_action( 'wp_import_post_exists', $post ); continue; } if ( isset( $this->processed_posts[ $post['post_id'] ] ) && ! empty( $post['post_id'] ) ) { continue; } if ( 'auto-draft' === $post['status'] ) { continue; } if ( 'nav_menu_item' === $post['post_type'] ) { $result['succeed'] += $this->process_menu_item( $post ); continue; } $post_type_object = get_post_type_object( $post['post_type'] ); $post_parent = (int) $post['post_parent']; if ( $post_parent ) { // if we already know the parent, map it to the new local ID. if ( isset( $this->processed_posts[ $post_parent ] ) ) { $post_parent = $this->processed_posts[ $post_parent ]; // otherwise record the parent for later. } else { $this->post_orphans[ (int) $post['post_id'] ] = $post_parent; $post_parent = 0; } } // Map the post author. $author = sanitize_user( $post['post_author'], true ); if ( isset( $this->author_mapping[ $author ] ) ) { $author = $this->author_mapping[ $author ]; } else { $author = (int) get_current_user_id(); } $postdata = [ 'post_author' => $author, 'post_content' => $post['post_content'], 'post_excerpt' => $post['post_excerpt'], 'post_title' => $post['post_title'], 'post_status' => $post['status'], 'post_name' => $post['post_name'], 'comment_status' => $post['comment_status'], 'ping_status' => $post['ping_status'], 'guid' => $post['guid'], 'post_parent' => $post_parent, 'menu_order' => $post['menu_order'], 'post_type' => $post['post_type'], 'post_password' => $post['post_password'], ]; $original_post_id = $post['post_id']; $postdata = apply_filters( 'wp_import_post_data_processed', $postdata, $post ); $postdata = wp_slash( $postdata ); if ( 'attachment' === $postdata['post_type'] ) { $remote_url = ! empty( $post['attachment_url'] ) ? $post['attachment_url'] : $post['guid']; // try to use _wp_attached file for upload folder placement to ensure the same location as the export site // e.g. location is 2003/05/image.jpg but the attachment post_date is 2010/09, see media_handle_upload() $postdata['upload_date'] = $post['post_date']; if ( isset( $post['postmeta'] ) ) { foreach ( $post['postmeta'] as $meta ) { if ( '_wp_attached_file' === $meta['key'] ) { if ( preg_match( '%^[0-9]{4}/[0-9]{2}%', $meta['value'], $matches ) ) { $postdata['upload_date'] = $matches[0]; } break; } } } $post_id = $this->process_attachment( $postdata, $remote_url ); $comment_post_id = $post_id; } else { $post_id = wp_insert_post( $postdata, true ); $this->update_post_meta( $post_id ); $comment_post_id = $post_id; do_action( 'wp_import_insert_post', $post_id, $original_post_id, $postdata, $post ); } if ( is_wp_error( $post_id ) ) { /* translators: 1: Post type singular label, 2: Post title. */ $error = sprintf( __( 'Failed to import %1$s %2$s', 'elementor' ), $post_type_object->labels->singular_name, $post['post_title'] ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { $error .= PHP_EOL . $post_id->get_error_message(); } $result['failed'][] = $original_post_id; $this->output['errors'][] = $error; continue; } $result['succeed'][ $original_post_id ] = $post_id; if ( 1 === $post['is_sticky'] ) { stick_post( $post_id ); } if ( $this->page_on_front === $original_post_id ) { update_option( 'page_on_front', $post_id ); } // Map pre-import ID to local ID. $this->processed_posts[ (int) $post['post_id'] ] = (int) $post_id; if ( ! isset( $post['terms'] ) ) { $post['terms'] = []; } $post['terms'] = apply_filters( 'wp_import_post_terms', $post['terms'], $post_id, $post ); // add categories, tags and other terms if ( ! empty( $post['terms'] ) ) { $terms_to_set = []; foreach ( $post['terms'] as $term ) { // back compat with WXR 1.0 map 'tag' to 'post_tag' $taxonomy = ( 'tag' === $term['domain'] ) ? 'post_tag' : $term['domain']; $term_exists = term_exists( $term['slug'], $taxonomy ); $term_id = is_array( $term_exists ) ? $term_exists['term_id'] : $term_exists; if ( ! $term_id ) { $t = wp_insert_term( $term['name'], $taxonomy, [ 'slug' => $term['slug'] ] ); if ( ! is_wp_error( $t ) ) { $term_id = $t['term_id']; $this->update_term_meta( $term_id ); do_action( 'wp_import_insert_term', $t, $term, $post_id, $post ); } else { /* translators: 1: Taxonomy name, 2: Term name. */ $error = sprintf( esc_html__( 'Failed to import %1$s %2$s', 'elementor' ), $taxonomy, $term['name'] ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { $error .= PHP_EOL . $t->get_error_message(); } $this->output['errors'][] = $error; do_action( 'wp_import_insert_term_failed', $t, $term, $post_id, $post ); continue; } } $terms_to_set[ $taxonomy ][] = (int) $term_id; } foreach ( $terms_to_set as $tax => $ids ) { $tt_ids = wp_set_post_terms( $post_id, $ids, $tax ); do_action( 'wp_import_set_post_terms', $tt_ids, $ids, $tax, $post_id, $post ); } unset( $post['terms'], $terms_to_set ); } if ( ! isset( $post['comments'] ) ) { $post['comments'] = []; } $post['comments'] = apply_filters( 'wp_import_post_comments', $post['comments'], $post_id, $post ); // Add/update comments. if ( ! empty( $post['comments'] ) ) { $num_comments = 0; $inserted_comments = []; foreach ( $post['comments'] as $comment ) { $comment_id = $comment['comment_id']; $newcomments[ $comment_id ]['comment_post_ID'] = $comment_post_id; $newcomments[ $comment_id ]['comment_author'] = $comment['comment_author']; $newcomments[ $comment_id ]['comment_author_email'] = $comment['comment_author_email']; $newcomments[ $comment_id ]['comment_author_IP'] = $comment['comment_author_IP']; $newcomments[ $comment_id ]['comment_author_url'] = $comment['comment_author_url']; $newcomments[ $comment_id ]['comment_date'] = $comment['comment_date']; $newcomments[ $comment_id ]['comment_date_gmt'] = $comment['comment_date_gmt']; $newcomments[ $comment_id ]['comment_content'] = $comment['comment_content']; $newcomments[ $comment_id ]['comment_approved'] = $comment['comment_approved']; $newcomments[ $comment_id ]['comment_type'] = $comment['comment_type']; $newcomments[ $comment_id ]['comment_parent'] = $comment['comment_parent']; $newcomments[ $comment_id ]['commentmeta'] = isset( $comment['commentmeta'] ) ? $comment['commentmeta'] : []; if ( isset( $this->processed_authors[ $comment['comment_user_id'] ] ) ) { $newcomments[ $comment_id ]['user_id'] = $this->processed_authors[ $comment['comment_user_id'] ]; } } ksort( $newcomments ); foreach ( $newcomments as $key => $comment ) { if ( isset( $inserted_comments[ $comment['comment_parent'] ] ) ) { $comment['comment_parent'] = $inserted_comments[ $comment['comment_parent'] ]; } $comment_data = wp_slash( $comment ); unset( $comment_data['commentmeta'] ); // Handled separately, wp_insert_comment() also expects `comment_meta`. $comment_data = wp_filter_comment( $comment_data ); $inserted_comments[ $key ] = wp_insert_comment( $comment_data ); do_action( 'wp_import_insert_comment', $inserted_comments[ $key ], $comment, $comment_post_id, $post ); foreach ( $comment['commentmeta'] as $meta ) { $value = maybe_unserialize( $meta['value'] ); add_comment_meta( $inserted_comments[ $key ], wp_slash( $meta['key'] ), wp_slash_strings_only( $value ) ); } $num_comments++; } unset( $newcomments, $inserted_comments, $post['comments'] ); } if ( ! isset( $post['postmeta'] ) ) { $post['postmeta'] = []; } $post['postmeta'] = apply_filters( 'wp_import_post_meta', $post['postmeta'], $post_id, $post ); // Add/update post meta. if ( ! empty( $post['postmeta'] ) ) { foreach ( $post['postmeta'] as $meta ) { $key = apply_filters( 'import_post_meta_key', $meta['key'], $post_id, $post ); $value = false; if ( '_edit_last' === $key ) { if ( isset( $this->processed_authors[ (int) $meta['value'] ] ) ) { $value = $this->processed_authors[ (int) $meta['value'] ]; } else { $key = false; } } if ( $key ) { // Export gets meta straight from the DB so could have a serialized string. if ( ! $value ) { $value = maybe_unserialize( $meta['value'] ); } add_post_meta( $post_id, wp_slash( $key ), wp_slash_strings_only( $value ) ); do_action( 'import_post_meta', $post_id, $key, $value ); // If the post has a featured image, take note of this in case of remap. if ( '_thumbnail_id' === $key ) { $this->featured_images[ $post_id ] = (int) $value; } } } } } unset( $this->posts ); return $result; } /** * Attempt to create a new menu item from import data * * Fails for draft, orphaned menu items and those without an associated nav_menu * or an invalid nav_menu term. If the post type or term object which the menu item * represents doesn't exist then the menu item will not be imported (waits until the * end of the import to retry again before discarding). * * @param array $item Menu item details from WXR file */ private function process_menu_item( $item ) { $result = []; // Skip draft, orphaned menu items. if ( 'draft' === $item['status'] ) { return; } $menu_slug = false; if ( isset( $item['terms'] ) ) { // Loop through terms, assume first nav_menu term is correct menu. foreach ( $item['terms'] as $term ) { if ( 'nav_menu' === $term['domain'] ) { $menu_slug = $term['slug']; break; } } } // No nav_menu term associated with this menu item. if ( ! $menu_slug ) { $this->output['errors'][] = esc_html__( 'Menu item skipped due to missing menu slug', 'elementor' ); return $result; } // If menu was already exists, refer the items to the duplicated menu created. if ( array_key_exists( $menu_slug, $this->mapped_terms_slug ) ) { $menu_slug = $this->mapped_terms_slug[ $menu_slug ]; } $menu_id = term_exists( $menu_slug, 'nav_menu' ); if ( ! $menu_id ) { /* translators: %s: Menu slug. */ $this->output['errors'][] = sprintf( esc_html__( 'Menu item skipped due to invalid menu slug: %s', 'elementor' ), $menu_slug ); return $result; } else { $menu_id = is_array( $menu_id ) ? $menu_id['term_id'] : $menu_id; } $post_meta_key_value = []; foreach ( $item['postmeta'] as $meta ) { $post_meta_key_value[ $meta['key'] ] = $meta['value']; } $_menu_item_type = $post_meta_key_value['_menu_item_type']; $_menu_item_url = $post_meta_key_value['_menu_item_url']; // Skip menu items 'taxonomy' type, when the taxonomy is not exits. if ( 'taxonomy' === $_menu_item_type && ! taxonomy_exists( $post_meta_key_value['_menu_item_object'] ) ) { return $result; } // Skip menu items 'post_type' type, when the post type is not exits. if ( 'post_type' === $_menu_item_type && ! post_type_exists( $post_meta_key_value['_menu_item_object'] ) ) { return $result; } $_menu_item_object_id = $post_meta_key_value['_menu_item_object_id']; if ( 'taxonomy' === $_menu_item_type && isset( $this->processed_terms[ (int) $_menu_item_object_id ] ) ) { $_menu_item_object_id = $this->processed_terms[ (int) $_menu_item_object_id ]; } elseif ( 'post_type' === $_menu_item_type && isset( $this->processed_posts[ (int) $_menu_item_object_id ] ) ) { $_menu_item_object_id = $this->processed_posts[ (int) $_menu_item_object_id ]; } elseif ( 'custom' === $_menu_item_type ) { $_menu_item_url = Url::migrate( $_menu_item_url, $this->base_blog_url ); } else { return $result; } $_menu_item_menu_item_parent = $post_meta_key_value['_menu_item_menu_item_parent']; if ( isset( $this->processed_menu_items[ (int) $_menu_item_menu_item_parent ] ) ) { $_menu_item_menu_item_parent = $this->processed_menu_items[ (int) $_menu_item_menu_item_parent ]; } elseif ( $_menu_item_menu_item_parent ) { $this->menu_item_orphans[ (int) $item['post_id'] ] = (int) $_menu_item_menu_item_parent; $_menu_item_menu_item_parent = 0; } // wp_update_nav_menu_item expects CSS classes as a space separated string $_menu_item_classes = maybe_unserialize( $post_meta_key_value['_menu_item_classes'] ); if ( is_array( $_menu_item_classes ) ) { $_menu_item_classes = implode( ' ', $_menu_item_classes ); } $args = [ 'menu-item-object-id' => $_menu_item_object_id, 'menu-item-object' => $post_meta_key_value['_menu_item_object'], 'menu-item-parent-id' => $_menu_item_menu_item_parent, 'menu-item-position' => (int) $item['menu_order'], 'menu-item-type' => $_menu_item_type, 'menu-item-title' => $item['post_title'], 'menu-item-url' => $_menu_item_url, 'menu-item-description' => $item['post_content'], 'menu-item-attr-title' => $item['post_excerpt'], 'menu-item-target' => $post_meta_key_value['_menu_item_target'], 'menu-item-classes' => $_menu_item_classes, 'menu-item-xfn' => $post_meta_key_value['_menu_item_xfn'], 'menu-item-status' => $item['status'], ]; $id = wp_update_nav_menu_item( $menu_id, 0, $args ); if ( $id && ! is_wp_error( $id ) ) { $this->processed_menu_items[ (int) $item['post_id'] ] = (int) $id; $result[ $item['post_id'] ] = $id; $this->update_post_meta( $id ); } return $result; } /** * If fetching attachments is enabled then attempt to create a new attachment * * @param array $post Attachment post details from WXR * @param string $url URL to fetch attachment from * * @return int|WP_Error Post ID on success, WP_Error otherwise */ private function process_attachment( $post, $url ) { if ( ! $this->fetch_attachments ) { return new WP_Error( 'attachment_processing_error', esc_html__( 'Fetching attachments is not enabled', 'elementor' ) ); } // if the URL is absolute, but does not contain address, then upload it assuming base_site_url. if ( preg_match( '|^/[\w\W]+$|', $url ) ) { $url = rtrim( $this->base_url, '/' ) . $url; } $upload = $this->fetch_remote_file( $url, $post ); if ( is_wp_error( $upload ) ) { return $upload; } $info = wp_check_filetype( $upload['file'] ); if ( $info ) { $post['post_mime_type'] = $info['type']; } else { return new WP_Error( 'attachment_processing_error', esc_html__( 'Invalid file type', 'elementor' ) ); } $post['guid'] = $upload['url']; // As per wp-admin/includes/upload.php. $post_id = wp_insert_attachment( $post, $upload['file'] ); $this->update_post_meta( $post_id ); wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) ); // Remap resized image URLs, works by stripping the extension and remapping the URL stub. if ( preg_match( '!^image/!', $info['type'] ) ) { $parts = pathinfo( $url ); $name = basename( $parts['basename'], ".{$parts['extension']}" ); // PATHINFO_FILENAME in PHP 5.2 $parts_new = pathinfo( $upload['url'] ); $name_new = basename( $parts_new['basename'], ".{$parts_new['extension']}" ); $this->url_remap[ $parts['dirname'] . '/' . $name ] = $parts_new['dirname'] . '/' . $name_new; } return $post_id; } /** * Attempt to download a remote file attachment * * @param string $url URL of item to fetch * @param array $post Attachment details * * @return array|WP_Error Local file location details on success, WP_Error otherwise */ private function fetch_remote_file( $url, $post ) { // Extract the file name from the URL. $file_name = basename( parse_url( $url, PHP_URL_PATH ) ); if ( ! $file_name ) { $file_name = md5( $url ); } $tmp_file_name = wp_tempnam( $file_name ); if ( ! $tmp_file_name ) { return new WP_Error( 'import_no_file', esc_html__( 'Could not create temporary file.', 'elementor' ) ); } // Fetch the remote URL and write it to the placeholder file. $remote_response = wp_safe_remote_get( $url, [ 'timeout' => 300, 'stream' => true, 'filename' => $tmp_file_name, 'headers' => [ 'Accept-Encoding' => 'identity', ], ] ); if ( is_wp_error( $remote_response ) ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', sprintf( /* translators: 1: WordPress error message, 2: WordPress error code. */ esc_html__( 'Request failed due to an error: %1$s (%2$s)', 'elementor' ), esc_html( $remote_response->get_error_message() ), esc_html( $remote_response->get_error_code() ) ) ); } $remote_response_code = (int) wp_remote_retrieve_response_code( $remote_response ); // Make sure the fetch was successful. if ( 200 !== $remote_response_code ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', sprintf( /* translators: 1: HTTP error message, 2: HTTP error code. */ esc_html__( 'Remote server returned the following unexpected result: %1$s (%2$s)', 'elementor' ), get_status_header_desc( $remote_response_code ), esc_html( $remote_response_code ) ) ); } $headers = wp_remote_retrieve_headers( $remote_response ); // Request failed. if ( ! $headers ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', esc_html__( 'Remote server did not respond', 'elementor' ) ); } $filesize = (int) filesize( $tmp_file_name ); if ( 0 === $filesize ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', esc_html__( 'Zero size file downloaded', 'elementor' ) ); } if ( ! isset( $headers['content-encoding'] ) && isset( $headers['content-length'] ) && $filesize !== (int) $headers['content-length'] ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', esc_html__( 'Downloaded file has incorrect size', 'elementor' ) ); } $max_size = (int) apply_filters( 'import_attachment_size_limit', self::DEFAULT_IMPORT_ATTACHMENT_SIZE_LIMIT ); if ( ! empty( $max_size ) && $filesize > $max_size ) { @unlink( $tmp_file_name ); /* translators: %s: Max file size. */ return new WP_Error( 'import_file_error', sprintf( esc_html__( 'Remote file is too large, limit is %s', 'elementor' ), size_format( $max_size ) ) ); } // Override file name with Content-Disposition header value. if ( ! empty( $headers['content-disposition'] ) ) { $file_name_from_disposition = self::get_filename_from_disposition( (array) $headers['content-disposition'] ); if ( $file_name_from_disposition ) { $file_name = $file_name_from_disposition; } } // Set file extension if missing. $file_ext = pathinfo( $file_name, PATHINFO_EXTENSION ); if ( ! $file_ext && ! empty( $headers['content-type'] ) ) { $extension = self::get_file_extension_by_mime_type( $headers['content-type'] ); if ( $extension ) { $file_name = "{$file_name}.{$extension}"; } } // Handle the upload like _wp_handle_upload() does. $wp_filetype = wp_check_filetype_and_ext( $tmp_file_name, $file_name ); $ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext']; $type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type']; $proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename']; // Check to see if wp_check_filetype_and_ext() determined the filename was incorrect. if ( $proper_filename ) { $file_name = $proper_filename; } if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) { return new WP_Error( 'import_file_error', esc_html__( 'Sorry, this file type is not permitted for security reasons.', 'elementor' ) ); } $uploads = wp_upload_dir( $post['upload_date'] ); if ( ! ( $uploads && false === $uploads['error'] ) ) { return new WP_Error( 'upload_dir_error', $uploads['error'] ); } // Move the file to the uploads dir. $file_name = wp_unique_filename( $uploads['path'], $file_name ); $new_file = $uploads['path'] . "/$file_name"; $move_new_file = copy( $tmp_file_name, $new_file ); if ( ! $move_new_file ) { @unlink( $tmp_file_name ); return new WP_Error( 'import_file_error', esc_html__( 'The uploaded file could not be moved', 'elementor' ) ); } // Set correct file permissions. $stat = stat( dirname( $new_file ) ); $perms = $stat['mode'] & 0000666; chmod( $new_file, $perms ); $upload = [ 'file' => $new_file, 'url' => $uploads['url'] . "/$file_name", 'type' => $wp_filetype['type'], 'error' => false, ]; // Keep track of the old and new urls so we can substitute them later. $this->url_remap[ $url ] = $upload['url']; $this->url_remap[ $post['guid'] ] = $upload['url']; // r13735, really needed? // Keep track of the destination if the remote url is redirected somewhere else. if ( isset( $headers['x-final-location'] ) && $headers['x-final-location'] !== $url ) { $this->url_remap[ $headers['x-final-location'] ] = $upload['url']; } return $upload; } /** * Attempt to associate posts and menu items with previously missing parents * * An imported post's parent may not have been imported when it was first created * so try again. Similarly for child menu items and menu items which were missing * the object (e.g. post) they represent in the menu */ private function backfill_parents() { global $wpdb; // Find parents for post orphans. foreach ( $this->post_orphans as $child_id => $parent_id ) { $local_child_id = false; $local_parent_id = false; if ( isset( $this->processed_posts[ $child_id ] ) ) { $local_child_id = $this->processed_posts[ $child_id ]; } if ( isset( $this->processed_posts[ $parent_id ] ) ) { $local_parent_id = $this->processed_posts[ $parent_id ]; } if ( $local_child_id && $local_parent_id ) { $wpdb->update( $wpdb->posts, [ 'post_parent' => $local_parent_id ], [ 'ID' => $local_child_id ], '%d', '%d' ); clean_post_cache( $local_child_id ); } } // Find parents for menu item orphans. foreach ( $this->menu_item_orphans as $child_id => $parent_id ) { $local_child_id = 0; $local_parent_id = 0; if ( isset( $this->processed_menu_items[ $child_id ] ) ) { $local_child_id = $this->processed_menu_items[ $child_id ]; } if ( isset( $this->processed_menu_items[ $parent_id ] ) ) { $local_parent_id = $this->processed_menu_items[ $parent_id ]; } if ( $local_child_id && $local_parent_id ) { update_post_meta( $local_child_id, '_menu_item_menu_item_parent', (int) $local_parent_id ); } } } /** * Use stored mapping information to update old attachment URLs */ private function backfill_attachment_urls() { global $wpdb; // Make sure we do the longest urls first, in case one is a substring of another. uksort( $this->url_remap, function ( $a, $b ) { // Return the difference in length between two strings. return strlen( $b ) - strlen( $a ); } ); foreach ( $this->url_remap as $from_url => $to_url ) { // Remap urls in post_content. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url ) ); // Remap enclosure urls. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url ) ); } } /** * Update _thumbnail_id meta to new, imported attachment IDs */ private function remap_featured_images() { // Cycle through posts that have a featured image. foreach ( $this->featured_images as $post_id => $value ) { if ( isset( $this->processed_posts[ $value ] ) ) { $new_id = $this->processed_posts[ $value ]; // Only update if there's a difference. if ( $new_id !== $value ) { update_post_meta( $post_id, '_thumbnail_id', $new_id ); } } } } /** * Parse a WXR file * * @param string $file Path to WXR file for parsing * * @return array Information gathered from the WXR file */ private function parse( $file ) { $parser = new WXR_Parser(); return $parser->parse( $file ); } /** * Decide if the given meta key maps to information we will want to import * * @param string $key The meta key to check * * @return string|bool The key if we do want to import, false if not */ private function is_valid_meta_key( $key ) { // Skip attachment metadata since we'll regenerate it from scratch. // Skip _edit_lock as not relevant for import if ( in_array( $key, [ '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock' ] ) ) { return false; } return $key; } /** * @param $term * @return mixed */ private function handle_duplicated_nav_menu_term( $term ) { $duplicate_slug = $term['slug'] . '-duplicate'; $duplicate_name = $term['term_name'] . ' duplicate'; while ( term_exists( $duplicate_slug, 'nav_menu' ) ) { $duplicate_slug .= '-duplicate'; $duplicate_name .= ' duplicate'; } $this->mapped_terms_slug[ $term['slug'] ] = $duplicate_slug; $term['slug'] = $duplicate_slug; $term['term_name'] = $duplicate_name; return $term; } /** * Add all term_meta to specified term. * * @param $term_id * @return void */ private function update_term_meta( $term_id ) { foreach ( $this->terms_meta as $meta_key => $meta_value ) { update_term_meta( $term_id, $meta_key, $meta_value ); } } /** * Add all post_meta to specified term. * * @param $post_id * @return void */ private function update_post_meta( $post_id ) { foreach ( $this->posts_meta as $meta_key => $meta_value ) { update_post_meta( $post_id, $meta_key, $meta_value ); } } public function run() { $this->import( $this->requested_file_path ); return $this->output; } /** * @param $file * @param $args */ public function __construct( $file, $args = [] ) { $this->requested_file_path = $file; $this->args = $args; if ( ! empty( $this->args['fetch_attachments'] ) ) { $this->fetch_attachments = true; } if ( isset( $this->args['posts'] ) && is_array( $this->args['posts'] ) ) { $this->processed_posts = $this->args['posts']; } if ( isset( $this->args['terms'] ) && is_array( $this->args['terms'] ) ) { $this->processed_terms = $this->args['terms']; } if ( isset( $this->args['taxonomies'] ) && is_array( $this->args['taxonomies'] ) ) { $this->processed_taxonomies = $this->args['taxonomies']; } if ( ! empty( $this->args['posts_meta'] ) ) { $this->posts_meta = $this->args['posts_meta']; } if ( ! empty( $this->args['terms_meta'] ) ) { $this->terms_meta = $this->args['terms_meta']; } } } utils/import-export/parsers/wxr-parser-xml.php 0000644 00000016712 14717626151 0015656 0 ustar 00 <?php namespace Elementor\Core\Utils\ImportExport\Parsers; use Elementor\Utils; use WP_Error; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * WordPress eXtended RSS file parser implementations, * Originally made by WordPress part of WordPress/Importer. * https://plugins.trac.wordpress.org/browser/wordpress-importer/trunk/parsers/class-wxr-parser-xml.php * * What was done: * Reformat of the code. * Added PHPDOC. * Changed text domain. * Added clear() method. * Added undeclared class properties. * Changed methods visibility. */ /** * WXR Parser that makes use of the XML Parser PHP extension. */ class WXR_Parser_XML { private static $wp_tags = [ 'wp:post_id', 'wp:post_date', 'wp:post_date_gmt', 'wp:comment_status', 'wp:ping_status', 'wp:attachment_url', 'wp:status', 'wp:post_name', 'wp:post_parent', 'wp:menu_order', 'wp:post_type', 'wp:post_password', 'wp:is_sticky', 'wp:term_id', 'wp:category_nicename', 'wp:category_parent', 'wp:cat_name', 'wp:category_description', 'wp:tag_slug', 'wp:tag_name', 'wp:tag_description', 'wp:term_taxonomy', 'wp:term_parent', 'wp:term_name', 'wp:term_description', 'wp:author_id', 'wp:author_login', 'wp:author_email', 'wp:author_display_name', 'wp:author_first_name', 'wp:author_last_name', ]; private static $wp_sub_tags = [ 'wp:comment_id', 'wp:comment_author', 'wp:comment_author_email', 'wp:comment_author_url', 'wp:comment_author_IP', 'wp:comment_date', 'wp:comment_date_gmt', 'wp:comment_content', 'wp:comment_approved', 'wp:comment_type', 'wp:comment_parent', 'wp:comment_user_id', ]; /** * @var string */ private $wxr_version; /** * @var string */ private $cdata; /** * @var array */ private $data; /** * @var array */ private $sub_data; /** * @var boolean */ private $in_post; /** * @var boolean */ private $in_tag; /** * @var boolean */ private $in_sub_tag; /** * @var array */ private $authors; /** * @var array */ private $posts; /** * @var array */ private $term; /** * @var array */ private $category; /** * @var array */ private $tag; /** * @var string */ private $base_url; /** * @var string */ private $base_blog_url; /** * @param string $file * * @return array|WP_Error */ public function parse( $file ) { $this->clear(); $xml = xml_parser_create( 'UTF-8' ); xml_parser_set_option( $xml, XML_OPTION_SKIP_WHITE, 1 ); xml_parser_set_option( $xml, XML_OPTION_CASE_FOLDING, 0 ); xml_set_object( $xml, $this ); xml_set_character_data_handler( $xml, function ( $parser, $cdata ) { $this->cdata( $cdata ); } ); $tag_open_callback = function ( $parse, $tag, $attr ) { $this->tag_open( $tag, $attr ); }; $tag_close_callback = function ( $parser, $tag ) { $this->tag_close( $tag ); }; xml_set_element_handler( $xml, $tag_open_callback, $tag_close_callback ); if ( ! xml_parse( $xml, Utils::file_get_contents( $file ), true ) ) { $current_line = xml_get_current_line_number( $xml ); $current_column = xml_get_current_column_number( $xml ); $error_code = xml_get_error_code( $xml ); $error_string = xml_error_string( $error_code ); return new WP_Error( 'XML_parse_error', 'There was an error when reading this WXR file', [ $current_line, $current_column, $error_string, ] ); } xml_parser_free( $xml ); if ( ! preg_match( '/^\d+\.\d+$/', $this->wxr_version ) ) { return new WP_Error( 'WXR_parse_error', esc_html__( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'elementor' ) ); } return array( 'authors' => $this->authors, 'posts' => $this->posts, 'categories' => $this->category, 'tags' => $this->tag, 'terms' => $this->term, 'base_url' => $this->base_url, 'base_blog_url' => $this->base_blog_url, 'version' => $this->wxr_version, ); } private function tag_open( $tag, $attr ) { if ( in_array( $tag, self::$wp_tags ) ) { $this->in_tag = substr( $tag, 3 ); return; } if ( in_array( $tag, self::$wp_sub_tags ) ) { $this->in_sub_tag = substr( $tag, 3 ); return; } switch ( $tag ) { case 'category': if ( isset( $attr['domain'], $attr['nicename'] ) ) { $this->sub_data['domain'] = $attr['domain']; $this->sub_data['slug'] = $attr['nicename']; } break; case 'item': $this->in_post = true; // No break !!!. case 'title': if ( $this->in_post ) { $this->in_tag = 'post_title'; } break; case 'guid': $this->in_tag = 'guid'; break; case 'dc:creator': $this->in_tag = 'post_author'; break; case 'content:encoded': $this->in_tag = 'post_content'; break; case 'excerpt:encoded': $this->in_tag = 'post_excerpt'; break; case 'wp:term_slug': $this->in_tag = 'slug'; break; case 'wp:meta_key': $this->in_sub_tag = 'key'; break; case 'wp:meta_value': $this->in_sub_tag = 'value'; break; } } private function cdata( $cdata ) { if ( ! trim( $cdata ) ) { return; } if ( false !== $this->in_tag || false !== $this->in_sub_tag ) { $this->cdata .= $cdata; } else { $this->cdata .= trim( $cdata ); } } private function tag_close( $tag ) { switch ( $tag ) { case 'wp:comment': unset( $this->sub_data['key'], $this->sub_data['value'] ); // Remove meta sub_data. if ( ! empty( $this->sub_data ) ) { $this->data['comments'][] = $this->sub_data; } $this->sub_data = []; break; case 'wp:commentmeta': $this->sub_data['commentmeta'][] = [ 'key' => $this->sub_data['key'], 'value' => $this->sub_data['value'], ]; break; case 'category': if ( ! empty( $this->sub_data ) ) { $this->sub_data['name'] = $this->cdata; $this->data['terms'][] = $this->sub_data; } $this->sub_data = []; break; case 'wp:postmeta': if ( ! empty( $this->sub_data ) ) { $this->data['postmeta'][] = $this->sub_data; } $this->sub_data = []; break; case 'item': $this->posts[] = $this->data; $this->data = []; break; case 'wp:category': case 'wp:tag': case 'wp:term': $n = substr( $tag, 3 ); array_push( $this->$n, $this->data ); $this->data = []; break; case 'wp:termmeta': if ( ! empty( $this->sub_data ) ) { $this->data['termmeta'][] = $this->sub_data; } $this->sub_data = []; break; case 'wp:author': if ( ! empty( $this->data['author_login'] ) ) { $this->authors[ $this->data['author_login'] ] = $this->data; } $this->data = []; break; case 'wp:base_site_url': $this->base_url = $this->cdata; if ( ! isset( $this->base_blog_url ) ) { $this->base_blog_url = $this->cdata; } break; case 'wp:base_blog_url': $this->base_blog_url = $this->cdata; break; case 'wp:wxr_version': $this->wxr_version = $this->cdata; break; default: if ( $this->in_sub_tag ) { $this->sub_data[ $this->in_sub_tag ] = $this->cdata; $this->in_sub_tag = false; } else if ( $this->in_tag ) { $this->data[ $this->in_tag ] = $this->cdata; $this->in_tag = false; } } $this->cdata = ''; } private function clear() { $this->wxr_version = ''; $this->cdata = ''; $this->data = []; $this->sub_data = []; $this->in_post = false; $this->in_tag = false; $this->in_sub_tag = false; $this->authors = []; $this->posts = []; $this->term = []; $this->category = []; $this->tag = []; } } utils/import-export/parsers/wxr-parser.php 0000644 00000002557 14717626151 0015062 0 ustar 00 <?php namespace Elementor\Core\Utils\ImportExport\Parsers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * WordPress eXtended RSS file parser implementations, * Originally made by WordPress part of WordPress/Importer. * https://plugins.trac.wordpress.org/browser/wordpress-importer/trunk/parsers/class-wxr-parser.php * * What was done: * Reformat of the code. * Changed text domain. */ /** * WordPress Importer class for managing parsing of WXR files. */ class WXR_Parser { public function parse( $file ) { // Attempt to use proper XML parsers first. if ( extension_loaded( 'simplexml' ) ) { $parser = new WXR_Parser_SimpleXML(); $result = $parser->parse( $file ); // If SimpleXML succeeds or this is an invalid WXR file then return the results. if ( ! is_wp_error( $result ) || 'SimpleXML_parse_error' != $result->get_error_code() ) { return $result; } } elseif ( extension_loaded( 'xml' ) ) { $parser = new WXR_Parser_XML(); $result = $parser->parse( $file ); // If XMLParser succeeds or this is an invalid WXR file then return the results. if ( ! is_wp_error( $result ) || 'XML_parse_error' != $result->get_error_code() ) { return $result; } } // Use regular expressions if nothing else available or this is bad XML. $parser = new WXR_Parser_Regex(); return $parser->parse( $file ); } } utils/import-export/parsers/wxr-parser-regex.php 0000644 00000026572 14717626151 0016175 0 ustar 00 <?php namespace Elementor\Core\Utils\ImportExport\Parsers; use WP_Error; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * WordPress eXtended RSS file parser implementations * Originally made by WordPress part of WordPress/Importer. * https://plugins.trac.wordpress.org/browser/wordpress-importer/trunk/parsers/class-wxr-parser-regex.php * * What was done: * Reformat of the code. * Changed text domain. * Changed methods visibility. */ /** * WXR Parser that uses regular expressions. Fallback for installs without an XML parser. */ class WXR_Parser_Regex { /** * @var bool */ private $has_gzip; private $authors = []; private $posts = []; private $categories = []; private $tags = []; private $terms = []; private $base_url = ''; private $base_blog_url = ''; /** * @param string $file * * @return array|\WP_Error */ public function parse( $file ) { $wxr_version = ''; $in_multiline = false; $multiline_content = ''; $multiline_tags = [ 'item' => [ 'posts', function ( $post ) { return $this->process_post( $post ); }, ], 'wp:category' => [ 'categories', function ( $category ) { return $this->process_category( $category ); }, ], 'wp:tag' => [ 'tags', function ( $tag ) { return $this->process_tag( $tag ); }, ], 'wp:term' => [ 'terms', function ( $term ) { return $this->process_term( $term ); }, ], ]; $fp = $this->fopen( $file, 'r' ); if ( $fp ) { while ( ! $this->feof( $fp ) ) { $importline = rtrim( $this->fgets( $fp ) ); if ( ! $wxr_version && preg_match( '|<wp:wxr_version>(\d+\.\d+)</wp:wxr_version>|', $importline, $version ) ) { $wxr_version = $version[1]; } if ( false !== strpos( $importline, '<wp:base_site_url>' ) ) { preg_match( '|<wp:base_site_url>(.*?)</wp:base_site_url>|is', $importline, $url ); $this->base_url = $url[1]; continue; } if ( false !== strpos( $importline, '<wp:base_blog_url>' ) ) { preg_match( '|<wp:base_blog_url>(.*?)</wp:base_blog_url>|is', $importline, $blog_url ); $this->base_blog_url = $blog_url[1]; continue; } else { $this->base_blog_url = $this->base_url; } if ( false !== strpos( $importline, '<wp:author>' ) ) { preg_match( '|<wp:author>(.*?)</wp:author>|is', $importline, $author ); $a = $this->process_author( $author[1] ); $this->authors[ $a['author_login'] ] = $a; continue; } foreach ( $multiline_tags as $tag => $handler ) { // Handle multi-line tags on a singular line. if ( preg_match( '|<' . $tag . '>(.*?)</' . $tag . '>|is', $importline, $matches ) ) { $this->{$handler[0]}[] = call_user_func( $handler[1], $matches[1] ); continue; } $pos = strpos( $importline, "<$tag>" ); if ( false !== $pos ) { // Take note of any content after the opening tag. $multiline_content = trim( substr( $importline, $pos + strlen( $tag ) + 2 ) ); // We don't want to have this line added to `$is_multiline` below. $importline = ''; $in_multiline = $tag; continue; } $pos = strpos( $importline, "</$tag>" ); if ( false !== $pos ) { $in_multiline = false; $multiline_content .= trim( substr( $importline, 0, $pos ) ); $this->{$handler[0]}[] = call_user_func( $handler[1], $multiline_content ); } } if ( $in_multiline && $importline ) { $multiline_content .= $importline . "\n"; } } $this->fclose( $fp ); } if ( ! $wxr_version ) { return new WP_Error( 'WXR_parse_error', esc_html__( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'elementor' ) ); } return [ 'authors' => $this->authors, 'posts' => $this->posts, 'categories' => $this->categories, 'tags' => $this->tags, 'terms' => $this->terms, 'base_url' => $this->base_url, 'base_blog_url' => $this->base_blog_url, 'version' => $wxr_version, ]; } private function process_category( $category ) { $term = [ 'term_id' => $this->get_tag( $category, 'wp:term_id' ), 'cat_name' => $this->get_tag( $category, 'wp:cat_name' ), 'category_nicename' => $this->get_tag( $category, 'wp:category_nicename' ), 'category_parent' => $this->get_tag( $category, 'wp:category_parent' ), 'category_description' => $this->get_tag( $category, 'wp:category_description' ), ]; $term_meta = $this->process_meta( $category, 'wp:termmeta' ); if ( ! empty( $term_meta ) ) { $term['termmeta'] = $term_meta; } return $term; } private function process_tag( $tag ) { $term = [ 'term_id' => $this->get_tag( $tag, 'wp:term_id' ), 'tag_name' => $this->get_tag( $tag, 'wp:tag_name' ), 'tag_slug' => $this->get_tag( $tag, 'wp:tag_slug' ), 'tag_description' => $this->get_tag( $tag, 'wp:tag_description' ), ]; $term_meta = $this->process_meta( $tag, 'wp:termmeta' ); if ( ! empty( $term_meta ) ) { $term['termmeta'] = $term_meta; } return $term; } private function process_term( $term ) { $term_data = [ 'term_id' => $this->get_tag( $term, 'wp:term_id' ), 'term_taxonomy' => $this->get_tag( $term, 'wp:term_taxonomy' ), 'slug' => $this->get_tag( $term, 'wp:term_slug' ), 'term_parent' => $this->get_tag( $term, 'wp:term_parent' ), 'term_name' => $this->get_tag( $term, 'wp:term_name' ), 'term_description' => $this->get_tag( $term, 'wp:term_description' ), ]; $term_meta = $this->process_meta( $term, 'wp:termmeta' ); if ( ! empty( $term_meta ) ) { $term_data['termmeta'] = $term_meta; } return $term_data; } private function process_meta( $string, $tag ) { $parsed_meta = []; preg_match_all( "|<$tag>(.+?)</$tag>|is", $string, $meta ); if ( ! isset( $meta[1] ) ) { return $parsed_meta; } foreach ( $meta[1] as $m ) { $parsed_meta[] = [ 'key' => $this->get_tag( $m, 'wp:meta_key' ), 'value' => $this->get_tag( $m, 'wp:meta_value' ), ]; } return $parsed_meta; } private function process_author( $a ) { return [ 'author_id' => $this->get_tag( $a, 'wp:author_id' ), 'author_login' => $this->get_tag( $a, 'wp:author_login' ), 'author_email' => $this->get_tag( $a, 'wp:author_email' ), 'author_display_name' => $this->get_tag( $a, 'wp:author_display_name' ), 'author_first_name' => $this->get_tag( $a, 'wp:author_first_name' ), 'author_last_name' => $this->get_tag( $a, 'wp:author_last_name' ), ]; } private function process_post( $post ) { $normalize_tag_callback = function ( $matches ) { return $this->normalize_tag( $matches ); }; $post_id = $this->get_tag( $post, 'wp:post_id' ); $post_title = $this->get_tag( $post, 'title' ); $post_date = $this->get_tag( $post, 'wp:post_date' ); $post_date_gmt = $this->get_tag( $post, 'wp:post_date_gmt' ); $comment_status = $this->get_tag( $post, 'wp:comment_status' ); $ping_status = $this->get_tag( $post, 'wp:ping_status' ); $status = $this->get_tag( $post, 'wp:status' ); $post_name = $this->get_tag( $post, 'wp:post_name' ); $post_parent = $this->get_tag( $post, 'wp:post_parent' ); $menu_order = $this->get_tag( $post, 'wp:menu_order' ); $post_type = $this->get_tag( $post, 'wp:post_type' ); $post_password = $this->get_tag( $post, 'wp:post_password' ); $is_sticky = $this->get_tag( $post, 'wp:is_sticky' ); $guid = $this->get_tag( $post, 'guid' ); $post_author = $this->get_tag( $post, 'dc:creator' ); $post_excerpt = $this->get_tag( $post, 'excerpt:encoded' ); $post_excerpt = preg_replace_callback( '|<(/?[A-Z]+)|', $normalize_tag_callback, $post_excerpt ); $post_excerpt = str_replace( '<br>', '<br />', $post_excerpt ); $post_excerpt = str_replace( '<hr>', '<hr />', $post_excerpt ); $post_content = $this->get_tag( $post, 'content:encoded' ); $post_content = preg_replace_callback( '|<(/?[A-Z]+)|', $normalize_tag_callback, $post_content ); $post_content = str_replace( '<br>', '<br />', $post_content ); $post_content = str_replace( '<hr>', '<hr />', $post_content ); $postdata = compact( 'post_id', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_excerpt', 'post_title', 'status', 'post_name', 'comment_status', 'ping_status', 'guid', 'post_parent', 'menu_order', 'post_type', 'post_password', 'is_sticky' ); $attachment_url = $this->get_tag( $post, 'wp:attachment_url' ); if ( $attachment_url ) { $postdata['attachment_url'] = $attachment_url; } preg_match_all( '|<category domain="([^"]+?)" nicename="([^"]+?)">(.+?)</category>|is', $post, $terms, PREG_SET_ORDER ); foreach ( $terms as $t ) { $post_terms[] = [ 'slug' => $t[2], 'domain' => $t[1], 'name' => str_replace( [ '<![CDATA[', ']]>' ], '', $t[3] ), ]; } if ( ! empty( $post_terms ) ) { $postdata['terms'] = $post_terms; } preg_match_all( '|<wp:comment>(.+?)</wp:comment>|is', $post, $comments ); $comments = $comments[1]; if ( $comments ) { foreach ( $comments as $comment ) { $post_comments[] = [ 'comment_id' => $this->get_tag( $comment, 'wp:comment_id' ), 'comment_author' => $this->get_tag( $comment, 'wp:comment_author' ), 'comment_author_email' => $this->get_tag( $comment, 'wp:comment_author_email' ), 'comment_author_IP' => $this->get_tag( $comment, 'wp:comment_author_IP' ), 'comment_author_url' => $this->get_tag( $comment, 'wp:comment_author_url' ), 'comment_date' => $this->get_tag( $comment, 'wp:comment_date' ), 'comment_date_gmt' => $this->get_tag( $comment, 'wp:comment_date_gmt' ), 'comment_content' => $this->get_tag( $comment, 'wp:comment_content' ), 'comment_approved' => $this->get_tag( $comment, 'wp:comment_approved' ), 'comment_type' => $this->get_tag( $comment, 'wp:comment_type' ), 'comment_parent' => $this->get_tag( $comment, 'wp:comment_parent' ), 'comment_user_id' => $this->get_tag( $comment, 'wp:comment_user_id' ), 'commentmeta' => $this->process_meta( $comment, 'wp:commentmeta' ), ]; } } if ( ! empty( $post_comments ) ) { $postdata['comments'] = $post_comments; } $post_meta = $this->process_meta( $post, 'wp:postmeta' ); if ( ! empty( $post_meta ) ) { $postdata['postmeta'] = $post_meta; } return $postdata; } private function get_tag( $string, $tag ) { preg_match( "|<$tag.*?>(.*?)</$tag>|is", $string, $return ); if ( isset( $return[1] ) ) { if ( substr( $return[1], 0, 9 ) == '<![CDATA[' ) { if ( strpos( $return[1], ']]]]><![CDATA[>' ) !== false ) { preg_match_all( '|<!\[CDATA\[(.*?)\]\]>|s', $return[1], $matches ); $return = ''; foreach ( $matches[1] as $match ) { $return .= $match; } } else { $return = preg_replace( '|^<!\[CDATA\[(.*)\]\]>$|s', '$1', $return[1] ); } } else { $return = $return[1]; } } else { $return = ''; } return $return; } private function normalize_tag( $matches ) { return '<' . strtolower( $matches[1] ); } private function fopen( $filename, $mode = 'r' ) { if ( $this->has_gzip ) { return gzopen( $filename, $mode ); } return fopen( $filename, $mode ); } private function feof( $fp ) { if ( $this->has_gzip ) { return gzeof( $fp ); } return feof( $fp ); } private function fgets( $fp, $len = 8192 ) { if ( $this->has_gzip ) { return gzgets( $fp, $len ); } return fgets( $fp, $len ); } private function fclose( $fp ) { if ( $this->has_gzip ) { return gzclose( $fp ); } return fclose( $fp ); } public function __construct() { $this->has_gzip = is_callable( 'gzopen' ); } } utils/import-export/parsers/wxr-parser-simple-xml.php 0000644 00000020477 14717626151 0017150 0 ustar 00 <?php namespace Elementor\Core\Utils\ImportExport\Parsers; use Elementor\Utils; use WP_Error; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * WordPress eXtended RSS file parser implementations, * Originally made by WordPress part of WordPress/Importer. * https://plugins.trac.wordpress.org/browser/wordpress-importer/trunk/parsers/class-wxr-parser-simplexml.php * * What was done: * Reformat of the code. * Removed variable '$internal_errors'. * Changed text domain. */ /** * WXR Parser that makes use of the SimpleXML PHP extension. */ class WXR_Parser_SimpleXML { /** * @param string $file * * @return array|\WP_Error */ public function parse( $file ) { $authors = []; $posts = []; $categories = []; $tags = []; $terms = []; libxml_use_internal_errors( true ); $dom = new \DOMDocument(); $old_value = null; $libxml_disable_entity_loader_exists = function_exists( 'libxml_disable_entity_loader' ); if ( $libxml_disable_entity_loader_exists ) { $old_value = libxml_disable_entity_loader( true ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated } $success = $dom->loadXML( Utils::file_get_contents( $file ) ); if ( $libxml_disable_entity_loader_exists && ! is_null( $old_value ) ) { libxml_disable_entity_loader( $old_value ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated } if ( ! $success || isset( $dom->doctype ) ) { return new WP_Error( 'SimpleXML_parse_error', esc_html__( 'There was an error when reading this WXR file', 'elementor' ), libxml_get_errors() ); } $xml = simplexml_import_dom( $dom ); unset( $dom ); // Halt if loading produces an error. if ( ! $xml ) { return new WP_Error( 'SimpleXML_parse_error', esc_html__( 'There was an error when reading this WXR file', 'elementor' ), libxml_get_errors() ); } $wxr_version = $xml->xpath( '/rss/channel/wp:wxr_version' ); if ( ! $wxr_version ) { return new WP_Error( 'WXR_parse_error', esc_html__( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'elementor' ) ); } $wxr_version = (string) trim( $wxr_version[0] ); // Confirm that we are dealing with the correct file format. if ( ! preg_match( '/^\d+\.\d+$/', $wxr_version ) ) { return new WP_Error( 'WXR_parse_error', esc_html__( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'elementor' ) ); } $base_url = $xml->xpath( '/rss/channel/wp:base_site_url' ); $base_url = (string) trim( isset( $base_url[0] ) ? $base_url[0] : '' ); $base_blog_url = $xml->xpath( '/rss/channel/wp:base_blog_url' ); if ( $base_blog_url ) { $base_blog_url = (string) trim( $base_blog_url[0] ); } else { $base_blog_url = $base_url; } $page_on_front = $xml->xpath( '/rss/channel/wp:page_on_front' ); if ( $page_on_front ) { $page_on_front = (int) $page_on_front[0]; } $namespaces = $xml->getDocNamespaces(); if ( ! isset( $namespaces['wp'] ) ) { $namespaces['wp'] = 'http://wordpress.org/export/1.1/'; } if ( ! isset( $namespaces['excerpt'] ) ) { $namespaces['excerpt'] = 'http://wordpress.org/export/1.1/excerpt/'; } // Grab authors. foreach ( $xml->xpath( '/rss/channel/wp:author' ) as $author_arr ) { $a = $author_arr->children( $namespaces['wp'] ); $login = (string) $a->author_login; $authors[ $login ] = [ 'author_id' => (int) $a->author_id, 'author_login' => $login, 'author_email' => (string) $a->author_email, 'author_display_name' => (string) $a->author_display_name, 'author_first_name' => (string) $a->author_first_name, 'author_last_name' => (string) $a->author_last_name, ]; } // Grab cats, tags and terms. foreach ( $xml->xpath( '/rss/channel/wp:category' ) as $term_arr ) { $t = $term_arr->children( $namespaces['wp'] ); $category = [ 'term_id' => (int) $t->term_id, 'category_nicename' => (string) $t->category_nicename, 'category_parent' => (string) $t->category_parent, 'cat_name' => (string) $t->cat_name, 'category_description' => (string) $t->category_description, ]; foreach ( $t->termmeta as $meta ) { $category['termmeta'][] = [ 'key' => (string) $meta->meta_key, 'value' => (string) $meta->meta_value, ]; } $categories[] = $category; } foreach ( $xml->xpath( '/rss/channel/wp:tag' ) as $term_arr ) { $t = $term_arr->children( $namespaces['wp'] ); $tag = [ 'term_id' => (int) $t->term_id, 'tag_slug' => (string) $t->tag_slug, 'tag_name' => (string) $t->tag_name, 'tag_description' => (string) $t->tag_description, ]; foreach ( $t->termmeta as $meta ) { $tag['termmeta'][] = [ 'key' => (string) $meta->meta_key, 'value' => (string) $meta->meta_value, ]; } $tags[] = $tag; } foreach ( $xml->xpath( '/rss/channel/wp:term' ) as $term_arr ) { $t = $term_arr->children( $namespaces['wp'] ); $term = [ 'term_id' => (int) $t->term_id, 'term_taxonomy' => (string) $t->term_taxonomy, 'slug' => (string) $t->term_slug, 'term_parent' => (string) $t->term_parent, 'term_name' => (string) $t->term_name, 'term_description' => (string) $t->term_description, ]; foreach ( $t->termmeta as $meta ) { $term['termmeta'][] = [ 'key' => (string) $meta->meta_key, 'value' => (string) $meta->meta_value, ]; } $terms[] = $term; } // Grab posts. foreach ( $xml->channel->item as $item ) { $post = [ 'post_title' => (string) $item->title, 'guid' => (string) $item->guid, ]; $dc = $item->children( 'http://purl.org/dc/elements/1.1/' ); $post['post_author'] = (string) $dc->creator; $content = $item->children( 'http://purl.org/rss/1.0/modules/content/' ); $excerpt = $item->children( $namespaces['excerpt'] ); $post['post_content'] = (string) $content->encoded; $post['post_excerpt'] = (string) $excerpt->encoded; $wp = $item->children( $namespaces['wp'] ); $post['post_id'] = (int) $wp->post_id; $post['post_date'] = (string) $wp->post_date; $post['post_date_gmt'] = (string) $wp->post_date_gmt; $post['comment_status'] = (string) $wp->comment_status; $post['ping_status'] = (string) $wp->ping_status; $post['post_name'] = (string) $wp->post_name; $post['status'] = (string) $wp->status; $post['post_parent'] = (int) $wp->post_parent; $post['menu_order'] = (int) $wp->menu_order; $post['post_type'] = (string) $wp->post_type; $post['post_password'] = (string) $wp->post_password; $post['is_sticky'] = (int) $wp->is_sticky; if ( isset( $wp->attachment_url ) ) { $post['attachment_url'] = (string) $wp->attachment_url; } foreach ( $item->category as $c ) { $att = $c->attributes(); if ( isset( $att['nicename'] ) ) { $post['terms'][] = [ 'name' => (string) $c, 'slug' => (string) $att['nicename'], 'domain' => (string) $att['domain'], ]; } } foreach ( $wp->postmeta as $meta ) { $post['postmeta'][] = [ 'key' => (string) $meta->meta_key, 'value' => (string) $meta->meta_value, ]; } foreach ( $wp->comment as $comment ) { $meta = []; if ( isset( $comment->commentmeta ) ) { foreach ( $comment->commentmeta as $m ) { $meta[] = [ 'key' => (string) $m->meta_key, 'value' => (string) $m->meta_value, ]; } } $post['comments'][] = [ 'comment_id' => (int) $comment->comment_id, 'comment_author' => (string) $comment->comment_author, 'comment_author_email' => (string) $comment->comment_author_email, 'comment_author_IP' => (string) $comment->comment_author_IP, 'comment_author_url' => (string) $comment->comment_author_url, 'comment_date' => (string) $comment->comment_date, 'comment_date_gmt' => (string) $comment->comment_date_gmt, 'comment_content' => (string) $comment->comment_content, 'comment_approved' => (string) $comment->comment_approved, 'comment_type' => (string) $comment->comment_type, 'comment_parent' => (string) $comment->comment_parent, 'comment_user_id' => (int) $comment->comment_user_id, 'commentmeta' => $meta, ]; } $posts[] = $post; } return [ 'authors' => $authors, 'posts' => $posts, 'categories' => $categories, 'tags' => $tags, 'terms' => $terms, 'base_url' => $base_url, 'base_blog_url' => $base_blog_url, 'page_on_front' => $page_on_front, 'version' => $wxr_version, ]; } } utils/import-export/wp-exporter.php 0000644 00000062707 14717626151 0013570 0 ustar 00 <?php namespace Elementor\Core\Utils\ImportExport; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /* * Originally made by WordPress. * * What changed: * Remove echos. * Fix indents. * Add methods * indent. * wxr_categories_list. * wxr_tags_list. * wxr_terms_list. * wxr_posts_list. */ class WP_Exporter { const WXR_VERSION = '1.2'; private static $default_args = [ 'content' => 'all', 'author' => false, 'category' => false, 'start_date' => false, 'end_date' => false, 'status' => false, 'offset' => 0, 'limit' => -1, 'meta_query' => [], // If specified `meta_key` then will include all post(s) that have this meta_key. ]; /** * @var array */ private $args; /** * @var \wpdb */ private $wpdb; private $terms; /** * Run export, by requested args. * Returns XML with exported data. * * @return array */ public function run() { if ( 'all' !== $this->args['content'] && post_type_exists( $this->args['content'] ) ) { $ptype = get_post_type_object( $this->args['content'] ); if ( ! $ptype->can_export ) { $this->args['content'] = 'post'; } $where = $this->wpdb->prepare( "{$this->wpdb->posts}.post_type = %s", $this->args['content'] );// phpcs:ignore } else { $post_types = get_post_types( [ 'can_export' => true ] ); $esses = array_fill( 0, count( $post_types ), '%s' ); $where = $this->wpdb->prepare( "{$this->wpdb->posts}.post_type IN (" . implode( ',', $esses ) . ')', $post_types );// phpcs:ignore } if ( $this->args['status'] && ( 'post' === $this->args['content'] || 'page' === $this->args['content'] || 'nav_menu_item' === $this->args['content'] ) ) { $where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_status = %s", $this->args['status'] );// phpcs:ignore } else { $where .= " AND {$this->wpdb->posts}.post_status != 'auto-draft'"; } $join = ''; if ( $this->args['category'] && 'post' === $this->args['content'] ) { $term = term_exists( $this->args['category'], 'category' ); if ( $term ) { $join = "INNER JOIN {$this->wpdb->term_relationships} ON ({$this->wpdb->posts}.ID = {$this->wpdb->term_relationships}.object_id)"; $where .= $this->wpdb->prepare( " AND {$this->wpdb->term_relationships}.term_taxonomy_id = %d", $term['term_taxonomy_id'] );// phpcs:ignore } } if ( in_array( $this->args['content'], [ 'post', 'page', 'attachment' ], true ) ) { if ( $this->args['author'] ) { $where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_author = %d", $this->args['author'] );// phpcs:ignore } if ( $this->args['start_date'] ) { $where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_date >= %s", gmdate( 'Y-m-d', strtotime( $this->args['start_date'] ) ) );// phpcs:ignore } if ( $this->args['end_date'] ) { $where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_date < %s", gmdate( 'Y-m-d', strtotime( '+1 month', strtotime( $this->args['end_date'] ) ) ) );// phpcs:ignore } } $limit = ''; if ( -1 !== (int) $this->args['limit'] ) { $limit = 'LIMIT ' . (int) $this->args['limit'] . ' OFFSET ' . (int) $this->args['offset']; } if ( ! empty( $this->args['meta_query'] ) ) { if ( $join ) { $join .= ' '; } if ( $where ) { $where .= ' '; } $meta_query = new \WP_Meta_Query( $this->args['meta_query'] ); global $wpdb; $query_clauses = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' ); $join .= $query_clauses['join']; $where .= $query_clauses['where']; } // Grab a snapshot of post IDs, just in case it changes during the export. $post_ids = $this->wpdb->get_col( "SELECT ID FROM {$this->wpdb->posts} $join WHERE $where $limit" );// phpcs:ignore $thumbnail_ids = []; if ( ! empty( $this->args['include_post_featured_image_as_attachment'] ) ) { foreach ( $post_ids as $post_id ) { $thumbnail_id = get_post_meta( $post_id, '_thumbnail_id', true ); if ( $thumbnail_id && ! in_array( $thumbnail_id, $post_ids, true ) ) { $thumbnail_ids [] = $thumbnail_id; } } } return [ 'ids' => $post_ids, 'xml' => $this->get_xml_export( array_merge( $post_ids, $thumbnail_ids ) ), ]; } /** * Return tabulation characters, by `$columns`. * * @param int $columns * * @return string */ private function indent( $columns = 1 ) { $output = ''; for ( $i = 0; $i < $columns; $i++ ) { $output .= "\t"; } return (string) $output; } /** * Return wrapped given string in XML CDATA tag. * * @param string $str String to wrap in XML CDATA tag. * * @return string */ private function wxr_cdata( $str ) { $str = (string) $str; if ( ! seems_utf8( $str ) ) { $str = utf8_encode( $str ); } $str = '<![CDATA[' . str_replace( ']]>', ']]]]><![CDATA[>', $str ) . ']]>'; return $str; } /** * Return the URL of the site. * * @return string Site URL. */ private function wxr_site_url() { if ( is_multisite() ) { // Multisite: the base URL. return network_home_url(); } else { // WordPress (single site): the blog URL. return get_bloginfo_rss( 'url' ); } } /** * Return a cat_name XML tag from a given category object. * * @param \WP_Term $category Category Object * * @return string */ private function wxr_cat_name( $category ) { if ( empty( $category->name ) ) { return ''; } return $this->indent( 3 ) . '<wp:cat_name>' . $this->wxr_cdata( $category->name ) . '</wp:cat_name>' . PHP_EOL; } /** * Return a category_description XML tag from a given category object. * * @param \WP_Term $category Category Object * * @return string */ private function wxr_category_description( $category ) { if ( empty( $category->description ) ) { return ''; } return $this->indent( 3 ) . '<wp:category_description>' . $this->wxr_cdata( $category->description ) . "</wp:category_description>\n"; } /** * Return a tag_name XML tag from a given tag object. * * @param \WP_Term $tag Tag Object * * @return string */ private function wxr_tag_name( $tag ) { if ( empty( $tag->name ) ) { return ''; } return $this->indent( 3 ) . '<wp:tag_name>' . $this->wxr_cdata( $tag->name ) . '</wp:tag_name>' . PHP_EOL; } /** * Return a tag_description XML tag from a given tag object. * * @param \WP_Term $tag Tag Object * * @return string */ private function wxr_tag_description( $tag ) { if ( empty( $tag->description ) ) { return ''; } return $this->indent( 3 ) . '<wp:tag_description>' . $this->wxr_cdata( $tag->description ) . '</wp:tag_description>' . PHP_EOL; } /** * Return a term_name XML tag from a given term object. * * @param \WP_Term $term Term Object * * @return string */ private function wxr_term_name( $term ) { if ( empty( $term->name ) ) { return ''; } return $this->indent( 3 ) . '<wp:term_name>' . $this->wxr_cdata( $term->name ) . '</wp:term_name>' . PHP_EOL; } /** * Return a term_description XML tag from a given term object. * * @param \WP_Term $term Term Object * * @return string */ private function wxr_term_description( $term ) { if ( empty( $term->description ) ) { return ''; } return $this->indent( 3 ) . '<wp:term_description>' . $this->wxr_cdata( $term->description ) . '</wp:term_description>' . PHP_EOL; } /** * Return term meta XML tags for a given term object. * * @param \WP_Term $term Term object. * * @return string */ private function wxr_term_meta( $term ) { $result = ''; $termmeta = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->termmeta} WHERE term_id = %d", $term->term_id ) );// phpcs:ignore foreach ( $termmeta as $meta ) { /** * Filters whether to selectively skip term meta used for WXR exports. * * Returning a truthy value from the filter will skip the current meta * object from being exported. * * @since 4.6.0 * * @param bool $skip Whether to skip the current piece of term meta. Default false. * @param string $meta_key Current meta key. * @param object $meta Current meta object. */ if ( ! apply_filters( 'wxr_export_skip_termmeta', false, $meta->meta_key, $meta ) ) { $result .= sprintf( $this->indent( 3 ) . "<wp:termmeta>\n\t\t\t<wp:meta_key>%s</wp:meta_key>\n\t\t\t<wp:meta_value>%s</wp:meta_value>\n\t\t</wp:termmeta>\n", $this->wxr_cdata( $meta->meta_key ), $this->wxr_cdata( $meta->meta_value ) ); } } return $result; } /** * Return list of authors with posts. * * @param int[] $post_ids Optional. Array of post IDs to filter the query by. * * @return string */ private function wxr_authors_list( array $post_ids = null ) { $result = ''; if ( ! empty( $post_ids ) ) { $post_ids = array_map( 'absint', $post_ids ); $and = 'AND ID IN ( ' . implode( ', ', $post_ids ) . ')'; } else { $and = ''; } $authors = []; $results = $this->wpdb->get_results( "SELECT DISTINCT post_author FROM {$this->wpdb->posts} WHERE post_status != 'auto-draft' $and" );// phpcs:ignore foreach ( (array) $results as $r ) { $authors[] = get_userdata( $r->post_author ); } $authors = array_filter( $authors ); foreach ( $authors as $author ) { $result .= $this->indent( 2 ) . '<wp:author>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:author_id>' . (int) $author->ID . '</wp:author_id>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:author_login>' . $this->wxr_cdata( $author->user_login ) . '</wp:author_login>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:author_email>' . $this->wxr_cdata( $author->user_email ) . '</wp:author_email>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:author_display_name>' . $this->wxr_cdata( $author->display_name ) . '</wp:author_display_name>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:author_first_name>' . $this->wxr_cdata( $author->first_name ) . '</wp:author_first_name>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:author_last_name>' . $this->wxr_cdata( $author->last_name ) . '</wp:author_last_name>' . PHP_EOL; $result .= $this->indent( 2 ) . '</wp:author>' . PHP_EOL; } return $result; } /** * Return list of categories. * * @param array $cats * * @return string */ private function wxr_categories_list( array $cats ) { $result = ''; foreach ( $cats as $c ) { $result .= $this->indent( 2 ) . '<wp:category>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:term_id>' . (int) $c->term_id . '</wp:term_id>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:category_nicename>' . $this->wxr_cdata( $c->slug ) . '</wp:category_nicename>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:category_parent>' . $this->wxr_cdata( $c->parent ? $cats[ $c->parent ]->slug : '' ) . '</wp:category_parent>' . PHP_EOL; $result .= $this->wxr_cat_name( $c ) . $this->wxr_category_description( $c ) . $this->wxr_term_meta( $c ); $result .= $this->indent( 2 ) . '</wp:category>' . PHP_EOL; } return $result; } /** * Return list of tags. * * @param array $tags * * @return string */ private function wxr_tags_list( array $tags ) { $result = ''; foreach ( $tags as $t ) { $result .= $this->indent( 2 ) . '<wp:tag>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:term_id>' . (int) $t->term_id . '</wp:term_id>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:tag_slug>' . $this->wxr_cdata( $t->slug ) . '</wp:tag_slug>' . PHP_EOL; $result .= $this->wxr_tag_name( $t ) . $this->wxr_tag_description( $t ) . $this->wxr_term_meta( $t ); $result .= $this->indent( 2 ) . '</wp:tag>' . PHP_EOL; } return $result; } /** * Return list of terms. * * @param array $terms * * @return string */ private function wxr_terms_list( array $terms ) { $result = ''; foreach ( $terms as $t ) { $result .= $this->indent( 2 ) . '<wp:term>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:term_id>' . $this->wxr_cdata( $t->term_id ) . '</wp:term_id>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:term_taxonomy>' . $this->wxr_cdata( $t->taxonomy ) . '</wp:term_taxonomy>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:term_slug>' . $this->wxr_cdata( $t->slug ) . '</wp:term_slug>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:term_parent>' . $this->wxr_cdata( $t->parent ? $terms[ $t->parent ]->slug : '' ) . '</wp:term_parent>' . PHP_EOL; $result .= $this->wxr_term_name( $t ) . $this->wxr_term_description( $t ) . $this->wxr_term_meta( $t ); $result .= $this->indent( 2 ) . '</wp:term>' . PHP_EOL; } return $result; } /** * Return list of posts, by requested `$post_ids`. * * @param array $post_ids * * @return string */ private function wxr_posts_list( array $post_ids ) { $result = ''; if ( $post_ids ) { global $wp_query; // Fake being in the loop. $wp_query->in_the_loop = true; // Fetch 20 posts at a time rather than loading the entire table into memory. while ( $next_posts = array_splice( $post_ids, 0, 20 ) ) { $where = 'WHERE ID IN (' . implode( ',', $next_posts ) . ')'; $posts = $this->wpdb->get_results( "SELECT * FROM {$this->wpdb->posts} $where" );// phpcs:ignore // Begin Loop. foreach ( $posts as $post ) { setup_postdata( $post ); $title = apply_filters( 'the_title_rss', $post->post_title ); /** * Filters the post content used for WXR exports. * * @since 2.5.0 * * @param string $post_content Content of the current post. */ $content = $this->wxr_cdata( apply_filters( 'the_content_export', $post->post_content ) ); /** * Filters the post excerpt used for WXR exports. * * @since 2.6.0 * * @param string $post_excerpt Excerpt for the current post. */ $excerpt = $this->wxr_cdata( apply_filters( 'the_excerpt_export', $post->post_excerpt ) ); $result .= $this->indent( 2 ) . '<item>' . PHP_EOL; $result .= $this->indent( 3 ) . '<title>' . $title . '</title>' . PHP_EOL; $result .= $this->indent( 3 ) . '<link>' . esc_url( get_permalink() ) . '</link>' . PHP_EOL; $result .= $this->indent( 3 ) . '<pubDate>' . mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ) . '</pubDate>' . PHP_EOL; $result .= $this->indent( 3 ) . '<dc:creator>' . $this->wxr_cdata( get_the_author_meta( 'login' ) ) . '</dc:creator>' . PHP_EOL; $result .= $this->indent( 3 ) . '<guid isPermaLink="false">' . $this->wxr_cdata( get_the_author_meta( 'login' ) ) . '</guid>' . PHP_EOL; $result .= $this->indent( 3 ) . '<description></description>' . PHP_EOL; $result .= $this->indent( 3 ) . '<content:encoded>' . $content . '</content:encoded>' . PHP_EOL; $result .= $this->indent( 3 ) . '<excerpt:encoded>' . $excerpt . '</excerpt:encoded>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:post_id>' . (int) $post->ID . '</wp:post_id>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:post_date>' . $this->wxr_cdata( $post->post_date ) . '</wp:post_date>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:post_date_gmt>' . $this->wxr_cdata( $post->post_date_gmt ) . '</wp:post_date_gmt>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:comment_status>' . $this->wxr_cdata( $post->comment_status ) . '</wp:comment_status>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:ping_status>' . $this->wxr_cdata( $post->ping_status ) . '</wp:ping_status>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:post_name>' . $this->wxr_cdata( $post->post_name ) . '</wp:post_name>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:status>' . $this->wxr_cdata( $post->post_status ) . '</wp:status>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:post_parent>' . $this->wxr_cdata( $post->post_parent ) . '</wp:post_parent>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:menu_order>' . (int) $post->menu_order . '</wp:menu_order>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:post_type>' . $this->wxr_cdata( $post->post_type ) . '</wp:post_type>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:post_password>' . $this->wxr_cdata( $post->post_password ) . '</wp:post_password>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:is_sticky>' . ( is_sticky( $post->ID ) ? 1 : 0 ) . '</wp:is_sticky>' . PHP_EOL; if ( 'attachment' === $post->post_type ) { $result .= $this->indent( 3 ) . '<wp:attachment_url>' . $this->wxr_cdata( wp_get_attachment_url( $post->ID ) ) . '</wp:attachment_url>' . PHP_EOL; } $result .= $this->wxr_post_taxonomy( $post ); $postmeta = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->postmeta} WHERE post_id = %d", $post->ID ) );// phpcs:ignore foreach ( $postmeta as $meta ) { /** * Filters whether to selectively skip post meta used for WXR exports. * * Returning a truthy value from the filter will skip the current meta * object from being exported. * * @since 3.3.0 * * @param bool $skip Whether to skip the current post meta. Default false. * @param string $meta_key Current meta key. * @param object $meta Current meta object. */ if ( apply_filters( 'wxr_export_skip_postmeta', false, $meta->meta_key, $meta ) ) { continue; } $result .= $this->indent( 3 ) . '<wp:postmeta>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:meta_key>' . $this->wxr_cdata( $meta->meta_key ) . '</wp:meta_key>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:meta_value>' . $this->wxr_cdata( $meta->meta_value ) . '</wp:meta_value>' . PHP_EOL; $result .= $this->indent( 3 ) . '</wp:postmeta>' . PHP_EOL; } $_comments = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->comments} WHERE comment_post_ID = %d AND comment_approved <> 'spam'", $post->ID ) );// phpcs:ignore $comments = array_map( 'get_comment', $_comments ); foreach ( $comments as $c ) { $result .= $this->indent( 3 ) . '<wp:comment>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_id>' . (int) $c->comment_ID . '</wp:comment_id>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_author>' . $this->wxr_cdata( $c->comment_author ) . '</wp:comment_author>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_author_email>' . $this->wxr_cdata( $c->comment_author_email ) . '</wp:comment_author_email>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_author_url>' . $this->wxr_cdata( $c->comment_author_url ) . '</wp:comment_author_url>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_author_IP>' . $this->wxr_cdata( $c->comment_author_IP ) . '</wp:comment_author_IP>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_date>' . $this->wxr_cdata( $c->comment_date ) . '</wp:comment_date>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_date_gmt>' . $this->wxr_cdata( $c->comment_date_gmt ) . '</wp:comment_date_gmt>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_content>' . $this->wxr_cdata( $c->comment_content ) . '</wp:comment_content>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_approved>' . $this->wxr_cdata( $c->comment_approved ) . '</wp:comment_approved>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_type>' . $this->wxr_cdata( $c->comment_type ) . '</wp:comment_type>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_parent>' . $this->wxr_cdata( $c->comment_parent ) . '</wp:comment_parent>' . PHP_EOL; $result .= $this->indent( 4 ) . '<wp:comment_user_id>' . (int) $c->user_id . '</wp:comment_user_id>' . PHP_EOL; $c_meta = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->commentmeta} WHERE comment_id = %d", $c->comment_ID ) );// phpcs:ignore foreach ( $c_meta as $meta ) { /** * Filters whether to selectively skip comment meta used for WXR exports. * * Returning a truthy value from the filter will skip the current meta * object from being exported. * * @since 4.0.0 * * @param bool $skip Whether to skip the current comment meta. Default false. * @param string $meta_key Current meta key. * @param object $meta Current meta object. */ if ( apply_filters( 'wxr_export_skip_commentmeta', false, $meta->meta_key, $meta ) ) { continue; } $result .= $this->indent( 4 ) . '<wp:commentmeta>' . PHP_EOL; $result .= $this->indent( 5 ) . '<wp:meta_key>' . $this->wxr_cdata( $meta->meta_key ) . '</wp:meta_key>' . PHP_EOL; $result .= $this->indent( 5 ) . '<wp:meta_value>' . $this->wxr_cdata( $meta->meta_key ) . '</wp:meta_value>' . PHP_EOL; $result .= $this->indent( 4 ) . '</wp:commentmeta>' . PHP_EOL; } $result .= $this->indent( 3 ) . '</wp:comment>' . PHP_EOL; } $result .= $this->indent( 2 ) . '</item>' . PHP_EOL; } } } return $result; } /** * Return all navigation menu terms * * @return string */ private function wxr_nav_menu_terms() { $nav_menus = wp_get_nav_menus(); if ( empty( $nav_menus ) || ! is_array( $nav_menus ) ) { return ''; } $result = ''; foreach ( $nav_menus as $menu ) { $this->terms[ $menu->term_id ] = $menu; $result .= $this->indent( 2 ) . '<wp:term>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:term_id>' . (int) $menu->term_id . '</wp:term_id>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:term_taxonomy>nav_menu</wp:term_taxonomy>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:term_slug>' . $this->wxr_cdata( $menu->slug ) . '</wp:term_slug>' . PHP_EOL; $result .= $this->indent( 3 ) . '<wp:term_name>' . $this->wxr_cdata( $menu->name ) . '</wp:term_name>' . PHP_EOL; $result .= $this->indent( 2 ) . '</wp:term>' . PHP_EOL; } return $result; } /** * Return list of taxonomy terms, in XML tag format, associated with a post * * @param \WP_Post $post * * @return string */ private function wxr_post_taxonomy( $post ) { $result = ''; $taxonomies = get_object_taxonomies( $post->post_type ); if ( empty( $taxonomies ) ) { return $result; } $terms = wp_get_object_terms( $post->ID, $taxonomies ); foreach ( (array) $terms as $term ) { $result .= $this->indent( 3 ) . "<category domain=\"{$term->taxonomy}\" nicename=\"{$term->slug}\">" . $this->wxr_cdata( $term->name ) . '</category>' . PHP_EOL; } return $result; } /** * Get's the XML export. * * @param $post_ids * * @return string */ private function get_xml_export( array $post_ids ) { $charset = get_bloginfo( 'charset' ); $generator = get_the_generator( 'export' ); $wxr_version = self::WXR_VERSION; $wxr_site_url = $this->wxr_site_url(); $rss_info_name = get_bloginfo_rss( 'name' ); $rss_info_url = get_bloginfo_rss( 'url' ); $rss_info_description = get_bloginfo_rss( 'description' ); $rss_info_language = get_bloginfo_rss( 'language' ); $pub_date = gmdate( 'D, d M Y H:i:s +0000' ); $show_page_on_front = 'page' === get_option( 'show_on_front' ); $page_on_front_xml = ''; if ( $show_page_on_front ) { $page_on_front_id = (int) get_option( 'page_on_front' ); if ( in_array( $page_on_front_id, $post_ids ) ) { $page_on_front_xml = "<wp:page_on_front>$page_on_front_id</wp:page_on_front>"; } } $dynamic = $this->wxr_authors_list( $post_ids ); ob_start(); /** This action is documented in wp-includes/feed-rss2.php */ do_action( 'rss2_head' ); $rss2_head = ob_get_clean(); $dynamic .= $rss2_head; if ( 'all' === $this->args['content'] || 'nav_menu_item' === $this->args['content'] ) { $dynamic .= $this->wxr_nav_menu_terms(); } $dynamic .= $this->wxr_posts_list( $post_ids ); $result = <<<EOT <?xml version="1.0" encoding="$charset" ?> <!-- This is a WordPress eXtended RSS file generated by WordPress as an export of your site. --> <!-- It contains information about your site's posts, pages, comments, categories, and other content. --> <!-- You may use this file to transfer that content from one site to another. --> <!-- This file is not intended to serve as a complete backup of your site. --> <!-- To import this information into a WordPress site follow these steps: --> <!-- 1. Log in to that site as an administrator. --> <!-- 2. Go to Tools: Import in the WordPress admin panel. --> <!-- 3. Install the "WordPress" importer from the list. --> <!-- 4. Activate & Run Importer. --> <!-- 5. Upload this file using the form provided on that page. --> <!-- 6. You will first be asked to map the authors in this export file to users --> <!-- on the site. For each author, you may choose to map to an --> <!-- existing user on the site or to create a new user. --> <!-- 7. WordPress will then import each of the posts, pages, comments, categories, etc. --> <!-- contained in this file into your site. --> $generator <rss version="2.0" xmlns:excerpt="http://wordpress.org/export/$wxr_version/excerpt/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:wp="http://wordpress.org/export/$wxr_version/" > <channel> <title>$rss_info_name</title> <link>$rss_info_url</link> <description>$rss_info_description</description> <pubDate>$pub_date</pubDate> <language>$rss_info_language</language> <wp:wxr_version>$wxr_version</wp:wxr_version> <wp:base_site_url>$wxr_site_url</wp:base_site_url> <wp:base_blog_url>$rss_info_url</wp:base_blog_url> $page_on_front_xml $dynamic </channel> </rss> EOT; return $result; } public function __construct( array $args = [] ) { global $wpdb; $this->args = wp_parse_args( $args, self::$default_args ); $this->wpdb = $wpdb; } } utils/import-export/url.php 0000644 00000003240 14717626151 0012061 0 ustar 00 <?php namespace Elementor\Core\Utils\ImportExport; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Url { /** * Migrate url to the current permalink structure. * The function will also check and change absolute url to relative one by the base url. * This is currently supports only "Post Name" permalink structure to any permalink structure. * * @param string $url The url that should be migrated. * @param string|Null $base_url The base url that should be clean from the url. * @return string The migrated url || the $url if it couldn't find a match in the current permalink structure. */ public static function migrate( $url, $base_url = '' ) { $full_url = $url; if ( ! empty( $base_url ) ) { $base_url = preg_quote( $base_url, '/' ); $url = preg_replace( "/^{$base_url}/", '', $url ); } $parsed_url = wp_parse_url( $url ); if ( $url === $full_url && ! empty( $parsed_url['host'] ) ) { return $full_url; } if ( ! empty( $parsed_url['path'] ) ) { $page = get_page_by_path( $parsed_url['path'] ); if ( ! $page ) { return $full_url; } $permalink = get_permalink( $page->ID ); } if ( empty( $permalink ) ) { return $full_url; } if ( ! empty( $parsed_url['query'] ) ) { parse_str( $parsed_url['query'], $parsed_query ); // Clean WP permalinks query args to prevent collision with the new permalink. unset( $parsed_query['p'] ); unset( $parsed_query['page_id'] ); $permalink = add_query_arg( $parsed_query, $permalink ); } if ( ! empty( $parsed_url['fragment'] ) ) { $permalink .= '#' . $parsed_url['fragment']; } return wp_make_link_relative( $permalink ); } } utils/svg/svg-sanitizer.php 0000644 00000034403 14717626151 0012037 0 ustar 00 <?php namespace Elementor\Core\Utils\Svg; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor SVG Sanitizer. * * A class that is responsible for sanitizing SVG files. * * @since 3.16.0 */ class Svg_Sanitizer { /** * @var \DOMDocument */ private $svg_dom = null; /** * Sanitize File * * @since 3.16.0 * @access public * * @param $filename * @return bool */ public function sanitize_file( $filename ) { $original_content = Utils::file_get_contents( $filename ); $is_encoded = $this->is_encoded( $original_content ); if ( $is_encoded ) { $decoded = $this->decode_svg( $original_content ); if ( false === $decoded ) { return false; } $original_content = $decoded; } $valid_svg = $this->sanitize( $original_content ); if ( false === $valid_svg ) { return false; } // If we were gzipped, we need to re-zip if ( $is_encoded ) { $valid_svg = $this->encode_svg( $valid_svg ); } file_put_contents( $filename, $valid_svg ); return true; } /** * Sanitize * * @since 3.16.0 * @access public * * @param $content * @return bool|string */ public function sanitize( $content ) { // Strip php tags $content = $this->strip_comments( $content ); $content = $this->strip_php_tags( $content ); $content = $this->strip_line_breaks( $content ); // Find the start and end tags so we can cut out miscellaneous garbage. $start = strpos( $content, '<svg' ); $end = strrpos( $content, '</svg>' ); if ( false === $start || false === $end ) { return false; } $content = substr( $content, $start, ( $end - $start + 6 ) ); // If the server's PHP version is 8 or up, make sure to Disable the ability to load external entities $php_version_under_eight = version_compare( PHP_VERSION, '8.0.0', '<' ); if ( $php_version_under_eight ) { $libxml_disable_entity_loader = libxml_disable_entity_loader( true ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated } // Suppress the errors $libxml_use_internal_errors = libxml_use_internal_errors( true ); // Create DomDocument instance $this->svg_dom = new \DOMDocument(); $this->svg_dom->formatOutput = false; $this->svg_dom->preserveWhiteSpace = false; $this->svg_dom->strictErrorChecking = false; $open_svg = $this->svg_dom->loadXML( $content ); if ( ! $open_svg ) { return false; } $this->strip_doctype(); $this->sanitize_elements(); // Export sanitized svg to string // Using documentElement to strip out <?xml version="1.0" encoding="UTF-8"... $sanitized = $this->svg_dom->saveXML( $this->svg_dom->documentElement, LIBXML_NOEMPTYTAG ); // Restore defaults if ( $php_version_under_eight ) { libxml_disable_entity_loader( $libxml_disable_entity_loader ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated } libxml_use_internal_errors( $libxml_use_internal_errors ); return $sanitized; } /** * Is Encoded * * Check if the contents of the SVG file are gzipped * @see http://www.gzip.org/zlib/rfc-gzip.html#member-format * * @since 3.16.0 * @access private * * @param $contents * * @return bool */ private function is_encoded( $contents ) { $needle = "\x1f\x8b\x08"; if ( function_exists( 'mb_strpos' ) ) { return 0 === mb_strpos( $contents, $needle ); } else { return 0 === strpos( $contents, $needle ); } } /** * Encode SVG * * @since 3.16.0 * @access private * * @param $content * @return string */ private function encode_svg( $content ) { return gzencode( $content ); } /** * Decode SVG * * @since 3.16.0 * @access private * * @param $content * * @return string */ private function decode_svg( $content ) { return gzdecode( $content ); } /** * Is Allowed Tag * * @since 3.16.0 * @access private * * @param $element * @return bool */ private function is_allowed_tag( $element ) { static $allowed_tags = false; if ( false === $allowed_tags ) { $allowed_tags = $this->get_allowed_elements(); } $tag_name = $element->tagName; // phpcs:ignore -- php DomDocument if ( ! in_array( strtolower( $tag_name ), $allowed_tags ) ) { $this->remove_element( $element ); return false; } return true; } /** * Remove Element * * Removes the passed element from its DomDocument tree * * @since 3.16.0 * @access private * * @param $element */ private function remove_element( $element ) { $element->parentNode->removeChild( $element ); // phpcs:ignore -- php DomDocument } /** * Is It An Attribute * * @since 3.16.0 * @access private * * @param $name * @param $check * @return bool */ private function is_a_attribute( $name, $check ) { return 0 === strpos( $name, $check . '-' ); } /** * Is Remote Value * * @since 3.16.0 * @access private * * @param $value * @return string */ private function is_remote_value( $value ) { $value = trim( preg_replace( '/[^ -~]/xu', '', $value ) ); $wrapped_in_url = preg_match( '~^url\(\s*[\'"]\s*(.*)\s*[\'"]\s*\)$~xi', $value, $match ); if ( ! $wrapped_in_url ) { return false; } $value = trim( $match[1], '\'"' ); return preg_match( '~^((https?|ftp|file):)?//~xi', $value ); } /** * Has JS Value * * @since 3.16.0 * @access private * * @param $value * @return false|int */ private function has_js_value( $value ) { return preg_match( '/base64|data|(?:java)?script|alert\(|window\.|document/i', $value ); } /** * Get Allowed Attributes * * Returns an array of allowed tag attributes in SVG files. * * @since 3.16.0 * @access private * * @return array */ private function get_allowed_attributes() { $allowed_attributes = [ 'class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemlanguage', 'transform', 'href', 'xlink:href', 'xlink:title', 'cx', 'cy', 'r', 'requiredfeatures', 'clippathunits', 'type', 'rx', 'ry', 'color-interpolation-filters', 'stddeviation', 'filterres', 'filterunits', 'height', 'primitiveunits', 'width', 'x', 'y', 'font-size', 'display', 'font-family', 'font-style', 'font-weight', 'text-anchor', 'marker-end', 'marker-mid', 'marker-start', 'x1', 'x2', 'y1', 'y2', 'gradienttransform', 'gradientunits', 'spreadmethod', 'markerheight', 'markerunits', 'markerwidth', 'orient', 'preserveaspectratio', 'refx', 'refy', 'viewbox', 'maskcontentunits', 'maskunits', 'd', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'fx', 'fy', 'offset', 'stop-color', 'stop-opacity', 'xmlns', 'xmlns:se', 'xmlns:xlink', 'xml:space', 'method', 'spacing', 'startoffset', 'dx', 'dy', 'rotate', 'textlength', ]; /** * Allowed attributes in SVG file. * * Filters the list of allowed attributes in SVG files. * * Since SVG files can run JS code that may inject malicious code, all attributes * are removed except the allowed attributes. * * This hook can be used to manage allowed SVG attributes. To either add new * attributes or delete existing attributes. To strengthen or weaken site security. * * @param array $allowed_attributes A list of allowed attributes. */ $allowed_attributes = apply_filters( 'elementor/files/svg/allowed_attributes', $allowed_attributes ); return $allowed_attributes; } /** * Get Allowed Elements * * Returns an array of allowed element tags to be in SVG files. * * @since 3.16.0 * @access private * * @return array */ private function get_allowed_elements() { $allowed_elements = [ 'a', 'circle', 'clippath', 'defs', 'style', 'desc', 'ellipse', 'fegaussianblur', 'filter', 'foreignobject', 'g', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'svg', 'switch', 'symbol', 'text', 'textpath', 'title', 'tspan', 'use', ]; /** * Allowed elements in SVG file. * * Filters the list of allowed elements in SVG files. * * Since SVG files can run JS code that may inject malicious code, all elements * are removed except the allowed elements. * * This hook can be used to manage SVG elements. To either add new elements or * delete existing elements. To strengthen or weaken site security. * * @param array $allowed_elements A list of allowed elements. */ $allowed_elements = apply_filters( 'elementor/files/svg/allowed_elements', $allowed_elements ); return $allowed_elements; } /** * Validate Allowed Attributes * * @since 3.16.0 * @access private * * @param \DOMElement $element */ private function validate_allowed_attributes( $element ) { static $allowed_attributes = false; if ( false === $allowed_attributes ) { $allowed_attributes = $this->get_allowed_attributes(); } for ( $index = $element->attributes->length - 1; $index >= 0; $index-- ) { // get attribute name $attr_name = $element->attributes->item( $index )->name; $attr_name_lowercase = strtolower( $attr_name ); // Remove attribute if not in whitelist if ( ! in_array( $attr_name_lowercase, $allowed_attributes ) && ! $this->is_a_attribute( $attr_name_lowercase, 'aria' ) && ! $this->is_a_attribute( $attr_name_lowercase, 'data' ) ) { $element->removeAttribute( $attr_name ); continue; } $attr_value = $element->attributes->item( $index )->value; // Remove attribute if it has a remote reference or js or data-URI/base64 if ( ! empty( $attr_value ) && ( $this->is_remote_value( $attr_value ) || $this->has_js_value( $attr_value ) ) ) { $element->removeAttribute( $attr_name ); continue; } } } /** * Strip xlinks * * @since 3.16.0 * @access private * * @param \DOMElement $element */ private function strip_xlinks( $element ) { $xlinks = $element->getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ); if ( ! $xlinks ) { return; } if ( ! $this->is_safe_href( $xlinks ) ) { $element->removeAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ); } } /** * @see https://github.com/darylldoyle/svg-sanitizer/blob/2321a914e/src/Sanitizer.php#L454 */ private function is_safe_href( $value ) { // Allow empty values. if ( empty( $value ) ) { return true; } // Allow fragment identifiers. if ( '#' === substr( $value, 0, 1 ) ) { return true; } // Allow relative URIs. if ( '/' === substr( $value, 0, 1 ) ) { return true; } // Allow HTTPS domains. if ( 'https://' === substr( $value, 0, 8 ) ) { return true; } // Allow HTTP domains. if ( 'http://' === substr( $value, 0, 7 ) ) { return true; } // Allow known data URIs. if ( in_array( substr( $value, 0, 14 ), [ 'data:image/png', // PNG 'data:image/gif', // GIF 'data:image/jpg', // JPG 'data:image/jpe', // JPEG 'data:image/pjp', // PJPEG ], true ) ) { return true; } // Allow known short data URIs. if ( in_array( substr( $value, 0, 12 ), [ 'data:img/png', // PNG 'data:img/gif', // GIF 'data:img/jpg', // JPG 'data:img/jpe', // JPEG 'data:img/pjp', // PJPEG ], true ) ) { return true; } return false; } /** * Validate Use Tag * * @since 3.16.0 * @access private * * @param $element */ private function validate_use_tag( $element ) { $xlinks = $element->getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ); if ( $xlinks && '#' !== substr( $xlinks, 0, 1 ) ) { $element->parentNode->removeChild( $element ); // phpcs:ignore -- php DomNode } } /** * Strip Doctype * * @since 3.16.0 * @access private * */ private function strip_doctype() { foreach ( $this->svg_dom->childNodes as $child ) { if ( XML_DOCUMENT_TYPE_NODE === $child->nodeType ) { // phpcs:ignore -- php DomDocument $child->parentNode->removeChild( $child ); // phpcs:ignore -- php DomDocument } } } /** * Sanitize Elements * * @since 3.16.0 * @access private */ private function sanitize_elements() { $elements = $this->svg_dom->getElementsByTagName( '*' ); // loop through all elements // we do this backwards so we don't skip anything if we delete a node // see comments at: http://php.net/manual/en/class.domnamednodemap.php for ( $index = $elements->length - 1; $index >= 0; $index-- ) { /** * @var \DOMElement $current_element */ $current_element = $elements->item( $index ); // If the tag isn't in the whitelist, remove it and continue with next iteration if ( ! $this->is_allowed_tag( $current_element ) ) { continue; } //validate element attributes $this->validate_allowed_attributes( $current_element ); $this->strip_xlinks( $current_element ); if ( 'use' === strtolower( $current_element->tagName ) ) { // phpcs:ignore -- php DomDocument $this->validate_use_tag( $current_element ); } } } /** * Strip PHP Tags * * @since 3.16.0 * @access private * * @param $string * @return string */ private function strip_php_tags( $string ) { $string = preg_replace( '/<\?(=|php)(.+?)\?>/i', '', $string ); // Remove XML, ASP, etc. $string = preg_replace( '/<\?(.*)\?>/Us', '', $string ); $string = preg_replace( '/<\%(.*)\%>/Us', '', $string ); if ( ( false !== strpos( $string, '<?' ) ) || ( false !== strpos( $string, '<%' ) ) ) { return ''; } return $string; } /** * Strip Comments * * @since 3.16.0 * @access private * * @param $string * @return string */ private function strip_comments( $string ) { // Remove comments. $string = preg_replace( '/<!--(.*)-->/Us', '', $string ); $string = preg_replace( '/\/\*(.*)\*\//Us', '', $string ); if ( ( false !== strpos( $string, '<!--' ) ) || ( false !== strpos( $string, '/*' ) ) ) { return ''; } return $string; } /** * Strip Line Breaks * * @since 3.16.0 * @access private * * @param $string * @return string */ private function strip_line_breaks( $string ) { // Remove line breaks. return preg_replace( '/\r|\n/', '', $string ); } } utils/collection.php 0000644 00000022573 14717626151 0010573 0 ustar 00 <?php /** * Inspired by Laravel Collection. * @link https://github.com/illuminate/collections */ namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Collection implements \ArrayAccess, \Countable, \IteratorAggregate { /** * The items contained in the collection. * * @var array */ protected $items; /** * Collection constructor. * * @param array $items */ public function __construct( array $items = [] ) { $this->items = $items; } /** * @param array $items * * @return static */ public static function make( array $items = [] ) { return new static( $items ); } /** * @param callable|null $callback * * @return $this */ public function filter( callable $callback = null ) { if ( ! $callback ) { return new static( array_filter( $this->items ) ); } return new static( array_filter( $this->items, $callback, ARRAY_FILTER_USE_BOTH ) ); } /** * @param $items * * @return $this */ public function merge( $items ) { if ( $items instanceof Collection ) { $items = $items->all(); } return new static( array_merge( $this->items, $items ) ); } /** * Union the collection with the given items. * * @param array $items * * @return $this */ public function union( array $items ) { return new static( $this->all() + $items ); } /** * Merge array recursively * * @param $items * * @return $this */ public function merge_recursive( $items ) { if ( $items instanceof Collection ) { $items = $items->all(); } return new static( array_merge_recursive( $this->items, $items ) ); } /** * Replace array recursively * * @param $items * * @return $this */ public function replace_recursive( $items ) { if ( $items instanceof Collection ) { $items = $items->all(); } return new static( array_replace_recursive( $this->items, $items ) ); } /** * Implode the items * * @param $glue * * @return string */ public function implode( $glue ) { return implode( $glue, $this->items ); } /** * Run a map over each of the items. * * @param callable $callback * @return $this */ public function map( callable $callback ) { $keys = array_keys( $this->items ); $items = array_map( $callback, $this->items, $keys ); return new static( array_combine( $keys, $items ) ); } /** * Run a callback over each of the items. * * @param callable $callback * @return $this */ public function each( callable $callback ) { foreach ( $this->items as $key => $value ) { if ( false === $callback( $value, $key ) ) { break; } } return $this; } /** * @param callable $callback * @param null $initial * * @return mixed|null */ public function reduce( callable $callback, $initial = null ) { $result = $initial; foreach ( $this->all() as $key => $value ) { $result = $callback( $result, $value, $key ); } return $result; } /** * @param callable $callback * * @return $this */ public function map_with_keys( callable $callback ) { $result = []; foreach ( $this->items as $key => $value ) { $assoc = $callback( $value, $key ); foreach ( $assoc as $map_key => $map_value ) { $result[ $map_key ] = $map_value; } } return new static( $result ); } /** * Get all items except for those with the specified keys. * * @param array $keys * * @return $this */ public function except( array $keys ) { return $this->filter( function ( $value, $key ) use ( $keys ) { return ! in_array( $key, $keys, true ); } ); } /** * Get the items with the specified keys. * * @param array $keys * * @return $this */ public function only( array $keys ) { return $this->filter( function ( $value, $key ) use ( $keys ) { return in_array( $key, $keys, true ); } ); } /** * Run over the collection to get specific prop from the collection item. * * @param $key * * @return $this */ public function pluck( $key ) { $result = []; foreach ( $this->items as $item ) { $result[] = $this->get_item_value( $item, $key ); } return new static( $result ); } /** * Group the collection items by specific key in each collection item. * * @param $group_by * * @return $this */ public function group_by( $group_by ) { $result = []; foreach ( $this->items as $item ) { $group_key = $this->get_item_value( $item, $group_by, 0 ); $result[ $group_key ][] = $item; } return new static( $result ); } /** * Sort keys * * @param false $descending * * @return $this */ public function sort_keys( $descending = false ) { $items = $this->items; if ( $descending ) { krsort( $items ); } else { ksort( $items ); } return new static( $items ); } /** * Get specific item from the collection. * * @param $key * @param null $default * * @return mixed|null */ public function get( $key, $default = null ) { if ( ! array_key_exists( $key, $this->items ) ) { return $default; } return $this->items[ $key ]; } /** * Get the first item. * * @param null $default * * @return mixed|null */ public function first( $default = null ) { if ( $this->is_empty() ) { return $default; } foreach ( $this->items as $item ) { return $item; } } /** * Find an element from the items. * * @param callable $callback * @param null $default * * @return mixed|null */ public function find( callable $callback, $default = null ) { foreach ( $this->all() as $key => $item ) { if ( $callback( $item, $key ) ) { return $item; } } return $default; } /** * @param callable|string|int $value * * @return bool */ public function contains( $value ) { $callback = $value instanceof \Closure ? $value : function ( $item ) use ( $value ) { return $item === $value; }; foreach ( $this->all() as $key => $item ) { if ( $callback( $item, $key ) ) { return true; } } return false; } /** * Make sure all the values inside the array are uniques. * * @param null|string|string[] $keys * * @return $this */ public function unique( $keys = null ) { if ( ! $keys ) { return new static( array_unique( $this->items ) ); } if ( ! is_array( $keys ) ) { $keys = [ $keys ]; } $exists = []; return $this->filter( function ( $item ) use ( $keys, &$exists ) { $value = null; foreach ( $keys as $key ) { $current_value = $this->get_item_value( $item, $key ); $value .= "{$key}:{$current_value};"; } // If no value for the specific key return the item. if ( null === $value ) { return true; } // If value is not exists, add to the exists array and return the item. if ( ! in_array( $value, $exists, true ) ) { $exists[] = $value; return true; } return false; } ); } /** * @return array */ public function keys() { return array_keys( $this->items ); } /** * @return bool */ public function is_empty() { return empty( $this->items ); } /** * @return array */ public function all() { return $this->items; } /** * @return array */ public function values() { return array_values( $this->all() ); } /** * Support only one level depth. * * @return $this */ public function flatten() { $result = []; foreach ( $this->all() as $item ) { $item = $item instanceof Collection ? $item->all() : $item; if ( ! is_array( $item ) ) { $result[] = $item; } else { $values = array_values( $item ); foreach ( $values as $value ) { $result[] = $value; } } } return new static( $result ); } /** * @param ...$values * * @return $this */ public function push( ...$values ) { foreach ( $values as $value ) { $this->items[] = $value; } return $this; } public function prepend( ...$values ) { $this->items = array_merge( $values, $this->items ); return $this; } public function some( callable $callback ) { foreach ( $this->items as $key => $item ) { if ( $callback( $item, $key ) ) { return true; } } return false; } /** * @param mixed $offset * * @return bool */ #[\ReturnTypeWillChange] public function offsetExists( $offset ) { return isset( $this->items[ $offset ] ); } /** * @param mixed $offset * * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet( $offset ) { return $this->items[ $offset ]; } /** * @param mixed $offset * @param mixed $value */ #[\ReturnTypeWillChange] public function offsetSet( $offset, $value ) { if ( is_null( $offset ) ) { $this->items[] = $value; } else { $this->items[ $offset ] = $value; } } /** * @param mixed $offset */ #[\ReturnTypeWillChange] public function offsetUnset( $offset ) { unset( $this->items[ $offset ] ); } /** * @return \ArrayIterator|\Traversable */ #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator( $this->items ); } /** * @return int|void */ #[\ReturnTypeWillChange] public function count() { return count( $this->items ); } /** * @param $item * @param $key * @param null $default * * @return mixed|null */ private function get_item_value( $item, $key, $default = null ) { $value = $default; if ( is_object( $item ) && isset( $item->{$key} ) ) { $value = $item->{$key}; } elseif ( is_array( $item ) && isset( $item[ $key ] ) ) { $value = $item[ $key ]; } return $value; } } utils/plugins-manager.php 0000644 00000005532 14717626151 0011525 0 ustar 00 <?php namespace Elementor\Core\Utils; require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php'; require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php'; use Elementor\Plugin; use Plugin_Upgrader; use WP_Ajax_Upgrader_Skin; class Plugins_Manager { /** * @var Plugin_Upgrader */ private $upgrader; public function __construct( $upgrader = null ) { // For tests if ( $upgrader ) { $this->upgrader = $upgrader; } else { $skin = new WP_Ajax_Upgrader_Skin(); $this->upgrader = new Plugin_Upgrader( $skin ); } } /** * Install plugin or an array of plugins. * * @since 3.6.2 * * @param string|array $plugins * @return array [ 'succeeded' => [] , 'failed' => [] ] */ public function install( $plugins ) { $succeeded = []; $failed = []; $already_installed_plugins = Plugin::$instance->wp->get_plugins(); if ( ! is_array( $plugins ) ) { $plugins = [ $plugins ]; } foreach ( $plugins as $plugin ) { if ( in_array( $plugin, $already_installed_plugins->keys(), true ) ) { $succeeded[] = $plugin; continue; } $slug = $this->clean_slug( $plugin ); $api = Plugin::$instance->wp->plugins_api('plugin_information', [ 'slug' => $slug, 'fields' => array( 'short_description' => false, 'sections' => false, 'requires' => false, 'rating' => false, 'ratings' => false, 'downloaded' => false, 'last_updated' => false, 'added' => false, 'tags' => false, 'compatibility' => false, 'homepage' => false, 'donate_link' => false, ), ] ); if ( ! isset( $api->download_link ) ) { $failed[] = $plugin; continue; } $installation = $this->upgrader->install( $api->download_link ); if ( $installation ) { $succeeded[] = $plugin; } else { $failed[] = $plugin; } } return [ 'succeeded' => $succeeded, 'failed' => $failed, ]; } /** * Activate plugin or array off plugins. * * @since 3.6.2 * * @param array|string $plugins * @return array [ 'succeeded' => [] , 'failed' => [] ] */ public function activate( $plugins ) { $succeeded = []; $failed = []; if ( ! is_array( $plugins ) ) { $plugins = [ $plugins ]; } foreach ( $plugins as $plugin ) { if ( Plugin::$instance->wp->is_plugin_active( $plugin ) ) { $succeeded[] = $plugin; continue; } Plugin::$instance->wp->activate_plugin( $plugin ); if ( Plugin::$instance->wp->is_plugin_active( $plugin ) ) { $succeeded[] = $plugin; } else { $failed[] = $plugin; } } return [ 'succeeded' => $succeeded, 'failed' => $failed, ]; } private function clean_slug( $initial_slug ) { return explode( '/', $initial_slug )[0]; } } utils/promotions/filtered-promotions-manager.php 0000644 00000004143 14717626151 0016257 0 ustar 00 <?php namespace Elementor\Core\Utils\Promotions; use function DI\string; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Filtered_Promotions_Manager { /** * @param array $promotion_data * @param string $filter_name * @param string $url_key * @return array */ public static function get_filtered_promotion_data( array $promotion_data, string $filter_name, string $url_key, string $url_sub_key = '' ): array { $new_promotion_data = apply_filters( $filter_name, $promotion_data ); if ( ! is_array( $new_promotion_data ) ) { return $promotion_data; } $new_promotion_data = self::retain_original_keys( $new_promotion_data, $promotion_data ); $new_promotion_data = self::filter_invalid_url( $new_promotion_data, $url_key, $url_sub_key ); return array_replace( $promotion_data, $new_promotion_data ); } private static function domain_is_on_elementor_dot_com( $url ): bool { $domain = wp_parse_url( $url, PHP_URL_HOST ); return isset( $domain ) && str_contains( $domain, 'elementor.com' ); } private static function filter_invalid_url( $new_promotion_data, string $url_key, string $url_sub_key ) { if ( ! isset( $new_promotion_data[ $url_key ] ) ) { return $new_promotion_data; } if ( empty( $url_sub_key ) ) { $new_promotion_data = self::filter_invalid_url_in_flat_array( $new_promotion_data, $url_key ); } else { $new_promotion_data[ $url_key ] = self::filter_invalid_url_in_flat_array( $new_promotion_data[ $url_key ], $url_sub_key ); } return $new_promotion_data; } private static function filter_invalid_url_in_flat_array( array $new_promotion_data, string $url_key ): array { if ( ! self::domain_is_on_elementor_dot_com( $new_promotion_data[ $url_key ] ) ) { unset( $new_promotion_data[ $url_key ] ); } else { $new_promotion_data[ $url_key ] = esc_url( $new_promotion_data[ $url_key ] ); } return $new_promotion_data; } private static function retain_original_keys( array $new_promotion_data, array $promotion_data ): array { return array_intersect_key( $new_promotion_data, array_flip( array_keys( $promotion_data ) ) ); } } utils/assets-config-provider.php 0000644 00000002762 14717626151 0013033 0 ustar 00 <?php namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Assets_Config_Provider extends Collection { /** * @var callable|null */ private $path_resolver = null; /** * @param callable $path_resolver * * @return $this */ public function set_path_resolver( callable $path_resolver ) { $this->path_resolver = $path_resolver; return $this; } /** * Load asset config from a file into the collection. * * @param $key * @param $path * * @return $this */ public function load( $key, $path = null ) { if ( ! $path && $this->path_resolver ) { $path_resolver_callback = $this->path_resolver; $path = $path_resolver_callback( $key ); } if ( ! $path || ! file_exists( $path ) ) { return $this; } $config = require $path; if ( ! $this->is_valid_handle( $config ) ) { return $this; } $this->items[ $key ] = [ 'handle' => $config['handle'], 'deps' => $this->is_valid_deps( $config ) ? $config['deps'] : [], ]; return $this; } /** * Check that the handle property in the config is a valid. * * @param $config * * @return bool */ private function is_valid_handle( $config ) { return ! empty( $config['handle'] ) && is_string( $config['handle'] ); } /** * Check that the deps property in the config is a valid. * * @param $config * * @return bool */ private function is_valid_deps( $config ) { return isset( $config['deps'] ) && is_array( $config['deps'] ); } } utils/static-collection.php 0000644 00000002127 14717626151 0012051 0 ustar 00 <?php namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Static_Collection { /** * The current Collection instance. * * @var Collection */ protected $collection; /** * Return only unique values. * * @var bool */ protected $unique_values = false; /** * @inheritDoc */ public function __construct( array $items = [], $unique_values = false ) { $this->collection = new Collection( $items ); $this->unique_values = $unique_values; } /** * Since this class is a wrapper, every call will be forwarded to wrapped class. * Most of the collection methods returns a new collection instance, and therefore * it will be assigned as the current collection instance after executing any method. * * @param string $name * @param array $arguments */ public function __call( $name, $arguments ) { $call = call_user_func_array( [ $this->collection, $name ], $arguments ); if ( $call instanceof Collection ) { $this->collection = $this->unique_values ? $call->unique() : $call; } return $call; } } utils/assets-translation-loader.php 0000644 00000004774 14717626151 0013545 0 ustar 00 <?php namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Assets_Translation_Loader { public static function for_handles( array $handles, $domain = null, $replace_callback = null ) { self::set_domain( $handles, $domain ); self::replace_translation_path( $handles, $replace_callback ); } private static function set_domain( array $handles, $domain = null ) { if ( empty( $domain ) || ! is_string( $domain ) ) { return; } foreach ( $handles as $handle ) { wp_set_script_translations( $handle, $domain ); } } /** * The purpose of this function is to replace the requested translation file * with a file that contains all the translations for specific scripts. * * When developing a module and using Webpack's dynamic load feature, the script will be split into multiple chunks. * As a result, the WordPress translations expressions will also be split into multiple files. * Therefore, we replace the requested translation file with another file (generated in the build process) * that contains all the translations for the specific script (including dynamically loaded chunks). * * Want to go deeper? Read the following article: * @see https://developer.wordpress.com/2022/01/06/wordpress-plugin-i18n-webpack-and-composer/ * * @param array $handles * @param callable|null $replace_callback */ private static function replace_translation_path( array $handles, $replace_callback = null ) { $sources = self::map_handles_to_src( $handles ); add_filter( 'load_script_textdomain_relative_path', function ( $relative_path, $src ) use ( $sources, $replace_callback ) { if ( ! in_array( $src, $sources, true ) ) { return $relative_path; } if ( is_callable( $replace_callback ) ) { return $replace_callback( $relative_path, $src ); } return self::default_replace_translation( $relative_path ); }, 10, 2 ); } private static function map_handles_to_src( array $handles ) { return array_map( function ( $handle ) { return wp_scripts()->registered[ $handle ]->src; }, $handles ); } private static function default_replace_translation( $relative_path ) { // Translations are always based on the non-minified filename. $relative_path_without_ext = preg_replace( '/(\.min)?\.js$/i', '', $relative_path ); // By default, we suffix the file with `.strings` (e.g 'assets/js/editor.js' => 'assets/js/editor.strings.js'). return implode( '.', [ $relative_path_without_ext, 'strings', 'js', ] ); } } utils/constants.php 0000644 00000000616 14717626151 0010446 0 ustar 00 <?php namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Constants { const ACCESS_TIER_FREE = 'free'; const ACCESS_TIER_ESSENTIAL = 'essential'; const ACCESS_TIER_ESSENTIAL_OCT_2023 = 'essential-oct2023'; const ACCESS_TIER_ADVANCED = 'advanced'; const ACCESS_TIER_EXPERT = 'expert'; const ACCESS_TIER_AGENCY = 'agency'; } schemes/manager.php 0000644 00000007762 14717626151 0010344 0 ustar 00 <?php namespace Elementor\Core\Schemes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor scheme manager. * * Elementor scheme manager handler class is responsible for registering and * initializing all the supported schemes. * * @since 1.0.0 */ class Manager { /** * Registered schemes. * * Holds the list of all the registered schemes. * * @access protected * * @var Base[] */ protected $_registered_schemes = []; /** * Enabled schemes. * * Holds the list of all the enabled schemes. * * @access private * @static * * @var array */ private static $_enabled_schemes; /** * Schemes types. * * Holds the list of the schemes types. Default types are `color`, * `typography` and `color-picker`. * * @access private * @static * * @var array */ private static $_schemes_types = [ 'color', 'typography', 'color-picker', ]; /** * Register new scheme. * * Add a new scheme to the schemes list. The method creates a new scheme * instance for any given scheme class and adds the scheme to the registered * schemes list. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @param string $scheme_class Scheme class name. */ public function register_scheme( $scheme_class ) {} /** * Unregister scheme. * * Removes a scheme from the list of registered schemes. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @param string $id Scheme ID. * * @return bool True if the scheme was removed, False otherwise. */ public function unregister_scheme( $id ) { return true; } /** * Get registered schemes. * * Retrieve the registered schemes list from the current instance. * * @since 1.0.0 * @access public * @deprecated 3.0.0 */ public function get_registered_schemes() { return []; } /** * Get schemes data. * * Retrieve all the registered schemes with data for each scheme. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return array Registered schemes with each scheme data. */ public function get_registered_schemes_data() { return []; } /** * Get default schemes. * * Retrieve all the registered schemes with default scheme for each scheme. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return array Registered schemes with with default scheme for each scheme. */ public function get_schemes_defaults() { return []; } /** * Get system schemes. * * Retrieve all the registered ui schemes with system schemes for each scheme. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return array Registered ui schemes with with system scheme for each scheme. */ public function get_system_schemes() { return []; } /** * Get scheme. * * Retrieve a single scheme from the list of all the registered schemes in * the current instance. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @param string $id Scheme ID. */ public function get_scheme( $id ) { return false; } /** * Get scheme value. * * Retrieve the scheme value from the list of all the registered schemes in * the current instance. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @param string $scheme_type Scheme type. * @param string $scheme_value Scheme value. */ public function get_scheme_value( $scheme_type, $scheme_value ) { return false; } /** * Print ui schemes templates. * * Used to generate the scheme templates on the editor using Underscore JS * template, for all the registered ui schemes. * * @since 1.0.0 * @access public * @deprecated 3.0.0 */ public function print_schemes_templates() {} /** * Get enabled schemes. * * Retrieve all enabled schemes from the list of the registered schemes in * the current instance. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * @static * * @return array Enabled schemes. */ public static function get_enabled_schemes() { return []; } } schemes/color.php 0000644 00000004461 14717626151 0010041 0 ustar 00 <?php namespace Elementor\Core\Schemes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor color scheme. * * Elementor color scheme class is responsible for initializing a scheme for * colors. * * @since 1.0.0 * @deprecated 3.0.0 Use `Global_Colors` instead. */ class Color { /** * 1st color scheme. * @deprecated 3.0.0 Use `Global_Colors::COLOR_PRIMARY` instead. */ const COLOR_1 = '1'; /** * 2nd color scheme. * @deprecated 3.0.0 Use `Global_Colors::COLOR_SECONDARY` instead. */ const COLOR_2 = '2'; /** * 3rd color scheme. * @deprecated 3.0.0 Use `Global_Colors::COLOR_TEXT` instead. */ const COLOR_3 = '3'; /** * 4th color scheme. * @deprecated 3.0.0 Use `Global_Colors::COLOR_ACCENT` instead. */ const COLOR_4 = '4'; /** * Get color scheme type. * * Retrieve the color scheme type. * * @since 1.0.0 * @access public * @static * @deprecated 3.0.0 * * @return string Color scheme type. */ public static function get_type() { return 'color'; } /** * Get color scheme title. * * Retrieve the color scheme title. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return string Color scheme title. */ public function get_title() { return ''; } /** * Get color scheme disabled title. * * Retrieve the color scheme disabled title. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return string Color scheme disabled title. */ public function get_disabled_title() { return ''; } /** * Get color scheme titles. * * Retrieve the color scheme titles. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return array Color scheme titles. */ public function get_scheme_titles() { return []; } /** * Get default color scheme. * * Retrieve the default color scheme. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return array Default color scheme. */ public function get_default_scheme() { return []; } /** * Print color scheme content template. * * Used to generate the HTML in the editor using Underscore JS template. The * variables for the class are available using `data` JS object. * * @since 1.0.0 * @access public * @deprecated 3.0.0 */ public function print_template_content() {} } schemes/typography.php 0000644 00000004751 14717626151 0011133 0 ustar 00 <?php namespace Elementor\Core\Schemes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor typography scheme. * * Elementor typography scheme class is responsible for initializing a scheme * for typography. * * @since 1.0.0 * @deprecated 3.0.0 Use `Global_Typography` instead. */ class Typography { /** * 1st typography scheme. * @deprecated 3.0.0 Use `Global_Typography::TYPOGRAPHY_PRIMARY` instead. */ const TYPOGRAPHY_1 = '1'; /** * 2nd typography scheme. * @deprecated 3.0.0 Use `Global_Typography::TYPOGRAPHY_SECONDARY` instead. */ const TYPOGRAPHY_2 = '2'; /** * 3rd typography scheme. * @deprecated 3.0.0 Use `Global_Typography::TYPOGRAPHY_TEXT` instead. */ const TYPOGRAPHY_3 = '3'; /** * 4th typography scheme. * @deprecated 3.0.0 Use `Global_Typography::TYPOGRAPHY_ACCENT` instead. */ const TYPOGRAPHY_4 = '4'; /** * Get typography scheme type. * * Retrieve the typography scheme type. * * @since 1.0.0 * @access public * @static * @deprecated 3.0.0 * * @return string Typography scheme type. */ public static function get_type() { return 'typography'; } /** * Get typography scheme title. * * Retrieve the typography scheme title. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return string Typography scheme title. */ public function get_title() { return ''; } /** * Get typography scheme disabled title. * * Retrieve the typography scheme disabled title. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return string Typography scheme disabled title. */ public function get_disabled_title() { return ''; } /** * Get typography scheme titles. * * Retrieve the typography scheme titles. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return array Typography scheme titles. */ public function get_scheme_titles() { return []; } /** * Get default typography scheme. * * Retrieve the default typography scheme. * * @since 1.0.0 * @access public * @deprecated 3.0.0 * * @return array Default typography scheme. */ public function get_default_scheme() { return []; } /** * Print typography scheme content template. * * Used to generate the HTML in the editor using Underscore JS template. The * variables for the class are available using `data` JS object. * * @since 1.0.0 * @access public * @deprecated 3.0.0 */ public function print_template_content() {} } modules-manager.php 0000644 00000006363 14717626151 0010357 0 ustar 00 <?php namespace Elementor\Core; use Elementor\Core\Base\Module; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor modules manager. * * Elementor modules manager handler class is responsible for registering and * managing Elementor modules. * * @since 1.6.0 */ class Modules_Manager { /** * Registered modules. * * Holds the list of all the registered modules. * * @since 1.6.0 * @access public * * @var array */ private $modules = []; /** * Modules manager constructor. * * Initializing the Elementor modules manager. * * @since 1.6.0 * @access public */ public function __construct() { $modules_namespace_prefix = $this->get_modules_namespace_prefix(); foreach ( $this->get_modules_names() as $module_name ) { $class_name = str_replace( '-', ' ', $module_name ); $class_name = str_replace( ' ', '', ucwords( $class_name ) ); $class_name = $modules_namespace_prefix . '\\Modules\\' . $class_name . '\Module'; /** @var Module $class_name */ $experimental_data = $class_name::get_experimental_data(); if ( $experimental_data ) { Plugin::$instance->experiments->add_feature( $experimental_data ); if ( ! Plugin::$instance->experiments->is_feature_active( $experimental_data['name'] ) ) { continue; } } if ( $class_name::is_active() ) { $this->modules[ $module_name ] = $class_name::instance(); } } } /** * Get modules names. * * Retrieve the modules names. * * @since 2.0.0 * @access public * * @return string[] Modules names. */ public function get_modules_names() { return [ 'admin-bar', 'history', 'library', 'dynamic-tags', 'page-templates', 'gutenberg', 'wp-cli', 'safe-mode', 'ai', 'notifications', 'usage', 'dev-tools', 'landing-pages', 'compatibility-tag', 'generator-tag', 'elements-color-picker', 'shapes', 'favorites', 'admin-top-bar', 'element-manager', 'nested-elements', // Depends on Nested Elements module 'nested-tabs', 'nested-accordion', 'container-converter', 'web-cli', 'promotions', 'notes', 'performance-lab', 'lazyload', 'image-loading-optimization', 'kit-elements-defaults', 'announcements', 'editor-app-bar', 'site-navigation', 'styleguide', 'element-cache', 'apps', 'home', 'link-in-bio', 'floating-buttons', 'content-sanitizer', 'editor-events', 'atomic-widgets', 'wc-product-editor', 'checklist', ]; } /** * Get modules. * * Retrieve all the registered modules or a specific module. * * @since 2.0.0 * @access public * * @param string $module_name Module name. * * @return null|Module|Module[] All the registered modules or a specific module. */ public function get_modules( $module_name ) { if ( $module_name ) { if ( isset( $this->modules[ $module_name ] ) ) { return $this->modules[ $module_name ]; } return null; } return $this->modules; } /** * Get modules namespace prefix. * * Retrieve the modules namespace prefix. * * @since 2.0.0 * @access protected * * @return string Modules namespace prefix. */ protected function get_modules_namespace_prefix() { return 'Elementor'; } } files/base.php 0000644 00000013135 14717626151 0007306 0 ustar 00 <?php namespace Elementor\Core\Files; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Base { const UPLOADS_DIR = 'elementor/'; const DEFAULT_FILES_DIR = 'css/'; const META_KEY = ''; private static $wp_uploads_dir = []; private $files_dir; private $file_name; /** * File path. * * Holds the file path. * * @access private * * @var string */ private $path; /** * Content. * * Holds the file content. * * @access private * * @var string */ private $content; /** * @since 2.1.0 * @access public * @static */ public static function get_base_uploads_dir() { $wp_upload_dir = self::get_wp_uploads_dir(); return $wp_upload_dir['basedir'] . '/' . self::UPLOADS_DIR; } /** * @since 2.1.0 * @access public * @static */ public static function get_base_uploads_url() { $wp_upload_dir = self::get_wp_uploads_dir(); return $wp_upload_dir['baseurl'] . '/' . self::UPLOADS_DIR; } /** * Use a create function for PhpDoc (@return static). * * @return static */ public static function create() { return Plugin::$instance->files_manager->get( get_called_class(), func_get_args() ); } /** * @since 2.1.0 * @access public */ public function __construct( $file_name ) { /** * Elementor File Name * * Filters the File name * * @since 2.3.0 * * @param string $file_name * @param object $this The file instance, which inherits Elementor\Core\Files */ $file_name = apply_filters( 'elementor/files/file_name', $file_name, $this ); $this->set_file_name( $file_name ); $this->set_files_dir( static::DEFAULT_FILES_DIR ); $this->set_path(); } /** * @since 2.1.0 * @access public */ public function set_files_dir( $files_dir ) { $this->files_dir = $files_dir; } /** * @since 2.1.0 * @access public */ public function set_file_name( $file_name ) { $this->file_name = $file_name; } /** * @since 2.1.0 * @access public */ public function get_file_name() { return $this->file_name; } /** * @since 2.1.0 * @access public */ public function get_url() { $url = set_url_scheme( self::get_base_uploads_url() . $this->files_dir . $this->file_name ); return add_query_arg( [ 'ver' => $this->get_meta( 'time' ) ], $url ); } /** * Get Path * * Returns the local path of the generated file. * * @since 3.5.0 * @access public * * @return string */ public function get_path() { return set_url_scheme( self::get_base_uploads_dir() . $this->files_dir . $this->file_name ); } /** * @since 2.1.0 * @access public */ public function get_content() { if ( ! $this->content ) { $this->content = $this->parse_content(); } return $this->content; } /** * @since 2.1.0 * @access public */ public function update() { $this->update_file(); $meta = $this->get_meta(); $meta['time'] = time(); $this->update_meta( $meta ); } /** * @since 2.1.0 * @access public */ public function update_file() { $this->content = $this->parse_content(); if ( $this->content ) { $this->write(); } else { $this->delete(); } } /** * @since 2.1.0 * @access public */ public function write() { return file_put_contents( $this->path, $this->content ); } /** * @since 2.1.0 * @access public */ public function delete() { if ( file_exists( $this->path ) ) { unlink( $this->path ); } $this->delete_meta(); } /** * Get meta data. * * Retrieve the CSS file meta data. Returns an array of all the data, or if * custom property is given it will return the property value, or `null` if * the property does not exist. * * @since 2.1.0 * @access public * * @param string $property Optional. Custom meta data property. Default is * null. * * @return array|null An array of all the data, or if custom property is * given it will return the property value, or `null` if * the property does not exist. */ public function get_meta( $property = null ) { $meta = array_merge( $this->get_default_meta(), (array) $this->load_meta() ); if ( $property ) { return isset( $meta[ $property ] ) ? $meta[ $property ] : null; } return $meta; } /** * @since 2.1.0 * @access protected * @abstract */ abstract protected function parse_content(); /** * Load meta. * * Retrieve the file meta data. * * @since 2.1.0 * @access protected */ protected function load_meta() { return get_option( static::META_KEY ); } /** * Update meta. * * Update the file meta data. * * @since 2.1.0 * @access protected * * @param array $meta New meta data. */ protected function update_meta( $meta ) { update_option( static::META_KEY, $meta ); } /** * Delete meta. * * Delete the file meta data. * * @since 2.1.0 * @access protected */ protected function delete_meta() { delete_option( static::META_KEY ); } /** * @since 2.1.0 * @access protected */ protected function get_default_meta() { return [ 'time' => 0, ]; } /** * @since 2.1.0 * @access private * @static */ private static function get_wp_uploads_dir() { global $blog_id; if ( empty( self::$wp_uploads_dir[ $blog_id ] ) ) { self::$wp_uploads_dir[ $blog_id ] = wp_upload_dir( null, false ); } return self::$wp_uploads_dir[ $blog_id ]; } /** * @since 2.1.0 * @access private */ private function set_path() { $dir_path = self::get_base_uploads_dir() . $this->files_dir; if ( ! is_dir( $dir_path ) ) { wp_mkdir_p( $dir_path ); } $this->path = $dir_path . $this->file_name; } } files/manager.php 0000644 00000013437 14717626151 0010013 0 ustar 00 <?php namespace Elementor\Core\Files; use Elementor\Core\Base\Document as Document_Base; use Elementor\Core\Base\Elements_Iteration_Actions\Assets; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Files\CSS\Post as Post_CSS; use Elementor\Core\Page_Assets\Data_Managers\Base as Page_Assets_Data_Manager; use Elementor\Core\Responsive\Files\Frontend; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor files manager. * * Elementor files manager handler class is responsible for creating files. * * @since 1.2.0 */ class Manager { private $files = []; /** * Files manager constructor. * * Initializing the Elementor files manager. * * @since 1.2.0 * @access public */ public function __construct() { $this->register_actions(); } public function get( $class, $args ) { $id = $class . '-' . wp_json_encode( $args ); if ( ! isset( $this->files[ $id ] ) ) { // Create an instance from dynamic args length. $reflection_class = new \ReflectionClass( $class ); $this->files[ $id ] = $reflection_class->newInstanceArgs( $args ); } return $this->files[ $id ]; } /** * On post delete. * * Delete post CSS immediately after a post is deleted from the database. * * Fired by `deleted_post` action. * * @since 1.2.0 * @access public * * @param string $post_id Post ID. */ public function on_delete_post( $post_id ) { if ( ! Utils::is_post_support( $post_id ) ) { return; } $css_file = Post_CSS::create( $post_id ); $css_file->delete(); } /** * On export post meta. * * When exporting data using WXR, skip post CSS file meta key. This way the * export won't contain the post CSS file data used by Elementor. * * Fired by `wxr_export_skip_postmeta` filter. * * @since 1.2.0 * @access public * * @param bool $skip Whether to skip the current post meta. * @param string $meta_key Current meta key. * * @return bool Whether to skip the post CSS meta. */ public function on_export_post_meta( $skip, $meta_key ) { if ( Post_CSS::META_KEY === $meta_key ) { $skip = true; } return $skip; } /** * Clear cache. * * Delete all meta containing files data. And delete the actual * files from the upload directory. * * @since 1.2.0 * @access public */ public function clear_cache() { // Delete files. $path = Base::get_base_uploads_dir() . Base::DEFAULT_FILES_DIR . '*'; foreach ( glob( $path ) as $file_path ) { unlink( $file_path ); } delete_post_meta_by_key( Post_CSS::META_KEY ); delete_post_meta_by_key( Document_Base::CACHE_META_KEY ); delete_post_meta_by_key( Assets::ASSETS_META_KEY ); delete_option( Frontend::META_KEY ); $this->reset_assets_data(); /** * Elementor clear files. * * Fires after Elementor clears files * * @since 2.1.0 */ do_action( 'elementor/core/files/clear_cache' ); } public function clear_custom_image_sizes() { if ( ! defined( 'BFITHUMB_UPLOAD_DIR' ) ) { return; } $upload_info = wp_upload_dir(); $upload_dir = $upload_info['basedir'] . '/' . BFITHUMB_UPLOAD_DIR; $path = $upload_dir . '/*'; foreach ( glob( $path ) as $file_path ) { unlink( $file_path ); } } /** * Register Ajax Actions * * Deprecated - use the Uploads Manager instead. * * @deprecated 3.5.0 * * @param Ajax $ajax */ public function register_ajax_actions( Ajax $ajax ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' ); Plugin::$instance->uploads_manager->register_ajax_actions( $ajax ); } /** * Ajax Unfiltered Files Upload * * Deprecated - use the Uploads Manager instead. * * @deprecated 3.5.0 */ public function ajax_unfiltered_files_upload() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' ); Plugin::$instance->uploads_manager->enable_unfiltered_files_upload(); } /** * Register actions. * * Register filters and actions for the files manager. * * @since 1.2.0 * @access private */ private function register_actions() { add_action( 'deleted_post', [ $this, 'on_delete_post' ] ); add_filter( 'wxr_export_skip_postmeta', [ $this, 'on_export_post_meta' ], 10, 2 ); add_action( 'update_option_home', function () { $this->reset_assets_data(); } ); add_action( 'update_option_siteurl', function () { $this->reset_assets_data(); } ); } /** * Reset Assets Data. * * Reset the page assets data. * * @since 3.3.0 * @access private */ private function reset_assets_data() { delete_option( Page_Assets_Data_Manager::ASSETS_DATA_KEY ); } /** * Generate CSS. * * Generates CSS for all posts built with Elementor. * * @since 3.25.0 * @access public */ public function generate_css() { $batch_size = apply_filters( 'elementor/core/files/generate_css/batch_size', 100 ); $processed_posts = 0; while ( true ) { $args = [ 'post_type' => get_post_types(), 'posts_per_page' => $batch_size, 'meta_query' => [ [ 'key' => Document_Base::BUILT_WITH_ELEMENTOR_META_KEY, 'compare' => 'EXISTS', ], ], 'offset' => $processed_posts, 'fields' => 'ids', ]; $query = new \WP_Query( $args ); if ( empty( $query->posts ) ) { break; } foreach ( $query->posts as $post_id ) { $document = Plugin::$instance->documents->get_doc_for_frontend( $post_id ); if ( $document ) { $css_file = Post_CSS::create( $post_id ); $css_file->update(); } } $processed_posts += $batch_size; } /** * Elementor Generate CSS files. * * Fires after Elementor generates new CSS files * * @since 3.25.0 */ do_action( 'elementor/core/files/after_generate_css' ); } } files/uploads-manager.php 0000644 00000044242 14717626151 0011456 0 ustar 00 <?php namespace Elementor\Core\Files; use Elementor\Core\Base\Base_Object; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Files\File_Types\Base as File_Type_Base; use Elementor\Core\Files\File_Types\Json; use Elementor\Core\Files\File_Types\Svg; use Elementor\Core\Files\File_Types\Zip; use Elementor\Core\Utils\Exceptions; use Elementor\User; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor uploads manager. * * Elementor uploads manager handler class is responsible for handling file uploads that are not done with WP Media. * * @since 3.3.0 */ class Uploads_Manager extends Base_Object { const UNFILTERED_FILE_UPLOADS_KEY = 'elementor_unfiltered_files_upload'; const INVALID_FILE_CONTENT = 'Invalid Content In File'; /** * @var File_Type_Base[] */ private $file_type_handlers = []; private $allowed_file_extensions; /** * @var bool */ private $is_elementor_upload = false; /** * @var string */ private $temp_dir; /** * Register File Types * * To Add a new file type to Elementor, with its own handling logic, you need to add it to the $file_types array here. * * @since 3.3.0 * @access public */ public function register_file_types() { // All file types that have handlers should be included here. $file_types = [ 'json' => new Json(), 'zip' => new Zip(), 'svg' => new Svg(), ]; foreach ( $file_types as $file_type => $file_handler ) { $this->file_type_handlers[ $file_type ] = $file_handler; } } /** * Extract and Validate Zip * * This method accepts a $file array (which minimally should include a 'tmp_name') * * @since 3.3.0 * @access public * * @param string $file_path * @param array $allowed_file_types * @return array|\WP_Error */ public function extract_and_validate_zip( $file_path, $allowed_file_types = null ) { $result = []; /** @var Zip $zip_handler - File Type */ $zip_handler = $this->file_type_handlers['zip']; // Returns an array of file paths. $extracted = $zip_handler->extract( $file_path, $allowed_file_types ); if ( is_wp_error( $extracted ) ) { return $extracted; } // If there are no extracted file names, no files passed the extraction validation. if ( empty( $extracted['files'] ) ) { // TODO: Decide what to do if no files passed the extraction validation return new \WP_Error( 'file_error', self::INVALID_FILE_CONTENT ); } $result['extraction_directory'] = $extracted['extraction_directory']; foreach ( $extracted['files'] as $extracted_file_path ) { // Each file is an array with a 'name' (file path) property. if ( ! is_wp_error( $this->validate_file( [ 'tmp_name' => $extracted_file_path ] ) ) ) { $result['files'][] = $extracted_file_path; } } return $result; } /** * Handle Elementor Upload * * This method receives a $file array. If the received file is a Base64 string, the $file array should include a * 'fileData' property containing the string, which is decoded and has its contents stored in a temporary file. * If the $file parameter passed is a standard $file array, the 'name' and 'tmp_name' properties are used for * validation. * * The file goes through validation; if it passes validation, the file is returned. Otherwise, an error is returned. * * @since 3.3.0 * @access public * * @param array $data * @param array $allowed_file_extensions Optional. an array of file types that are allowed to pass validation for each * upload. * @return array|\WP_Error */ public function handle_elementor_upload( array $data, $allowed_file_extensions = null ) { // If $file['fileData'] is set, it signals that the passed file is a Base64 string that needs to be decoded and // saved to a temporary file. if ( isset( $data['fileData'] ) ) { $data = $this->save_base64_to_tmp_file( $data, $allowed_file_extensions ); } if ( is_wp_error( $data ) ) { return $data; } $validation_result = $this->validate_file( $data, $allowed_file_extensions ); if ( is_wp_error( $validation_result ) ) { return $validation_result; } return $data; } /** * are Unfiltered Uploads Enabled * * @since 3.5.0 * @access public * * @return bool */ final public static function are_unfiltered_uploads_enabled() { $enabled = ! ! get_option( self::UNFILTERED_FILE_UPLOADS_KEY ) && Svg::file_sanitizer_can_run() && User::is_current_user_can_upload_json(); /** * Allow Unfiltered Files Upload. * * Determines whether to enable unfiltered file uploads. * * @since 3.0.0 * * @param bool $enabled Whether upload is enabled or not. */ $enabled = apply_filters( 'elementor/files/allow_unfiltered_upload', $enabled ); return $enabled; } /** * Handle Elementor WP Media Upload * * Runs on the 'wp_handle_upload_prefilter' filter. * * @since 3.2.0 * @access public * * @param $file * @return mixed */ public function handle_elementor_wp_media_upload( $file ) { // If it isn't a file uploaded by Elementor, we do not intervene. if ( ! $this->is_elementor_wp_media_upload() ) { return $file; } $result = $this->validate_file( $file ); if ( is_wp_error( $result ) ) { $file['error'] = $result->get_error_message(); } return $file; } /** * Get File Type Handler * * Initialize the proper file type handler according to the file extension * and assign it to the file type handlers array. * * @since 3.3.0 * @access public * * @param string|null $file_extension - file extension * @return File_Type_Base[]|File_Type_Base */ public function get_file_type_handlers( $file_extension = null ) { return self::get_items( $this->file_type_handlers, $file_extension ); } /** * Check filetype and ext * * A workaround for upload validation which relies on a PHP extension (fileinfo) * with inconsistent reporting behaviour. * ref: https://core.trac.wordpress.org/ticket/39550 * ref: https://core.trac.wordpress.org/ticket/40175 * * @since 3.5.0 * @access public * * @param $data * @param $file * @param $filename * @param $mimes * * @return mixed */ public function check_filetype_and_ext( $data, $file, $filename, $mimes ) { if ( ! empty( $data['ext'] ) && ! empty( $data['type'] ) ) { return $data; } $wp_file_type = wp_check_filetype( $filename, $mimes ); $file_type_handlers = $this->get_file_type_handlers(); if ( isset( $file_type_handlers[ $wp_file_type['ext'] ] ) ) { $file_type_handler = $file_type_handlers[ $wp_file_type['ext'] ]; $data['ext'] = $file_type_handler->get_file_extension(); $data['type'] = $file_type_handler->get_mime_type(); } return $data; } /** * Remove File Or Directory * * Directory is deleted recursively with all of its contents (subdirectories and files). * * @since 3.3.0 * @access public * * @param string $path */ public function remove_file_or_dir( $path ) { if ( is_dir( $path ) ) { $this->remove_directory_with_files( $path ); } elseif ( is_file( $path ) ) { unlink( $path ); } } /** * Create Temp File * * Create a random temporary file. * * @since 3.3.0 * @access public * * @param string $file_content * @param string $file_name * @return string|\WP_Error */ public function create_temp_file( $file_content, $file_name ) { $file_name = str_replace( ' ', '', sanitize_file_name( $file_name ) ); if ( empty( $file_name ) ) { return new \WP_Error( 'invalid_file_name', esc_html__( 'Invalid file name.', 'elementor' ) ); } $temp_filename = $this->create_unique_dir() . $file_name; /** * Temp File Path * * Allows modifying the full path of the temporary file. * * @since 3.7.0 * * @param string full path to file */ $temp_filename = apply_filters( 'elementor/files/temp-file-path', $temp_filename ); file_put_contents( $temp_filename, $file_content ); // phpcs:ignore return $temp_filename; } /** * Get Temp Directory * * Get the temporary files directory path. If the directory does not exist, this method creates it. * * @since 3.3.0 * @access public * * @return string $temp_dir */ public function get_temp_dir() { if ( ! $this->temp_dir ) { $wp_upload_dir = wp_upload_dir(); $temp_dir = implode( DIRECTORY_SEPARATOR, [ $wp_upload_dir['basedir'], 'elementor', 'tmp' ] ) . DIRECTORY_SEPARATOR; /** * Temp File Path * * Allows modifying the full path of the temporary file. * * @since 3.7.0 * * @param string temporary directory */ $this->temp_dir = apply_filters( 'elementor/files/temp-dir', $temp_dir ); if ( ! is_dir( $this->temp_dir ) ) { wp_mkdir_p( $this->temp_dir ); } } return $this->temp_dir; } /** * Create Unique Temp Dir * * Create a unique temporary directory * * @since 3.3.0 * @access public * * @return string the new directory path */ public function create_unique_dir() { $unique_dir_path = $this->get_temp_dir() . uniqid() . DIRECTORY_SEPARATOR; wp_mkdir_p( $unique_dir_path ); return $unique_dir_path; } /** * Register Ajax Actions * * Runs on the 'elementor/ajax/register_actions' hook. Receives the AJAX module as a parameter and registers * callbacks for specified action IDs. * * @since 3.5.0 * @access public * * @param Ajax $ajax */ public function register_ajax_actions( Ajax $ajax ) { $ajax->register_ajax_action( 'enable_unfiltered_files_upload', [ $this, 'enable_unfiltered_files_upload' ] ); } /** * Set Unfiltered Files Upload * * @since 3.5.0 * @access public */ public function enable_unfiltered_files_upload() { if ( ! current_user_can( 'manage_options' ) ) { return; } update_option( self::UNFILTERED_FILE_UPLOADS_KEY, 1 ); } /** * Support Unfiltered File Uploads * * When uploading a file within Elementor, this method adds the registered * file types to WordPress' allowed mimes list. This will only happen if the user allowed unfiltered file uploads * in Elementor's settings in the admin dashboard. * * @since 3.5.0 * @access public * * @param array $allowed_mimes * @return array allowed mime types */ final public function support_unfiltered_elementor_file_uploads( $allowed_mimes ) { if ( $this->is_elementor_upload() && $this->are_unfiltered_uploads_enabled() ) { foreach ( $this->file_type_handlers as $file_type_handler ) { $allowed_mimes[ $file_type_handler->get_file_extension() ] = $file_type_handler->get_mime_type(); } } return $allowed_mimes; } /** * Set Elementor Upload State * * @since 3.5.0 * @access public * * @param $state */ public function set_elementor_upload_state( $state ) { $this->is_elementor_upload = $state; } /** * Is Elementor Upload * * This method checks if the current session includes a request to upload files made via Elementor. * * @since 3.5.0 * @access private * * @return bool */ private function is_elementor_upload() { return $this->is_elementor_upload || $this->is_elementor_media_upload() || $this->is_elementor_wp_media_upload(); } /** * Is Elementor Media Upload * * Checks whether the current request includes uploading files via Elementor which are not destined for the Media * Library. * * @since 3.5.0 * @access public * * @return bool */ public function is_elementor_media_upload() { // Sometimes `uploadTypeCaller` passed as a GET parameter when using the WP Media Library REST API, where the // whole request body is occupied by the uploaded file. return isset( $_REQUEST['uploadTypeCaller'] ) && 'elementor-media-upload' === $_REQUEST['uploadTypeCaller']; // phpcs:ignore } /** * Is Elementor WP Media Upload * * Checks whether the current request is a request to upload files into the WP Media Library via Elementor. * * @since 3.3.0 * @access private * * @return bool */ private function is_elementor_wp_media_upload() { return isset( $_REQUEST['uploadTypeCaller'] ) && 'elementor-wp-media-upload' === $_REQUEST['uploadTypeCaller']; // phpcs:ignore } /** * Add File Extension To Allowed Extensions List * * @since 3.3.0 * @access private * * @param string $file_type */ private function add_file_extension_to_allowed_extensions_list( $file_type ) { $file_handler = $this->file_type_handlers[ $file_type ]; $file_extension = $file_handler->get_file_extension(); // Only add the file extension to the list if it doesn't already exist in it. if ( ! in_array( $file_extension, $this->allowed_file_extensions, true ) ) { $this->allowed_file_extensions[] = $file_extension; } } /** * Save Base64 as File * * Saves a Base64 string as a .tmp file in Elementor's temporary files directory. * * @since 3.3.0 * @access private * * @param $file * @param array|null $allowed_file_extensions * * @return array|\WP_Error */ private function save_base64_to_tmp_file( $file, $allowed_file_extensions = null ) { if ( empty( $file['fileName'] ) || empty( $file['fileData'] ) ) { return new \WP_Error( 'file_error', self::INVALID_FILE_CONTENT ); } $file_extension = pathinfo( $file['fileName'], PATHINFO_EXTENSION ); $is_file_type_allowed = $this->is_file_type_allowed( $file_extension, $allowed_file_extensions ); if ( is_wp_error( $is_file_type_allowed ) ) { return $is_file_type_allowed; } $file_content = base64_decode( $file['fileData'] ); // phpcs:ignore // If the decode fails if ( ! $file_content ) { return new \WP_Error( 'file_error', self::INVALID_FILE_CONTENT ); } $temp_filename = $this->create_temp_file( $file_content, $file['fileName'] ); if ( is_wp_error( $temp_filename ) ) { return $temp_filename; } return [ // the original uploaded file name 'name' => $file['fileName'], // The path to the temporary file 'tmp_name' => $temp_filename, ]; } /** * Validate File * * @since 3.3.0 * @access private * * @param array $file * @param array $file_extensions Optional * @return bool|\WP_Error */ private function validate_file( array $file, $file_extensions = [] ) { $uploaded_file_name = isset( $file['name'] ) ? $file['name'] : $file['tmp_name']; $file_extension = pathinfo( $uploaded_file_name, PATHINFO_EXTENSION ); if ( ! $this->is_elementor_wp_media_upload() ) { $is_file_type_allowed = $this->is_file_type_allowed( $file_extension, $file_extensions ); if ( is_wp_error( $is_file_type_allowed ) ) { return $is_file_type_allowed; } } $file_type_handler = $this->get_file_type_handlers( $file_extension ); // If Elementor does not have a handler for this file type, don't block it. if ( ! $file_type_handler ) { return true; } // If there is a File Type Handler for the uploaded file, it means it is a non-standard file type. In this case, // we check if unfiltered file uploads are enabled or not before allowing it. if ( ! self::are_unfiltered_uploads_enabled() ) { $error = 'json' === $file_extension ? esc_html__( 'You do not have permission to upload JSON files.', 'elementor' ) : esc_html__( 'This file is not allowed for security reasons.', 'elementor' ); return new \WP_Error( Exceptions::FORBIDDEN, $error ); } // Here is each file type handler's chance to run its own specific validations return $file_type_handler->validate_file( $file ); } /** * Is File Type Allowed * * Checks whether the passed file extension is allowed for upload. * * @since 3.5.0 * @access private * * @param $file_extension * @param $filtered_file_extensions * @return bool|\WP_Error */ private function is_file_type_allowed( $file_extension, $filtered_file_extensions ) { $allowed_file_extensions = $this->get_allowed_file_extensions(); if ( $filtered_file_extensions ) { $allowed_file_extensions = array_intersect( $allowed_file_extensions, $filtered_file_extensions ); } $is_allowed = false; // Check if the file type (extension) is in the allowed extensions list. If it is a non-standard file type (not // enabled by default in WordPress) and unfiltered file uploads are not enabled, it will not be in the allowed // file extensions list. foreach ( $allowed_file_extensions as $allowed_extension ) { if ( preg_match( '/' . $allowed_extension . '/', $file_extension ) ) { $is_allowed = true; break; } } if ( ! $is_allowed ) { $is_allowed = new \WP_Error( Exceptions::FORBIDDEN, 'Uploading this file type is not allowed.' ); } /** * Elementor File Type Allowed * * Allows setting file types * * @since 3.5.0 * * @param bool|\WP_Error $is_allowed */ return apply_filters( 'elementor/files/allow-file-type/' . $file_extension, $is_allowed ); } /** * Remove Directory with Files * * @since 3.3.0 * @access private * * @param string $dir * @return bool */ private function remove_directory_with_files( $dir ) { $dir_iterator = new \RecursiveDirectoryIterator( $dir, \RecursiveDirectoryIterator::SKIP_DOTS ); foreach ( new \RecursiveIteratorIterator( $dir_iterator, \RecursiveIteratorIterator::CHILD_FIRST ) as $name => $item ) { if ( is_dir( $name ) ) { rmdir( $name ); } elseif ( is_file( $name ) ) { unlink( $name ); } } return rmdir( $dir ); } /** * Get Allowed File Extensions * * Retrieve an array containing the list of file extensions allowed for upload. * * @since 3.3.0 * @access private * * @return array file extension/s */ private function get_allowed_file_extensions() { if ( ! $this->allowed_file_extensions ) { $this->allowed_file_extensions = array_keys( get_allowed_mime_types() ); foreach ( $this->get_file_type_handlers() as $file_type => $handler ) { if ( $handler->is_upload_allowed() ) { // Add the file extension to the allowed extensions list only if unfiltered files upload is enabled. $this->add_file_extension_to_allowed_extensions_list( $file_type ); } } } return $this->allowed_file_extensions; } public function __construct() { $this->register_file_types(); add_filter( 'upload_mimes', [ $this, 'support_unfiltered_elementor_file_uploads' ] ); add_filter( 'wp_handle_upload_prefilter', [ $this, 'handle_elementor_wp_media_upload' ] ); add_filter( 'wp_check_filetype_and_ext', [ $this, 'check_filetype_and_ext' ], 10, 4 ); // Ajax. add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); } } files/file-types/base.php 0000644 00000002503 14717626151 0011364 0 ustar 00 <?php namespace Elementor\Core\Files\File_Types; use Elementor\Core\Base\Base_Object; use Elementor\Core\Utils\Exceptions; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor File Types Base. * * The File Types Base class provides base methods used by all file type handler classes. * These methods are used in file upl * * @since 3.3.0 */ abstract class Base extends Base_Object { /** * Get File Extension * * Returns the file type's file extension * * @since 3.3.0 * * @return string - file extension */ abstract public function get_file_extension(); /** * Get Mime Type * * Returns the file type's mime type * * @since 3.5.0 * * @return string - file extension */ abstract public function get_mime_type(); /** * Validate File * * This method give file types the chance to run file-type-specific validations before returning the file for upload. * * @since 3.3.0 * * @param $file * @return bool|\WP_Error */ public function validate_file( $file ) { return true; } /** * Is Upload Allowed * * This method returns whether the file type is allowed to be uploaded, even if unfiltered uploads are disabled. * * @since 3.3.0 * * @return bool */ public function is_upload_allowed() { return true; } } files/file-types/json.php 0000644 00000001040 14717626151 0011416 0 ustar 00 <?php namespace Elementor\Core\Files\File_Types; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Json extends Base { /** * Get File Extension * * Returns the file type's file extension * * @since 3.3.0 * * @return string - file extension */ public function get_file_extension() { return 'json'; } /** * Get Mime Type * * Returns the file type's mime type * * @since 3.5.0 * * @return string mime type */ public function get_mime_type() { return 'application/json'; } } files/file-types/svg.php 0000644 00000013230 14717626151 0011250 0 ustar 00 <?php namespace Elementor\Core\Files\File_Types; use Elementor\Core\Utils\Exceptions; use Elementor\Core\Utils\Svg\Svg_Sanitizer; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Svg extends Base { /** * Inline svg attachment meta key */ const META_KEY = '_elementor_inline_svg'; const SCRIPT_REGEX = '/(?:\w+script|data):/xi'; /** * Get File Extension * * Returns the file type's file extension * * @since 3.5.0 * @access public * * @return string - file extension */ public function get_file_extension() { return 'svg'; } /** * Get Mime Type * * Returns the file type's mime type * * @since 3.5.0 * @access public * * @return string mime type */ public function get_mime_type() { return 'image/svg+xml'; } /** * Sanitize SVG * * @since 3.5.0 * @access public * * @param $filename * @return bool */ public function sanitize_svg( $filename ) { return ( new SVG_Sanitizer() )->sanitize_file( $filename ); } /** * Validate File * * @since 3.3.0 * @access public * * @param $file * @return bool|\WP_Error */ public function validate_file( $file ) { if ( ! $this->sanitize_svg( $file['tmp_name'] ) ) { return new \WP_Error( Exceptions::FORBIDDEN, esc_html__( 'This file is not allowed for security reasons.', 'elementor' ) ); } return true; } /** * Sanitizer * * @since 3.5.0 * @access public * * @param $content * @return bool|string */ public function sanitizer( $content ) { return ( new SVG_Sanitizer() )->sanitize( $content ); } /** * WP Prepare Attachment For J * * Runs on the `wp_prepare_attachment_for_js` filter. * * @since 3.5.0 * @access public * * @param $attachment_data * @param $attachment * @param $meta * * @return mixed */ public function wp_prepare_attachment_for_js( $attachment_data, $attachment, $meta ) { if ( 'image' !== $attachment_data['type'] || 'svg+xml' !== $attachment_data['subtype'] || ! class_exists( 'SimpleXMLElement' ) ) { return $attachment_data; } $svg = self::get_inline_svg( $attachment->ID ); if ( ! $svg ) { return $attachment_data; } try { $svg = new \SimpleXMLElement( $svg ); } catch ( \Exception $e ) { return $attachment_data; } $src = $attachment_data['url']; $width = (int) $svg['width']; $height = (int) $svg['height']; // Media Gallery $attachment_data['image'] = compact( 'src', 'width', 'height' ); $attachment_data['thumb'] = compact( 'src', 'width', 'height' ); // Single Details of Image $attachment_data['sizes']['full'] = [ 'height' => $height, 'width' => $width, 'url' => $src, 'orientation' => $height > $width ? 'portrait' : 'landscape', ]; return $attachment_data; } /** * Set Svg Meta Data * * Adds dimensions metadata to uploaded SVG files, since WordPress doesn't do it. * * @since 3.5.0 * @access public * * @return mixed */ public function set_svg_meta_data( $data, $id ) { $attachment = get_post( $id ); // Filter makes sure that the post is an attachment. $mime_type = $attachment->post_mime_type; // If the attachment is an svg if ( 'image/svg+xml' === $mime_type ) { // If the svg metadata are empty or the width is empty or the height is empty. // then get the attributes from xml. if ( empty( $data ) || empty( $data['width'] ) || empty( $data['height'] ) ) { $attachment = wp_get_attachment_url( $id ); $xml = simplexml_load_file( $attachment ); if ( ! empty( $xml ) ) { $attr = $xml->attributes(); $view_box = explode( ' ', $attr->viewBox );// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $data['width'] = isset( $attr->width ) && preg_match( '/\d+/', $attr->width, $value ) ? (int) $value[0] : ( 4 === count( $view_box ) ? (int) $view_box[2] : null ); $data['height'] = isset( $attr->height ) && preg_match( '/\d+/', $attr->height, $value ) ? (int) $value[0] : ( 4 === count( $view_box ) ? (int) $view_box[3] : null ); } } } return $data; } /** * Delete Meta Cache * * Deletes the Inline SVG post meta entry. * * @since 3.5.0 * @access public */ public function delete_meta_cache() { delete_post_meta_by_key( self::META_KEY ); } /** * File Sanitizer Can Run * * Checks if the classes required for the file sanitizer are in memory. * * @since 3.5.0 * @access public * @static * * @return bool */ public static function file_sanitizer_can_run() { return class_exists( 'DOMDocument' ) && class_exists( 'SimpleXMLElement' ); } /** * Get Inline SVG * * @since 3.5.0 * @access public * @static * * @param $attachment_id * @return bool|mixed|string */ public static function get_inline_svg( $attachment_id ) { $svg = get_post_meta( $attachment_id, self::META_KEY, true ); if ( ! empty( $svg ) ) { $valid_svg = ( new SVG_Sanitizer() )->sanitize( $svg ); return ( false === $valid_svg ) ? '' : $valid_svg; } $attachment_file = get_attached_file( $attachment_id ); if ( ! file_exists( $attachment_file ) ) { return ''; } $svg = Utils::file_get_contents( $attachment_file ); $valid_svg = ( new SVG_Sanitizer() )->sanitize( $svg ); if ( false === $valid_svg ) { return ''; } if ( ! empty( $valid_svg ) ) { update_post_meta( $attachment_id, self::META_KEY, $valid_svg ); } return $valid_svg; } public function __construct() { add_filter( 'wp_update_attachment_metadata', [ $this, 'set_svg_meta_data' ], 10, 2 ); add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ], 10, 3 ); add_action( 'elementor/core/files/clear_cache', [ $this, 'delete_meta_cache' ] ); } } files/file-types/zip.php 0000644 00000011142 14717626151 0011253 0 ustar 00 <?php namespace Elementor\Core\Files\File_Types; use Elementor\Core\Base\Document; use Elementor\Core\Utils\Exceptions; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor File Types Base. * * The File Types Base class provides base methods used by all file type handler classes. * These methods are used in file upl * * @since 3.3.0 */ class Zip extends Base { /** * Get File Extension * * Returns the file type's file extension * * @since 3.3.0 * * @return string - file extension */ public function get_file_extension() { return 'zip'; } /** * Get Mime Type * * Returns the file type's mime type * * @since 3.5.0 * * @return string mime type */ public function get_mime_type() { return 'application/zip'; } /** * Get File Property Name * * Get the property name to look for in the $_FILES superglobal * * @since 3.3.0 * * @return string */ public function get_file_property_name() { return 'zip_upload'; } /** * Extract * * Performs the extraction of the zip files to a temporary directory. * Returns an error if for some reason the ZipArchive utility isn't available. * Otherwise, Returns an array containing the temporary extraction directory, and the list of extracted files. * * @since 3.3.0 * * @param string $file_path * @param array|null $allowed_file_types * @return array|\WP_Error */ public function extract( $file_path, $allowed_file_types ) { if ( ! class_exists( '\ZipArchive' ) ) { return new \WP_Error( 'zip_error', 'PHP Zip extension not loaded' ); } $zip = new \ZipArchive(); /** * Zip Extraction Directory * * Filters the extraction directory for uploaded zip files. * * @since 3.6.0 * * @param string $extraction_directory A temporary upload directory generated by Elementor's Uploads Manager */ $extraction_directory = apply_filters( 'elementor/files/zip/extraction-directory', '' ); if ( ! $extraction_directory ) { $extraction_directory = Plugin::$instance->uploads_manager->create_unique_dir(); } $zip->open( $file_path ); // if an array of allowed file types is provided, get the filtered file list to extract. $allowed_files = $allowed_file_types ? $this->get_allowed_files( $zip, $allowed_file_types ) : null; $zip->extractTo( $extraction_directory, $allowed_files ); $zip->close(); return [ 'extraction_directory' => $extraction_directory, 'files' => $this->find_temp_files( $extraction_directory ), ]; } /** * Get Allowed Files * * Accepts a zipArchive instance and an array of allowed file types. Iterates over the zip archive's files and * checks if their extensions are in the list of allowed file types. Returns an array containing all valid files. * * @since 3.3.0 * * @param \ZipArchive $zip * @param array $allowed_file_types * @return array */ private function get_allowed_files( $zip, $allowed_file_types ) { $allowed_files = []; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase for ( $i = 0; $i < $zip->numFiles; $i++ ) { $filename = $zip->getNameIndex( $i ); $extension = pathinfo( $filename, PATHINFO_EXTENSION ); // Skip files with transversal paths. if ( strpos( $filename, '..' ) !== false ) { continue; } if ( in_array( $extension, $allowed_file_types, true ) ) { $allowed_files[] = $filename; } } return $allowed_files; } /** * Find temporary files. * * Recursively finds a list of temporary files from the extracted zip file. * * Example return data: * * [ * 0 => '/www/wp-content/uploads/elementor/tmp/5eb3a7a411d44/templates/block-2-col-marble-title.json', * 1 => '/www/wp-content/uploads/elementor/tmp/5eb3a7a411d44/templates/block-2-col-text-and-photo.json', * ] * * @since 2.9.8 * @access private * * @param string $temp_path - The temporary file path to scan for template files * * @return array An array of temporary files on the filesystem */ private function find_temp_files( $temp_path ) { $file_names = []; $possible_file_names = array_diff( scandir( $temp_path ), [ '.', '..' ] ); // Find nested files in the unzipped path. This happens for example when the user imports a Website Kit. foreach ( $possible_file_names as $possible_file_name ) { $full_possible_file_name = $temp_path . $possible_file_name; if ( is_dir( $full_possible_file_name ) ) { $file_names = array_merge( $file_names, $this->find_temp_files( $full_possible_file_name . '/' ) ); } else { $file_names[] = $full_possible_file_name; } } return $file_names; } } files/css/base.php 0000644 00000067042 14717626151 0010104 0 ustar 00 <?php namespace Elementor\Core\Files\CSS; use Elementor\Base_Data_Control; use Elementor\Control_Repeater; use Elementor\Controls_Manager; use Elementor\Controls_Stack; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Core\Files\Base as Base_File; use Elementor\Core\DynamicTags\Manager; use Elementor\Core\DynamicTags\Tag; use Elementor\Core\Frontend\Performance; use Elementor\Core\Kits\Documents\Tabs\Global_Typography; use Elementor\Plugin; use Elementor\Stylesheet; use Elementor\Icons_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor CSS file. * * Elementor CSS file handler class is responsible for generating CSS files. * * @since 1.2.0 * @abstract */ abstract class Base extends Base_File { /** * Elementor CSS file generated status. * * The parsing result after generating CSS file. */ const CSS_STATUS_FILE = 'file'; /** * Elementor inline CSS status. * * The parsing result after generating inline CSS. */ const CSS_STATUS_INLINE = 'inline'; /** * Elementor CSS empty status. * * The parsing result when an empty CSS returned. */ const CSS_STATUS_EMPTY = 'empty'; /** * Fonts. * * Holds the list of fonts. * * @access private * * @var array */ private $fonts = []; private $icons_fonts = []; private $dynamic_elements_ids = []; private $preserved_dynamic_style_values = []; /** * Stylesheet object. * * Holds the CSS file stylesheet instance. * * @access protected * * @var Stylesheet */ protected $stylesheet_obj; /** * Printed. * * Holds the list of printed files. * * @access protected * * @var array */ private static $printed = []; /** * Get CSS file name. * * Retrieve the CSS file name. * * @since 1.6.0 * @access public * @abstract */ abstract public function get_name(); protected function is_global_parsing_supported() { return false; } /** * Use external file. * * Whether to use external CSS file of not. When there are new schemes or settings * updates. * * @since 1.9.0 * @access protected * * @return bool True if the CSS requires an update, False otherwise. */ protected function use_external_file() { return 'internal' !== get_option( 'elementor_css_print_method' ); } /** * Update the CSS file. * * Delete old CSS, parse the CSS, save the new file and update the database. * * This method also sets the CSS status to be used later on in the render posses. * * @since 1.2.0 * @access public */ public function update() { $this->update_file(); $meta = $this->get_meta(); $meta['time'] = time(); $content = $this->get_content(); if ( empty( $content ) ) { $meta['status'] = self::CSS_STATUS_EMPTY; $meta['css'] = ''; } else { $use_external_file = $this->use_external_file(); if ( $use_external_file ) { $meta['status'] = self::CSS_STATUS_FILE; } else { $meta['status'] = self::CSS_STATUS_INLINE; $meta['css'] = $content; } } $meta['dynamic_elements_ids'] = $this->dynamic_elements_ids; $this->update_meta( $meta ); } /** * @since 2.1.0 * @access public */ public function write() { if ( $this->use_external_file() ) { parent::write(); } } /** * @since 3.0.0 * @access public */ public function delete() { if ( $this->use_external_file() ) { parent::delete(); } else { $this->delete_meta(); } } /** * Get Responsive Control Duplication Mode * * @since 3.4.0 * * @return string */ protected function get_responsive_control_duplication_mode() { return 'on'; } /** * Enqueue CSS. * * Either enqueue the CSS file in Elementor or add inline style. * * This method is also responsible for loading the fonts. * * @since 1.2.0 * @access public */ public function enqueue() { $handle_id = $this->get_file_handle_id(); if ( isset( self::$printed[ $handle_id ] ) ) { return; } self::$printed[ $handle_id ] = true; $meta = $this->get_meta(); if ( self::CSS_STATUS_EMPTY === $meta['status'] ) { return; } /** * Enqueue CSS file. * * Fires before enqueuing a CSS file. * * @param Base $this The current CSS file. */ do_action( 'elementor/css-file/before_enqueue', $this ); // First time after clear cache and etc. if ( '' === $meta['status'] || $this->is_update_required() ) { $this->update(); $meta = $this->get_meta(); } if ( self::CSS_STATUS_INLINE === $meta['status'] ) { $dep = $this->get_inline_dependency(); // If the dependency has already been printed ( like a template in footer ) if ( wp_styles()->query( $dep, 'done' ) ) { printf( '<style id="%1$s">%2$s</style>', $this->get_file_handle_id(), $meta['css'] ); // XSS ok. } else { wp_add_inline_style( $dep, $meta['css'] ); } } elseif ( self::CSS_STATUS_FILE === $meta['status'] ) { // Re-check if it's not empty after CSS update. wp_enqueue_style( $this->get_file_handle_id(), $this->get_url(), $this->get_enqueue_dependencies(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion } // Handle fonts. if ( ! empty( $meta['fonts'] ) ) { foreach ( $meta['fonts'] as $font ) { Plugin::$instance->frontend->enqueue_font( $font ); } } if ( ! empty( $meta['icons'] ) ) { $icons_types = Icons_Manager::get_icon_manager_tabs(); foreach ( $meta['icons'] as $icon_font ) { if ( ! isset( $icons_types[ $icon_font ] ) ) { continue; } Plugin::$instance->frontend->enqueue_font( $icon_font ); } } $name = $this->get_name(); /** * Enqueue CSS file. * * Fires when CSS file is enqueued on Elementor. * * The dynamic portion of the hook name, `$name`, refers to the CSS file name. * * @since 2.0.0 * * @param Base $this The current CSS file. */ do_action( "elementor/css-file/{$name}/enqueue", $this ); /** * Enqueue CSS file. * * Fires after enqueuing a CSS file. * * @param Base $this The current CSS file. */ do_action( 'elementor/css-file/after_enqueue', $this ); } /** * Print CSS. * * Output the final CSS inside the `<style>` tags and all the frontend fonts in * use. * * @since 1.9.4 * @access public */ public function print_css() { echo '<style>' . $this->get_content() . '</style>'; // XSS ok. Plugin::$instance->frontend->print_fonts_links(); } /** * Add control rules. * * Parse the CSS for all the elements inside any given control. * * This method recursively renders the CSS for all the selectors in the control. * * @since 1.2.0 * @access public * * @param array $control The controls. * @param array $controls_stack The controls stack. * @param callable $value_callback Callback function for the value. * @param array $placeholders Placeholders. * @param array $replacements Replacements. * @param array $values Global Values. */ public function add_control_rules( array $control, array $controls_stack, callable $value_callback, array $placeholders, array $replacements, array $values = [] ) { if ( empty( $control['selectors'] ) ) { return; } $control_global_key = $control['name']; if ( ! empty( $control['groupType'] ) ) { $control_global_key = $control['groupPrefix'] . $control['groupType']; } $global_values = []; $global_key = ''; if ( ! empty( $values['__globals__'] ) ) { $global_values = $values['__globals__']; } if ( ! empty( $global_values[ $control_global_key ] ) ) { $global_key = $global_values[ $control_global_key ]; } if ( ! $global_key ) { $value = call_user_func( $value_callback, $control ); if ( null === $value ) { return; } } $stylesheet = $this->get_stylesheet(); $control = apply_filters( 'elementor/files/css/selectors', $control, $value ?? [], $this ); foreach ( $control['selectors'] as $selector => $css_property ) { $output_css_property = ''; if ( $global_key ) { $selector_global_value = $this->get_selector_global_value( $control, $global_key ); if ( $selector_global_value ) { $output_css_property = preg_replace( '/(:)[^;]+(;?)/', '$1' . $selector_global_value . '$2', $css_property ); } } else { try { if ( $this->unit_has_custom_selector( $control, $value ) ) { $css_property = $control['unit_selectors_dictionary'][ $value['unit'] ]; } $output_css_property = preg_replace_callback( '/{{(?:([^.}]+)\.)?([^}| ]*)(?: *\|\| *(?:([^.}]+)\.)?([^}| ]*) *)*}}/', function( $matches ) use ( $control, $value_callback, $controls_stack, $value, $css_property ) { $external_control_missing = $matches[1] && ! isset( $controls_stack[ $matches[1] ] ); $parsed_value = ''; $value = apply_filters( 'elementor/files/css/property', $value, $css_property, $matches, $control ); if ( ! $external_control_missing ) { $parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[2], $matches[1] ); } if ( '' === $parsed_value ) { if ( isset( $matches[4] ) ) { $parsed_value = $matches[4]; $is_string_value = preg_match( '/^([\'"])(.*)\1$/', $parsed_value, $string_matches ); if ( $is_string_value ) { $parsed_value = $string_matches[2]; } elseif ( ! is_numeric( $parsed_value ) ) { if ( $matches[3] && ! isset( $controls_stack[ $matches[3] ] ) ) { return ''; } $parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[4], $matches[3] ); } } if ( '' === $parsed_value ) { if ( $external_control_missing ) { return ''; } throw new \Exception(); } } if ( '__EMPTY__' === $parsed_value ) { $parsed_value = ''; } return $parsed_value; }, $css_property ); } catch ( \Exception $e ) { return; } } if ( ! $output_css_property ) { continue; } $device_pattern = '/^(?:\([^\)]+\)){1,2}/'; preg_match( $device_pattern, $selector, $device_rules ); $query = []; if ( $device_rules ) { $selector = preg_replace( $device_pattern, '', $selector ); preg_match_all( '/\(([^)]+)\)/', $device_rules[0], $pure_device_rules ); $pure_device_rules = $pure_device_rules[1]; foreach ( $pure_device_rules as $device_rule ) { if ( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $device_rule ) { continue; } $device = preg_replace( '/\+$/', '', $device_rule ); $endpoint = $device === $device_rule ? 'max' : 'min'; $query[ $endpoint ] = $device; } } $parsed_selector = str_replace( $placeholders, $replacements, $selector ); if ( ! $query && ! empty( $control['responsive'] ) ) { $query = array_intersect_key( $control['responsive'], array_flip( [ 'min', 'max' ] ) ); if ( ! empty( $query['max'] ) && Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $query['max'] ) { unset( $query['max'] ); } } $stylesheet->add_rules( $parsed_selector, $output_css_property, $query ); } } protected function unit_has_custom_selector( $control, $value ) { return isset( $control['unit_selectors_dictionary'] ) && isset( $control['unit_selectors_dictionary'][ $value['unit'] ] ); } /** * @param array $control * @param mixed $value * @param array $controls_stack * @param callable $value_callback * @param string $placeholder * @param string $parser_control_name * * @return string */ public function parse_property_placeholder( array $control, $value, array $controls_stack, $value_callback, $placeholder, $parser_control_name = null ) { if ( $parser_control_name ) { // If both the processed control and the control name found in the placeholder are responsive if ( ! empty( $control['responsive'] ) && ! empty( $controls_stack[ $parser_control_name ]['responsive'] ) ) { $device_suffix = Controls_Manager::get_responsive_control_device_suffix( $control ); $control = $controls_stack[ $parser_control_name . $device_suffix ] ?? $controls_stack[ $parser_control_name ]; } else { $control = $controls_stack[ $parser_control_name ]; } $value = call_user_func( $value_callback, $control ); } // If the control value is empty, check for global default. `0` (integer, string) are falsy but are valid values. if ( empty( $value ) && '0' !== $value && 0 !== $value ) { $value = $this->get_control_global_default_value( $control ); } if ( Controls_Manager::FONT === $control['type'] ) { $this->fonts[] = $value; } /** @var Base_Data_Control $control_obj */ $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] ); return (string) $control_obj->get_style_value( $placeholder, $value, $control ); } /** * Get the fonts. * * Retrieve the list of fonts. * * @since 1.9.0 * @access public * * @return array Fonts. */ public function get_fonts() { return $this->fonts; } /** * Get stylesheet. * * Retrieve the CSS file stylesheet instance. * * @since 1.2.0 * @access public * * @return Stylesheet The stylesheet object. */ public function get_stylesheet() { if ( ! $this->stylesheet_obj ) { $this->init_stylesheet(); } return $this->stylesheet_obj; } /** * Add controls stack style rules. * * Parse the CSS for all the elements inside any given controls stack. * * This method recursively renders the CSS for all the child elements in the stack. * * @since 1.6.0 * @access public * * @param Controls_Stack $controls_stack The controls stack. * @param array $controls Controls array. * @param array $values Values array. * @param array $placeholders Placeholders. * @param array $replacements Replacements. * @param array $all_controls All controls. */ public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, array $all_controls = null ) { if ( ! $all_controls ) { $all_controls = $controls_stack->get_controls(); } $parsed_dynamic_settings = $controls_stack->parse_dynamic_settings( $values, $controls ); foreach ( $controls as $control ) { if ( ! empty( $control['style_fields'] ) ) { $this->add_repeater_control_style_rules( $controls_stack, $control, $values[ $control['name'] ], $placeholders, $replacements ); } if ( ! empty( $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) { $this->add_dynamic_control_style_rules( $control, $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ); } if ( Controls_Manager::ICONS === $control['type'] ) { $this->icons_fonts[] = $values[ $control['name'] ]['library']; } if ( ! empty( $parsed_dynamic_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) { // Dynamic CSS should not be added to the CSS files. // Instead it's handled by \Elementor\Core\DynamicTags\Dynamic_CSS // and printed in a style tag. $should_preserve_value = isset( $control['control_type'] ) && 'content' === $control['control_type']; if ( $should_preserve_value ) { $this->preserved_dynamic_style_values[ $control['name'] ] = $parsed_dynamic_settings[ $control['name'] ]; } unset( $parsed_dynamic_settings[ $control['name'] ] ); $this->dynamic_elements_ids[] = $controls_stack->get_id(); continue; } if ( empty( $control['selectors'] ) ) { continue; } $this->add_control_style_rules( $control, $parsed_dynamic_settings, $all_controls, $placeholders, $replacements ); } } /** * Get file handle ID. * * Retrieve the file handle ID. * * @since 1.2.0 * @access protected * @abstract * * @return string CSS file handle ID. */ abstract protected function get_file_handle_id(); /** * Render CSS. * * Parse the CSS. * * @since 1.2.0 * @access protected * @abstract */ abstract protected function render_css(); protected function get_default_meta() { return array_merge( parent::get_default_meta(), [ 'fonts' => array_unique( $this->fonts ), 'icons' => array_unique( $this->icons_fonts ), 'dynamic_elements_ids' => [], 'status' => '', ] ); } /** * Get enqueue dependencies. * * Retrieve the name of the stylesheet used by `wp_enqueue_style()`. * * @since 1.2.0 * @access protected * * @return array Name of the stylesheet. */ protected function get_enqueue_dependencies() { return []; } /** * Get inline dependency. * * Retrieve the name of the stylesheet used by `wp_add_inline_style()`. * * @since 1.2.0 * @access protected * * @return string Name of the stylesheet. */ protected function get_inline_dependency() { return ''; } /** * Is update required. * * Whether the CSS requires an update. When there are new schemes or settings * updates. * * @since 1.2.0 * @access protected * * @return bool True if the CSS requires an update, False otherwise. */ protected function is_update_required() { return false; } /** * Parse CSS. * * Parsing the CSS file. * * @since 1.2.0 * @access protected */ protected function parse_content() { Performance::set_use_style_controls( true ); $initial_responsive_controls_duplication_mode = Plugin::$instance->breakpoints->get_responsive_control_duplication_mode(); Plugin::$instance->breakpoints->set_responsive_control_duplication_mode( $this->get_responsive_control_duplication_mode() ); $this->render_css(); $name = $this->get_name(); /** * Parse CSS file. * * Fires when CSS file is parsed on Elementor. * * The dynamic portion of the hook name, `$name`, refers to the CSS file name. * * @since 2.0.0 * * @param Base $this The current CSS file. */ do_action( "elementor/css-file/{$name}/parse", $this ); Plugin::$instance->breakpoints->set_responsive_control_duplication_mode( $initial_responsive_controls_duplication_mode ); Performance::set_use_style_controls( false ); return $this->get_stylesheet()->__toString(); } /** * Add control style rules. * * Register new style rules for the control. * * @since 1.6.0 * @access private * * @param array $control The control. * @param array $values Values array. * @param array $controls The controls stack. * @param array $placeholders Placeholders. * @param array $replacements Replacements. */ protected function add_control_style_rules( array $control, array $values, array $controls, array $placeholders, array $replacements ) { $this->add_control_rules( $control, $controls, function( $control ) use ( $values ) { return $this->get_style_control_value( $control, $values ); }, $placeholders, $replacements, $values ); } /** * Get Control Global Default Value * * If the control has a global default value, and the corresponding global default setting is enabled, this method * fetches and returns the global default value. Otherwise, it returns null. * * @since 3.7.0 * @access private * * @param $control * @return string|null */ private function get_control_global_default_value( $control ) { if ( empty( $control['global']['default'] ) ) { return null; } // If the control value is empty, and the control has a global default set, fetch the global value and use it. $global_enabled = false; if ( 'color' === $control['type'] ) { $global_enabled = Plugin::$instance->kits_manager->is_custom_colors_enabled(); } elseif ( isset( $control['groupType'] ) && 'typography' === $control['groupType'] ) { $global_enabled = Plugin::$instance->kits_manager->is_custom_typography_enabled(); } $value = null; // Only apply the global default if Global Colors are enabled. if ( $global_enabled ) { $value = $this->get_selector_global_value( $control, $control['global']['default'] ); } return $value; } /** * Get style control value. * * Retrieve the value of the style control for any give control and values. * * It will retrieve the control name and return the style value. * * @since 1.6.0 * @access private * * @param array $control The control. * @param array $values Values array. * * @return mixed Style control value. */ private function get_style_control_value( array $control, array $values ) { if ( ! empty( $values['__globals__'][ $control['name'] ] ) ) { // When the control itself has no global value, but it refers to another control global value return $this->get_selector_global_value( $control, $values['__globals__'][ $control['name'] ] ); } $value = isset( $values[ $control['name'] ] ) ? $values[ $control['name'] ] : $this->preserved_dynamic_style_values[ $control['name'] ] ?? null; if ( isset( $control['selectors_dictionary'][ $value ] ) ) { $value = $control['selectors_dictionary'][ $value ]; } if ( ! is_numeric( $value ) && ! is_float( $value ) && empty( $value ) ) { return null; } return $value; } /** * Init stylesheet. * * Initialize CSS file stylesheet by creating a new `Stylesheet` object and register new * breakpoints for the stylesheet. * * @since 1.2.0 * @access private */ private function init_stylesheet() { $this->stylesheet_obj = new Stylesheet(); $active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints(); foreach ( $active_breakpoints as $breakpoint_name => $breakpoint ) { $this->stylesheet_obj->add_device( $breakpoint_name, $breakpoint->get_value() ); } } /** * Add repeater control style rules. * * Register new style rules for the repeater control. * * @since 2.0.0 * @access private * * @param Controls_Stack $controls_stack The control stack. * @param array $repeater_control The repeater control. * @param array $repeater_values Repeater values array. * @param array $placeholders Placeholders. * @param array $replacements Replacements. */ protected function add_repeater_control_style_rules( Controls_Stack $controls_stack, array $repeater_control, array $repeater_values, array $placeholders, array $replacements ) { $placeholders = array_merge( $placeholders, [ '{{CURRENT_ITEM}}' ] ); foreach ( $repeater_control['style_fields'] as $index => $item ) { $this->add_controls_stack_style_rules( $controls_stack, $item, $repeater_values[ $index ], $placeholders, array_merge( $replacements, [ '.elementor-repeater-item-' . $repeater_values[ $index ]['_id'] ] ), $repeater_control['fields'] ); } } /** * Add dynamic control style rules. * * Register new style rules for the dynamic control. * * @since 2.0.0 * @access private * * @param array $control The control. * @param string $value The value. */ protected function add_dynamic_control_style_rules( array $control, $value ) { Plugin::$instance->dynamic_tags->parse_tags_text( $value, $control, function( $id, $name, $settings ) { $tag = Plugin::$instance->dynamic_tags->create_tag( $id, $name, $settings ); if ( ! $tag instanceof Tag ) { return; } $this->add_controls_stack_style_rules( $tag, $this->get_style_controls( $tag ), $tag->get_active_settings(), [ '{{WRAPPER}}' ], [ '#elementor-tag-' . $id ] ); } ); } private function get_selector_global_value( $control, $global_key ) { $data = Plugin::$instance->data_manager_v2->run( $global_key ); if ( empty( $data['value'] ) ) { return null; } $global_args = explode( '?id=', $global_key ); $id = $global_args[1]; if ( ! empty( $control['groupType'] ) ) { $strings_to_replace = [ $control['groupPrefix'] ]; $active_breakpoint_keys = array_keys( Plugin::$instance->breakpoints->get_active_breakpoints() ); foreach ( $active_breakpoint_keys as $breakpoint ) { $strings_to_replace[] = '_' . $breakpoint; } $property_name = str_replace( $strings_to_replace, '', $control['name'] ); // TODO: This check won't retrieve the proper answer for array values (multiple controls). if ( empty( $data['value'][ Global_Typography::TYPOGRAPHY_GROUP_PREFIX . $property_name ] ) ) { return null; } $property_name = str_replace( '_', '-', $property_name ); $value = "var( --e-global-$control[groupType]-$id-$property_name )"; if ( $control['groupPrefix'] . 'font_family' === $control['name'] ) { $default_generic_fonts = Plugin::$instance->kits_manager->get_current_settings( 'default_generic_fonts' ); if ( $default_generic_fonts ) { $value .= ", $default_generic_fonts"; } } } else { $value = "var( --e-global-$control[type]-$id )"; } return $value; } final protected function get_active_controls( Controls_Stack $controls_stack, array $controls = null, array $settings = null ) { if ( ! $controls ) { $controls = $controls_stack->get_controls(); } if ( ! $settings ) { $settings = $controls_stack->get_controls_settings(); } if ( $this->is_global_parsing_supported() ) { $settings = $this->parse_global_settings( $settings, $controls ); } $active_controls = array_reduce( array_keys( $controls ), function( $active_controls, $control_key ) use ( $controls_stack, $controls, $settings ) { $control = $controls[ $control_key ]; if ( $controls_stack->is_control_visible( $control, $settings, $controls ) ) { $active_controls[ $control_key ] = $control; } return $active_controls; }, [] ); return $active_controls; } final public function get_style_controls( Controls_Stack $controls_stack, array $controls = null, array $settings = null ) { $controls = $this->get_active_controls( $controls_stack, $controls, $settings ); $style_controls = []; foreach ( $controls as $control_name => $control ) { $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] ); if ( ! $control_obj instanceof Base_Data_Control ) { continue; } $control = array_merge( $control_obj->get_settings(), $control ); if ( $control_obj instanceof Control_Repeater ) { $style_fields = []; foreach ( $controls_stack->get_settings( $control_name ) as $item ) { $style_fields[] = $this->get_style_controls( $controls_stack, $control['fields'], $item ); } $control['style_fields'] = $style_fields; } if ( ! empty( $control['selectors'] ) || ! empty( $control['dynamic'] ) || $this->is_global_control( $controls_stack, $control_name, $controls ) || ! empty( $control['style_fields'] ) ) { $style_controls[ $control_name ] = $control; } } return $style_controls; } private function parse_global_settings( array $settings, array $controls ) { foreach ( $controls as $control ) { $control_name = $control['name']; $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] ); if ( ! $control_obj instanceof Base_Data_Control ) { continue; } if ( $control_obj instanceof Control_Repeater ) { foreach ( $settings[ $control_name ] as & $field ) { $field = $this->parse_global_settings( $field, $control['fields'] ); } continue; } if ( empty( $control['global']['active'] ) ) { continue; } if ( empty( $settings['__globals__'][ $control_name ] ) ) { continue; } $settings[ $control_name ] = 'global'; } return $settings; } private function is_global_control( Controls_Stack $controls_stack, $control_name, $controls ) { $control = $controls[ $control_name ]; $control_global_key = $control_name; if ( ! empty( $control['groupType'] ) ) { $control_global_key = $control['groupPrefix'] . $control['groupType']; } if ( empty( $controls[ $control_global_key ]['global']['active'] ) ) { return false; } $globals = $controls_stack->get_settings( '__globals__' ); return ! empty( $globals[ $control_global_key ] ); } } files/css/global-css.php 0000644 00000010004 14717626151 0011202 0 ustar 00 <?php // TODO: Delete this file in v3.26.0 - It is not in use anymore [ED-15471]. namespace Elementor\Core\Files\CSS; use Elementor\Core\Kits\Manager; use Elementor\Plugin; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor global CSS file. * * Elementor CSS file handler class is responsible for generating the global CSS * file. * * @since 1.2.0 */ class Global_CSS extends Base { /** * Elementor global CSS file handler ID. */ const FILE_HANDLER_ID = 'elementor-global'; const META_KEY = '_elementor_global_css'; /** * Get CSS file name. * * Retrieve the CSS file name. * * @since 1.6.0 * @access public * * @return string CSS file name. */ public function get_name() { return 'global'; } /** * Get file handle ID. * * Retrieve the handle ID for the global post CSS file. * * @since 1.2.0 * @access protected * * @return string CSS file handle ID. */ protected function get_file_handle_id() { return self::FILE_HANDLER_ID; } /** * Render CSS. * * Parse the CSS for all the widgets and all the scheme controls. * * @since 1.2.0 * @access protected */ protected function render_css() { $this->render_schemes_and_globals_css(); } /** * Get inline dependency. * * Retrieve the name of the stylesheet used by `wp_add_inline_style()`. * * @since 1.2.0 * @access protected * * @return string Name of the stylesheet. */ protected function get_inline_dependency() { return 'elementor-frontend'; } /** * Is update required. * * Whether the CSS requires an update. When there are new schemes or settings * updates. * * @since 1.2.0 * @access protected * * @return bool True if the CSS requires an update, False otherwise. */ protected function is_update_required() { return $this->get_meta( 'time' ) < get_option( Settings::UPDATE_TIME_FIELD ); } /** * Render schemes CSS. * * Parse the CSS for all the widgets and all the scheme controls. * * @since 1.2.0 * @access private */ private function render_schemes_and_globals_css() { $elementor = Plugin::$instance; /** @var Manager $module */ $kits_manager = Plugin::$instance->kits_manager; $custom_colors_enabled = $kits_manager->is_custom_colors_enabled(); $custom_typography_enabled = $kits_manager->is_custom_typography_enabled(); // If both default colors and typography are disabled, there is no need to render schemes and default global css. if ( ! $custom_colors_enabled && ! $custom_typography_enabled ) { return; } foreach ( $elementor->widgets_manager->get_widget_types() as $widget ) { $controls = $widget->get_controls(); $global_controls = []; $global_values['__globals__'] = []; foreach ( $controls as $control ) { $is_color_control = 'color' === $control['type']; $is_typography_control = isset( $control['groupType'] ) && 'typography' === $control['groupType']; // If it is a color/typography control and default colors/typography are disabled, // don't add the default CSS. if ( ( $is_color_control && ! $custom_colors_enabled ) || ( $is_typography_control && ! $custom_typography_enabled ) ) { continue; } $global_control = $control; // Handle group controls that don't have a default global property. if ( ! empty( $control['groupType'] ) ) { $global_control = $controls[ $control['groupPrefix'] . $control['groupType'] ]; } // If the control has a default global defined, add it to the globals array // that is used in add_control_rules. if ( ! empty( $control['global']['default'] ) ) { $global_values['__globals__'][ $control['name'] ] = $global_control['global']['default']; } if ( ! empty( $global_control['global']['default'] ) ) { $global_controls[] = $control; } } foreach ( $global_controls as $control ) { $this->add_control_rules( $control, $controls, function( $control ) {}, [ '{{WRAPPER}}' ], [ '.elementor-widget-' . $widget->get_name() ], $global_values ); } } } } files/css/post-local-cache.php 0000644 00000001366 14717626151 0012305 0 ustar 00 <?php namespace Elementor\Core\Files\CSS; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Post_Local_Cache extends Post { /** * Meta cache * * @var array */ private $meta_cache = []; abstract protected function get_post_id_for_data(); public function is_update_required() { return true; } protected function load_meta() { return $this->meta_cache; } protected function delete_meta() { $this->meta_cache = []; } protected function update_meta( $meta ) { $this->meta_cache = $meta; } protected function get_data() { $document = Plugin::$instance->documents->get( $this->get_post_id_for_data() ); return $document ? $document->get_elements_data() : []; } } files/css/post.php 0000644 00000021662 14717626151 0010155 0 ustar 00 <?php namespace Elementor\Core\Files\CSS; use Elementor\Controls_Stack; use Elementor\Core\DynamicTags\Dynamic_CSS; use Elementor\Core\Kits\Manager; use Elementor\Element_Base; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor post CSS file. * * Elementor CSS file handler class is responsible for generating the single * post CSS file. * * @since 1.2.0 */ class Post extends Base { /** * Elementor post CSS file prefix. */ const FILE_PREFIX = 'post-'; const META_KEY = '_elementor_css'; /** * Post ID. * * Holds the current post ID. * * @var int */ private $post_id; protected function is_global_parsing_supported() { return true; } /** * Post CSS file constructor. * * Initializing the CSS file of the post. Set the post ID and initiate the stylesheet. * * @since 1.2.0 * @access public * * @param int $post_id Post ID. */ public function __construct( $post_id ) { $this->post_id = $post_id; parent::__construct( static::FILE_PREFIX . $post_id . '.css' ); } /** * Get CSS file name. * * Retrieve the CSS file name. * * @since 1.6.0 * @access public * * @return string CSS file name. */ public function get_name() { return 'post'; } /** * Get post ID. * * Retrieve the ID of current post. * * @since 1.2.0 * @access public * * @return int Post ID. */ public function get_post_id() { return $this->post_id; } /** * Get unique element selector. * * Retrieve the unique selector for any given element. * * @since 1.2.0 * @access public * * @param Element_Base $element The element. * * @return string Unique element selector. */ public function get_element_unique_selector( Element_Base $element ) { return '.elementor-' . $this->post_id . ' .elementor-element' . $element->get_unique_selector(); } /** * Load meta data. * * Retrieve the post CSS file meta data. * * @since 1.2.0 * @access protected * * @return array Post CSS file meta data. */ protected function load_meta() { return get_post_meta( $this->post_id, static::META_KEY, true ); } /** * Update meta data. * * Update the global CSS file meta data. * * @since 1.2.0 * @access protected * * @param array $meta New meta data. */ protected function update_meta( $meta ) { update_post_meta( $this->post_id, static::META_KEY, $meta ); } /** * Delete meta. * * Delete the file meta data. * * @since 2.1.0 * @access protected */ protected function delete_meta() { delete_post_meta( $this->post_id, static::META_KEY ); } /** * Get post data. * * Retrieve raw post data from the database. * * @since 1.9.0 * @access protected * * @return array Post data. */ protected function get_data() { $document = Plugin::$instance->documents->get( $this->post_id ); return $document ? $document->get_elements_data() : []; } /** * Render CSS. * * Parse the CSS for all the elements. * * @since 1.2.0 * @access protected */ protected function render_css() { $data = $this->get_data(); if ( ! empty( $data ) ) { foreach ( $data as $element_data ) { $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( ! $element ) { continue; } $this->render_styles( $element ); } } } /** * Enqueue CSS. * * Enqueue the post CSS file in Elementor. * * This method ensures that the post was actually built with elementor before * enqueueing the post CSS file. * * @since 1.2.2 * @access public */ public function enqueue() { $document = Plugin::$instance->documents->get( $this->post_id ); if ( ! $document || ! $document->is_built_with_elementor() ) { return; } parent::enqueue(); } /** * Add controls-stack style rules. * * Parse the CSS for all the elements inside any given controls stack. * * This method recursively renders the CSS for all the child elements in the stack. * * @since 1.6.0 * @access public * * @param Controls_Stack $controls_stack The controls stack. * @param array $controls Controls array. * @param array $values Values array. * @param array $placeholders Placeholders. * @param array $replacements Replacements. * @param array $all_controls All controls. */ public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, array $all_controls = null ) { parent::add_controls_stack_style_rules( $controls_stack, $controls, $values, $placeholders, $replacements, $all_controls ); if ( $controls_stack instanceof Element_Base ) { foreach ( $controls_stack->get_children() as $child_element ) { $this->render_styles( $child_element ); } } } /** * Get enqueue dependencies. * * Retrieve the name of the stylesheet used by `wp_enqueue_style()`. * * @since 1.2.0 * @access protected * * @return array Name of the stylesheet. */ protected function get_enqueue_dependencies() { return [ 'elementor-frontend' ]; } /** * Get inline dependency. * * Retrieve the name of the stylesheet used by `wp_add_inline_style()`. * * @since 1.2.0 * @access protected * * @return string Name of the stylesheet. */ protected function get_inline_dependency() { return 'elementor-frontend'; } /** * Get file handle ID. * * Retrieve the handle ID for the post CSS file. * * @since 1.2.0 * @access protected * * @return string CSS file handle ID. */ protected function get_file_handle_id() { return 'elementor-post-' . $this->post_id; } /** * Render styles. * * Parse the CSS for any given element. * * @since 1.2.0 * @access protected * * @param Element_Base $element The element. */ protected function render_styles( Element_Base $element ) { /** * Before element parse CSS. * * Fires before the CSS of the element is parsed. * * @since 1.2.0 * * @param Post $this The post CSS file. * @param Element_Base $element The element. */ do_action( 'elementor/element/before_parse_css', $this, $element ); $this->render_element_global_styles( $element ); $this->render_element_styles( $element ); /** * After element parse CSS. * * Fires after the CSS of the element is parsed. * * @since 1.2.0 * * @param Post $this The post CSS file. * @param Element_Base $element The element. */ do_action( 'elementor/element/parse_css', $this, $element ); } private function render_element_styles( Element_Base $element ) { $this->add_controls_stack_style_rules( $element, $this->get_style_controls( $element, null, $element->get_parsed_dynamic_settings() ), $element->get_settings(), [ '{{ID}}', '{{WRAPPER}}' ], [ $element->get_id(), $this->get_element_unique_selector( $element ) ] ); } private function render_element_global_styles( Element_Base $element ) { if ( $this instanceof Dynamic_CSS ) { return; } /** @var Manager $module */ $kits_manager = Plugin::$instance->kits_manager; $custom_colors_enabled = $kits_manager->is_custom_colors_enabled(); $custom_typography_enabled = $kits_manager->is_custom_typography_enabled(); $controls = $element->get_controls(); $global_controls = []; $global_values['__globals__'] = []; foreach ( $controls as $control ) { $this->build_global_controls_and_values( $control, $controls, $global_controls, $global_values, $custom_colors_enabled, $custom_typography_enabled ); } foreach ( $global_controls as $control ) { $this->add_control_rules( $control, $controls, function( $control ) {}, [ '{{WRAPPER}}' ], [ '.elementor-widget-' . $element->get_name() ], $global_values ); } } private function build_global_controls_and_values( $control, $controls, &$global_controls, &$global_values, $custom_colors_enabled, $custom_typography_enabled ) { $is_color_control = 'color' === $control['type']; $is_typography_control = isset( $control['groupType'] ) && 'typography' === $control['groupType']; // If it is a color/typography control and default colors/typography are disabled, // don't add the default CSS. if ( ( $is_color_control && ! $custom_colors_enabled ) || ( $is_typography_control && ! $custom_typography_enabled ) ) { return; } $global_control = $control; // Handle group controls that don't have a default global property. if ( ! empty( $control['groupType'] ) ) { $global_control = $controls[ $control['groupPrefix'] . $control['groupType'] ]; } // If the control has a default global defined, add it to the globals array // that is used in add_control_rules. if ( ! empty( $control['global']['default'] ) ) { $global_values['__globals__'][ $control['name'] ] = $global_control['global']['default']; } if ( ! empty( $global_control['global']['default'] ) ) { $global_controls[] = $control; } } } files/css/post-preview.php 0000644 00000002375 14717626151 0011634 0 ustar 00 <?php namespace Elementor\Core\Files\CSS; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor post preview CSS file. * * Elementor CSS file handler class is responsible for generating the post * preview CSS file. * * @since 1.9.0 */ class Post_Preview extends Post_Local_Cache { /** * Preview ID. * * Holds the ID of the current post being previewed. * * @var int */ private $post_id_for_data; /** * Post preview CSS file constructor. * * Initializing the CSS file of the post preview. Set the post ID and the * parent ID and initiate the stylesheet. * * @since 1.9.0 * @access public * * @param int $post_id Post ID. */ public function __construct( $post_id ) { $this->post_id_for_data = $post_id; $parent_id = wp_get_post_parent_id( $post_id ); parent::__construct( $parent_id ); } protected function get_post_id_for_data() { return $this->post_id_for_data; } /** * Get file handle ID. * * Retrieve the handle ID for the previewed post CSS file. * * @since 1.9.0 * @access protected * * @return string CSS file handle ID. */ protected function get_file_handle_id() { return 'elementor-preview-' . $this->get_post_id_for_data(); } } files/assets/json/json-handler.php 0000644 00000001302 14717626151 0013224 0 ustar 00 <?php namespace Elementor\Core\Files\Assets\Json; use Elementor\Core\Files\Assets\Files_Upload_Handler; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Json Handler * * @deprecated 3.5.0 Use `Elementor\Core\Files\File_Types\Svg` instead, accessed by calling `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );` */ class Json_Handler extends Files_Upload_Handler { /** * @deprecated 3.5.0 */ public static function get_name() { return 'json-handler'; } /** * @deprecated 3.5.0 */ public function get_mime_type() { return 'application/json'; } /** * @deprecated 3.5.0 */ public function get_file_type() { return 'json'; } } files/assets/manager.php 0000644 00000002325 14717626151 0011307 0 ustar 00 <?php namespace Elementor\Core\Files\Assets; use Elementor\Core\Files\Assets\Svg\Svg_Handler; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor files manager. * * Elementor files manager handler class is responsible for creating files. * * @since 2.6.0 */ class Manager { /** * Holds registered asset types * @var array */ protected $asset_types = []; /** * Assets manager constructor. * * Initializing the Elementor assets manager. * * @access public */ public function __construct() { $this->register_asset_types(); /** * Elementor files assets registered. * * Fires after Elementor registers assets types * * @since 2.6.0 */ do_action( 'elementor/core/files/assets/assets_registered', $this ); } public function get_asset( $name ) { return isset( $this->asset_types[ $name ] ) ? $this->asset_types[ $name ] : false; } /** * Add Asset * @param $instance */ public function add_asset( $instance ) { $this->asset_types[ $instance::get_name() ] = $instance; } /** * Register Asset Types * * Registers Elementor Asset Types */ private function register_asset_types() { $this->add_asset( new Svg_Handler() ); } } files/assets/svg/svg-handler.php 0000644 00000015541 14717626151 0012712 0 ustar 00 <?php namespace Elementor\Core\Files\Assets\Svg; use Elementor\Core\Files\Assets\Files_Upload_Handler; use Elementor\Core\Files\File_Types\Svg; use Elementor\Core\Files\Uploads_Manager; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * SVG Handler * * @deprecated 3.5.0 Use `Elementor\Core\Files\File_Types\Svg` instead, accessed by calling: `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );` */ class Svg_Handler extends Files_Upload_Handler { /** * Inline svg attachment meta key * * @deprecated 3.5.0 */ const META_KEY = '_elementor_inline_svg'; /** * @deprecated 3.5.0 */ const SCRIPT_REGEX = '/(?:\w+script|data):/xi'; /** * Attachment ID. * * Holds the current attachment ID. * * @deprecated 3.5.0 * * @var int */ private $attachment_id; /** * @deprecated 3.5.0 */ public static function get_name() { return 'svg-handler'; } /** * get_meta * * @deprecated 3.5.0 * * @return mixed */ protected function get_meta() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' ); return get_post_meta( $this->attachment_id, self::META_KEY, true ); } /** * update_meta * * @deprecated 3.5.0 * * @param $meta */ protected function update_meta( $meta ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' ); update_post_meta( $this->attachment_id, self::META_KEY, $meta ); } /** * delete_meta * * @deprecated 3.5.0 */ protected function delete_meta() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' ); delete_post_meta( $this->attachment_id, self::META_KEY ); } /** * @deprecated 3.5.0 */ public function get_mime_type() { return 'image/svg+xml'; } /** * @deprecated 3.5.0 */ public function get_file_type() { return 'svg'; } /** * delete_meta_cache * * @deprecated 3.5.0 Use `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' )->delete_meta_cache()` instead. */ public function delete_meta_cache() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Plugin::$instance->uploads_manager->get_file_type_handlers( \'svg\' )->delete_meta_cache()' ); /** @var Svg $svg_handler */ $svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' ); $svg_handler->delete_meta_cache(); } /** * get_inline_svg * * @deprecated 3.5.0 Use `Elementor\Core\Files\File_Types\Svg::get_inline_svg()` instead. * * @param $attachment_id * * @return bool|mixed|string */ public static function get_inline_svg( $attachment_id ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Elementor\Core\Files\File_Types\Svg::get_inline_svg()' ); return Svg::get_inline_svg( $attachment_id ); } /** * sanitize_svg * * @deprecated 3.5.0 Use `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' )->delete_meta_cache()->sanitize_svg()` instead. * * @param $filename * * @return bool */ public function sanitize_svg( $filename ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Plugin::$instance->uploads_manager->get_file_type_handlers( \'svg\' )->delete_meta_cache()->sanitize_svg()' ); /** @var Svg $svg_handler */ $svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' ); return $svg_handler->sanitize_svg( $filename ); } /** * sanitizer * * @deprecated 3.5.0 Use `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' )->sanitizer()` instead. * * @param $content * * @return bool|string */ public function sanitizer( $content ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Plugin::$instance->uploads_manager->get_file_type_handlers( \'svg\' )->sanitizer()' ); /** @var Svg $svg_handler */ $svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' ); return $svg_handler->sanitizer( $content ); } /** * wp_prepare_attachment_for_js * * @deprecated 3.5.0 Use `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' )->wp_prepare_attachment_for_js()` instead. * * @param $attachment_data * @param $attachment * @param $meta * * @return mixed */ public function wp_prepare_attachment_for_js( $attachment_data, $attachment, $meta ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Plugin::$instance->uploads_manager->get_file_type_handlers( \'svg\' )->wp_prepare_attachment_for_js()' ); /** @var Svg $svg_handler */ $svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' ); return $svg_handler->wp_prepare_attachment_for_js( $attachment_data, $attachment, $meta ); } /** * set_attachment_id * * @deprecated 3.5.0 * * @param $attachment_id * * @return int */ public function set_attachment_id( $attachment_id ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' ); $this->attachment_id = $attachment_id; return $this->attachment_id; } /** * get_attachment_id * * @deprecated 3.5.0 * * @return int */ public function get_attachment_id() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' ); return $this->attachment_id; } /** * set_svg_meta_data * * @deprecated 3.5.0 Use `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' )->set_svg_meta_data()` instead. * * @return mixed */ public function set_svg_meta_data( $data, $id ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Plugin::$instance->uploads_manager->get_file_type_handlers( \'svg\' )->set_svg_meta_data()' ); /** @var Svg $svg_handler */ $svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' ); return $svg_handler->set_svg_meta_data( $data, $id ); } /** * handle_upload_prefilter * * @deprecated 3.5.0 Use `Elementor\Plugin::$instance->uploads_manager->handle_elementor_wp_media_upload()` instead. * * @param $file * * @return mixed */ public function handle_upload_prefilter( $file ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Elementor\Plugin::$instance->uploads_manager->handle_elementor_wp_media_upload()' ); return Plugin::$instance->uploads_manager->handle_elementor_wp_media_upload( $file ); } } files/assets/files-upload-handler.php 0000644 00000010527 14717626151 0013677 0 ustar 00 <?php namespace Elementor\Core\Files\Assets; use Elementor\Core\Files\File_Types\Svg; use Elementor\Core\Files\Uploads_Manager; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Files Upload Handler * * @deprecated 3.5.0 Use `Elementor\Core\Files\Uploads_Manager` class instead. */ abstract class Files_Upload_Handler { /** * @deprecated 3.5.0 */ const OPTION_KEY = 'elementor_unfiltered_files_upload'; /** * @deprecated 3.5.0 */ abstract public function get_mime_type(); /** * @deprecated 3.5.0 */ abstract public function get_file_type(); /** * Is Elementor Media Upload * * @deprecated 3.5.0 Use `Elementor\Plugin::$instance->uploads_manager->are_unfiltered_uploads_enabled()` instead. * * @return bool */ private function is_elementor_media_upload() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Elementor\Plugin::$instance->uploads_manager->are_unfiltered_uploads_enabled()' ); return Plugin::$instance->uploads_manager->is_elementor_media_upload(); } /** * Is Enabled * * @deprecated 3.5.0 Use `Elementor\Plugin::$instance->uploads_manager->are_unfiltered_uploads_enabled()` instead. * * @return bool */ final public static function is_enabled() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Elementor\Plugin::$instance->uploads_manager->are_unfiltered_uploads_enabled()' ); return Plugin::$instance->uploads_manager->are_unfiltered_uploads_enabled(); } /** * @deprecated 3.5.0 Use `Elementor\Plugin::$instance->uploads_manager->are_unfiltered_uploads_enabled()` instead. */ final public function support_unfiltered_files_upload( $existing_mimes ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Elementor\Plugin::$instance->uploads_manager->support_unfiltered_file_uploads()' ); return Plugin::$instance->uploads_manager->support_unfiltered_elementor_file_uploads( $existing_mimes ); } /** * handle_upload_prefilter * * @deprecated 3.5.0 Use `Elementor\Plugin::$instance->uploads_manager->handle_elementor_wp_media_upload()` instead. * * @param $file * * @return mixed */ public function handle_upload_prefilter( $file ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Elementor\Plugin::$instance->uploads_manager->handle_elementor_wp_media_upload()' ); return Plugin::$instance->uploads_manager->handle_elementor_wp_media_upload( $file ); } /** * is_file_should_handled * * @deprecated 3.5.0 * * @param $file * * @return bool */ protected function is_file_should_handled( $file ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' ); $ext = pathinfo( $file['name'], PATHINFO_EXTENSION ); return $this->is_elementor_media_upload() && $this->get_file_type() === $ext; } /** * file_sanitizer_can_run * * @deprecated 3.5.0 Use `Elementor\Core\Files\File_Types\Svg::file_sanitizer_can_run()` instead. * * @return bool */ public static function file_sanitizer_can_run() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Elementor\Core\Files\File_Types\Svg::file_sanitizer_can_run()' ); return Svg::file_sanitizer_can_run(); } /** * Check filetype and ext * * A workaround for upload validation which relies on a PHP extension (fileinfo) * with inconsistent reporting behaviour. * ref: https://core.trac.wordpress.org/ticket/39550 * ref: https://core.trac.wordpress.org/ticket/40175 * * @deprecated 3.5.0 Use `Elementor\Plugin::$instance->uploads_manager->check_filetype_and_ext()` instead. * * @param $data * @param $file * @param $filename * @param $mimes * * @return mixed */ public function check_filetype_and_ext( $data, $file, $filename, $mimes ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Elementor\Plugin::$instance->uploads_manager->check_filetype_and_ext()' ); Plugin::$instance->uploads_manager->check_filetype_and_ext( $data, $file, $filename, $mimes ); } } logger/log-reporter.php 0000644 00000006005 14717626151 0011170 0 ustar 00 <?php namespace Elementor\Core\Logger; use Elementor\Modules\System_Info\Reporters\Base; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Elementor Log reporter. * * Elementor log reporter handler class is responsible for generating the * debug reports. * * @since 2.4.0 */ class Log_Reporter extends Base { const MAX_ENTRIES = 20; const CLEAR_LOG_ACTION = 'elementor-clear-log'; public function get_title() { return esc_html__( 'Log', 'elementor' ); } public function get_fields() { return [ 'log_entries' => '', ]; } public function print_html_label( $log_label ) { $title = $this->get_title(); if ( empty( $_GET[ self::CLEAR_LOG_ACTION ] ) ) { // phpcs:ignore -- nonce validation is not require here. $nonce = wp_create_nonce( self::CLEAR_LOG_ACTION ); $url = add_query_arg( [ self::CLEAR_LOG_ACTION => 1, '_wpnonce' => $nonce, ] ); $title .= '<a href="' . esc_url( $url ) . '#elementor-clear-log" class="box-title-tool">' . esc_html__( 'Clear Log', 'elementor' ) . '</a>'; $title .= '<span id="elementor-clear-log"></span>'; } parent::print_html_label( $title ); } public function get_log_entries() { /** @var \Elementor\Core\Logger\Manager $manager */ $manager = Manager::instance(); /** @var \Elementor\Core\Logger\Loggers\Db $logger */ $logger = $manager->get_logger( 'db' ); if ( ! empty( $_GET[ self::CLEAR_LOG_ACTION ] ) ) { $nonce = Utils::get_super_global_value( $_GET, '_wpnonce' ); if ( ! wp_verify_nonce( $nonce, self::CLEAR_LOG_ACTION ) ) { wp_die( 'Invalid Nonce', 'Invalid Nonce', [ 'back_link' => true, ] ); } $logger->clear(); } $log_string = 'No entries to display'; $log_entries = $logger->get_formatted_log_entries( self::MAX_ENTRIES, false ); if ( ! empty( $log_entries ) ) { $entries_string = ''; foreach ( $log_entries as $key => $log_entry ) { if ( $log_entry['count'] ) { $entries_string .= '<h3>' . sprintf( '%s: showing %s of %s', $key, $log_entry['count'], $log_entry['total_count'] ) . '</h3>'; $entries_string .= '<div class="elementor-log-entries">' . $log_entry['entries'] . '</div>'; } } if ( ! empty( $entries_string ) ) { $log_string = $entries_string; } } return [ 'value' => $log_string, ]; } public function get_raw_log_entries() { $log_string = 'No entries to display'; /** @var \Elementor\Core\Logger\Manager $manager */ $manager = Manager::instance(); $logger = $manager->get_logger(); $log_entries = $logger->get_formatted_log_entries( self::MAX_ENTRIES, false ); if ( ! empty( $log_entries ) ) { $entries_string = PHP_EOL; foreach ( $log_entries as $key => $log_entry ) { if ( $log_entry['count'] ) { $entries_string .= sprintf( '%s: showing %s of %s', $key, $log_entry['count'], $log_entry['total_count'] ) . $log_entry['entries'] . PHP_EOL; } } if ( ! empty( $entries_string ) ) { $log_string = $entries_string; } } return [ 'value' => $log_string, ]; } } logger/manager.php 0000644 00000015661 14717626151 0010171 0 ustar 00 <?php namespace Elementor\Core\Logger; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\Ajax\Module; use Elementor\Core\Editor\Editor; use Elementor\Core\Logger\Loggers\Logger_Interface; use Elementor\Core\Logger\Items\PHP; use Elementor\Core\Logger\Items\JS; use Elementor\Plugin; use Elementor\Modules\System_Info\Module as System_Info; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Manager extends BaseModule { protected $loggers = []; protected $default_logger = ''; public function get_name() { return 'log'; } public function shutdown( $last_error = null, $should_exit = false ) { if ( ! $last_error ) { $last_error = error_get_last(); } if ( ! $last_error ) { return; } if ( empty( $last_error['file'] ) ) { return; } if ( ! Utils::is_elementor_path( $last_error['file'] ) ) { return; } $last_error['type'] = $this->get_log_type_from_php_error( $last_error['type'] ); $last_error['trace'] = true; $item = new PHP( $last_error ); $this->get_logger()->log( $item ); if ( $should_exit ) { exit; } } public function rest_error_handler( $error_number, $error_message, $error_file, $error_line ) { // Temporary solution until all PHP notices will be fixed in the core and pro. if ( Utils::is_wp_cli() ) { return null; } $error = new \WP_Error( $error_number, $error_message, [ 'type' => $error_number, 'message' => $error_message, 'file' => $error_file, 'line' => $error_line, ] ); if ( ! Utils::is_elementor_path( $error_file ) ) { // Do execute PHP internal error handler. return false; } $is_an_error = in_array( // It can be notice or warning $error_number, [ E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR ], true ); $error_data = $error->get_error_data(); // TODO: This part should be modular, temporary hard-coded. // Notify $e.data. if ( $is_an_error && ! headers_sent() ) { header( 'Content-Type: application/json; charset=UTF-8' ); http_response_code( 500 ); if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { echo wp_json_encode( $error_data ); } else { echo wp_json_encode( [ 'message' => 'Server error, see Elementor => System Info', ] ); } } $this->shutdown( $error_data, $is_an_error ); } public function register_error_handler() { set_error_handler( [ $this, 'rest_error_handler' ], E_ALL ); } public function add_system_info_report() { System_Info::add_report( 'log', [ 'file_name' => __DIR__ . '/log-reporter.php', 'class_name' => __NAMESPACE__ . '\Log_Reporter', ] ); } /** * Javascript log. * * Log Elementor errors and save them in the database. * * Fired by `wp_ajax_elementor_js_log` action. * */ public function js_log() { /** @var Module $ajax */ $ajax = Plugin::$instance->common->get_component( 'ajax' ); // PHPCS ignore is added throughout this method because nonce verification happens in the $ajax->verify_request_nonce() method. if ( ! $ajax->verify_request_nonce() || empty( $_POST['data'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing wp_send_json_error(); } if ( ! current_user_can( Editor::EDITING_CAPABILITY ) ) { wp_send_json_error( 'Permission denied' ); } // PHPCS - See comment above. $data = Utils::get_super_global_value( $_POST, 'data' ) ?? []; // phpcs:ignore WordPress.Security.NonceVerification.Missing array_walk_recursive( $data, function( &$value ) { $value = sanitize_text_field( $value ); } ); // PHPCS - See comment above. foreach ( $data as $error ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $error['type'] = Logger_Interface::LEVEL_ERROR; if ( ! empty( $error['customFields'] ) ) { $error['meta'] = $error['customFields']; } $item = new JS( $error ); $this->get_logger()->log( $item ); } wp_send_json_success(); } public function register_logger( $name, $class ) { $this->loggers[ $name ] = $class; } public function set_default_logger( $name ) { if ( ! empty( $this->loggers[ $name ] ) ) { $this->default_logger = $name; } } public function register_default_loggers() { $this->register_logger( 'db', 'Elementor\Core\Logger\Loggers\Db' ); $this->set_default_logger( 'db' ); } /** * @param string $name * * @return Logger_Interface */ public function get_logger( $name = '' ) { $this->register_loggers(); if ( empty( $name ) || ! isset( $this->loggers[ $name ] ) ) { $name = $this->default_logger; } if ( ! $this->get_component( $name ) ) { $this->add_component( $name, new $this->loggers[ $name ]() ); } return $this->get_component( $name ); } /** * @param string $message * @param array $args * * @return void */ public function log( $message, $args = [] ) { $this->get_logger()->log( $message, $args ); } /** * @param string $message * @param array $args * * @return void */ public function info( $message, $args = [] ) { $this->get_logger()->info( $message, $args ); } /** * @param string $message * @param array $args * * @return void */ public function notice( $message, $args = [] ) { $this->get_logger()->notice( $message, $args ); } /** * @param string $message * @param array $args * * @return void */ public function warning( $message, $args = [] ) { $this->get_logger()->warning( $message, $args ); } /** * @param string $message * @param array $args * * @return void */ public function error( $message, $args = [] ) { $this->get_logger()->error( $message, $args ); } private function get_log_type_from_php_error( $type ) { $error_map = [ E_CORE_ERROR => Logger_Interface::LEVEL_ERROR, E_ERROR => Logger_Interface::LEVEL_ERROR, E_USER_ERROR => Logger_Interface::LEVEL_ERROR, E_COMPILE_ERROR => Logger_Interface::LEVEL_ERROR, E_RECOVERABLE_ERROR => Logger_Interface::LEVEL_ERROR, E_PARSE => Logger_Interface::LEVEL_ERROR, E_STRICT => Logger_Interface::LEVEL_ERROR, E_WARNING => Logger_Interface::LEVEL_WARNING, E_USER_WARNING => Logger_Interface::LEVEL_WARNING, E_CORE_WARNING => Logger_Interface::LEVEL_WARNING, E_COMPILE_WARNING => Logger_Interface::LEVEL_WARNING, E_NOTICE => Logger_Interface::LEVEL_NOTICE, E_USER_NOTICE => Logger_Interface::LEVEL_NOTICE, E_DEPRECATED => Logger_Interface::LEVEL_NOTICE, E_USER_DEPRECATED => Logger_Interface::LEVEL_NOTICE, ]; return isset( $error_map[ $type ] ) ? $error_map[ $type ] : Logger_Interface::LEVEL_ERROR; } private function register_loggers() { if ( ! did_action( 'elementor/loggers/register' ) ) { do_action( 'elementor/loggers/register', $this ); } } public function __construct() { register_shutdown_function( [ $this, 'shutdown' ] ); add_action( 'admin_init', [ $this, 'add_system_info_report' ], 80 ); add_action( 'wp_ajax_elementor_js_log', [ $this, 'js_log' ] ); add_action( 'elementor/loggers/register', [ $this, 'register_default_loggers' ] ); } } logger/loggers/base.php 0000644 00000004520 14717626151 0011123 0 ustar 00 <?php namespace Elementor\Core\Logger\Loggers; use Elementor\Core\Logger\Items\Base as Log_Item; use Elementor\Core\Logger\Items\Log_Item_Interface as Log_Item_Interface; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Base implements Logger_Interface { abstract protected function save_log( Log_Item_Interface $item ); /** * @return Log_Item_Interface[] */ abstract public function get_log(); public function log( $item, $type = self::LEVEL_INFO, $args = [] ) { if ( ! $item instanceof Log_Item ) { $item = $this->create_item( $item, $type, $args ); } $this->save_log( $item ); } public function info( $message, $args = [] ) { $this->log( $message, self::LEVEL_INFO, $args ); } public function notice( $message, $args = [] ) { $this->log( $message, self::LEVEL_NOTICE, $args ); } public function warning( $message, $args = [] ) { $this->log( $message, self::LEVEL_WARNING, $args ); } public function error( $message, $args = [] ) { $this->log( $message, self::LEVEL_ERROR, $args ); } /** * @param string $message * @param string $type * @param array $args * * @return Log_Item_Interface */ private function create_item( $message, $type, $args = [] ) { $args['message'] = $message; $args['type'] = $type; $item = new Log_Item( $args ); return $item; } public function get_formatted_log_entries( $max_entries, $table = true ) { $entries = $this->get_log(); if ( empty( $entries ) ) { return [ 'All' => [ 'total_count' => 0, 'count' => 0, 'entries' => '', ], ]; } $sorted_entries = []; $open_tag = $table ? '<tr><td>' : ''; $close_tab = $table ? '</td></tr>' : PHP_EOL; $format = $table ? 'html' : 'raw'; foreach ( $entries as $entry ) { /** @var Log_Item $entry */ $sorted_entries[ $entry->get_name() ][] = $open_tag . $entry->format( $format ) . $close_tab; } $formatted_entries = []; foreach ( $sorted_entries as $key => $sorted_entry ) { $formatted_entries[ $key ]['total_count'] = count( $sorted_entry ); $formatted_entries[ $key ]['count'] = count( $sorted_entry ); $sorted_entry = array_slice( $sorted_entry, -$max_entries ); $formatted_entries[ $key ]['count'] = count( $sorted_entry ); $formatted_entries[ $key ]['entries'] = implode( $sorted_entry ); } return $formatted_entries; } } logger/loggers/db.php 0000644 00000002006 14717626151 0010573 0 ustar 00 <?php namespace Elementor\Core\Logger\Loggers; use Elementor\Core\Logger\Items\Log_Item_Interface as Log_Item; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Db extends Base { public function save_log( Log_Item $item ) { $log = $this->maybe_truncate_log(); $id = $item->get_fingerprint(); if ( empty( $log[ $id ] ) ) { $log[ $id ] = $item; } $log[ $id ]->increase_times( $item ); update_option( self::LOG_NAME, $log, 'no' ); } public function clear() { delete_option( self::LOG_NAME ); } private function maybe_truncate_log() { /** @var Log_Item[] $log */ $log = $this->get_log(); if ( Log_Item::MAX_LOG_ENTRIES < count( $log ) ) { $log = array_slice( $log, -Log_Item::MAX_LOG_ENTRIES ); } return $log; } public function get_log() { // Clear cache. wp_cache_delete( self::LOG_NAME, 'options' ); $log = get_option( self::LOG_NAME, [] ); // In case the DB log is corrupted. if ( ! is_array( $log ) ) { $log = []; } return $log; } } logger/loggers/logger-interface.php 0000644 00000002364 14717626151 0013432 0 ustar 00 <?php namespace Elementor\Core\Logger\Loggers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } interface Logger_Interface { const LEVEL_INFO = 'info'; const LEVEL_NOTICE = 'notice'; const LEVEL_WARNING = 'warning'; const LEVEL_ERROR = 'error'; const LOG_NAME = 'elementor_log'; /** * @param string $message * @param string $type * @param array $meta * * @return void */ public function log( $message, $type = self::LEVEL_INFO, $meta = [] ); /** * @param string $message * @param array $meta * * @return void */ public function info( $message, $meta = [] ); /** * @param string $message * @param array $meta * * @return void */ public function notice( $message, $meta = [] ); /** * @param string $message * @param array $meta * * @return void */ public function warning( $message, $meta = [] ); /** * @param string $message * @param array $meta * * @return void */ public function error( $message, $meta = [] ); /** * @param int $max_entries * @param bool $table use <td> in format * * @return array [ 'key' => [ 'total_count' => int, 'count' => int, 'entries' => Log_Item[] ] ] */ public function get_formatted_log_entries( $max_entries, $table = true ); } logger/items/log-item-interface.php 0000644 00000002307 14717626151 0013344 0 ustar 00 <?php namespace Elementor\Core\Logger\Items; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Interface Log_Item_Interface * * @package Elementor\Core\Logger * * @property string $date * @property string $type * @property string $message * @property int $times * @property array $meta * @property array $times_dates * @property array $args * */ interface Log_Item_Interface extends \JsonSerializable { const MAX_LOG_ENTRIES = 42; /** * Log_Item_Interface constructor. * * @param array $args */ public function __construct( $args ); /** * @param string $name * * @return string */ public function __get( $name ); /** * @return string */ public function __toString(); /** * @param $str * @return Log_Item_Interface | null */ public static function from_json( $str ); /** * @param string $format * @return string */ public function format( $format = 'html' ); /** * @return string */ public function get_fingerprint(); /** * @param Log_Item_Interface $item * @param bool $truncate */ public function increase_times( $item, $truncate = true ); /** * @return string */ public function get_name(); } logger/items/php.php 0000644 00000000412 14717626151 0010453 0 ustar 00 <?php namespace Elementor\Core\Logger\Items; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class PHP extends File { const FORMAT = 'PHP: date [type X times][file::line] message [meta]'; public function get_name() { return 'PHP'; } } logger/items/base.php 0000644 00000012275 14717626151 0010610 0 ustar 00 <?php namespace Elementor\Core\Logger\Items; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Base implements Log_Item_Interface { const FORMAT = 'date [type] message [meta]'; const TRACE_FORMAT = '#key: file(line): class type function()'; const TRACE_LIMIT = 5; protected $date; protected $type; protected $message; protected $meta = []; protected $times = 0; protected $times_dates = []; protected $args = []; public function __construct( $args ) { $this->date = current_time( 'mysql' ); $this->message = ! empty( $args['message'] ) ? esc_html( $args['message'] ) : ''; $this->type = ! empty( $args['type'] ) ? $args['type'] : 'info'; $this->meta = ! empty( $args['meta'] ) ? $args['meta'] : []; $this->args = $args; $this->set_trace(); } public function __get( $name ) { if ( property_exists( $this, $name ) ) { return $this->{$name}; } return ''; } public function __toString() { $vars = get_object_vars( $this ); return strtr( static::FORMAT, $vars ); } #[\ReturnTypeWillChange] public function jsonSerialize() { return [ 'class' => get_class( $this ), 'item' => [ 'date' => $this->date, 'message' => $this->message, 'type' => $this->type, 'meta' => $this->meta, 'times' => $this->times, 'times_dates' => $this->times_dates, 'args' => $this->args, ], ]; } public function deserialize( $properties ) { $this->date = ! empty( $properties['date'] ) && is_string( $properties['date'] ) ? $properties['date'] : ''; $this->message = ! empty( $properties['message'] ) && is_string( $properties['message'] ) ? $properties['message'] : ''; $this->type = ! empty( $properties['type'] ) && is_string( $properties['type'] ) ? $properties['type'] : ''; $this->meta = ! empty( $properties['meta'] ) && is_array( $properties['meta'] ) ? $properties['meta'] : []; $this->times = ! empty( $properties['times'] ) && is_string( $properties['times'] ) ? $properties['times'] : ''; $this->times_dates = ! empty( $properties['times_dates'] ) && is_array( $properties['times_dates'] ) ? $properties['times_dates'] : []; $this->args = ! empty( $properties['args'] ) && is_array( $properties['args'] ) ? $properties['args'] : []; } /** * @return Log_Item_Interface | null */ public static function from_json( $str ) { $obj = json_decode( $str, true ); if ( ! array_key_exists( 'class', $obj ) ) { return null; } $class = $obj['class']; if ( class_exists( $class ) ) { /** @var Base $item */ $item = new $class( $obj['item']['message'] ); $item->deserialize( $obj['item'] ); return $item; } return null; } public function to_formatted_string( $output_format = 'html' ) { $vars = get_object_vars( $this ); $format = static::FORMAT; if ( 'html' === $output_format ) { $format = str_replace( 'message', '<strong>message</strong>', static::FORMAT ); } if ( empty( $vars['meta'] ) ) { $format = str_replace( '[meta]', '', $format ); } else { $vars['meta'] = stripslashes( var_export( $vars['meta'], true ) ); // @codingStandardsIgnoreLine } return strtr( $format, $vars ); } public function get_fingerprint() { $unique_key = $this->type . $this->message . var_export( $this->meta, true ); // @codingStandardsIgnoreLine //info messages are not be aggregated: if ( 'info' === $this->type ) { $unique_key .= $this->date; } return md5( $unique_key ); } public function increase_times( $item, $truncate = true ) { $this->times++; $this->times_dates[] = $item->date; if ( $truncate && ( self::MAX_LOG_ENTRIES < count( $this->times_dates ) ) ) { $this->times_dates = array_slice( $this->times_dates, -self::MAX_LOG_ENTRIES ); } } public function format( $format = 'html' ) { $trace = $this->format_trace(); if ( empty( $trace ) ) { return $this->to_formatted_string( $format ); } $copy = clone $this; $copy->meta['trace'] = $trace; return $copy->to_formatted_string( $format ); } public function get_name() { return 'Log'; } private function format_trace() { $trace = empty( $this->meta['trace'] ) ? '' : $this->meta['trace']; if ( is_string( $trace ) ) { return $trace; } $trace_str = ''; foreach ( $trace as $key => $trace_line ) { $format = static::TRACE_FORMAT; $trace_line['key'] = $key; if ( empty( $trace_line['file'] ) ) { $format = str_replace( 'file(line): ', '', $format ); } $trace_str .= PHP_EOL . strtr( $format, $trace_line ); $trace_str .= empty( $trace_line['args'] ) ? '' : var_export( $trace_line['args'], true ); // @codingStandardsIgnoreLine } return $trace_str . PHP_EOL; } private function set_trace() { if ( ! empty( $this->args['trace'] ) && true === $this->args['trace'] ) { $limit = empty( $this->args['trace_limit'] ) ? static::TRACE_LIMIT : $this->args['trace_limit']; $stack = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // @codingStandardsIgnoreLine while ( ! empty( $stack ) && ! empty( $stack[0]['file'] ) && ( false !== strpos( $stack[0]['file'], 'core' . DIRECTORY_SEPARATOR . 'logger' ) ) ) { array_shift( $stack ); } $this->meta['trace'] = array_slice( $stack, 0, $limit ); return; } if ( is_array( $this->args ) ) { unset( $this->args['trace'] ); } } } logger/items/js.php 0000644 00000001536 14717626151 0010310 0 ustar 00 <?php namespace Elementor\Core\Logger\Items; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class JS extends File { const FORMAT = 'JS: date [type X times][file:line:column] message [meta]'; protected $column; public function __construct( $args ) { parent::__construct( $args ); $this->column = $args['column']; $this->file = $args['url']; $this->date = gmdate( 'Y-m-d H:i:s', $args['timestamp'] ); } #[\ReturnTypeWillChange] public function jsonSerialize() { $json_arr = parent::jsonSerialize(); $json_arr['column'] = $this->column; return $json_arr; } public function deserialize( $properties ) { parent::deserialize( $properties ); $this->column = ! empty( $properties['column'] ) && is_string( $properties['column'] ) ? $properties['column'] : ''; } public function get_name() { return 'JS'; } } logger/items/file.php 0000644 00000001752 14717626151 0010613 0 ustar 00 <?php namespace Elementor\Core\Logger\Items; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class File extends Base { const FORMAT = 'date [type X times][file:line] message [meta]'; protected $file; protected $line; public function __construct( $args ) { parent::__construct( $args ); $this->file = empty( $args['file'] ) ? '' : $args['file']; $this->line = empty( $args['line'] ) ? '' : $args['line']; } #[\ReturnTypeWillChange] public function jsonSerialize() { $json_arr = parent::jsonSerialize(); $json_arr['file'] = $this->file; $json_arr['line'] = $this->line; return $json_arr; } public function deserialize( $properties ) { parent::deserialize( $properties ); $this->file = ! empty( $properties['file'] ) && is_string( $properties['file'] ) ? $properties['file'] : ''; $this->line = ! empty( $properties['line'] ) && is_string( $properties['line'] ) ? $properties['line'] : ''; } public function get_name() { return 'File'; } } common/app.php 0000644 00000014555 14717626151 0007351 0 ustar 00 <?php namespace Elementor\Core\Common; use Elementor\Core\Base\App as BaseApp; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Common\Modules\Finder\Module as Finder; use Elementor\Core\Common\Modules\Connect\Module as Connect; use Elementor\Core\Common\Modules\EventTracker\Module as Event_Tracker; use Elementor\Core\Files\Uploads_Manager; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Icons_Manager; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * App * * Elementor's common app that groups shared functionality, components and configuration * * @since 2.3.0 */ class App extends BaseApp { private $templates = []; /** * App constructor. * * @since 2.3.0 * @access public */ public function __construct() { $this->add_default_templates(); add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'elementor/editor/before_enqueue_styles', [ $this, 'register_styles' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'register_styles' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'register_styles' ], 9 ); add_action( 'elementor/editor/footer', [ $this, 'print_templates' ] ); add_action( 'admin_footer', [ $this, 'print_templates' ] ); add_action( 'wp_footer', [ $this, 'print_templates' ] ); } /** * Init components * * Initializing common components. * * @since 2.3.0 * @access public */ public function init_components() { $this->add_component( 'ajax', new Ajax() ); if ( current_user_can( 'manage_options' ) ) { if ( ! is_customize_preview() ) { $this->add_component( 'finder', new Finder() ); } } $this->add_component( 'connect', new Connect() ); $this->add_component( 'event-tracker', new Event_Tracker() ); } /** * Get name. * * Retrieve the app name. * * @since 2.3.0 * @access public * * @return string Common app name. */ public function get_name() { return 'common'; } /** * Register scripts. * * Register common scripts. * * @since 2.3.0 * @access public */ public function register_scripts() { wp_register_script( 'elementor-common-modules', $this->get_js_assets_url( 'common-modules' ), [], ELEMENTOR_VERSION, true ); wp_register_script( 'backbone-marionette', $this->get_js_assets_url( 'backbone.marionette', 'assets/lib/backbone/' ), [ 'backbone', ], '2.4.5.e1', true ); wp_register_script( 'backbone-radio', $this->get_js_assets_url( 'backbone.radio', 'assets/lib/backbone/' ), [ 'backbone', ], '1.0.4', true ); wp_register_script( 'elementor-dialog', $this->get_js_assets_url( 'dialog', 'assets/lib/dialog/' ), [ 'jquery-ui-position', ], '4.9.0', true ); wp_enqueue_script( 'elementor-common', $this->get_js_assets_url( 'common' ), [ 'jquery', 'jquery-ui-draggable', 'backbone-marionette', 'backbone-radio', 'elementor-common-modules', 'elementor-web-cli', 'elementor-dialog', 'wp-api-request', 'elementor-dev-tools', ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-common', 'elementor' ); $this->print_config(); // Used for external plugins. do_action( 'elementor/common/after_register_scripts', $this ); } /** * Register styles. * * Register common styles. * * @since 2.3.0 * @access public */ public function register_styles() { wp_register_style( 'elementor-icons', $this->get_css_assets_url( 'elementor-icons', 'assets/lib/eicons/css/' ), [], Icons_Manager::ELEMENTOR_ICONS_VERSION ); wp_enqueue_style( 'elementor-common', $this->get_css_assets_url( 'common', null, 'default', true ), [ 'elementor-icons', ], ELEMENTOR_VERSION ); wp_enqueue_style( 'e-theme-ui-light', $this->get_css_assets_url( 'theme-light' ), [], ELEMENTOR_VERSION ); } /** * Add template. * * @since 2.3.0 * @access public * * @param string $template Can be either a link to template file or template * HTML content. * @param string $type Optional. Whether to handle the template as path * or text. Default is `path`. */ public function add_template( $template, $type = 'path' ) { if ( 'path' === $type ) { ob_start(); include $template; $template = ob_get_clean(); } $this->templates[] = $template; } /** * Print Templates * * Prints all registered templates. * * @since 2.3.0 * @access public */ public function print_templates() { foreach ( $this->templates as $template ) { echo $template; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } /** * Get init settings. * * Define the default/initial settings of the common app. * * @since 2.3.0 * @access protected * * @return array */ protected function get_init_settings() { $active_experimental_features = Plugin::$instance->experiments->get_active_features(); $active_experimental_features = array_fill_keys( array_keys( $active_experimental_features ), true ); $config = [ 'version' => ELEMENTOR_VERSION, 'isRTL' => is_rtl(), 'isDebug' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ), 'isElementorDebug' => ( defined( 'ELEMENTOR_DEBUG' ) && ELEMENTOR_DEBUG ), 'activeModules' => array_keys( $this->get_components() ), 'experimentalFeatures' => $active_experimental_features, 'urls' => [ 'assets' => ELEMENTOR_ASSETS_URL, 'rest' => get_rest_url(), ], 'filesUpload' => [ 'unfilteredFiles' => Uploads_Manager::are_unfiltered_uploads_enabled(), ], ]; /** * Localize common settings. * * Filters the editor localized settings. * * @since 1.0.0 * * @param array $config Common configuration. */ return apply_filters( 'elementor/common/localize_settings', $config ); } /** * Add default templates. * * Register common app default templates. * @since 2.3.0 * @access private */ private function add_default_templates() { $default_templates = [ 'includes/editor-templates/library-layout.php', ]; foreach ( $default_templates as $template ) { $this->add_template( ELEMENTOR_PATH . $template ); } } } common/modules/finder/template.php 0000644 00000003631 14717626151 0013314 0 ustar 00 <?php namespace Elementor\Modules\Finder; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } ?> <script type="text/template" id="tmpl-elementor-finder"> <div id="elementor-finder__search"> <i class="eicon-search" aria-hidden="true"></i> <input id="elementor-finder__search__input" placeholder="<?php echo esc_attr__( 'Type to find anything in Elementor', 'elementor' ); ?>" autocomplete="off"> </div> <div id="elementor-finder__content"></div> </script> <script type="text/template" id="tmpl-elementor-finder-results-container"> <div id="elementor-finder__no-results"><?php echo esc_html__( 'No Results Found', 'elementor' ); ?></div> <div id="elementor-finder__results"></div> </script> <script type="text/template" id="tmpl-elementor-finder__results__category"> <div class="elementor-finder__results__category__title">{{{ title }}}</div> <div class="elementor-finder__results__category__items"></div> </script> <script type="text/template" id="tmpl-elementor-finder__results__item"> <a href="{{ url }}" class="elementor-finder__results__item__link"> <div class="elementor-finder__results__item__icon"> <i class="eicon-{{{ icon }}}" aria-hidden="true"></i> </div> <div class="elementor-finder__results__item__title">{{{ title }}}</div> <# if ( description ) { #> <div class="elementor-finder__results__item__description">- {{{ description }}}</div> <# } #> <# if ( lock ) { #> <div class="elementor-finder__results__item__badge"><i class="{{{ lock.badge.icon }}}"></i>{{ lock.badge.text }}</div> <# } #> </a> <# if ( actions.length ) { #> <div class="elementor-finder__results__item__actions"> <# jQuery.each( actions, function() { #> <a class="elementor-finder__results__item__action elementor-finder__results__item__action--{{ this.name }}" href="{{ this.url }}" target="_blank"> <i class="eicon-{{{ this.icon }}}"></i> </a> <# } ); #> </div> <# } #> </script> common/modules/finder/module.php 0000644 00000005063 14717626151 0012767 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Finder; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Finder Module * * Responsible for initializing Elementor Finder functionality */ class Module extends BaseModule { /** * Categories manager. * * @access private * * @var Categories_Manager */ private $categories_manager; /** * Module constructor. * * @since 2.3.0 * @access public */ public function __construct() { $this->categories_manager = new Categories_Manager(); $this->add_template(); add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); } /** * Get name. * * @since 2.3.0 * @access public * * @return string */ public function get_name() { return 'finder'; } /** * Add template. * * @since 2.3.0 * @access public */ public function add_template() { Plugin::$instance->common->add_template( __DIR__ . '/template.php' ); } /** * Register ajax actions. * * @since 2.3.0 * @access public * * @param Ajax $ajax */ public function register_ajax_actions( Ajax $ajax ) { $ajax->register_ajax_action( 'finder_get_category_items', [ $this, 'ajax_get_category_items' ] ); } /** * Ajax get category items. * * @since 2.3.0 * @access public * * @param array $data * * @return array */ public function ajax_get_category_items( array $data ) { if ( ! current_user_can( 'manage_options' ) ) { throw new \Exception( 'Access denied.' ); } $category = $this->categories_manager->get_categories( $data['category'] ); return $category->get_category_items( $data ); } /** * Get init settings. * * @since 2.3.0 * @access protected * * @return array */ protected function get_init_settings() { $categories = $this->categories_manager->get_categories(); $categories_data = []; foreach ( $categories as $category_name => $category ) { $categories_data[ $category_name ] = array_merge( $category->get_settings(), [ 'name' => $category_name ] ); } /** * Finder categories. * * Filters the list of finder categories. This hook is used to manage Finder * categories - to add new categories, remove and edit existing categories. * * @since 2.3.0 * * @param array $categories_data A list of finder categories. */ $categories_data = apply_filters( 'elementor/finder/categories', $categories_data ); return [ 'data' => $categories_data, ]; } } common/modules/finder/base-category.php 0000644 00000003126 14717626151 0014225 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Finder; use Elementor\Core\Base\Base_Object; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Base Category * * Base class for Elementor Finder categories. */ abstract class Base_Category extends Base_Object { /** * Get title. * * @since 2.3.0 * @abstract * @access public * * @return string */ abstract public function get_title(); /** * Get a unique category ID. * * TODO: Make abstract. * * @since 3.5.0 * @deprecated 3.5.0 * @access public * * @return string */ public function get_id() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( get_class( $this ) . '::' . __FUNCTION__, '3.5.0', 'This method will be replaced with an abstract method.' ); return ''; } /** * Get category items. * * @since 2.3.0 * @abstract * @access public * * @param array $options * * @return array */ abstract public function get_category_items( array $options = [] ); /** * Is dynamic. * * Determine if the category is dynamic. * * @since 2.3.0 * @access public * * @return bool */ public function is_dynamic() { return false; } /** * Get init settings. * * @since 2.3.0 * @access protected * * @return array */ protected function get_init_settings() { $settings = [ 'title' => $this->get_title(), 'dynamic' => $this->is_dynamic(), ]; if ( ! $settings['dynamic'] ) { $settings['items'] = $this->get_category_items(); } return $settings; } } common/modules/finder/categories-manager.php 0000644 00000007272 14717626151 0015243 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Finder; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Categories_Manager { /** * @access private * * @var Base_Category[] */ private $categories; /** * @var array */ private $categories_list = [ 'edit', 'general', 'create', 'site', 'settings', 'tools', ]; /** * Add category. * * @since 2.3.0 * @deprecated 3.5.0 Use `register()` method instead. * @access public * * @param string $category_name * @param Base_Category $category * * @deprecated 3.5.0 Use `register()` method instead. */ public function add_category( $category_name, Base_Category $category ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'register()' ); $this->register( $category, $category_name ); } /** * Register finder category. * * @since 3.5.0 * @access public * * @param Base_Category $finder_category_instance An Instance of a category. * @param string $finder_category_name A Category name. Deprecated parameter. * * @return void */ public function register( Base_Category $finder_category_instance, $finder_category_name = null ) { // TODO: For BC. Remove in the future. if ( $finder_category_name ) { Plugin::instance()->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_argument( '$finder_category_name', '3.5.0' ); } else { $finder_category_name = $finder_category_instance->get_id(); } $this->categories[ $finder_category_name ] = $finder_category_instance; } /** * Unregister a finder category. * * @param string $finder_category_name - Category to unregister. * * @return void * @since 3.6.0 * @access public */ public function unregister( $finder_category_name ) { unset( $this->categories[ $finder_category_name ] ); } /** * Get categories. * * Retrieve the registered categories, or a specific category if the category name * is provided as a parameter. * * @since 2.3.0 * @access public * * @param string $category Category name. * * @return Base_Category|Base_Category[]|null */ public function get_categories( $category = '' ) { if ( ! $this->categories ) { $this->init_categories(); } if ( $category ) { if ( isset( $this->categories[ $category ] ) ) { return $this->categories[ $category ]; } return null; } return $this->categories; } /** * Init categories. * * Used to initialize the native finder categories. * * @since 2.3.0 * @access private */ private function init_categories() { foreach ( $this->categories_list as $category_name ) { $class_name = __NAMESPACE__ . '\Categories\\' . $category_name; $this->register( new $class_name() ); } /** * Elementor Finder categories init. * * Fires after Elementor Finder initialize it's native categories. * * This hook should be used to add your own Finder categories. * * @since 2.3.0 * @deprecated 3.5.0 Use `elementor/finder/register` hook instead. * * @param Categories_Manager $this. */ Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->do_deprecated_action( 'elementor/finder/categories/init', [ $this ], '3.5.0', 'elementor/finder/register' ); /** * Elementor Finder categories registration. * * Fires after Elementor Finder initialize it's native categories. * * This hook should be used to register your own Finder categories. * * @since 3.5.0 * * @param Categories_Manager $this Finder Categories manager. */ do_action( 'elementor/finder/register', $this ); } } common/modules/finder/categories/tools.php 0000644 00000004054 14717626151 0014766 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Finder\Categories; use Elementor\Core\Common\Modules\Finder\Base_Category; use Elementor\Tools as ElementorTools; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Tools Category * * Provides items related to Elementor's tools. */ class Tools extends Base_Category { /** * Get title. * * @since 2.3.0 * @access public * * @return string */ public function get_title() { return esc_html__( 'Tools', 'elementor' ); } public function get_id() { return 'tools'; } /** * Get category items. * * @since 2.3.0 * @access public * * @param array $options * * @return array */ public function get_category_items( array $options = [] ) { $tools_url = ElementorTools::get_url(); $items = [ 'tools' => [ 'title' => esc_html__( 'Tools', 'elementor' ), 'icon' => 'tools', 'url' => $tools_url, 'keywords' => [ 'tools', 'regenerate css', 'safe mode', 'debug bar', 'sync library', 'elementor' ], ], 'replace-url' => [ 'title' => esc_html__( 'Replace URL', 'elementor' ), 'icon' => 'tools', 'url' => $tools_url . '#tab-replace_url', 'keywords' => [ 'tools', 'replace url', 'domain', 'elementor' ], ], 'maintenance-mode' => [ 'title' => esc_html__( 'Maintenance Mode', 'elementor' ), 'icon' => 'tools', 'url' => $tools_url . '#tab-maintenance_mode', 'keywords' => [ 'tools', 'maintenance', 'coming soon', 'elementor' ], ], 'import-export' => [ 'title' => esc_html__( 'Import Export', 'elementor' ), 'icon' => 'import-export', 'url' => $tools_url . '#tab-import-export-kit', 'keywords' => [ 'tools', 'import export', 'import', 'export', 'kit' ], ], ]; if ( ElementorTools::can_user_rollback_versions() ) { $items['version-control'] = [ 'title' => esc_html__( 'Version Control', 'elementor' ), 'icon' => 'time-line', 'url' => $tools_url . '#tab-versions', 'keywords' => [ 'tools', 'version', 'control', 'rollback', 'beta', 'elementor' ], ]; } return $items; } } common/modules/finder/categories/settings.php 0000644 00000004533 14717626151 0015470 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Finder\Categories; use Elementor\Core\Common\Modules\Finder\Base_Category; use Elementor\Modules\ElementManager\Module as ElementManagerModule; use Elementor\Settings as ElementorSettings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Settings Category * * Provides items related to Elementor's settings. */ class Settings extends Base_Category { /** * Get title. * * @since 2.3.0 * @access public * * @return string */ public function get_title() { return esc_html__( 'Settings', 'elementor' ); } public function get_id() { return 'settings'; } /** * Get category items. * * @since 2.3.0 * @access public * * @param array $options * * @return array */ public function get_category_items( array $options = [] ) { return [ 'general-settings' => [ 'title' => esc_html__( 'General Settings', 'elementor' ), 'url' => ElementorSettings::get_settings_tab_url( 'general' ), 'keywords' => [ 'general', 'settings', 'elementor' ], ], 'integrations' => [ 'title' => esc_html__( 'Integrations', 'elementor' ), 'url' => ElementorSettings::get_settings_tab_url( 'integrations' ), 'keywords' => [ 'integrations', 'settings', 'elementor' ], ], 'advanced' => [ 'title' => esc_html__( 'Advanced', 'elementor' ), 'url' => ElementorSettings::get_settings_tab_url( 'advanced' ), 'keywords' => [ 'advanced', 'settings', 'elementor' ], ], 'performance' => [ 'title' => esc_html__( 'Performance', 'elementor' ), 'url' => ElementorSettings::get_settings_tab_url( 'performance' ), 'keywords' => [ 'performance', 'settings', 'elementor' ], ], 'experiments' => [ 'title' => esc_html__( 'Experiments', 'elementor' ), 'url' => ElementorSettings::get_settings_tab_url( 'experiments' ), 'keywords' => [ 'settings', 'elementor', 'experiments' ], ], 'features' => [ 'title' => esc_html__( 'Features', 'elementor' ), 'url' => ElementorSettings::get_settings_tab_url( 'experiments' ), 'keywords' => [ 'settings', 'elementor', 'features' ], ], 'element-manager' => [ 'title' => esc_html__( 'Element Manager', 'elementor' ), 'url' => admin_url( 'admin.php?page=' . ElementManagerModule::PAGE_ID ), 'keywords' => [ 'settings', 'elements', 'widgets', 'manager' ], ], ]; } } common/modules/finder/categories/create.php 0000644 00000005535 14717626151 0015076 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Finder\Categories; use Elementor\Core\Common\Modules\Finder\Base_Category; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Create Category * * Provides items related to creation of new posts/pages/templates etc. */ class Create extends Base_Category { /** * Get title. * * @since 2.3.0 * @access public * * @return string */ public function get_title() { return esc_html__( 'Create', 'elementor' ); } public function get_id() { return 'create'; } /** * Get category items. * * @since 2.3.0 * @access public * * @param array $options * * @return array */ public function get_category_items( array $options = [] ) { $result = []; $registered_document_types = Plugin::$instance->documents->get_document_types(); // TODO: Remove - Support 'post' backwards compatibility - See `Documents_Manager::register_default_types()`. unset( $registered_document_types['post'] ); $elementor_supported_post_types = array_flip( get_post_types_by_support( 'elementor' ) ); foreach ( $registered_document_types as $document_name => $document_class ) { $document_properties = $document_class::get_properties(); if ( empty( $document_properties['show_in_finder'] ) ) { continue; } if ( ! empty( $document_properties['cpt'] ) ) { foreach ( $document_properties['cpt'] as $cpt ) { unset( $elementor_supported_post_types[ $cpt ] ); } } $result[ $document_name ] = $this->create_item_url_by_document_class( $document_class ); } foreach ( $elementor_supported_post_types as $post_type => $val ) { $result[ $post_type ] = $this->create_item_url_by_post_type( $post_type ); } return $result; } private function create_item_url_by_post_type( $post_type ) { $post_type_object = get_post_type_object( $post_type ); // If there is an old post type from inactive plugins. if ( ! $post_type_object ) { return false; } return $this->get_create_new_template( sprintf( __( 'Add New %s', 'elementor' ), $post_type_object->labels->singular_name ), Plugin::$instance->documents->get_create_new_post_url( $post_type ) ); } private function create_item_url_by_document_class( $document_class ) { $result = $this->get_create_new_template( $document_class::get_add_new_title(), $document_class::get_create_url() ); $lock_behavior = $document_class::get_lock_behavior_v2(); $is_locked = ! empty( $lock_behavior ) && $lock_behavior->is_locked(); if ( $is_locked ) { $result['lock'] = $lock_behavior->get_config(); } return $result; } private function get_create_new_template( $add_new_title, $url ) { return [ 'title' => $add_new_title, 'icon' => 'plus-circle-o', 'url' => $url, 'keywords' => [ $add_new_title, 'post', 'page', 'template', 'new', 'create' ], ]; } } common/modules/finder/categories/general.php 0000644 00000004450 14717626151 0015243 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Finder\Categories; use Elementor\Core\Common\Modules\Finder\Base_Category; use Elementor\Core\RoleManager\Role_Manager; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * General Category * * Provides general items related to Elementor Admin. */ class General extends Base_Category { /** * Get title. * * @since 2.3.0 * @access public * * @return string */ public function get_title() { return esc_html__( 'General', 'elementor' ); } public function get_id() { return 'general'; } /** * Get category items. * * @since 2.3.0 * @access public * * @param array $options * * @return array */ public function get_category_items( array $options = [] ) { return [ 'saved-templates' => [ 'title' => esc_html__( 'Saved Templates', 'elementor' ), 'icon' => 'library-save', 'url' => Source_Local::get_admin_url(), 'keywords' => [ 'template', 'section', 'page', 'library' ], ], 'system-info' => [ 'title' => esc_html__( 'System Info', 'elementor' ), 'icon' => 'info-circle-o', 'url' => admin_url( 'admin.php?page=elementor-system-info' ), 'keywords' => [ 'system', 'info', 'environment', 'elementor' ], ], 'role-manager' => [ 'title' => esc_html__( 'Role Manager', 'elementor' ), 'icon' => 'person', 'url' => Role_Manager::get_url(), 'keywords' => [ 'role', 'manager', 'user', 'elementor' ], ], 'knowledge-base' => [ 'title' => esc_html__( 'Knowledge Base', 'elementor' ), 'url' => admin_url( 'admin.php?page=go_knowledge_base_site' ), 'keywords' => [ 'help', 'knowledge', 'docs', 'elementor' ], ], 'theme-builder' => [ 'title' => esc_html__( 'Theme Builder', 'elementor' ), 'icon' => 'library-save', 'url' => Plugin::$instance->app->get_settings( 'menu_url' ), 'keywords' => [ 'template', 'header', 'footer', 'single', 'archive', 'search', '404', 'library' ], ], 'kit-library' => [ 'title' => esc_html__( 'Kit Library', 'elementor' ), 'icon' => 'kit-parts', 'url' => Plugin::$instance->app->get_base_url() . '#/kit-library', 'keywords' => [ 'kit library', 'kit', 'library', 'site parts', 'parts', 'assets', 'templates' ], ], ]; } } common/modules/finder/categories/site.php 0000644 00000004046 14717626151 0014573 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Finder\Categories; use Elementor\Core\Common\Modules\Finder\Base_Category; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Site Category * * Provides general site items. */ class Site extends Base_Category { /** * Get title. * * @since 2.3.0 * @access public * * @return string */ public function get_title() { return esc_html__( 'Site', 'elementor' ); } public function get_id() { return 'site'; } /** * Get category items. * * @since 2.3.0 * @access public * * @param array $options * * @return array */ public function get_category_items( array $options = [] ) { return [ 'homepage' => [ 'title' => esc_html__( 'Homepage', 'elementor' ), 'url' => home_url(), 'icon' => 'home-heart', 'keywords' => [ 'home', 'page' ], ], 'wordpress-dashboard' => [ 'title' => esc_html__( 'Dashboard', 'elementor' ), 'icon' => 'dashboard', 'url' => admin_url(), 'keywords' => [ 'dashboard', 'wordpress' ], ], 'wordpress-menus' => [ 'title' => esc_html__( 'Menus', 'elementor' ), 'icon' => 'wordpress', 'url' => admin_url( 'nav-menus.php' ), 'keywords' => [ 'menu', 'wordpress' ], ], 'wordpress-themes' => [ 'title' => esc_html__( 'Themes', 'elementor' ), 'icon' => 'wordpress', 'url' => admin_url( 'themes.php' ), 'keywords' => [ 'themes', 'wordpress' ], ], 'wordpress-customizer' => [ 'title' => esc_html__( 'Customizer', 'elementor' ), 'icon' => 'wordpress', 'url' => admin_url( 'customize.php' ), 'keywords' => [ 'customizer', 'wordpress' ], ], 'wordpress-plugins' => [ 'title' => esc_html__( 'Plugins', 'elementor' ), 'icon' => 'wordpress', 'url' => admin_url( 'plugins.php' ), 'keywords' => [ 'plugins', 'wordpress' ], ], 'wordpress-users' => [ 'title' => esc_html__( 'Users', 'elementor' ), 'icon' => 'wordpress', 'url' => admin_url( 'users.php' ), 'keywords' => [ 'users', 'profile', 'wordpress' ], ], ]; } } common/modules/finder/categories/edit.php 0000644 00000005270 14717626151 0014554 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Finder\Categories; use Elementor\Core\Base\Document; use Elementor\Core\Common\Modules\Finder\Base_Category; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Edit Category * * Provides items related to editing of posts/pages/templates etc. */ class Edit extends Base_Category { /** * Get title. * * @since 2.3.0 * @access public * * @return string */ public function get_title() { return esc_html__( 'Edit', 'elementor' ); } public function get_id() { return 'edit'; } /** * Is dynamic. * * Determine if the category is dynamic. * * @since 2.3.0 * @access public * * @return bool */ public function is_dynamic() { return true; } /** * Get category items. * * @since 2.3.0 * @access public * * @param array $options * * @return array */ public function get_category_items( array $options = [] ) { $post_types = get_post_types( [ 'exclude_from_search' => false, ] ); $post_types[] = Source_Local::CPT; $document_types = Plugin::$instance->documents->get_document_types( [ 'is_editable' => true, 'show_in_finder' => true, ] ); $recently_edited_query_args = [ 'no_found_rows' => true, 'post_type' => $post_types, 'post_status' => [ 'publish', 'draft', 'private', 'pending', 'future' ], 'posts_per_page' => '10', 'meta_query' => [ [ 'key' => '_elementor_edit_mode', 'value' => 'builder', ], [ 'relation' => 'or', [ 'key' => Document::TYPE_META_KEY, 'compare' => 'NOT EXISTS', ], [ 'key' => Document::TYPE_META_KEY, 'value' => array_keys( $document_types ), ], ], ], 'orderby' => 'modified', 's' => $options['filter'], ]; $recently_edited_query = new \WP_Query( $recently_edited_query_args ); $items = []; /** @var \WP_Post $post */ foreach ( $recently_edited_query->posts as $post ) { $document = Plugin::$instance->documents->get( $post->ID ); if ( ! $document ) { continue; } $is_template = Source_Local::CPT === $post->post_type; $description = $document->get_title(); $icon = 'document-file'; if ( $is_template ) { $description = esc_html__( 'Template', 'elementor' ) . ' / ' . $description; $icon = 'post-title'; } $items[] = [ 'icon' => $icon, 'title' => esc_html( $post->post_title ), 'description' => $description, 'url' => $document->get_edit_url(), 'actions' => [ [ 'name' => 'view', 'url' => $document->get_permalink(), 'icon' => 'preview-medium', ], ], ]; } return $items; } } common/modules/ajax/module.php 0000644 00000016235 14717626151 0012446 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Ajax; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Utils\Exceptions; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor ajax manager. * * Elementor ajax manager handler class is responsible for handling Elementor * ajax requests, ajax responses and registering actions applied on them. * * @since 2.0.0 */ class Module extends BaseModule { const NONCE_KEY = 'elementor_ajax'; /** * Ajax actions. * * Holds all the register ajax action. * * @since 2.0.0 * @access private * * @var array */ private $ajax_actions = []; /** * Ajax requests. * * Holds all the register ajax requests. * * @since 2.0.0 * @access private * * @var array */ private $requests = []; /** * Ajax response data. * * Holds all the response data for all the ajax requests. * * @since 2.0.0 * @access private * * @var array */ private $response_data = []; /** * Current ajax action ID. * * Holds all the ID for the current ajax action. * * @since 2.0.0 * @access private * * @var string|null */ private $current_action_id = null; /** * Ajax manager constructor. * * Initializing Elementor ajax manager. * * @since 2.0.0 * @access public */ public function __construct() { add_action( 'wp_ajax_elementor_ajax', [ $this, 'handle_ajax_request' ] ); } /** * Get module name. * * Retrieve the module name. * * @since 1.7.0 * @access public * * @return string Module name. */ public function get_name() { return 'ajax'; } /** * Register ajax action. * * Add new actions for a specific ajax request and the callback function to * be handle the response. * * @since 2.0.0 * @access public * * @param string $tag Ajax request name/tag. * @param callable $callback The callback function. */ public function register_ajax_action( $tag, $callback ) { if ( ! did_action( 'elementor/ajax/register_actions' ) ) { _doing_it_wrong( __METHOD__, esc_html( sprintf( 'Use `%s` hook to register ajax action.', 'elementor/ajax/register_actions' ) ), '2.0.0' ); } $this->ajax_actions[ $tag ] = compact( 'tag', 'callback' ); } /** * Handle ajax request. * * Verify ajax nonce, and run all the registered actions for this request. * * Fired by `wp_ajax_elementor_ajax` action. * * @since 2.0.0 * @access public */ public function handle_ajax_request() { if ( ! $this->verify_request_nonce() ) { $this->add_response_data( false, esc_html__( 'Token Expired.', 'elementor' ) ) ->send_error( Exceptions::UNAUTHORIZED ); } $editor_post_id = 0; if ( ! empty( $_REQUEST['editor_post_id'] ) ) { $editor_post_id = absint( $_REQUEST['editor_post_id'] ); Plugin::$instance->db->switch_to_post( $editor_post_id ); } /** * Register ajax actions. * * Fires when an ajax request is received and verified. * * Used to register new ajax action handles. * * @since 2.0.0 * * @param self $this An instance of ajax manager. */ do_action( 'elementor/ajax/register_actions', $this ); if ( ! empty( $_REQUEST['actions'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, each action should sanitize its own data. $this->requests = json_decode( wp_unslash( $_REQUEST['actions'] ), true ); } foreach ( $this->requests as $id => $action_data ) { $this->current_action_id = $id; if ( ! isset( $this->ajax_actions[ $action_data['action'] ] ) ) { $this->add_response_data( false, esc_html__( 'Action not found.', 'elementor' ), Exceptions::BAD_REQUEST ); continue; } if ( $editor_post_id ) { $action_data['data']['editor_post_id'] = $editor_post_id; } try { $data = $action_data['data'] ?? []; $results = call_user_func( $this->ajax_actions[ $action_data['action'] ]['callback'], $data, $this ); if ( false === $results ) { $this->add_response_data( false ); } else { $this->add_response_data( true, $results ); } } catch ( \Exception $e ) { $this->add_response_data( false, $e->getMessage(), $e->getCode() ); } } $this->current_action_id = null; $this->send_success(); } /** * Get current action data. * * Retrieve the data for the current ajax request. * * @since 2.0.1 * @access public * * @return bool|mixed Ajax request data if action exist, False otherwise. */ public function get_current_action_data() { if ( ! $this->current_action_id ) { return false; } return $this->requests[ $this->current_action_id ]; } /** * Create nonce. * * Creates a cryptographic token to * give the user an access to Elementor ajax actions. * * @since 2.3.0 * @access public * * @return string The nonce token. */ public function create_nonce() { return wp_create_nonce( self::NONCE_KEY ); } /** * Verify request nonce. * * Whether the request nonce verified or not. * * @since 2.3.0 * @access public * * @return bool True if request nonce verified, False otherwise. */ public function verify_request_nonce() { return wp_verify_nonce( Utils::get_super_global_value( $_REQUEST, '_nonce' ), self::NONCE_KEY ); } protected function get_init_settings() { return [ 'url' => admin_url( 'admin-ajax.php' ), 'nonce' => $this->create_nonce(), ]; } /** * Ajax success response. * * Send a JSON response data back to the ajax request, indicating success. * * @since 2.0.0 * @access protected */ private function send_success() { $response = [ 'success' => true, 'data' => [ 'responses' => $this->response_data, ], ]; $json = wp_json_encode( $response ); while ( ob_get_status() ) { ob_end_clean(); } if ( function_exists( 'gzencode' ) ) { $response = gzencode( $json ); header( 'Content-Type: application/json; charset=utf-8' ); header( 'Content-Encoding: gzip' ); header( 'Content-Length: ' . strlen( $response ) ); echo $response; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } else { echo $json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } wp_die( '', '', [ 'response' => null ] ); } /** * Ajax failure response. * * Send a JSON response data back to the ajax request, indicating failure. * * @since 2.0.0 * @access protected * * @param null $code */ private function send_error( $code = null ) { wp_send_json_error( [ 'responses' => $this->response_data, ], $code ); } /** * Add response data. * * Add new response data to the array of all the ajax requests. * * @since 2.0.0 * @access protected * * @param bool $success True if the requests returned successfully, False * otherwise. * @param mixed $data Optional. Response data. Default is null. * * @param int $code Optional. Response code. Default is 200. * * @return Module An instance of ajax manager. */ private function add_response_data( $success, $data = null, $code = 200 ) { $this->response_data[ $this->current_action_id ] = [ 'success' => $success, 'code' => $code, 'data' => $data, ]; return $this; } } common/modules/event-tracker/data/controller.php 0000644 00000003513 14717626151 0016077 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\EventTracker\Data; use Elementor\Core\Common\Modules\EventTracker\DB as Events_DB_Manager; use Elementor\Plugin; use WP_REST_Server; use Elementor\Data\V2\Base\Controller as Controller_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Controller_Base { public function get_name() { return 'send-event'; } public function register_endpoints() { $this->index_endpoint->register_items_route( \WP_REST_Server::CREATABLE, [ 'event_data' => [ 'description' => 'All the recorded event data in JSON format', 'type' => 'object', 'required' => true, ], ] ); } /** * Get Permissions Callback * * This endpoint should only accept POST requests, and currently we only track site administrator actions. * * @since 3.6.0 * * @param \WP_REST_Request $request * @return bool */ public function get_permission_callback( $request ) { if ( WP_REST_Server::CREATABLE !== $request->get_method() ) { return false; } return current_user_can( 'manage_options' ); } /** * Create Items * * Receives a request for adding an event data entry into the database. If the request contains event data, this * method initiates creation of a database entry with the event data in the Events DB table. * * @since 3.6.0 * * @param \WP_REST_Request $request * @return bool */ public function create_items( $request ) { $request_body = $request->get_json_params(); if ( empty( $request_body['event_data'] ) ) { return false; } /** @var Events_DB_Manager $event_tracker_db_manager */ $event_tracker_db_manager = Plugin::$instance->common ->get_component( 'event-tracker' ) ->get_component( 'events-db' ); $event_tracker_db_manager->create_entry( $request_body['event_data'] ); return true; } } common/modules/event-tracker/db.php 0000644 00000011405 14717626151 0013367 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\EventTracker; use Elementor\Core\Base\Base_Object; use Elementor\Core\Common\Modules\Connect\Apps\Common_App; use Elementor\Core\Common\Modules\Connect\Apps\Library; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class DB extends Base_Object { /** * @var \wpdb */ private $wpdb; const TABLE_NAME = 'e_events'; const DB_VERSION_OPTION_KEY = 'elementor_events_db_version'; const CURRENT_DB_VERSION = '1.0.0'; /** * Get Table Name * * Returns the Events database table's name with the `wpdb` prefix. * * @since 3.6.0 * * @return string */ public function get_table_name() { return $this->wpdb->prefix . self::TABLE_NAME; } /** * Prepare Database for Entry * * The events database should have a limit of up to 1000 event entries stored daily. * Before adding a new entry to the database, we make sure that the limit of 1000 events is not reached. * If there are 1000 or more entries in the DB, we delete the earliest-inserted entry before inserting a new one. * * @since 3.6.0 */ public function prepare_db_for_entry() { $events = $this->get_event_ids_from_db(); if ( 1000 <= count( $events ) ) { $event_ids = []; foreach ( $events as $event ) { $event_ids[] = $event->id; } // Sort the array by entry ID array_multisort( $event_ids, SORT_ASC, $events ); // Delete the smallest ID (which is the earliest DB entry) $this->wpdb->delete( $this->get_table_name(), [ 'ID' => $events[0]->id ] ); } } /** * Create Entry * * Adds an event entry to the database. * * @since 3.6.0 */ public function create_entry( $event_data ) { $this->prepare_db_for_entry(); $connect = Plugin::$instance->common->get_component( 'connect' ); /** @var Library $library */ $library = $connect->get_apps()['library']; if ( ! isset( $event_data['details'] ) ) { $event_data['details'] = []; } if ( $library->is_connected() ) { $user_connect_data = get_user_option( Common_App::OPTION_CONNECT_COMMON_DATA_KEY ); // Add the user's client ID to the event. $event_data['details']['client_id'] = $user_connect_data['client_id']; } $event_data['details'] = json_encode( $event_data['details'] ); $entry = [ 'event_data' => wp_json_encode( $event_data ), 'created_at' => $event_data['ts'], ]; $this->wpdb->insert( $this->get_table_name(), $entry ); } /** * Get Event IDs From DB * * Fetches the IDs of all events saved in the database. * * @since 3.6.0 * * @return array|object|null */ public function get_event_ids_from_db() { // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $this->wpdb->get_results( "SELECT id FROM {$this->get_table_name()}" ); } /** * Reset Table * * Empties the contents of the Events DB table. * * @since 3.6.0 */ public static function reset_table() { global $wpdb; $table_name = $wpdb->prefix . self::TABLE_NAME; // Delete all content of the table. // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( "TRUNCATE TABLE {$table_name}" ); } /** * Create Table * * Creates the `wp_e_events` database table. * * @since 3.6.0 * * @param string $query to that looks for the Events table in the DB. Used for checking if table was created. */ private function create_table( $query ) { require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $table_name = $this->get_table_name(); $charset_collate = $this->wpdb->get_charset_collate(); $e_events_table = "CREATE TABLE `{$table_name}` ( id bigint(20) unsigned auto_increment primary key, event_data text null, created_at datetime not null ) {$charset_collate};"; // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $this->wpdb->query( $e_events_table ); // Check if table was created successfully. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared if ( $this->wpdb->get_var( $query ) === $table_name ) { update_option( self::DB_VERSION_OPTION_KEY, self::CURRENT_DB_VERSION, false ); } } /** * Add Indexes * * Adds an index to the events table for the creation date column. * * @since 3.6.0 */ private function add_indexes() { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $this->wpdb->query( 'ALTER TABLE ' . $this->get_table_name() . ' ADD INDEX `created_at_index` (`created_at`) ' ); } public function __construct() { global $wpdb; $this->wpdb = $wpdb; // Check if table exists. If not, create it. $query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $this->get_table_name() ) ); // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared if ( $wpdb->get_var( $query ) !== $this->get_table_name() ) { $this->create_table( $query ); $this->add_indexes(); } } } common/modules/event-tracker/module.php 0000644 00000001642 14717626151 0014271 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\EventTracker; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\EventTracker\Data\Controller; use Elementor\Plugin; use Elementor\Tracker; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Event Tracker Module Class * * @since 3.6.0 */ class Module extends BaseModule { public function get_name() { return 'event-tracker'; } /** * Get init settings. * * @since 3.6.0 * @access protected * * @return array */ protected function get_init_settings() { return [ 'isUserDataShared' => Tracker::is_allow_track(), ]; } public function __construct() { // Initialize Events Database Table $this->add_component( 'events-db', new DB() ); // Handle User Data Deletion/Export requests. new Personal_Data(); Plugin::$instance->data_manager_v2->register_controller( new Controller() ); } } common/modules/event-tracker/personal-data.php 0000644 00000003711 14717626151 0015535 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\EventTracker; use Elementor\Core\Base\Base_Object; use Elementor\Core\Common\Modules\EventTracker\DB as Events_DB_Manager; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Personal_Data extends Base_Object { const WP_KEY = 'elementor-event-tracker'; /** * Get Title * * @since 3.6.0 * * @return string */ private function get_title() { return esc_html__( 'Elementor Event Tracker', 'elementor' ); } /** * Erase all the submissions related to specific email. * * Since event data is saved globally per site and not per user, we remove all saved events from the DB upon a * user's data deletion request. * * @return array */ private function erase_data() { // Get number of events saved in the DB. /** @var Events_DB_Manager $event_tracker_db_manager */ $event_tracker_db_manager = Plugin::$instance->common ->get_component( 'event-tracker' ) ->get_component( 'events-db' ); $events = $event_tracker_db_manager->get_event_ids_from_db(); $events_count = count( $events ); DB::reset_table(); // Validate table deleted $updated_events = $event_tracker_db_manager->get_event_ids_from_db(); $updated_events_count = count( $updated_events ); return [ 'items_removed' => $events_count - $updated_events_count, 'items_retained' => 0, 'messages' => [], 'done' => 0 === $updated_events_count, ]; } /** * Add eraser to the list of erasers. * * @param $erasers * * @return array[] */ private function add_eraser( $erasers ) { return $erasers + [ self::WP_KEY => [ 'eraser_friendly_name' => $this->get_title(), 'callback' => function () { return $this->erase_data(); }, ], ]; } /** * Personal_Data constructor. */ public function __construct() { add_filter( 'wp_privacy_personal_data_erasers', function ( $exporters ) { return $this->add_eraser( $exporters ); } ); } } common/modules/connect/module.php 0000644 00000013202 14717626151 0013143 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\Connect\Apps\Base_App; use Elementor\Core\Common\Modules\Connect\Apps\Common_App; use Elementor\Core\Common\Modules\Connect\Apps\Connect; use Elementor\Core\Common\Modules\Connect\Apps\Library; use Elementor\Plugin; use Elementor\Utils; use WP_User_Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Module extends BaseModule { const ACCESS_LEVEL_CORE = 0; const ACCESS_LEVEL_PRO = 1; const ACCESS_LEVEL_EXPERT = 20; const ACCESS_TIER_FREE = 'free'; const ACCESS_TIER_ESSENTIAL = 'essential'; const ACCESS_TIER_ESSENTIAL_OCT_2023 = 'essential-oct2023'; const ACCESS_TIER_ADVANCED = 'advanced'; const ACCESS_TIER_EXPERT = 'expert'; const ACCESS_TIER_AGENCY = 'agency'; /** * @since 2.3.0 * @access public */ public function get_name() { return 'connect'; } /** * @var array */ protected $registered_apps = []; /** * Apps Instances. * * Holds the list of all the apps instances. * * @since 2.3.0 * @access protected * * @var Base_App[] */ protected $apps = []; /** * Registered apps categories. * * Holds the list of all the registered apps categories. * * @since 2.3.0 * @access protected * * @var array */ protected $categories = []; protected $admin_page; /** * @since 2.3.0 * @access public */ public function __construct() { $this->registered_apps = [ 'connect' => Connect::get_class_name(), 'library' => Library::get_class_name(), ]; // When using REST API the parent module is construct after the action 'elementor/init' // so this part of code make sure to register the module "apps". if ( did_action( 'elementor/init' ) ) { $this->init(); } else { // Note: The priority 11 is for allowing plugins to add their register callback on elementor init. add_action( 'elementor/init', [ $this, 'init' ], 11 ); } add_filter( 'elementor/tracker/send_tracking_data_params', function ( $params ) { return $this->add_tracking_data( $params ); } ); } /** * Register default apps. * * Registers the default apps. * * @since 2.3.0 * @access public */ public function init() { if ( is_admin() ) { $this->admin_page = new Admin(); } /** * Register Elementor apps. * * Fires after Elementor registers the default apps. * * @since 2.3.0 * * @param self $this The apps manager instance. */ do_action( 'elementor/connect/apps/register', $this ); foreach ( $this->registered_apps as $slug => $class ) { $this->apps[ $slug ] = new $class(); } } /** * Register app. * * Registers an app. * * @since 2.3.0 * @access public * * @param string $slug App slug. * @param string $class App full class name. * * @return self The updated apps manager instance. */ public function register_app( $slug, $class ) { $this->registered_apps[ $slug ] = $class; return $this; } /** * Get app instance. * * Retrieve the app instance. * * @since 2.3.0 * @access public * * @param $slug * * @return Base_App|null */ public function get_app( $slug ) { if ( isset( $this->apps[ $slug ] ) ) { return $this->apps[ $slug ]; } return null; } /** * @since 2.3.0 * @access public * @return Base_App[] */ public function get_apps() { return $this->apps; } /** * @since 2.3.0 * @access public */ public function register_category( $slug, $args ) { $this->categories[ $slug ] = $args; return $this; } /** * @since 2.3.0 * @access public */ public function get_categories() { return $this->categories; } /** * @param string $context Where this subscription plan should be shown. * * @return array */ public function get_subscription_plans( $context = '' ) { $base_url = Utils::has_pro() ? 'https://my.elementor.com/upgrade-subscription' : 'https://elementor.com/pro'; $promotion_url = $base_url . '/?utm_source=' . $context . '&utm_medium=wp-dash&utm_campaign=gopro'; return [ static::ACCESS_TIER_FREE => [ 'label' => null, 'promotion_url' => null, 'color' => null, ], static::ACCESS_TIER_ESSENTIAL => [ 'label' => 'Pro', 'promotion_url' => $promotion_url, 'color' => '#92003B', ], static::ACCESS_TIER_ESSENTIAL_OCT_2023 => [ 'label' => 'Advanced', // Should be the same label as "Advanced". 'promotion_url' => $promotion_url, 'color' => '#92003B', ], static::ACCESS_TIER_ADVANCED => [ 'label' => 'Advanced', 'promotion_url' => $promotion_url, 'color' => '#92003B', ], static::ACCESS_TIER_EXPERT => [ 'label' => 'Expert', 'promotion_url' => $promotion_url, 'color' => '#92003B', ], static::ACCESS_TIER_AGENCY => [ 'label' => 'Agency', 'promotion_url' => $promotion_url, 'color' => '#92003B', ], ]; } private function add_tracking_data( $params ) { $users = []; $users_query = new WP_User_Query( [ 'count_total' => false, // Disable SQL_CALC_FOUND_ROWS. 'meta_query' => [ 'key' => Common_App::OPTION_CONNECT_COMMON_DATA_KEY, 'compare' => 'EXISTS', ], ] ); foreach ( $users_query->get_results() as $user ) { $connect_common_data = get_user_option( Common_App::OPTION_CONNECT_COMMON_DATA_KEY, $user->ID ); if ( $connect_common_data ) { $users [] = [ 'id' => $user->ID, 'email' => $connect_common_data['user']->email, 'roles' => implode( ', ', $user->roles ), ]; } } $params['usages'][ $this->get_name() ] = [ 'site_key' => get_option( Base_App::OPTION_CONNECT_SITE_KEY ), 'count' => count( $users ), 'users' => $users, ]; return $params; } } common/modules/connect/connect-menu-item.php 0000644 00000002236 14717626151 0015212 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Core\Common\Modules\Connect\Apps\Base_App; use Elementor\Plugin; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Connect_Menu_Item implements Admin_Menu_Item_With_Page { public function is_visible() { return false; } public function get_parent_slug() { return Settings::PAGE_ID; } public function get_label() { return esc_html__( 'Connect', 'elementor' ); } public function get_page_title() { return esc_html__( 'Connect', 'elementor' ); } public function get_capability() { return 'edit_posts'; } public function render() { $apps = Plugin::$instance->common->get_component( 'connect' )->get_apps(); ?> <style> .elementor-connect-app-wrapper{ margin-bottom: 50px; overflow: hidden; } </style> <div class="wrap"> <?php /** @var Base_App $app */ foreach ( $apps as $app ) { echo '<div class="elementor-connect-app-wrapper">'; $app->render_admin_widget(); echo '</div>'; } ?> </div><!-- /.wrap --> <?php } } common/modules/connect/apps/connect.php 0000644 00000000673 14717626151 0014262 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect\Apps; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Connect extends Common_App { public function get_title() { return esc_html__( 'Connect', 'elementor' ); } /** * @since 2.3.0 * @access public */ protected function get_slug() { return 'connect'; } /** * @since 2.3.0 * @access public */ public function render_admin_widget() {} } common/modules/connect/apps/base-user-app.php 0000644 00000001060 14717626151 0015264 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect\Apps; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Base_User_App extends Base_App { /** * @since 2.3.0 * @access protected */ protected function update_settings() { update_user_option( get_current_user_id(), $this->get_option_name(), $this->data ); } /** * @since 2.3.0 * @access protected */ protected function init_data() { $this->data = get_user_option( $this->get_option_name() ); if ( ! $this->data ) { $this->data = []; } } } common/modules/connect/apps/common-app.php 0000644 00000001614 14717626151 0014673 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect\Apps; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Common_App extends Base_User_App { const OPTION_CONNECT_COMMON_DATA_KEY = self::OPTION_NAME_PREFIX . 'common_data'; protected static $common_data = null; /** * @since 2.3.0 * @access public */ public function get_option_name() { return static::OPTION_NAME_PREFIX . 'common_data'; } /** * @since 2.3.0 * @access protected */ protected function init_data() { if ( is_null( self::$common_data ) ) { self::$common_data = get_user_option( static::get_option_name() ); if ( ! self::$common_data ) { self::$common_data = []; }; } $this->data = & self::$common_data; } public function action_reset() { delete_user_option( get_current_user_id(), static::OPTION_CONNECT_COMMON_DATA_KEY ); parent::action_reset(); } } common/modules/connect/apps/library.php 0000644 00000007342 14717626151 0014275 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect\Apps; use Elementor\Api; use Elementor\User; use Elementor\Plugin; use Elementor\Core\Common\Modules\Connect\Module as ConnectModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Library extends Common_App { public function get_title() { return esc_html__( 'Library', 'elementor' ); } /** * @since 2.3.0 * @access protected */ protected function get_slug() { return 'library'; } public function get_template_content( $id ) { if ( ! $this->is_connected() ) { return new \WP_Error( '401', esc_html__( 'Connecting to the Library failed. Please try reloading the page and try again', 'elementor' ) ); } $body_args = [ 'id' => $id, // Which API version is used. 'api_version' => ELEMENTOR_VERSION, // Which language to return. 'site_lang' => get_bloginfo( 'language' ), ]; /** * API: Template body args. * * Filters the body arguments send with the GET request when fetching the content. * * @since 1.0.0 * * @param array $body_args Body arguments. */ $body_args = apply_filters( 'elementor/api/get_templates/body_args', $body_args ); $template_content = $this->request( 'get_template_content', $body_args, true ); if ( is_wp_error( $template_content ) && 401 === $template_content->get_error_code() ) { // Normalize 401 message return new \WP_Error( 401, esc_html__( 'Connecting to the Library failed. Please try reloading the page and try again', 'elementor' ) ); } return $template_content; } public function localize_settings( $settings ) { $is_connected = $this->is_connected(); /** @var ConnectModule $connect */ $connect = Plugin::$instance->common->get_component( 'connect' ); return array_replace_recursive( $settings, [ 'library_connect' => [ 'is_connected' => $is_connected, 'subscription_plans' => $connect->get_subscription_plans( 'template-library' ), // TODO: Remove `base_access_level`. 'base_access_level' => ConnectModule::ACCESS_LEVEL_CORE, 'base_access_tier' => ConnectModule::ACCESS_TIER_FREE, 'current_access_level' => ConnectModule::ACCESS_LEVEL_CORE, 'current_access_tier' => ConnectModule::ACCESS_TIER_FREE, ], ] ); } public function library_connect_popup_seen() { User::set_introduction_viewed( [ 'introductionKey' => 'library_connect', ] ); } /** * @param \Elementor\Core\Common\Modules\Ajax\Module $ajax_manager */ public function register_ajax_actions( $ajax_manager ) { $ajax_manager->register_ajax_action( 'library_connect_popup_seen', [ $this, 'library_connect_popup_seen' ] ); } /** * After Connect * * After Connecting to the library, re-fetch the library data to get it up to date. * * @since 3.7.0 */ protected function after_connect() { Api::get_library_data( true ); } protected function get_app_info() { return [ 'user_common_data' => [ 'label' => 'User Common Data', 'value' => get_user_option( $this->get_option_name(), get_current_user_id() ), ], 'connect_site_key' => [ 'label' => 'Site Key', 'value' => get_option( self::OPTION_CONNECT_SITE_KEY ), ], 'remote_info_library' => [ 'label' => 'Remote Library Info', 'value' => get_option( 'elementor_remote_info_library' ), ], ]; } protected function get_popup_success_event_data() { return [ 'access_level' => ConnectModule::ACCESS_LEVEL_CORE, 'access_tier' => ConnectModule::ACCESS_TIER_FREE, ]; } protected function init() { add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] ); add_filter( 'elementor/common/localize_settings', [ $this, 'localize_settings' ] ); add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); } } common/modules/connect/apps/base-app.php 0000644 00000047652 14717626151 0014331 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect\Apps; use Elementor\Core\Admin\Admin_Notices; use Elementor\Core\Common\Modules\Connect\Admin; use Elementor\Core\Utils\Collection; use Elementor\Core\Utils\Http; use Elementor\Core\Utils\Str; use Elementor\Plugin; use Elementor\Tracker; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Base_App { const OPTION_NAME_PREFIX = 'elementor_connect_'; const OPTION_CONNECT_SITE_KEY = self::OPTION_NAME_PREFIX . 'site_key'; const SITE_URL = 'https://my.elementor.com/connect/v1'; const API_URL = 'https://my.elementor.com/api/connect/v1'; const HTTP_RETURN_TYPE_OBJECT = 'object'; const HTTP_RETURN_TYPE_ARRAY = 'array'; protected $data = []; protected $auth_mode = ''; /** * @var Http */ protected $http; /** * @since 2.3.0 * @access protected * @abstract * TODO: make it public. */ abstract protected function get_slug(); /** * @since 2.8.0 * @access public * TODO: make it abstract. */ public function get_title() { return $this->get_slug(); } /** * @since 2.3.0 * @access protected * @abstract */ abstract protected function update_settings(); /** * @since 2.3.0 * @access public * @static */ public static function get_class_name() { return get_called_class(); } /** * @access public * @abstract */ public function render_admin_widget() { // PHPCS - the method get_title return a plain string. echo '<h2>' . $this->get_title() . '</h2>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped if ( $this->is_connected() ) { $remote_user = $this->get( 'user' ); $title = sprintf( /* translators: %s: Remote user. */ esc_html__( 'Connected as %s', 'elementor' ), '<strong>' . esc_html( $remote_user->email ) . '</strong>' ); $label = esc_html__( 'Disconnect', 'elementor' ); $url = $this->get_admin_url( 'disconnect' ); $attr = ''; echo sprintf( '%s <a %s href="%s">%s</a>', // PHPCS - the variable $title is already escaped above. $title, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped // PHPCS - the variable $attr is a plain string. $attr, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped esc_attr( $url ), esc_html( $label ) ); } else { echo 'Not Connected'; } echo '<hr>'; $this->print_app_info(); if ( current_user_can( 'manage_options' ) ) { printf( '<div><a href="%s">%s</a></div>', esc_url( $this->get_admin_url( 'reset' ) ), esc_html__( 'Reset Data', 'elementor' ) ); } echo '<hr>'; } /** * @since 2.3.0 * @access protected */ protected function get_option_name() { return static::OPTION_NAME_PREFIX . $this->get_slug(); } /** * @since 2.3.0 * @access public */ public function admin_notice() { $notices = $this->get( 'notices' ); if ( ! $notices ) { return; } $this->print_notices( $notices ); $this->delete( 'notices' ); } public function get_app_token_from_cli_token( $cli_token ) { $response = $this->request( 'get_app_token_from_cli_token', [ 'cli_token' => $cli_token, ] ); if ( is_wp_error( $response ) ) { // PHPCS - the variable $response does not contain a user input value. wp_die( $response, $response->get_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } // Use state as usual. $_REQUEST['state'] = $this->get( 'state' ); $_REQUEST['code'] = $response->code; } /** * @since 2.3.0 * @access public */ public function action_authorize() { if ( $this->is_connected() ) { $this->add_notice( esc_html__( 'Already connected.', 'elementor' ), 'info' ); $this->redirect_to_admin_page(); return; } $this->set_client_id(); $this->set_request_state(); $this->redirect_to_remote_authorize_url(); } public function action_reset() { if ( current_user_can( 'manage_options' ) ) { delete_option( static::OPTION_CONNECT_SITE_KEY ); delete_option( 'elementor_remote_info_library' ); } $this->redirect_to_admin_page(); } /** * @since 2.3.0 * @access public */ public function action_get_token() { if ( $this->is_connected() ) { $this->redirect_to_admin_page(); } //phpcs:ignore WordPress.Security.NonceVerification.Recommended - The user as been authorized before in 'connect'. $state = Utils::get_super_global_value( $_REQUEST, 'state' ); if ( $state !== $this->get( 'state' ) ) { $this->add_notice( 'Get Token: Invalid Request.', 'error' ); $this->redirect_to_admin_page(); } $response = $this->request( 'get_token', [ 'grant_type' => 'authorization_code', 'code' => Utils::get_super_global_value( $_REQUEST, 'code' ), //phpcs:ignore WordPress.Security.NonceVerification.Recommended 'redirect_uri' => rawurlencode( $this->get_admin_url( 'get_token' ) ), 'client_id' => $this->get( 'client_id' ), ] ); if ( is_wp_error( $response ) ) { $notice = 'Cannot Get Token:' . $response->get_error_message(); $this->add_notice( $notice, 'error' ); $this->redirect_to_admin_page(); } $this->delete( 'state' ); $this->set( (array) $response ); if ( ! empty( $response->data_share_opted_in ) && current_user_can( 'manage_options' ) ) { Tracker::set_opt_in( true ); } $this->after_connect(); // Add the notice *after* the method `after_connect`, so an app can redirect without the notice. $this->add_notice( esc_html__( 'Connected successfully.', 'elementor' ) ); $this->redirect_to_admin_page(); } /** * @since 2.3.0 * @access public */ public function action_disconnect() { if ( $this->is_connected() ) { $this->disconnect(); $this->add_notice( esc_html__( 'Disconnected successfully.', 'elementor' ) ); } $this->redirect_to_admin_page(); } /** * @since 2.8.0 * @access public */ public function action_reconnect() { $this->disconnect(); $this->action_authorize(); } /** * @since 2.3.0 * @access public */ public function get_admin_url( $action, $params = [] ) { $params = [ 'app' => $this->get_slug(), 'action' => $action, 'nonce' => wp_create_nonce( $this->get_slug() . $action ), ] + $params; $admin_url = Str::encode_idn_url( get_admin_url() ); $admin_url .= 'admin.php?page=' . Admin::PAGE_ID; return add_query_arg( $params, $admin_url ); } /** * @since 2.3.0 * @access public */ public function is_connected() { return (bool) $this->get( 'access_token' ); } /** * @since 2.3.0 * @access protected */ protected function init() {} /** * @since 2.3.0 * @access protected */ protected function init_data() {} /** * @since 2.3.0 * @access protected */ protected function after_connect() {} /** * @since 2.3.0 * @access public */ public function get( $key, $default = null ) { $this->init_data(); return isset( $this->data[ $key ] ) ? $this->data[ $key ] : $default; } /** * @since 2.3.0 * @access protected */ protected function set( $key, $value = null ) { $this->init_data(); if ( is_array( $key ) ) { $this->data = array_replace_recursive( $this->data, $key ); } else { $this->data[ $key ] = $value; } $this->update_settings(); } /** * @since 2.3.0 * @access protected */ protected function delete( $key = null ) { $this->init_data(); if ( $key ) { unset( $this->data[ $key ] ); } else { $this->data = []; } $this->update_settings(); } /** * @since 2.3.0 * @access protected */ protected function add( $key, $value, $default = '' ) { $new_value = $this->get( $key, $default ); if ( is_array( $new_value ) ) { $new_value[] = $value; } elseif ( is_string( $new_value ) ) { $new_value .= $value; } elseif ( is_numeric( $new_value ) ) { $new_value += $value; } $this->set( $key, $new_value ); } /** * @since 2.3.0 * @access protected */ protected function add_notice( $content, $type = 'success' ) { $this->add( 'notices', compact( 'content', 'type' ), [] ); } /** * @param $action * @param array $request_body * @param false $as_array * * @return mixed|\WP_Error */ protected function request( $action, $request_body = [], $as_array = false ) { $request_body = $this->get_connect_info() + $request_body; return $this->http_request( 'POST', $action, [ 'timeout' => 25, 'body' => $request_body, 'headers' => $this->is_connected() ? [ 'X-Elementor-Signature' => $this->generate_signature( $request_body ) ] : [], ], [ 'return_type' => $as_array ? static::HTTP_RETURN_TYPE_ARRAY : static::HTTP_RETURN_TYPE_OBJECT, ] ); } /** * Get Base Connect Info * * Returns an array of connect info. * * @return array */ protected function get_base_connect_info() { return [ 'app' => $this->get_slug(), 'access_token' => $this->get( 'access_token' ), 'client_id' => $this->get( 'client_id' ), 'local_id' => get_current_user_id(), 'site_key' => $this->get_site_key(), 'home_url' => trailingslashit( home_url() ), ]; } /** * Get all the connect information * * @return array */ protected function get_connect_info() { $connect_info = $this->get_base_connect_info(); $additional_info = []; /** * Additional connect info. * * Filters the connection information when connecting to Elementor servers. * This hook can be used to add more information or add more data. * * @param array $additional_info Additional connecting information array. * @param Base_App $this The base app instance. */ $additional_info = apply_filters( 'elementor/connect/additional-connect-info', $additional_info, $this ); return array_merge( $connect_info, $additional_info ); } /** * @param $endpoint * * @return array */ protected function generate_authentication_headers( $endpoint ) { $connect_info = ( new Collection( $this->get_connect_info() ) ) ->map_with_keys( function ( $value, $key ) { // For bc `get_connect_info` returns the connect info with underscore, // headers with underscore are not valid, so all the keys with underscore will be replaced to hyphen. return [ str_replace( '_', '-', $key ) => $value ]; } ) ->replace_recursive( [ 'endpoint' => $endpoint ] ) ->sort_keys(); return $connect_info ->merge( [ 'X-Elementor-Signature' => $this->generate_signature( $connect_info->all() ) ] ) ->all(); } /** * Send an http request * * @param $method * @param $endpoint * @param array $args * @param array $options * * @return mixed|\WP_Error */ protected function http_request( $method, $endpoint, $args = [], $options = [] ) { $options = wp_parse_args( $options, [ 'return_type' => static::HTTP_RETURN_TYPE_OBJECT, ] ); $args = array_replace_recursive( [ 'headers' => $this->is_connected() ? $this->generate_authentication_headers( $endpoint ) : [], 'method' => $method, 'timeout' => 10, ], $args ); $response = $this->http->request_with_fallback( $this->get_generated_urls( $endpoint ), $args ); if ( is_wp_error( $response ) && empty( $options['with_error_data'] ) ) { // PHPCS - the variable $response does not contain a user input value. wp_die( $response, [ 'back_link' => true ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } $body = wp_remote_retrieve_body( $response ); $response_code = (int) wp_remote_retrieve_response_code( $response ); if ( ! $response_code ) { return new \WP_Error( 500, 'No Response' ); } // Server sent a success message without content. if ( 'null' === $body ) { $body = true; } $body = json_decode( $body, static::HTTP_RETURN_TYPE_ARRAY === $options['return_type'] ); if ( false === $body ) { return new \WP_Error( 422, 'Wrong Server Response' ); } if ( 200 !== $response_code ) { // In case $as_array = true. $body = (object) $body; $message = isset( $body->message ) ? $body->message : wp_remote_retrieve_response_message( $response ); $code = (int) ( isset( $body->code ) ? $body->code : $response_code ); if ( ! $code ) { $code = $response_code; } if ( 401 === $code ) { $this->delete(); $should_retry = ! in_array( $this->auth_mode, [ 'xhr', 'cli' ], true ); if ( $should_retry ) { $this->action_authorize(); } } if ( isset( $options['with_error_data'] ) && true === $options['with_error_data'] ) { return new \WP_Error( $code, $message, $body ); } return new \WP_Error( $code, $message ); } return $body; } /** * Create a signature for the http request * * @param array $payload * * @return false|string */ private function generate_signature( $payload = [] ) { return hash_hmac( 'sha256', wp_json_encode( $payload, JSON_NUMERIC_CHECK ), $this->get( 'access_token_secret' ) ); } /** * @since 2.3.0 * @access protected */ protected function get_api_url() { return static::API_URL . '/' . $this->get_slug(); } /** * @since 2.3.0 * @access protected */ protected function get_remote_site_url() { return static::SITE_URL . '/' . $this->get_slug(); } /** * @since 2.3.0 * @access protected */ protected function get_remote_authorize_url() { $redirect_uri = $this->get_auth_redirect_uri(); $allowed_query_params_to_propagate = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'source', 'screen_hint', ]; $query_params = ( new Collection( $_GET ) ) // phpcs:ignore ->only( $allowed_query_params_to_propagate ) ->merge( [ 'action' => 'authorize', 'response_type' => 'code', 'client_id' => $this->get( 'client_id' ), 'auth_secret' => $this->get( 'auth_secret' ), 'state' => $this->get( 'state' ), 'redirect_uri' => rawurlencode( $redirect_uri ), 'may_share_data' => current_user_can( 'manage_options' ) && ! Tracker::is_allow_track(), 'reconnect_nonce' => wp_create_nonce( $this->get_slug() . 'reconnect' ), ] ); return add_query_arg( $query_params->all(), $this->get_remote_site_url() ); } /** * @since 2.3.0 * @access protected */ protected function redirect_to_admin_page( $url = '' ) { if ( ! $url ) { $url = Admin::$url; } switch ( $this->auth_mode ) { case 'popup': $this->print_popup_close_script( $url ); break; case 'cli': $this->admin_notice(); die; default: wp_safe_redirect( $url ); die; } } /** * @since 2.3.0 * @access protected */ protected function set_client_id() { $source = Utils::get_super_global_value( $_REQUEST, 'source' ) ?? ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. $response = $this->request( 'get_client_id', [ 'source' => esc_attr( $source ), ] ); if ( is_wp_error( $response ) ) { // PHPCS - the variable $response does not contain a user input value. wp_die( $response, $response->get_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } $this->set( 'client_id', $response->client_id ); $this->set( 'auth_secret', $response->auth_secret ); } /** * @since 2.3.0 * @access protected */ protected function set_request_state() { $this->set( 'state', wp_generate_password( 12, false ) ); } protected function get_popup_success_event_data() { return []; } /** * @since 2.3.0 * @access protected */ protected function print_popup_close_script( $url ) { $data = $this->get_popup_success_event_data(); ?> <script> if ( opener && opener !== window ) { opener.jQuery( 'body' ).trigger( 'elementor/connect/success/<?php echo esc_attr( Utils::get_super_global_value( $_REQUEST, 'callback_id' ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. ?>', <?php echo wp_json_encode( $data ); ?> ); opener.dispatchEvent( new CustomEvent( 'elementor/connect/success' ), <?php echo wp_json_encode( $data ); ?> ); window.close(); opener.focus(); } else { location = '<?php echo esc_url( $url ); ?>'; } </script> <?php die; } /** * @since 2.3.0 * @access protected */ protected function disconnect() { if ( $this->is_connected() ) { // Try update the server, but not needed to handle errors. $this->request( 'disconnect' ); } $this->delete(); } /** * @since 2.3.0 * @access protected */ public function get_site_key() { $site_key = get_option( static::OPTION_CONNECT_SITE_KEY ); if ( ! $site_key ) { $site_key = md5( uniqid( wp_generate_password() ) ); update_option( static::OPTION_CONNECT_SITE_KEY, $site_key ); } return $site_key; } protected function redirect_to_remote_authorize_url() { switch ( $this->auth_mode ) { case 'cli': $this->get_app_token_from_cli_token( Utils::get_super_global_value( $_REQUEST, 'token' ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. return; default: wp_redirect( $this->get_remote_authorize_url() ); //phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Safe redirect is used here. die; } } protected function get_auth_redirect_uri() { $redirect_uri = $this->get_admin_url( 'get_token' ); switch ( $this->auth_mode ) { case 'popup': $redirect_uri = add_query_arg( [ 'mode' => 'popup', 'callback_id' => esc_attr( Utils::get_super_global_value( $_REQUEST, 'callback_id' ) ), //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. ], $redirect_uri ); break; } return $redirect_uri; } protected function print_notices( $notices ) { switch ( $this->auth_mode ) { case 'cli': foreach ( $notices as $notice ) { printf( '[%s] %s', wp_kses_post( $notice['type'] ), wp_kses_post( $notice['content'] ) ); } break; default: /** * @var Admin_Notices $admin_notices */ $admin_notices = Plugin::$instance->admin->get_component( 'admin-notices' ); foreach ( $notices as $notice ) { $options = [ 'description' => wp_kses_post( wpautop( $notice['content'] ) ), 'type' => $notice['type'], 'icon' => false, ]; $admin_notices->print_admin_notice( $options ); } } } protected function get_app_info() { return []; } protected function print_app_info() { $app_info = $this->get_app_info(); foreach ( $app_info as $key => $item ) { if ( $item['value'] ) { $status = 'Exist'; $color = 'green'; } else { $status = 'Empty'; $color = 'red'; } // PHPCS - the values of $item['label'], $color, $status are plain strings. printf( '%s: <strong style="color:%s">%s</strong><br>', $item['label'], $color, $status ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } private function get_generated_urls( $endpoint ) { $base_urls = $this->get_api_url(); if ( ! is_array( $base_urls ) ) { $base_urls = [ $base_urls ]; } return array_map( function ( $base_url ) use ( $endpoint ) { return trailingslashit( $base_url ) . $endpoint; }, $base_urls ); } private function init_auth_mode() { $is_rest = defined( 'REST_REQUEST' ) && REST_REQUEST; $is_ajax = wp_doing_ajax(); if ( $is_rest || $is_ajax ) { // Set default to 'xhr' if rest or ajax request. $this->set_auth_mode( 'xhr' ); } $mode = Utils::get_super_global_value( $_REQUEST, 'mode' ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. if ( $mode ) { $allowed_auth_modes = [ 'popup', ]; if ( defined( 'WP_CLI' ) && WP_CLI ) { $allowed_auth_modes[] = 'cli'; } if ( in_array( $mode, $allowed_auth_modes, true ) ) { $this->set_auth_mode( $mode ); } } } public function set_auth_mode( $mode ) { $this->auth_mode = $mode; } /** * @since 2.3.0 * @access public */ public function __construct() { add_action( 'admin_notices', [ $this, 'admin_notice' ] ); $this->init_auth_mode(); $this->http = new Http(); /** * Allow extended apps to customize the __construct without call parent::__construct. */ $this->init(); } } common/modules/connect/admin.php 0000644 00000003645 14717626151 0012760 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect; use Elementor\Core\Admin\Menu\Admin_Menu_Manager; use Elementor\Plugin; use Elementor\Settings; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin { const PAGE_ID = 'elementor-connect'; public static $url = ''; /** * @since 2.3.0 * @access public */ public function register_admin_menu( Admin_Menu_Manager $admin_menu ) { $admin_menu->register( static::PAGE_ID, new Connect_Menu_Item() ); } /** * @since 2.3.0 * @access public */ public function on_load_page() { if ( isset( $_GET['action'], $_GET['app'] ) ) { $manager = Plugin::$instance->common->get_component( 'connect' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $app_slug = Utils::get_super_global_value( $_GET, 'app' ); $app = $manager->get_app( $app_slug ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $action = Utils::get_super_global_value( $_GET, 'action' ); $nonce_action = $app_slug . $action; if ( ! $app ) { wp_die( 'Unknown app: ' . esc_attr( $app_slug ) ); } if ( ! wp_verify_nonce( Utils::get_super_global_value( $_GET, 'nonce' ), $nonce_action ) ) { wp_die( 'Invalid Nonce', 'Invalid Nonce', [ 'back_link' => true, ] ); } $method = 'action_' . $action; if ( method_exists( $app, $method ) ) { call_user_func( [ $app, $method ] ); } } } /** * @since 2.3.0 * @access public */ public function __construct() { self::$url = admin_url( 'admin.php?page=' . self::PAGE_ID ); add_action( 'elementor/admin/menu/register', [ $this, 'register_admin_menu' ] ); add_action( 'elementor/admin/menu/after_register', function ( Admin_Menu_Manager $admin_menu, array $hooks ) { if ( ! empty( $hooks[ static::PAGE_ID ] ) ) { add_action( 'load-' . $hooks[ static::PAGE_ID ], [ $this, 'on_load_page' ] ); } }, 10, 2 ); } } upgrade/custom-tasks-manager.php 0000644 00000004663 14717626151 0012774 0 ustar 00 <?php namespace Elementor\Core\Upgrade; use Elementor\Core\Base\Background_Task_Manager; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Custom_Tasks_Manager extends Background_Task_Manager { const TASKS_OPTION_KEY = 'elementor_custom_tasks'; const QUERY_LIMIT = 100; public function get_name() { return 'custom-task-manager'; } public function get_action() { return 'custom_task_manger'; } public function get_plugin_name() { return 'elementor'; } public function get_plugin_label() { return esc_html__( 'Elementor', 'elementor' ); } public function get_task_runner_class() { return Task::class; } public function get_query_limit() { return self::QUERY_LIMIT; } protected function start_run() { $custom_tasks_callbacks = $this->get_custom_tasks(); if ( empty( $custom_tasks_callbacks ) ) { return; } $task_runner = $this->get_task_runner(); foreach ( $custom_tasks_callbacks as $callback ) { $task_runner->push_to_queue( [ 'callback' => $callback, ] ); } $this->clear_tasks_requested_to_run(); Plugin::$instance->logger->get_logger()->info( 'Elementor custom task(s) process has been queued.', [ 'meta' => [ $custom_tasks_callbacks ], ] ); $task_runner->save()->dispatch(); } public function get_tasks_class() { return Custom_Tasks::class; } public function get_tasks_requested_to_run() { return get_option( self::TASKS_OPTION_KEY, [] ); } public function clear_tasks_requested_to_run() { return update_option( self::TASKS_OPTION_KEY, [], false ); } public function add_tasks_requested_to_run( $tasks = [] ) { $current_tasks = $this->get_tasks_requested_to_run(); $current_tasks = array_merge( $current_tasks, $tasks ); update_option( self::TASKS_OPTION_KEY, $current_tasks, false ); } private function get_custom_tasks() { $tasks_requested_to_run = $this->get_tasks_requested_to_run(); $tasks_class = $this->get_tasks_class(); $tasks_reflection = new \ReflectionClass( $tasks_class ); $callbacks = []; foreach ( $tasks_reflection->getMethods() as $method ) { $method_name = $method->getName(); if ( in_array( $method_name, $tasks_requested_to_run, true ) ) { $callbacks[] = [ $tasks_class, $method_name ]; } } return $callbacks; } public function __construct() { $task_runner = $this->get_task_runner(); if ( $task_runner->is_running() ) { return; } $this->start_run(); } } upgrade/task.php 0000644 00000001015 14717626151 0007655 0 ustar 00 <?php namespace Elementor\Core\Upgrade; use Elementor\Core\Base\Background_Task; use Elementor\Core\Base\DB_Upgrades_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Task extends Background_Task { /** * @var DB_Upgrades_Manager */ protected $manager; protected function format_callback_log( $item ) { return $this->manager->get_plugin_label() . '/Tasks - ' . $item['callback'][1]; } public function set_limit( $limit ) { $this->manager->set_query_limit( $limit ); } } upgrade/upgrades.php 0000644 00000064730 14717626151 0010542 0 ustar 00 <?php namespace Elementor\Core\Upgrade; use Elementor\Api; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Core\Settings\Page\Manager as SettingsPageManager; use Elementor\Icons_Manager; use Elementor\Includes\Elements\Container; use Elementor\Modules\Usage\Module; use Elementor\Plugin; use Elementor\Tracker; use Elementor\App\Modules\ImportExport\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor upgrades. * * Elementor upgrades handler class is responsible for updating different * Elementor versions. * * @since 1.0.0 */ class Upgrades { public static function _on_each_version( $updater ) { self::recalc_usage_data( $updater ); self::remove_remote_info_api_data(); $uploads_manager = Plugin::$instance->uploads_manager; $temp_dir = $uploads_manager->get_temp_dir(); if ( file_exists( $temp_dir ) ) { $uploads_manager->remove_file_or_dir( $temp_dir ); } } /** * Upgrade Elementor 0.3.2 * * Change the image widget link URL, setting is to `custom` link. * * @since 2.0.0 * @static * @access public */ public static function _v_0_3_2() { global $wpdb; $post_ids = $wpdb->get_col( 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` WHERE `meta_key` = \'_elementor_version\' AND `meta_value` = \'0.1\';' ); if ( empty( $post_ids ) ) { return; } foreach ( $post_ids as $post_id ) { $document = Plugin::$instance->documents->get( $post_id ); if ( $document ) { $data = $document->get_elements_data(); } if ( empty( $data ) ) { continue; } $data = Plugin::$instance->db->iterate_data( $data, function( $element ) { if ( empty( $element['widgetType'] ) || 'image' !== $element['widgetType'] ) { return $element; } if ( ! empty( $element['settings']['link']['url'] ) && ! isset( $element['settings']['link_to'] ) ) { $element['settings']['link_to'] = 'custom'; } return $element; } ); $document = Plugin::$instance->documents->get( $post_id ); $document->save( [ 'elements' => $data, ] ); } } /** * Upgrade Elementor 0.9.2 * * Change the icon widget, icon-box widget and the social-icons widget, * setting their icon padding size to an empty string. * * Change the image widget, setting the image size to full image size. * * @since 2.0.0 * @static * @access public */ public static function _v_0_9_2() { global $wpdb; // Fix Icon/Icon Box Widgets padding. $post_ids = $wpdb->get_col( 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` WHERE `meta_key` = \'_elementor_version\' AND `meta_value` = \'0.2\';' ); if ( empty( $post_ids ) ) { return; } foreach ( $post_ids as $post_id ) { $document = Plugin::$instance->documents->get( $post_id ); if ( $document ) { $data = $document->get_elements_data(); } if ( empty( $data ) ) { continue; } $data = Plugin::$instance->db->iterate_data( $data, function( $element ) { if ( empty( $element['widgetType'] ) ) { return $element; } if ( in_array( $element['widgetType'], [ 'icon', 'icon-box', 'social-icons' ] ) ) { if ( ! empty( $element['settings']['icon_padding']['size'] ) ) { $element['settings']['icon_padding']['size'] = ''; } } if ( 'image' === $element['widgetType'] ) { if ( empty( $element['settings']['image_size'] ) ) { $element['settings']['image_size'] = 'full'; } } return $element; } ); $document = Plugin::$instance->documents->get( $post_id ); $document->save( [ 'elements' => $data, ] ); } } /** * Upgrade Elementor 0.11.0 * * Change the button widget sizes, setting up new button sizes. * * @since 2.0.0 * @static * @access public */ public static function _v_0_11_0() { global $wpdb; // Fix Button widget to new sizes options. $post_ids = $wpdb->get_col( 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` WHERE `meta_key` = \'_elementor_version\' AND `meta_value` = \'0.3\';' ); if ( empty( $post_ids ) ) { return; } foreach ( $post_ids as $post_id ) { $document = Plugin::$instance->documents->get( $post_id ); if ( $document ) { $data = $document->get_elements_data(); } if ( empty( $data ) ) { continue; } $data = Plugin::$instance->db->iterate_data( $data, function( $element ) { if ( empty( $element['widgetType'] ) ) { return $element; } if ( 'button' === $element['widgetType'] ) { $size_to_replace = [ 'small' => 'xs', 'medium' => 'sm', 'large' => 'md', 'xl' => 'lg', 'xxl' => 'xl', ]; if ( ! empty( $element['settings']['size'] ) ) { $old_size = $element['settings']['size']; if ( isset( $size_to_replace[ $old_size ] ) ) { $element['settings']['size'] = $size_to_replace[ $old_size ]; } } } return $element; } ); $document = Plugin::$instance->documents->get( $post_id ); $document->save( [ 'elements' => $data, ] ); } } /** * Upgrade Elementor 2.0.0 * * Fix post titles for old autosave drafts that saved with the format 'Auto Save 2018-03-18 17:24'. * * @static * @since 2.0.0 * @access public */ public static function _v_2_0_0() { global $wpdb; $posts = $wpdb->get_results( 'SELECT `ID`, `post_title`, `post_parent` FROM `' . $wpdb->posts . '` p LEFT JOIN `' . $wpdb->postmeta . '` m ON p.ID = m.post_id WHERE `post_status` = \'inherit\' AND `post_title` = CONCAT(\'Auto Save \', DATE_FORMAT(post_date, "%Y-%m-%d %H:%i")) AND m.`meta_key` = \'_elementor_data\';' ); if ( empty( $posts ) ) { return; } foreach ( $posts as $post ) { wp_update_post( [ 'ID' => $post->ID, 'post_title' => get_the_title( $post->post_parent ), ] ); } } /** * Upgrade Elementor 2.0.1 * * Fix post titles for old autosave drafts that saved with the format 'Auto Save...'. * * @since 2.0.2 * @static * @access public */ public static function _v_2_0_1() { global $wpdb; $posts = $wpdb->get_results( 'SELECT `ID`, `post_title`, `post_parent` FROM `' . $wpdb->posts . '` p LEFT JOIN `' . $wpdb->postmeta . '` m ON p.ID = m.post_id WHERE `post_status` = \'inherit\' AND `post_title` REGEXP \'^Auto Save [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$\' AND m.`meta_key` = \'_elementor_data\';' ); if ( empty( $posts ) ) { return; } foreach ( $posts as $post ) { $parent = get_post( $post->post_parent ); $title = isset( $parent->post_title ) ? $parent->post_title : ''; wp_update_post( [ 'ID' => $post->ID, 'post_title' => $title, ] ); } } /** * Upgrade Elementor 2.0.10 * * Fix post titles for old autosave drafts that saved with the format 'Auto Save...'. * Fix also Translated titles. * * @since 2.0.10 * @static * @access public */ public static function _v_2_0_10() { global $wpdb; $posts = $wpdb->get_results( 'SELECT `ID`, `post_title`, `post_parent` FROM `' . $wpdb->posts . '` p LEFT JOIN `' . $wpdb->postmeta . '` m ON p.ID = m.post_id WHERE `post_status` = \'inherit\' AND `post_title` REGEXP \'[[:alnum:]]+ [[:alnum:]]+ [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$\' AND m.`meta_key` = \'_elementor_data\';' ); if ( empty( $posts ) ) { return; } foreach ( $posts as $post ) { $parent = get_post( $post->post_parent ); $title = isset( $parent->post_title ) ? $parent->post_title : ''; wp_update_post( [ 'ID' => $post->ID, 'post_title' => $title, ] ); } } public static function _v_2_1_0() { global $wpdb; // upgrade `video` widget settings (merge providers). $post_ids = $wpdb->get_col( 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` WHERE `meta_key` = "_elementor_data" AND `meta_value` LIKE \'%"widgetType":"video"%\';' ); if ( empty( $post_ids ) ) { return; } foreach ( $post_ids as $post_id ) { $do_update = false; $document = Plugin::$instance->documents->get( $post_id ); if ( $document ) { $data = $document->get_elements_data(); } if ( empty( $data ) ) { continue; } $data = Plugin::$instance->db->iterate_data( $data, function( $element ) use ( &$do_update ) { if ( empty( $element['widgetType'] ) || 'video' !== $element['widgetType'] ) { return $element; } $replacements = []; if ( empty( $element['settings']['video_type'] ) || 'youtube' === $element['settings']['video_type'] ) { $replacements = [ 'yt_autoplay' => 'autoplay', 'yt_controls' => 'controls', 'yt_mute' => 'mute', 'yt_rel' => 'rel', 'link' => 'youtube_url', ]; } elseif ( 'vimeo' === $element['settings']['video_type'] ) { $replacements = [ 'vimeo_autoplay' => 'autoplay', 'vimeo_loop' => 'loop', 'vimeo_color' => 'color', 'vimeo_link' => 'vimeo_url', ]; } // cleanup old unused settings. unset( $element['settings']['yt_rel_videos'] ); foreach ( $replacements as $old => $new ) { if ( ! empty( $element['settings'][ $old ] ) ) { $element['settings'][ $new ] = $element['settings'][ $old ]; $do_update = true; } } return $element; } ); // Only update if needed. if ( ! $do_update ) { continue; } // We need the `wp_slash` in order to avoid the unslashing during the `update_post_meta` $json_value = wp_slash( wp_json_encode( $data ) ); update_metadata( 'post', $post_id, '_elementor_data', $json_value ); // Clear WP cache for next step. wp_cache_flush(); } // End foreach(). } /** * @param Updater $updater * * @return bool */ public static function _v_2_3_0_widget_image( $updater ) { global $wpdb; // upgrade `video` widget settings (merge providers). $post_ids = $updater->query_col( 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` WHERE `meta_key` = "_elementor_data" AND ( `meta_value` LIKE \'%"widgetType":"image"%\' OR `meta_value` LIKE \'%"widgetType":"theme-post-featured-image"%\' OR `meta_value` LIKE \'%"widgetType":"theme-site-logo"%\' OR `meta_value` LIKE \'%"widgetType":"woocommerce-category-image"%\' );' ); if ( empty( $post_ids ) ) { return false; } $widgets = [ 'image', 'theme-post-featured-image', 'theme-site-logo', 'woocommerce-category-image', ]; foreach ( $post_ids as $post_id ) { // Clear WP cache for next step. wp_cache_flush(); $do_update = false; $document = Plugin::$instance->documents->get( $post_id ); if ( ! $document ) { continue; } $data = $document->get_elements_data(); if ( empty( $data ) ) { continue; } $data = Plugin::$instance->db->iterate_data( $data, function( $element ) use ( &$do_update, $widgets ) { if ( empty( $element['widgetType'] ) || ! in_array( $element['widgetType'], $widgets ) ) { return $element; } if ( ! empty( $element['settings']['caption'] ) ) { if ( ! isset( $element['settings']['caption_source'] ) ) { $element['settings']['caption_source'] = 'custom'; $do_update = true; } } return $element; } ); // Only update if needed. if ( ! $do_update ) { continue; } // We need the `wp_slash` in order to avoid the unslashing during the `update_post_meta` $json_value = wp_slash( wp_json_encode( $data ) ); update_metadata( 'post', $post_id, '_elementor_data', $json_value ); } // End foreach(). return $updater->should_run_again( $post_ids ); } /** * @param Updater $updater * * @return bool */ public static function _v_2_3_0_template_type( $updater ) { global $wpdb; $post_ids = $updater->query_col( 'SELECT p.ID FROM `' . $wpdb->posts . '` AS p LEFT JOIN `' . $wpdb->postmeta . '` AS pm1 ON (p.ID = pm1.post_id) LEFT JOIN `' . $wpdb->postmeta . '` AS pm2 ON (pm1.post_id = pm2.post_id AND pm2.meta_key = "_elementor_template_type") WHERE p.post_status != "inherit" AND pm1.`meta_key` = "_elementor_data" AND pm2.post_id IS NULL;' ); if ( empty( $post_ids ) ) { return false; } foreach ( $post_ids as $post_id ) { // Clear WP cache for next step. wp_cache_flush(); $document = Plugin::$instance->documents->get( $post_id ); if ( ! $document ) { continue; } $document->save_template_type(); } // End foreach(). return $updater->should_run_again( $post_ids ); } /** * Set FontAwesome Migration needed flag */ public static function _v_2_6_0_fa4_migration_flag() { add_option( 'elementor_icon_manager_needs_update', 'yes' ); add_option( 'elementor_load_fa4_shim', 'yes' ); } /** * migrate Icon control string value to Icons control array value * * @param array $element * @param array $args * * @return mixed */ public static function _migrate_icon_fa4_value( $element, $args ) { $widget_id = $args['widget_id']; if ( empty( $element['widgetType'] ) || $widget_id !== $element['widgetType'] ) { return $element; } foreach ( $args['control_ids'] as $old_name => $new_name ) { // exit if new value exists if ( isset( $element['settings'][ $new_name ] ) ) { continue; } // exit if no value to migrate if ( ! isset( $element['settings'][ $old_name ] ) ) { continue; } $element['settings'][ $new_name ] = Icons_Manager::fa4_to_fa5_value_migration( $element['settings'][ $old_name ] ); $args['do_update'] = true; } return $element; } /** * Set FontAwesome 5 value Migration on for button widget * * @param Updater $updater */ public static function _v_2_6_6_fa4_migration_button( $updater ) { $changes = [ [ 'callback' => [ 'Elementor\Core\Upgrade\Upgrades', '_migrate_icon_fa4_value' ], 'control_ids' => [ 'icon' => 'selected_icon', ], ], ]; Upgrade_Utils::_update_widget_settings( 'button', $updater, $changes ); Upgrade_Utils::_update_widget_settings( 'icon-box', $updater, $changes ); } /** * Update database to separate page from post. * * @param Updater $updater * * @param string $type * * @return bool */ public static function rename_document_base_to_wp( $updater, $type ) { global $wpdb; $post_ids = $updater->query_col( $wpdb->prepare( "SELECT p1.ID FROM {$wpdb->posts} AS p LEFT JOIN {$wpdb->posts} AS p1 ON (p.ID = p1.post_parent || p.ID = p1.ID) WHERE p.post_type = %s;", $type ) ); if ( empty( $post_ids ) ) { return false; } $sql_post_ids = implode( ',', $post_ids ); $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = %s WHERE meta_key = '_elementor_template_type' && post_id in ( %s ); ", 'wp-' . $type, $sql_post_ids ) ); return $updater->should_run_again( $post_ids ); } /** * Update database to separate page from post. * * @param Updater $updater * * @return bool */ // Because the query is slow on large sites, temporary don't upgrade. /* public static function _v_2_7_0_rename_document_types_to_wp( $updater ) { return self::rename_document_base_to_wp( $updater, 'post' ) || self::rename_document_base_to_wp( $updater, 'page' ); }*/ // Upgrade code was fixed & moved to _v_2_7_1_remove_old_usage_data. /* public static function _v_2_7_0_remove_old_usage_data() {} */ // Upgrade code moved to _v_2_7_1_recalc_usage_data. /* public static function _v_2_7_0_recalc_usage_data( $updater ) {} */ /** * Don't use the old data anymore. * Since 2.7.1 the key was changed from `elementor_elements_usage` to `elementor_controls_usage`. */ public static function _v_2_7_1_remove_old_usage_data() { delete_option( 'elementor_elements_usage' ); delete_post_meta_by_key( '_elementor_elements_usage' ); } /** * Recalc usage. * * @param Updater $updater * * @return bool */ public static function recalc_usage_data( $updater ) { if ( ! Tracker::is_allow_track() ) { return false; } /** @var Module $module */ $module = Plugin::$instance->modules_manager->get_modules( 'usage' ); $post_count = $module->recalc_usage( $updater->get_limit(), $updater->get_current_offset() ); return ( $post_count === $updater->get_limit() ); } public static function _v_2_7_1_recalc_usage_data( $updater ) { return self::recalc_usage_data( $updater ); } public static function _v_2_8_3_recalc_usage_data( $updater ) { // Re-calc since older version(s) had invalid values. return self::recalc_usage_data( $updater ); } /** * Move general & lightbox settings to active kit and all it's revisions. * * @param Updater $updater * * @return bool */ public static function _v_3_0_0_move_general_settings_to_kit( $updater ) { $callback = function( $kit_id ) { $kit = Plugin::$instance->documents->get( $kit_id ); if ( ! $kit ) { self::notice( 'Kit not found. nothing to do.' ); return; } $meta_key = SettingsPageManager::META_KEY; $current_settings = get_option( '_elementor_general_settings', [] ); // Take the `space_between_widgets` from the option due to a bug on E < 3.0.0 that the value `0` is stored separated. $current_settings['space_between_widgets'] = get_option( 'elementor_space_between_widgets', '' ); $current_settings[ Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX . 'md' ] = get_option( 'elementor_viewport_md', '' ); $current_settings[ Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX . 'lg' ] = get_option( 'elementor_viewport_lg', '' ); $kit_settings = $kit->get_meta( $meta_key ); // Already exist. if ( isset( $kit_settings['default_generic_fonts'] ) ) { self::notice( 'General Settings already exist. nothing to do.' ); return; } if ( empty( $current_settings ) ) { self::notice( 'Current settings are empty. nothing to do.' ); return; } if ( ! $kit_settings ) { $kit_settings = []; } // Convert some setting to Elementor slider format. $settings_to_slider = [ 'container_width', 'space_between_widgets', ]; foreach ( $settings_to_slider as $setting ) { if ( isset( $current_settings[ $setting ] ) ) { $current_settings[ $setting ] = [ 'unit' => 'px', 'size' => $current_settings[ $setting ], ]; } } $kit_settings = array_merge( $kit_settings, $current_settings ); $page_settings_manager = SettingsManager::get_settings_managers( 'page' ); $page_settings_manager->save_settings( $kit_settings, $kit_id ); }; return self::move_settings_to_kit( $callback, $updater ); } public static function _v_3_2_0_migrate_breakpoints_to_new_system( $updater, $include_revisions = true ) { $callback = function( $kit_id ) { $kit = Plugin::$instance->documents->get( $kit_id ); $kit_settings = $kit->get_meta( SettingsPageManager::META_KEY ); if ( ! $kit_settings ) { // Nothing to upgrade. return; } $prefix = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX; $old_mobile_option_key = $prefix . 'md'; $old_tablet_option_key = $prefix . 'lg'; $breakpoint_values = [ $old_mobile_option_key => Plugin::$instance->kits_manager->get_current_settings( $old_mobile_option_key ), $old_tablet_option_key => Plugin::$instance->kits_manager->get_current_settings( $old_tablet_option_key ), ]; // Breakpoint values are either a number, or an empty string (empty setting). array_walk( $breakpoint_values, function( &$breakpoint_value, $breakpoint_key ) { if ( $breakpoint_value ) { // If the saved breakpoint value is a number, 1px is reduced because the new breakpoints system is // based on max-width, as opposed to the old breakpoints system that worked based on min-width. $breakpoint_value--; } return $breakpoint_value; } ); $kit_settings[ $prefix . Breakpoints_Manager::BREAKPOINT_KEY_MOBILE ] = $breakpoint_values[ $old_mobile_option_key ]; $kit_settings[ $prefix . Breakpoints_Manager::BREAKPOINT_KEY_TABLET ] = $breakpoint_values[ $old_tablet_option_key ]; $page_settings_manager = SettingsManager::get_settings_managers( 'page' ); $page_settings_manager->save_settings( $kit_settings, $kit_id ); }; return self::move_settings_to_kit( $callback, $updater, $include_revisions ); } public static function _v_3_4_8_fix_font_awesome_default_value_from_1_to_yes() { // if `Icons_Manager::LOAD_FA4_SHIM_OPTION_KEY` value is '1', then set it to `yes`. $load_fa4_shim_option = get_option( Icons_Manager::LOAD_FA4_SHIM_OPTION_KEY ); if ( '1' === $load_fa4_shim_option ) { update_option( Icons_Manager::LOAD_FA4_SHIM_OPTION_KEY, 'yes' ); } } public static function _v_3_5_0_remove_old_elementor_scheme() { global $wpdb; $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE 'elementor_scheme_%';" ); } public static function _v_3_8_0_fix_php8_image_custom_size() { global $wpdb; $attachment_ids = $wpdb->get_col( 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` WHERE `meta_key` = "_wp_attachment_metadata" AND ( `meta_value` LIKE \'%elementor_custom_%\' );' ); foreach ( $attachment_ids as $attachment_id ) { $attachment_metadata = wp_get_attachment_metadata( $attachment_id ); if ( empty( $attachment_metadata['sizes'] ) || ! is_array( $attachment_metadata['sizes'] ) ) { continue; } $old_attachment_metadata = $attachment_metadata; foreach ( $attachment_metadata['sizes'] as $size_key => $size_value ) { if ( 0 !== strpos( $size_key, 'elementor_custom_' ) ) { continue; } if ( absint( $size_value['width'] ) !== $size_value['width'] ) { $attachment_metadata['sizes'][ $size_key ]['width'] = (int) $size_value['width']; } if ( absint( $size_value['height'] ) !== $size_value['height'] ) { $attachment_metadata['sizes'][ $size_key ]['height'] = (int) $size_value['height']; } } if ( $old_attachment_metadata['sizes'] === $attachment_metadata['sizes'] ) { continue; } wp_update_attachment_metadata( $attachment_id, $attachment_metadata ); } } public static function _v_3_16_0_container_updates( $updater ) { $post_ids = self::get_post_ids_by_element_type( $updater, 'container' ); if ( empty( $post_ids ) ) { return false; } foreach ( $post_ids as $post_id ) { $document = Plugin::$instance->documents->get( $post_id ); if ( $document ) { $data = $document->get_elements_data(); } if ( empty( $data ) ) { continue; } $data = self::iterate_containers( $data ); self::save_updated_document( $post_id, $data ); } } public static function _v_3_17_0_site_settings_updates() { $options = [ 'elementor_active_kit', 'elementor_previous_kit' ]; foreach ( $options as $option_name ) { self::maybe_add_gap_control_data( $option_name ); } } private static function maybe_add_gap_control_data( $option_name ) { $kit_id = get_option( $option_name ); if ( ! $kit_id ) { return; } $kit_data_array = get_post_meta( (int) $kit_id, '_elementor_page_settings', true ); $setting_not_exist = ! isset( $kit_data_array['space_between_widgets'] ); $already_processed = isset( $kit_data_array['space_between_widgets']['column'] ); if ( $setting_not_exist || $already_processed ) { return; } $kit_data_array['space_between_widgets'] = Utils::update_space_between_widgets_values( $kit_data_array['space_between_widgets'] ); update_post_meta( (int) $kit_id, '_elementor_page_settings', $kit_data_array ); } public static function remove_remote_info_api_data() { global $wpdb; $key = Api::TRANSIENT_KEY_PREFIX; return $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '{$key}%';" ); // phpcs:ignore } /** * @param callback $callback * @param Updater $updater * * @param bool $include_revisions * * @return mixed */ private static function move_settings_to_kit( $callback, $updater, $include_revisions = true ) { $active_kit_id = Plugin::$instance->kits_manager->get_active_id(); if ( ! $active_kit_id ) { self::notice( 'Active kit not found. nothing to do.' ); return false; } $offset = $updater->get_current_offset(); // On first iteration apply on active kit itself. // (don't include it with revisions in order to avoid offset/iteration count wrong numbers) if ( 0 === $offset ) { $callback( $active_kit_id ); } if ( ! $include_revisions ) { return false; } $revisions_ids = wp_get_post_revisions( $active_kit_id, [ 'fields' => 'ids', 'posts_per_page' => $updater->get_limit(), 'offset' => $offset, ] ); foreach ( $revisions_ids as $revision_id ) { $callback( $revision_id ); } return $updater->should_run_again( $revisions_ids ); } private static function notice( $message ) { $logger = Plugin::$instance->logger->get_logger(); $logger->notice( $message ); } /** * @param \wpdb $wpdb * @param string $element_type * * @return array */ public static function get_post_ids_by_element_type( $updater, string $element_type ): array { global $wpdb; return $updater->query_col( 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` WHERE `meta_key` = "_elementor_data" AND `meta_value` LIKE \'%"elType":"' . $element_type . '"%\';' ); } /** * @param $data * * @return array|mixed */ private static function iterate_containers( $data ) { return Plugin::$instance->db->iterate_data( $data, function ( $element ) { if ( 'container' !== $element['elType'] || ! isset( $element['elements'] ) ) { return $element; } $element = self::maybe_convert_to_inner_container( $element ); $element = self::maybe_convert_to_grid_container( $element ); return Container::slider_to_gaps_converter( $element ); } ); } /** * @param $element * * @return array */ private static function maybe_convert_to_inner_container( $element ) { foreach ( $element['elements'] as &$inner_element ) { if ( 'container' === $inner_element['elType'] && ! $inner_element['isInner'] ) { $inner_element['isInner'] = true; } } return $element; } /** * @param $element * * @return array */ private static function maybe_convert_to_grid_container( $element ) { $is_grid_container = isset( $element['settings']['container_type'] ) && 'grid' === $element['settings']['container_type']; if ( 'container' !== $element['elType'] || empty( $element['settings'] ) || ! $is_grid_container ) { return $element; } $element['settings']['presetTitle'] = 'Grid'; $element['settings']['presetIcon'] = 'eicon-container-grid'; return $element; } /** * @param $post_id * @param $data * * @return void */ private static function save_updated_document( $post_id, $data ) { $json_value = wp_slash( wp_json_encode( $data ) ); update_metadata( 'post', $post_id, '_elementor_data', $json_value ); } } upgrade/custom-tasks.php 0000644 00000000564 14717626151 0011360 0 ustar 00 <?php namespace Elementor\Core\Upgrade; use Elementor\Tracker; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Custom_Tasks { public static function opt_in_recalculate_usage( $updater ) { return Upgrades::recalc_usage_data( $updater ); } public static function opt_in_send_tracking_data() { Tracker::send_tracking_data( true ); } } upgrade/upgrade-utils.php 0000644 00000003446 14717626151 0011512 0 ustar 00 <?php namespace Elementor\Core\Upgrade; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Upgrade_Utils { /** * _update_widget_settings * * @param string $widget_id widget type id * @param Updater $updater updater instance * @param array $changes array containing updating control_ids, callback and other data needed by the callback * * @return bool */ public static function _update_widget_settings( $widget_id, $updater, $changes ) { global $wpdb; $post_ids = $updater->query_col( 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` WHERE `meta_key` = "_elementor_data" AND `meta_value` LIKE \'%"widgetType":"' . $widget_id . '"%\';' ); if ( empty( $post_ids ) ) { return false; } foreach ( $post_ids as $post_id ) { $do_update = false; $document = Plugin::instance()->documents->get( $post_id ); if ( ! $document ) { continue; } $data = $document->get_elements_data(); if ( empty( $data ) ) { continue; } // loop thru callbacks & array foreach ( $changes as $change ) { $args = [ 'do_update' => &$do_update, 'widget_id' => $widget_id, 'control_ids' => $change['control_ids'], ]; if ( isset( $change['prefix'] ) ) { $args['prefix'] = $change['prefix']; $args['new_id'] = $change['new_id']; } $data = Plugin::instance()->db->iterate_data( $data, $change['callback'], $args ); if ( ! $do_update ) { continue; } // We need the `wp_slash` in order to avoid the unslashing during the `update_metadata` $json_value = wp_slash( wp_json_encode( $data ) ); update_metadata( 'post', $post_id, '_elementor_data', $json_value ); } } // End foreach(). return $updater->should_run_again( $post_ids ); } } upgrade/manager.php 0000644 00000004720 14717626151 0010333 0 ustar 00 <?php namespace Elementor\Core\Upgrade; use Elementor\Core\Base\DB_Upgrades_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Manager extends DB_Upgrades_Manager { /** * @deprecated 3.17.0 */ const INSTALLS_HISTORY_META = 'elementor_install_history'; public static function get_install_history_meta() { return 'elementor_install_history'; } // todo: remove in future releases public function should_upgrade() { if ( ( 'elementor' === $this->get_plugin_name() ) && version_compare( get_option( $this->get_version_option_name() ), '2.4.2', '<' ) ) { delete_option( 'elementor_log' ); } return parent::should_upgrade(); } public function get_name() { return 'upgrade'; } public function get_action() { return 'elementor_updater'; } public function get_plugin_name() { return 'elementor'; } public function get_plugin_label() { return esc_html__( 'Elementor', 'elementor' ); } public function get_updater_label() { return esc_html__( 'Elementor Data Updater', 'elementor' ); } public function get_new_version() { return ELEMENTOR_VERSION; } public function get_version_option_name() { return 'elementor_version'; } public function get_upgrades_class() { return 'Elementor\Core\Upgrade\Upgrades'; } public static function get_installs_history() { return get_option( static::get_install_history_meta(), [] ); } public static function install_compare( $version, $operator ) { $installs_history = self::get_installs_history(); return version_compare( key( $installs_history ), $version ? $version : '0.0.0', // when no version assigned $operator ); } protected function update_db_version() { parent::update_db_version(); $installs_history = self::get_installs_history(); $time = time(); $installs_history[ $this->get_new_version() ] = $time; $old_version = $this->get_current_version(); // If there was an old version of Elementor, and there's no record for that install yet if ( $old_version && empty( $installs_history[ $old_version ] ) ) { $installs_history[ $old_version ] = $installs_history[ $this->get_new_version() ] - 1; } uksort( $installs_history, 'version_compare' ); update_option( static::get_install_history_meta(), $installs_history ); } public static function is_new_installation() : bool { $installs_history = self::get_installs_history(); return empty( $installs_history ) || static::install_compare( ELEMENTOR_VERSION, '>=' ); } } upgrade/updater.php 0000644 00000000753 14717626151 0010367 0 ustar 00 <?php namespace Elementor\Core\Upgrade; use Elementor\Core\Base\Background_Task; use Elementor\Core\Base\DB_Upgrades_Manager; defined( 'ABSPATH' ) || exit; class Updater extends Background_Task { /** * @var DB_Upgrades_Manager */ protected $manager; protected function format_callback_log( $item ) { return $this->manager->get_plugin_label() . '/Upgrades - ' . $item['callback'][1]; } public function set_limit( $limit ) { $this->manager->set_query_limit( $limit ); } } role-manager/role-manager-menu-item.php 0000644 00000001527 14717626151 0014114 0 ustar 00 <?php namespace Elementor\Core\RoleManager; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Role_Manager_Menu_Item implements Admin_Menu_Item_With_Page { private $role_manager; public function __construct( Role_Manager $role_manager ) { $this->role_manager = $role_manager; } public function is_visible() { return true; } public function get_parent_slug() { return Settings::PAGE_ID; } public function get_label() { return esc_html__( 'Role Manager', 'elementor' ); } public function get_page_title() { return esc_html__( 'Role Manager', 'elementor' ); } public function get_capability() { return 'manage_options'; } public function render() { $this->role_manager->display_settings_page(); } } role-manager/role-manager.php 0000644 00000024011 14717626151 0012207 0 ustar 00 <?php namespace Elementor\Core\RoleManager; use Elementor\Core\Admin\Menu\Admin_Menu_Manager; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Plugin; use Elementor\Settings; use Elementor\Settings_Page; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Role_Manager extends Settings_Page { const PAGE_ID = 'elementor-role-manager'; const ROLE_MANAGER_OPTION_NAME = 'exclude_user_roles'; const ROLE_MANAGER_ADVANCED = 'role-manager'; private static $advanced_options = []; /** * @since 2.0.0 * @access public */ public function get_role_manager_options() { return get_option( 'elementor_' . self::ROLE_MANAGER_OPTION_NAME, [] ); } public function get_role_manager_advanced_options() { return get_option( 'elementor_' . self::ROLE_MANAGER_ADVANCED, [] ); } public function get_user_advanced_options() { if ( ! empty( static::$advanced_options ) ) { return static::$advanced_options; } static::$advanced_options = $this->get_role_manager_advanced_options(); return static::$advanced_options; } /** * @since 2.0.0 * @access protected */ protected function get_page_title() { return esc_html__( 'Role Manager', 'elementor' ); } /** * @since 2.0.0 * @access public */ public function register_admin_menu( Admin_Menu_Manager $admin_menu ) { $admin_menu->register( static::PAGE_ID, new Role_Manager_Menu_Item( $this ) ); } /** * @since 2.0.0 * @access protected */ protected function create_tabs() { $validation_class = 'Elementor\Settings_Validations'; return [ 'general' => [ 'label' => esc_html__( 'General', 'elementor' ), 'sections' => [ 'tools' => [ 'fields' => [ 'exclude_user_roles' => [ 'label' => esc_html__( 'Exclude Roles', 'elementor' ), 'field_args' => [ 'type' => 'checkbox_list_roles', 'exclude' => [ 'super_admin', 'administrator' ], ], 'setting_args' => [ 'sanitize_callback' => [ $validation_class, 'checkbox_list' ], ], ], self::ROLE_MANAGER_ADVANCED => [ 'field_args' => [ 'type' => 'raw_html', 'html' => '', ], 'setting_args' => [ 'sanitize_callback' => [ $this, 'save_advanced_options' ], ], ], ], ], ], ], ]; } public function save_advanced_options( $input ) { return $input; } /** * @since 2.0.0 * @access public */ public function display_settings_page() { $this->get_tabs(); ?> <div class="wrap"> <h1 class="wp-heading-inline"><?php echo esc_html( $this->get_page_title() ); ?></h1> <div id="elementor-role-manager"> <h3><?php echo esc_html__( 'Manage What Your Users Can Edit In Elementor', 'elementor' ); ?></h3> <form id="elementor-settings-form" method="post" action="options.php"> <?php settings_fields( static::PAGE_ID ); echo '<div class="elementor-settings-form-page elementor-active">'; foreach ( get_editable_roles() as $role_slug => $role_data ) { if ( 'administrator' === $role_slug ) { continue; } $this->display_role_controls( $role_slug, $role_data ); } submit_button(); ?> </form> </div> </div><!-- /.wrap --> <?php } /** * @since 2.0.0 * @access private * * @param string $role_slug The role slug. * @param array $role_data An array with role data. */ private function display_role_controls( $role_slug, $role_data ) { static $excluded_options = false; if ( false === $excluded_options ) { $excluded_options = $this->get_role_manager_options(); } ?> <div class="elementor-role-row <?php echo esc_attr( $role_slug ); ?>"> <div class="elementor-role-label"> <span class="elementor-role-name"><?php echo esc_html( translate_user_role( $role_data['name'] ) ); ?></span> <span data-excluded-label="<?php esc_attr_e( 'Role Excluded', 'elementor' ); ?>" class="elementor-role-excluded-indicator"></span> <span class="elementor-role-toggle"><span class="dashicons dashicons-arrow-down"></span></span> </div> <div class="elementor-role-controls hidden"> <div class="elementor-role-control"> <label> <input type="checkbox" name="elementor_exclude_user_roles[]" value="<?php echo esc_attr( $role_slug ); ?>"<?php checked( in_array( $role_slug, $excluded_options, true ), true ); ?>> <?php echo esc_html__( 'No access to editor', 'elementor' ); ?> </label> </div> <div class="elementor-role-controls-advanced"> <?php /** * Role restrictions controls. * * Fires after the role manager checkbox that allows the user to * exclude the role. * * This filter allows developers to add custom controls to the role * manager. * * @since 2.0.0 * * @param string $role_slug The role slug. * @param array $role_data An array with role data. */ do_action( 'elementor/role/restrictions/controls', $role_slug, $role_data ); ?> </div> </div> </div> <?php } public function add_json_enable_control( $role_slug ) { $value = 'json-upload'; $id = self::ROLE_MANAGER_ADVANCED . '_' . $role_slug . '_' . $value; $name = 'elementor_' . self::ROLE_MANAGER_ADVANCED . '[' . $role_slug . '][]'; $advanced_options = $this->get_user_advanced_options(); $checked = isset( $advanced_options[ $role_slug ] ) ? $advanced_options[ $role_slug ] : []; ?> <div class="elementor-role-control"> <label for="<?php echo esc_attr( $id ); ?>"> <input type="checkbox" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $id ); ?>" value="<?php echo esc_attr( $value ); ?>" <?php checked( in_array( $value, $checked ), true ); ?>> <?php echo esc_html__( 'Enable the option to upload JSON files', 'elementor' ); ?> </label> <p class="elementor-role-control-warning"><strong><?php echo esc_html__( 'Heads up', 'elementor' ); ?>:</strong> <?php echo esc_html__( 'Giving broad access to upload JSON files can pose a security risk to your website because such files may contain malicious scripts, etc.', 'elementor' ); ?></p> </div> <?php } public function add_custom_html_enable_control( $role_slug ) { $value = 'custom-html'; $id = self::ROLE_MANAGER_ADVANCED . '_' . $role_slug . '_' . $value; $name = 'elementor_' . self::ROLE_MANAGER_ADVANCED . '[' . $role_slug . '][]'; $advanced_options = $this->get_user_advanced_options(); $checked = isset( $advanced_options[ $role_slug ] ) ? $advanced_options[ $role_slug ] : []; ?> <div class="elementor-role-control"> <label for="<?php echo esc_attr( $id ); ?>"> <input type="checkbox" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $id ); ?>" value="<?php echo esc_attr( $value ); ?>" <?php checked( in_array( $value, $checked ), true ); ?>> <?php echo esc_html__( 'Enable the option to use the HTML widget', 'elementor' ); ?> </label> <p class="elementor-role-control-warning"><strong><?php echo esc_html__( 'Heads up', 'elementor' ); ?>:</strong> <?php echo esc_html__( 'Giving broad access to edit the HTML widget can pose a security risk to your website because it enables users to run malicious scripts, etc.', 'elementor' ); ?></p> </div> <?php } /** * @since 2.0.0 * @access public */ public function get_go_pro_link_html() { $promotion = $this->get_go_pro_link_content(); ?> <div class="elementor-role-go-pro"> <div class="elementor-role-go-pro__desc"><?php echo esc_html( $promotion['description'] ); ?></div> <div class="elementor-role-go-pro__link"><a class="elementor-button go-pro" target="_blank" href="<?php echo esc_url( $promotion['upgrade_url'] ); ?>"><?php echo esc_html( $promotion['upgrade_text'] ); ?></a></div> </div> <?php } public function get_go_pro_link_content() { $upgrade_url = 'https://go.elementor.com/go-pro-role-manager/'; $promotion = [ 'description' => esc_html__( 'Want to give access only to content?', 'elementor' ), 'upgrade_url' => esc_url( $upgrade_url ), 'upgrade_text' => esc_html__( 'Upgrade', 'elementor' ), ]; return Filtered_Promotions_Manager::get_filtered_promotion_data( $promotion, 'elementor/role/custom_promotion', 'upgrade_url' ); } /** * @since 2.0.0 * @access public */ public function get_user_restrictions_array() { $user = wp_get_current_user(); $user_roles = $user->roles; $options = $this->get_user_restrictions(); $restrictions = []; if ( empty( $options ) ) { return $restrictions; } foreach ( $user_roles as $role ) { if ( ! isset( $options[ $role ] ) ) { continue; } $restrictions = array_merge( $restrictions, $options[ $role ] ); } return array_unique( $restrictions ); } /** * @since 2.0.0 * @access private */ private function get_user_restrictions() { static $restrictions = false; if ( ! $restrictions ) { $restrictions = []; /** * Editor user restrictions. * * Filters the user restrictions in the editor. * * @since 2.0.0 * * @param array $restrictions User restrictions. */ $restrictions = apply_filters( 'elementor/editor/user/restrictions', $restrictions ); } return $restrictions; } /** * @since 2.0.0 * @access public * * @param $capability * * @return bool */ public function user_can( $capability ) { $options = $this->get_user_restrictions_array(); if ( in_array( $capability, $options, true ) ) { return false; } return true; } /** * @since 2.0.0 * @access public */ public function __construct() { parent::__construct(); add_action( 'elementor/admin/menu/register', function ( Admin_Menu_Manager $admin_menu ) { $this->register_admin_menu( $admin_menu ); }, Settings::ADMIN_MENU_PRIORITY + 10 ); add_action( 'elementor/role/restrictions/controls', [ $this, 'add_json_enable_control' ] ); add_action( 'elementor/role/restrictions/controls', [ $this, 'add_custom_html_enable_control' ] ); add_action( 'elementor/role/restrictions/controls', [ $this, 'get_go_pro_link_html' ] ); add_filter( 'elementor/editor/user/restrictions', [ $this, 'get_role_manager_advanced_options' ] ); } } dynamic-tags/base-tag.php 0000644 00000007656 14717626151 0011350 0 ustar 00 <?php namespace Elementor\Core\DynamicTags; use Elementor\Controls_Stack; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor base tag. * * An abstract class to register new Elementor tags. * * @since 2.0.0 * @abstract */ abstract class Base_Tag extends Controls_Stack { /** * @since 2.0.0 * @access public * @static */ final public static function get_type() { return 'tag'; } /** * @since 2.0.0 * @access public * @abstract */ abstract public function get_categories(); /** * @since 2.0.0 * @access public * @abstract */ abstract public function get_group(); /** * @since 2.0.0 * @access public * @abstract */ abstract public function get_title(); /** * @since 2.0.0 * @access public * @abstract * * @param array $options */ abstract public function get_content( array $options = [] ); /** * @since 2.0.0 * @access public * @abstract */ abstract public function get_content_type(); /** * @since 2.0.0 * @access public */ public function get_panel_template_setting_key() { return ''; } /** * @since 2.0.0 * @access public */ public function is_settings_required() { return false; } /** * @since 2.0.9 * @access public */ public function get_editor_config() { ob_start(); $this->print_panel_template(); $panel_template = ob_get_clean(); return [ 'name' => $this->get_name(), 'title' => $this->get_title(), 'panel_template' => $panel_template, 'categories' => $this->get_categories(), 'group' => $this->get_group(), 'controls' => $this->get_controls(), 'content_type' => $this->get_content_type(), 'settings_required' => $this->is_settings_required(), 'editable' => $this->is_editable(), ]; } /** * @since 2.0.0 * @access public */ public function print_panel_template() { $panel_template_setting_key = $this->get_panel_template_setting_key(); if ( ! $panel_template_setting_key ) { return; } ?><# var key = <?php echo esc_html( $panel_template_setting_key ); ?>; if ( key ) { var settingsKey = "<?php echo esc_html( $panel_template_setting_key ); ?>"; /* * If the tag has controls, * and key is an existing control (and not an old one), * and the control has options (select/select2), * and the key is an existing option (and not in a group or an old one). */ if ( controls && controls[settingsKey] ) { var controlSettings = controls[settingsKey]; if ( controlSettings.options && controlSettings.options[ key ] ) { key = controlSettings.options[ key ]; } else if ( controlSettings.groups ) { var label = _.filter( _.pluck( _.pluck( controls.key.groups, 'options' ), key ) ); if ( label[0] ) { key = label[0]; } } } print( '(' + _.escape( key ) + ')' ); } #> <?php } /** * @since 2.0.0 * @access public */ final public function get_unique_name() { return 'tag-' . $this->get_name(); } /** * @since 2.0.0 * @access protected */ protected function register_advanced_section() {} /** * @since 2.0.0 * @access protected */ final protected function init_controls() { Plugin::$instance->controls_manager->open_stack( $this ); $this->start_controls_section( 'settings', [ 'label' => esc_html__( 'Settings', 'elementor' ), ] ); if ( $this->has_own_method( '_register_controls' ) ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_register_controls', '3.1.0', __CLASS__ . '::register_controls()' ); $this->_register_controls(); } else { $this->register_controls(); } $this->end_controls_section(); // If in fact no controls were registered, empty the stack if ( 1 === count( Plugin::$instance->controls_manager->get_stacks( $this->get_unique_name() )['controls'] ) ) { Plugin::$instance->controls_manager->open_stack( $this ); } $this->register_advanced_section(); } } dynamic-tags/data-tag.php 0000644 00000001363 14717626151 0011334 0 ustar 00 <?php namespace Elementor\Core\DynamicTags; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor base data tag. * * An abstract class to register new Elementor data tags. * * @since 2.0.0 * @abstract */ abstract class Data_Tag extends Base_Tag { /** * @since 2.0.0 * @access protected * @abstract * * @param array $options */ abstract protected function get_value( array $options = [] ); /** * @since 2.0.0 * @access public */ final public function get_content_type() { return 'plain'; } /** * @since 2.0.0 * @access public * * @param array $options * * @return mixed */ public function get_content( array $options = [] ) { return $this->get_value( $options ); } } dynamic-tags/manager.php 0000644 00000026121 14717626151 0011263 0 ustar 00 <?php namespace Elementor\Core\DynamicTags; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Files\CSS\Post; use Elementor\Core\Files\CSS\Post_Preview; use Elementor\Plugin; use Elementor\User; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Manager { const TAG_LABEL = 'elementor-tag'; const MODE_RENDER = 'render'; const MODE_REMOVE = 'remove'; const DYNAMIC_SETTING_KEY = '__dynamic__'; private $tags_groups = []; private $tags_info = []; private $parsing_mode = self::MODE_RENDER; /** * Dynamic tags manager constructor. * * Initializing Elementor dynamic tags manager. * * @since 2.0.0 * @access public */ public function __construct() { $this->add_actions(); } /** * Parse dynamic tags text. * * Receives the dynamic tag text, and returns a single value or multiple values * from the tag callback function. * * @since 2.0.0 * @access public * * @param string $text Dynamic tag text. * @param array $settings The dynamic tag settings. * @param callable $parse_callback The functions that renders the dynamic tag. * * @return string|string[]|mixed A single string or an array of strings with * the return values from each tag callback * function. */ public function parse_tags_text( $text, array $settings, callable $parse_callback ) { if ( ! empty( $settings['returnType'] ) && 'object' === $settings['returnType'] ) { $value = $this->parse_tag_text( $text, $settings, $parse_callback ); } else { $value = preg_replace_callback( '/\[' . self::TAG_LABEL . '.+?(?=\])\]/', function( $tag_text_match ) use ( $settings, $parse_callback ) { return $this->parse_tag_text( $tag_text_match[0], $settings, $parse_callback ); }, $text ); } return $value; } /** * Parse dynamic tag text. * * Receives the dynamic tag text, and returns the value from the callback * function. * * @since 2.0.0 * @access public * * @param string $tag_text Dynamic tag text. * @param array $settings The dynamic tag settings. * @param callable $parse_callback The functions that renders the dynamic tag. * * @return string|array|mixed If the tag was not found an empty string or an * empty array will be returned, otherwise the * return value from the tag callback function. */ public function parse_tag_text( $tag_text, array $settings, callable $parse_callback ) { $tag_data = $this->tag_text_to_tag_data( $tag_text ); if ( ! $tag_data ) { if ( ! empty( $settings['returnType'] ) && 'object' === $settings['returnType'] ) { return []; } return ''; } return call_user_func_array( $parse_callback, array_values( $tag_data ) ); } /** * @since 2.0.0 * @access public * * @param string $tag_text * * @return array|null */ public function tag_text_to_tag_data( $tag_text ) { preg_match( '/id="(.*?(?="))"/', $tag_text, $tag_id_match ); preg_match( '/name="(.*?(?="))"/', $tag_text, $tag_name_match ); preg_match( '/settings="(.*?(?="]))/', $tag_text, $tag_settings_match ); if ( ! $tag_id_match || ! $tag_name_match || ! $tag_settings_match ) { return null; } return [ 'id' => $tag_id_match[1], 'name' => $tag_name_match[1], 'settings' => json_decode( urldecode( $tag_settings_match[1] ), true ), ]; } /** * Dynamic tag to text. * * Retrieve the shortcode that represents the dynamic tag. * * @since 2.0.0 * @access public * * @param Base_Tag $tag An instance of the dynamic tag. * * @return string The shortcode that represents the dynamic tag. */ public function tag_to_text( Base_Tag $tag ) { return sprintf( '[%1$s id="%2$s" name="%3$s" settings="%4$s"]', self::TAG_LABEL, $tag->get_id(), $tag->get_name(), urlencode( wp_json_encode( $tag->get_settings(), JSON_FORCE_OBJECT ) ) ); } /** * @since 2.0.0 * @access public * @param string $tag_id * @param string $tag_name * @param array $settings * * @return string */ public function tag_data_to_tag_text( $tag_id, $tag_name, array $settings = [] ) { $tag = $this->create_tag( $tag_id, $tag_name, $settings ); if ( ! $tag ) { return ''; } return $this->tag_to_text( $tag ); } /** * @since 2.0.0 * @access public * @param string $tag_id * @param string $tag_name * @param array $settings * * @return Tag|null */ public function create_tag( $tag_id, $tag_name, array $settings = [] ) { $tag_info = $this->get_tag_info( $tag_name ); if ( ! $tag_info ) { return null; } $tag_class = $tag_info['class']; return new $tag_class( [ 'settings' => $settings, 'id' => $tag_id, ] ); } /** * @since 2.0.0 * @access public * * @param $tag_id * @param $tag_name * @param array $settings * * @return null|string */ public function get_tag_data_content( $tag_id, $tag_name, array $settings = [] ) { if ( self::MODE_REMOVE === $this->parsing_mode ) { return null; } $tag = $this->create_tag( $tag_id, $tag_name, $settings ); if ( ! $tag ) { return null; } return $tag->get_content(); } /** * @since 2.0.0 * @access public * * @param $tag_name * * @return mixed|null */ public function get_tag_info( $tag_name ) { $tags = $this->get_tags(); if ( empty( $tags[ $tag_name ] ) ) { return null; } return $tags[ $tag_name ]; } /** * @since 2.0.9 * @access public */ public function get_tags() { if ( ! did_action( 'elementor/dynamic_tags/register_tags' ) ) { /** * Register dynamic tags. * * Fires when Elementor registers dynamic tags. * * @since 2.0.9 * @deprecated 3.5.0 Use `elementor/dynamic_tags/register` hook instead. * * @param Manager $this Dynamic tags manager. */ Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->do_deprecated_action( 'elementor/dynamic_tags/register_tags', [ $this ], '3.5.0', 'elementor/dynamic_tags/register' ); } if ( ! did_action( 'elementor/dynamic_tags/register' ) ) { /** * Register dynamic tags. * * Fires when Elementor registers dynamic tags. * * @since 3.5.0 * * @param Manager $this Dynamic tags manager. */ do_action( 'elementor/dynamic_tags/register', $this ); } return $this->tags_info; } /** * @since 2.0.0 * @access public * @deprecated 3.5.0 Use `register()` method instead. * * @param string $class */ public function register_tag( $class ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'register()' ); /** @var Base_Tag $tag */ $instance = new $class(); $this->register( $instance ); } /** * Register a new Dynamic Tag. * * @param Base_Tag $dynamic_tag_instance * * @return void * @since 3.5.0 * @access public * */ public function register( Base_Tag $dynamic_tag_instance ) { $this->tags_info[ $dynamic_tag_instance->get_name() ] = [ 'class' => get_class( $dynamic_tag_instance ), 'instance' => $dynamic_tag_instance, ]; } /** * @since 2.0.9 * @access public * @deprecated 3.5.0 Use `unregister()` method instead. * * @param string $tag_name */ public function unregister_tag( $tag_name ) { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'unregister()' ); $this->unregister( $tag_name ); } /** * Unregister a dynamic tag. * * @since 3.5.0 * @access public * * @param string $tag_name Dynamic Tag name to unregister. * * @return void */ public function unregister( $tag_name ) { unset( $this->tags_info[ $tag_name ] ); } /** * @since 2.0.0 * @access public * * @param $group_name * @param array $group_settings */ public function register_group( $group_name, array $group_settings ) { $default_group_settings = [ 'title' => '', ]; $group_settings = array_merge( $default_group_settings, $group_settings ); $this->tags_groups[ $group_name ] = $group_settings; } /** * @since 2.0.0 * @access public */ public function print_templates() { foreach ( $this->get_tags() as $tag_name => $tag_info ) { $tag = $tag_info['instance']; if ( ! $tag instanceof Tag ) { continue; } $tag->print_template(); } } /** * @since 2.0.0 * @access public */ public function get_tags_config() { $config = []; foreach ( $this->get_tags() as $tag_name => $tag_info ) { /** @var Tag $tag */ $tag = $tag_info['instance']; $config[ $tag_name ] = $tag->get_editor_config(); } return $config; } /** * @since 2.0.0 * @access public */ public function get_config() { return [ 'tags' => $this->get_tags_config(), 'groups' => $this->tags_groups, ]; } /** * @since 2.0.0 * @access public * * @throws \Exception If post ID is missing. * @throws \Exception If current user don't have permissions to edit the post. */ public function ajax_render_tags( $data ) { if ( empty( $data['post_id'] ) ) { throw new \Exception( 'Missing post id.' ); } if ( ! User::is_current_user_can_edit( $data['post_id'] ) ) { throw new \Exception( 'Access denied.' ); } Plugin::$instance->db->switch_to_post( $data['post_id'] ); /** * Before dynamic tags rendered. * * Fires before Elementor renders the dynamic tags. * * @since 2.0.0 */ do_action( 'elementor/dynamic_tags/before_render' ); $tags_data = []; foreach ( $data['tags'] as $tag_key ) { $tag_key_parts = explode( '-', $tag_key ); $tag_name = base64_decode( $tag_key_parts[0] ); $tag_settings = json_decode( urldecode( base64_decode( $tag_key_parts[1] ) ), true ); $tag = $this->create_tag( null, $tag_name, $tag_settings ); $tags_data[ $tag_key ] = $tag->get_content(); } /** * After dynamic tags rendered. * * Fires after Elementor renders the dynamic tags. * * @since 2.0.0 */ do_action( 'elementor/dynamic_tags/after_render' ); return $tags_data; } /** * @since 2.0.0 * @access public * * @param $mode */ public function set_parsing_mode( $mode ) { $this->parsing_mode = $mode; } /** * @since 2.0.0 * @access public */ public function get_parsing_mode() { return $this->parsing_mode; } /** * @since 2.1.0 * @access public * @param Post $css_file */ public function after_enqueue_post_css( $css_file ) { $post_id = $css_file->get_post_id(); $should_enqueue = apply_filters( 'elementor/css-file/dynamic/should_enqueue', true, $post_id ); if ( $should_enqueue ) { $css_file = Dynamic_CSS::create( $post_id, $css_file ); $css_file->enqueue(); } } /** * @since 2.3.0 * @access public */ public function register_ajax_actions( Ajax $ajax ) { $ajax->register_ajax_action( 'render_tags', [ $this, 'ajax_render_tags' ] ); } /** * @since 2.0.0 * @access private */ private function add_actions() { add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); add_action( 'elementor/css-file/post/enqueue', [ $this, 'after_enqueue_post_css' ] ); } } dynamic-tags/tag.php 0000644 00000004337 14717626151 0010431 0 ustar 00 <?php namespace Elementor\Core\DynamicTags; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Elementor tag. * * An abstract class to register new Elementor tag. * * @since 2.0.0 * @abstract */ abstract class Tag extends Base_Tag { const WRAPPED_TAG = false; /** * @since 2.0.0 * @access public * * @param array $options * * @return string */ public function get_content( array $options = [] ) { $settings = $this->get_settings(); ob_start(); $this->render(); $value = ob_get_clean(); if ( ! Utils::is_empty( $value ) ) { // TODO: fix spaces in `before`/`after` if WRAPPED_TAG ( conflicted with .elementor-tag { display: inline-flex; } ); if ( ! Utils::is_empty( $settings, 'before' ) ) { $value = wp_kses_post( $settings['before'] ) . $value; } if ( ! Utils::is_empty( $settings, 'after' ) ) { $value .= wp_kses_post( $settings['after'] ); } if ( static::WRAPPED_TAG ) : $value = '<span id="elementor-tag-' . esc_attr( $this->get_id() ) . '" class="elementor-tag">' . $value . '</span>'; endif; } elseif ( ! Utils::is_empty( $settings, 'fallback' ) ) { $value = wp_kses_post_deep( $settings['fallback'] ); } return $value; } /** * @since 2.0.0 * @access public */ final public function get_content_type() { return 'ui'; } /** * @since 2.0.9 * @access public */ public function get_editor_config() { $config = parent::get_editor_config(); $config['wrapped_tag'] = $this::WRAPPED_TAG; return $config; } /** * @since 2.0.0 * @access protected */ protected function register_advanced_section() { $this->start_controls_section( 'advanced', [ 'label' => esc_html__( 'Advanced', 'elementor' ), ] ); $this->add_control( 'before', [ 'label' => esc_html__( 'Before', 'elementor' ), 'ai' => [ 'active' => false, ], ] ); $this->add_control( 'after', [ 'label' => esc_html__( 'After', 'elementor' ), 'ai' => [ 'active' => false, ], ] ); $this->add_control( 'fallback', [ 'label' => esc_html__( 'Fallback', 'elementor' ), 'ai' => [ 'active' => false, ], ] ); $this->end_controls_section(); } } dynamic-tags/dynamic-css.php 0000644 00000005731 14717626151 0012067 0 ustar 00 <?php namespace Elementor\Core\DynamicTags; use Elementor\Controls_Stack; use Elementor\Core\Files\CSS\Post as Post_CSS; use Elementor\Core\Files\CSS\Post_Local_Cache; use Elementor\Core\Files\CSS\Post_Preview; use Elementor\Element_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Dynamic_CSS extends Post_Local_Cache { private $post_dynamic_elements_ids; private $post_id_for_data; protected function get_post_id_for_data() { if ( empty( $this->post_dynamic_elements_ids ) ) { return null; } return $this->post_id_for_data; } protected function is_global_parsing_supported() { return false; } protected function render_styles( Element_Base $element ) { $id = $element->get_id(); if ( in_array( $id, $this->post_dynamic_elements_ids ) ) { parent::render_styles( $element ); } foreach ( $element->get_children() as $child_element ) { $this->render_styles( $child_element ); } } /** * Dynamic_CSS constructor. * * @since 2.0.13 * @access public * * @param int $post_id Post ID * @param Post_CSS $post_css_file */ public function __construct( $post_id, Post_CSS $post_css_file ) { if ( $post_css_file instanceof Post_Preview ) { $this->post_id_for_data = $post_css_file->get_post_id_for_data(); } else { $this->post_id_for_data = $post_id; } $this->post_dynamic_elements_ids = $post_css_file->get_meta( 'dynamic_elements_ids' ); parent::__construct( $post_id ); } /** * @since 2.0.13 * @access public */ public function get_name() { return 'dynamic'; } /** * Get Responsive Control Duplication Mode * * @since 3.4.0 * * @return string */ protected function get_responsive_control_duplication_mode() { return 'dynamic'; } /** * @since 2.0.13 * @access protected */ protected function use_external_file() { return false; } /** * @since 2.0.13 * @access protected */ protected function get_file_handle_id() { return 'elementor-post-dynamic-' . $this->get_post_id_for_data(); } /** * @since 2.0.13 * @access public */ public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, array $all_controls = null ) { $dynamic_settings = $controls_stack->get_settings( '__dynamic__' ); if ( ! empty( $dynamic_settings ) ) { $controls = array_intersect_key( $controls, $dynamic_settings ); $all_controls = $controls_stack->get_controls(); $parsed_dynamic_settings = $controls_stack->parse_dynamic_settings( $values, $controls ); foreach ( $controls as $control ) { if ( ! empty( $control['style_fields'] ) ) { $this->add_repeater_control_style_rules( $controls_stack, $control, $values[ $control['name'] ], $placeholders, $replacements ); } if ( empty( $control['selectors'] ) ) { continue; } $this->add_control_style_rules( $control, $parsed_dynamic_settings, $all_controls, $placeholders, $replacements ); } } } } debug/classes/htaccess.php 0000644 00000002412 14717626151 0011606 0 ustar 00 <?php namespace Elementor\Core\Debug\Classes; use Elementor\Modules\SafeMode\Module as Safe_Mode; use Elementor\Utils; class Htaccess extends Inspection_Base { private $message = ''; public function __construct() { $this->message = esc_html__( 'Your site\'s .htaccess file appears to be missing.', 'elementor' ); } public function run() { $safe_mode_enabled = get_option( Safe_Mode::OPTION_ENABLED, '' ); if ( empty( $safe_mode_enabled ) || is_multisite() ) { return true; } $permalink_structure = get_option( 'permalink_structure' ); if ( empty( $permalink_structure ) || empty( $_SERVER['SERVER_SOFTWARE'] ) ) { return true; } $server = strtoupper( Utils::get_super_global_value( $_SERVER, 'SERVER_SOFTWARE' ) ); if ( strstr( $server, 'APACHE' ) ) { $htaccess_file = get_home_path() . '.htaccess'; /* translators: %s: Path to .htaccess file. */ $this->message .= ' ' . sprintf( esc_html__( 'File Path: %s', 'elementor' ), $htaccess_file ) . ' '; return file_exists( $htaccess_file ); } return true; } public function get_name() { return 'apache-htaccess'; } public function get_message() { return $this->message; } public function get_help_doc_url() { return 'https://go.elementor.com/preview-not-loaded/#htaccess'; } } debug/classes/shop-page-edit.php 0000644 00000001305 14717626151 0012617 0 ustar 00 <?php namespace Elementor\Core\Debug\Classes; class Shop_Page_Edit extends Inspection_Base { public function run() { return false; } public function get_name() { return 'shop-page-edit'; } public function get_message() { return esc_html__( 'You are trying to edit the Shop Page although it is a Product Archive. Use the Theme Builder to create your Shop Archive template instead', 'elementor' ); } public function get_help_doc_url() { return 'https://elementor.com/help/the-content-area-was-not-found-error/#error-appears-on-woocommerce-pages'; } public function get_header_message() { return esc_html__( 'Sorry, The content area was not been found on your page', 'elementor' ); } } debug/classes/theme-missing.php 0000644 00000001205 14717626151 0012561 0 ustar 00 <?php namespace Elementor\Core\Debug\Classes; use Elementor\Modules\SafeMode\Module as Safe_Mode; class Theme_Missing extends Inspection_Base { public function run() { $safe_mode_enabled = get_option( Safe_Mode::OPTION_ENABLED, '' ); if ( ! empty( $safe_mode_enabled ) ) { return true; } $theme = wp_get_theme(); return $theme->exists(); } public function get_name() { return 'theme-missing'; } public function get_message() { return esc_html__( 'Some of your theme files are missing.', 'elementor' ); } public function get_help_doc_url() { return 'https://go.elementor.com/preview-not-loaded/#theme-files'; } } debug/classes/inspection-base.php 0000644 00000000766 14717626151 0013106 0 ustar 00 <?php namespace Elementor\Core\Debug\Classes; abstract class Inspection_Base { /** * @return bool */ abstract public function run(); /** * @return string */ abstract public function get_name(); /** * @return string */ abstract public function get_message(); /** * @return string */ public function get_header_message() { return esc_html__( 'The preview could not be loaded', 'elementor' ); } /** * @return string */ abstract public function get_help_doc_url(); } debug/inspector.php 0000644 00000006467 14717626151 0010400 0 ustar 00 <?php namespace Elementor\Core\Debug; use Elementor\Settings; use Elementor\Tools; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Inspector { protected $is_enabled = false; protected $log = []; /** * @since 2.1.2 * @access public */ public function __construct() { $is_debug = ( defined( 'WP_DEBUG' ) && WP_DEBUG ); $option = get_option( 'elementor_enable_inspector', null ); $this->is_enabled = is_null( $option ) ? $is_debug : 'enable' === $option; if ( $this->is_enabled ) { add_action( 'admin_bar_menu', [ $this, 'add_menu_in_admin_bar' ], 201 ); } add_action( 'elementor/admin/after_create_settings/' . Tools::PAGE_ID, [ $this, 'register_admin_tools_fields' ], 50 ); } /** * @since 2.1.3 * @access public */ public function is_enabled() { return $this->is_enabled; } /** * @since 2.1.3 * @access public */ public function register_admin_tools_fields( Tools $tools ) { $tools->add_fields( Settings::TAB_GENERAL, 'tools', [ 'enable_inspector' => [ 'label' => esc_html__( 'Debug Bar', 'elementor' ), 'field_args' => [ 'type' => 'select', 'std' => $this->is_enabled ? 'enable' : '', 'options' => [ '' => esc_html__( 'Disable', 'elementor' ), 'enable' => esc_html__( 'Enable', 'elementor' ), ], 'desc' => esc_html__( 'Debug Bar adds an admin bar menu that lists all the templates that are used on a page that is being displayed.', 'elementor' ), ], ], ] ); } /** * @since 2.1.2 * @access public */ public function parse_template_path( $template ) { // `untrailingslashit` for windows path style. if ( 0 === strpos( $template, untrailingslashit( ELEMENTOR_PATH ) ) ) { return 'Elementor - ' . basename( $template ); } if ( 0 === strpos( $template, get_stylesheet_directory() ) ) { return wp_get_theme()->get( 'Name' ) . ' - ' . basename( $template ); } $plugins_dir = dirname( ELEMENTOR_PATH ); if ( 0 === strpos( $template, $plugins_dir ) ) { return ltrim( str_replace( $plugins_dir, '', $template ), '/\\' ); } return str_replace( WP_CONTENT_DIR, '', $template ); } /** * @since 2.1.2 * @access public */ public function add_log( $module, $title, $url = '' ) { if ( ! $this->is_enabled ) { return; } if ( ! isset( $this->log[ $module ] ) ) { $this->log[ $module ] = []; } $this->log[ $module ][] = [ 'title' => $title, 'url' => $url, ]; } /** * @since 2.1.2 * @access public */ public function add_menu_in_admin_bar( \WP_Admin_Bar $wp_admin_bar ) { if ( empty( $this->log ) ) { return; } $wp_admin_bar->add_node( [ 'id' => 'elementor_inspector', 'title' => esc_html__( 'Elementor Debugger', 'elementor' ), ] ); foreach ( $this->log as $module => $log ) { $module_id = sanitize_key( $module ); $wp_admin_bar->add_menu( [ 'id' => 'elementor_inspector_' . $module_id, 'parent' => 'elementor_inspector', 'title' => $module, ] ); foreach ( $log as $index => $row ) { $url = $row['url']; unset( $row['url'] ); $wp_admin_bar->add_menu( [ 'id' => 'elementor_inspector_log_' . $module_id . '_' . $index, 'parent' => 'elementor_inspector_' . $module_id, 'href' => $url, 'title' => implode( ' > ', $row ), 'meta' => [ 'target' => '_blank', ], ] ); } } } } debug/loading-inspection-manager.php 0000644 00000003370 14717626151 0013556 0 ustar 00 <?php namespace Elementor\Core\Debug; use Elementor\Core\Debug\Classes\Inspection_Base; use Elementor\Core\Debug\Classes\Shop_Page_Edit; use Elementor\Core\Debug\Classes\Theme_Missing; use Elementor\Core\Debug\Classes\Htaccess; use Elementor\Utils; class Loading_Inspection_Manager { public static $_instance = null; public static function instance() { if ( null === self::$_instance ) { self::$_instance = new Loading_Inspection_Manager(); } return self::$_instance; } /** @var Inspection_Base[] */ private $inspections = []; public function register_inspections() { $this->inspections['theme-missing'] = new Theme_Missing(); $this->inspections['htaccess'] = new Htaccess(); $is_editing_shop_page = Utils::get_super_global_value( $_GET, 'post' ) == get_option( 'woocommerce_shop_page_id' ); if ( $is_editing_shop_page ) { $this->inspections['shop-page-edit'] = new Shop_Page_Edit(); } } /** * @param Inspection_Base $inspection */ public function register_inspection( $inspection ) { $this->inspections[ $inspection->get_name() ] = $inspection; } public function run_inspections() { $debug_data = [ 'message' => esc_html__( "We’re sorry, but something went wrong. Click on 'Learn more' and follow each of the steps to quickly solve it.", 'elementor' ), 'header' => esc_html__( 'The preview could not be loaded', 'elementor' ), 'doc_url' => 'https://go.elementor.com/preview-not-loaded/', ]; foreach ( $this->inspections as $inspection ) { if ( ! $inspection->run() ) { $debug_data = [ 'message' => $inspection->get_message(), 'header' => $inspection->get_header_message(), 'doc_url' => $inspection->get_help_doc_url(), 'error' => true, ]; break; } } return $debug_data; } } settings/page/manager.php 0000644 00000022150 14717626151 0011455 0 ustar 00 <?php namespace Elementor\Core\Settings\Page; use Elementor\Core\Base\Document; use Elementor\Core\Files\CSS\Base; use Elementor\Core\Files\CSS\Post; use Elementor\Core\Files\CSS\Post_Preview; use Elementor\Core\Settings\Base\CSS_Manager; use Elementor\Core\Utils\Exceptions; use Elementor\Core\Settings\Base\Model as BaseModel; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor page settings manager. * * Elementor page settings manager handler class is responsible for registering * and managing Elementor page settings managers. * * @since 1.6.0 */ class Manager extends CSS_Manager { /** * Meta key for the page settings. */ const META_KEY = '_elementor_page_settings'; /** * Get manager name. * * Retrieve page settings manager name. * * @since 1.6.0 * @access public * * @return string Manager name. */ public function get_name() { return 'page'; } /** * Get model for config. * * Retrieve the model for settings configuration. * * @since 1.6.0 * @access public * * @return BaseModel The model object. */ public function get_model_for_config() { if ( ! is_singular() && ! Plugin::$instance->editor->is_edit_mode() ) { return null; } if ( Plugin::$instance->editor->is_edit_mode() ) { $post_id = Plugin::$instance->editor->get_post_id(); $document = Plugin::$instance->documents->get_doc_or_auto_save( $post_id ); } else { $post_id = get_the_ID(); $document = Plugin::$instance->documents->get_doc_for_frontend( $post_id ); } if ( ! $document ) { return null; } $model = $this->get_model( $document->get_post()->ID ); if ( $document->is_autosave() ) { $model->set_settings( 'post_status', $document->get_main_post()->post_status ); } return $model; } /** * Ajax before saving settings. * * Validate the data before saving it and updating the data in the database. * * @since 1.6.0 * @access public * * @param array $data Post data. * @param int $id Post ID. * * @throws \Exception If invalid post returned using the `$id`. * @throws \Exception If current user don't have permissions to edit the post. */ public function ajax_before_save_settings( array $data, $id ) { $post = get_post( $id ); if ( empty( $post ) ) { throw new \Exception( 'Invalid post.', Exceptions::NOT_FOUND ); } if ( ! Utils::is_wp_cli() && ! current_user_can( 'edit_post', $id ) ) { throw new \Exception( 'Access denied.', Exceptions::FORBIDDEN ); } // Avoid save empty post title. if ( ! empty( $data['post_title'] ) ) { $post->post_title = $data['post_title']; } if ( isset( $data['post_excerpt'] ) && post_type_supports( $post->post_type, 'excerpt' ) ) { $post->post_excerpt = $data['post_excerpt']; } if ( isset( $data['menu_order'] ) && is_post_type_hierarchical( $post->post_type ) ) { $post->menu_order = $data['menu_order']; } if ( isset( $data['post_status'] ) ) { $this->save_post_status( $id, $data['post_status'] ); unset( $post->post_status ); } if ( isset( $data['comment_status'] ) && post_type_supports( $post->post_type, 'comments' ) ) { $post->comment_status = $data['comment_status']; } wp_update_post( $post ); // Check updated status if ( Document::STATUS_PUBLISH === get_post_status( $id ) ) { $autosave = wp_get_post_autosave( $post->ID ); if ( $autosave ) { wp_delete_post_revision( $autosave->ID ); } } if ( isset( $data['post_featured_image'] ) && post_type_supports( $post->post_type, 'thumbnail' ) ) { // Check if the user is at least an Author before allowing them to modify the thumbnail if ( ! current_user_can( 'publish_posts' ) ) { throw new \Exception( 'You do not have permission to modify the featured image.', Exceptions::FORBIDDEN ); } if ( empty( $data['post_featured_image']['id'] ) ) { delete_post_thumbnail( $post->ID ); } else { set_post_thumbnail( $post->ID, $data['post_featured_image']['id'] ); } } if ( Utils::is_cpt_custom_templates_supported() ) { $template = get_metadata( 'post', $post->ID, '_wp_page_template', true ); if ( isset( $data['template'] ) ) { $template = $data['template']; } if ( empty( $template ) ) { $template = 'default'; } // Use `update_metadata` in order to save also for revisions. update_metadata( 'post', $post->ID, '_wp_page_template', $template ); } } /** * @inheritDoc * * Override parent because the page setting moved to document.settings. */ protected function print_editor_template_content( $name ) { ?> <# const tabs = elementor.config.document.settings.tabs; if ( Object.values( tabs ).length > 1 ) { #> <div class="elementor-panel-navigation"> <# _.each( tabs, function( tabTitle, tabSlug ) { $e.bc.ensureTab( 'panel/page-settings', tabSlug ); #> <button class="elementor-component-tab elementor-panel-navigation-tab elementor-tab-control-{{ tabSlug }}" data-tab="{{ tabSlug }}"> <span>{{{ tabTitle }}}</span> </button> <# } ); #> </div> <# } #> <div id="elementor-panel-<?php echo esc_attr( $name ); ?>-settings-controls"></div> <?php } /** * Save settings to DB. * * Save page settings to the database, as post meta data. * * @since 1.6.0 * @access protected * * @param array $settings Settings. * @param int $id Post ID. */ protected function save_settings_to_db( array $settings, $id ) { // Use update/delete_metadata in order to handle also revisions. if ( ! empty( $settings ) ) { // Use `wp_slash` in order to avoid the unslashing during the `update_post_meta`. update_metadata( 'post', $id, self::META_KEY, wp_slash( $settings ) ); } else { delete_metadata( 'post', $id, self::META_KEY ); } } /** * Get CSS file for update. * * Retrieve the CSS file before updating it. * * This method overrides the parent method to disallow updating CSS files for pages. * * @since 1.6.0 * @access protected * * @param int $id Post ID. * * @return false Disallow The updating CSS files for pages. */ protected function get_css_file_for_update( $id ) { return false; } /** * Get saved settings. * * Retrieve the saved settings from the post meta. * * @since 1.6.0 * @access protected * * @param int $id Post ID. * * @return array Saved settings. */ protected function get_saved_settings( $id ) { $settings = get_post_meta( $id, self::META_KEY, true ); if ( ! $settings ) { $settings = []; } if ( Utils::is_cpt_custom_templates_supported() ) { $saved_template = get_post_meta( $id, '_wp_page_template', true ); if ( $saved_template ) { $settings['template'] = $saved_template; } } return $settings; } /** * Get CSS file name. * * Retrieve CSS file name for the page settings manager. * * @since 1.6.0 * @access protected * * @return string CSS file name. */ protected function get_css_file_name() { return 'post'; } /** * Get model for CSS file. * * Retrieve the model for the CSS file. * * @since 1.6.0 * @access protected * * @param Base $css_file The requested CSS file. * * @return BaseModel The model object. */ protected function get_model_for_css_file( Base $css_file ) { if ( ! $css_file instanceof Post ) { return null; } $post_id = $css_file->get_post_id(); if ( $css_file instanceof Post_Preview ) { $autosave = Utils::get_post_autosave( $post_id ); if ( $autosave ) { $post_id = $autosave->ID; } } return $this->get_model( $post_id ); } /** * Get special settings names. * * Retrieve the names of the special settings that are not saved as regular * settings. Those settings have a separate saving process. * * @since 1.6.0 * @access protected * * @return array Special settings names. */ protected function get_special_settings_names() { return [ 'id', 'post_title', 'post_status', 'template', 'post_excerpt', 'post_featured_image', 'menu_order', 'comment_status', ]; } /** * @since 2.0.0 * @access public * * @param $post_id * @param $status */ public function save_post_status( $post_id, $status ) { $parent_id = wp_is_post_revision( $post_id ); if ( $parent_id ) { // Don't update revisions post-status return; } $parent_id = $post_id; $post = get_post( $parent_id ); $allowed_post_statuses = get_post_statuses(); if ( $this->is_contributor_user() && $this->has_invalid_post_status_for_contributor( $status ) ) { // If the status is not allowed, set it to 'pending' by default $status = 'pending'; $post->post_status = $status; } if ( isset( $allowed_post_statuses[ $status ] ) ) { $post_type_object = get_post_type_object( $post->post_type ); if ( 'publish' !== $status || current_user_can( $post_type_object->cap->publish_posts ) ) { $post->post_status = $status; } } wp_update_post( $post ); } private function is_contributor_user(): bool { return current_user_can( 'edit_posts' ) && ! current_user_can( 'publish_posts' ); } private function has_invalid_post_status_for_contributor( $status ): bool { return 'draft' !== $status && 'pending' !== $status; } } settings/page/model.php 0000644 00000007576 14717626151 0011162 0 ustar 00 <?php namespace Elementor\Core\Settings\Page; use Elementor\Core\Settings\Base\CSS_Model; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor page settings model. * * Elementor page settings model handler class is responsible for registering * and managing Elementor page settings models. * * @since 1.6.0 */ class Model extends CSS_Model { /** * WordPress post object. * * Holds an instance of `WP_Post` containing the post object. * * @since 1.6.0 * @access public * * @var \WP_Post */ private $post; /** * @var \WP_Post */ private $post_parent; /** * Model constructor. * * Initializing Elementor page settings model. * * @since 1.6.0 * @access public * * @param array $data Optional. Model data. Default is an empty array. */ public function __construct( array $data = [] ) { $this->post = get_post( $data['id'] ); if ( ! $this->post ) { $this->post = new \WP_Post( (object) [] ); } if ( wp_is_post_revision( $this->post->ID ) ) { $this->post_parent = get_post( $this->post->post_parent ); } else { $this->post_parent = $this->post; } parent::__construct( $data ); } /** * Get model name. * * Retrieve page settings model name. * * @since 1.6.0 * @access public * * @return string Model name. */ public function get_name() { return 'page-settings'; } /** * Get model unique name. * * Retrieve page settings model unique name. * * @since 1.6.0 * @access public * * @return string Model unique name. */ public function get_unique_name() { return $this->get_name() . '-' . $this->post->ID; } /** * Get CSS wrapper selector. * * Retrieve the wrapper selector for the page settings model. * * @since 1.6.0 * @access public * * @return string CSS wrapper selector. */ public function get_css_wrapper_selector() { $document = Plugin::$instance->documents->get( $this->post_parent->ID ); return $document->get_css_wrapper_selector(); } /** * Get panel page settings. * * Retrieve the panel setting for the page settings model. * * @since 1.6.0 * @access public * * @return array { * Panel settings. * * @type string $title The panel title. * } */ public function get_panel_page_settings() { $document = Plugin::$instance->documents->get( $this->post->ID ); return [ 'title' => sprintf( /* translators: %s: Document title. */ esc_html__( '%s Settings', 'elementor' ), $document::get_title() ), ]; } /** * On export post meta. * * When exporting data, check if the post is not using page template and * exclude it from the exported Elementor data. * * @since 1.6.0 * @access public * * @param array $element_data Element data. * * @return array Element data to be exported. */ public function on_export( $element_data ) { if ( ! empty( $element_data['settings']['template'] ) ) { /** * @var \Elementor\Modules\PageTemplates\Module $page_templates_module */ $page_templates_module = Plugin::$instance->modules_manager->get_modules( 'page-templates' ); $is_elementor_template = ! ! $page_templates_module->get_template_path( $element_data['settings']['template'] ); if ( ! $is_elementor_template ) { unset( $element_data['settings']['template'] ); } } return $element_data; } /** * Register model controls. * * Used to add new controls to the page settings model. * * @since 3.1.0 * @access protected */ protected function register_controls() { // Check if it's a real model, or abstract (for example - on import ) if ( $this->post->ID ) { $document = Plugin::$instance->documents->get_doc_or_auto_save( $this->post->ID ); if ( $document ) { $controls = $document->get_controls(); foreach ( $controls as $control_id => $args ) { $this->add_control( $control_id, $args ); } } } } } settings/editor-preferences/manager.php 0000644 00000002662 14717626151 0014334 0 ustar 00 <?php namespace Elementor\Core\Settings\EditorPreferences; use Elementor\Core\Settings\Base\Manager as BaseManager; use Elementor\Core\Settings\Base\Model as BaseModel; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Manager extends BaseManager { const META_KEY = 'elementor_preferences'; /** * Get model for config. * * Retrieve the model for settings configuration. * * @since 2.8.0 * @access public * * @return BaseModel The model object. * */ public function get_model_for_config() { return $this->get_model(); } /** * Get manager name. * * Retrieve settings manager name. * * @since 2.8.0 * @access public */ public function get_name() { return 'editorPreferences'; } /** * Get saved settings. * * Retrieve the saved settings from the database. * * @since 2.8.0 * @access protected * * @param int $id. * @return array * */ protected function get_saved_settings( $id ) { $settings = get_user_meta( get_current_user_id(), self::META_KEY, true ); if ( ! $settings ) { $settings = []; } return $settings; } /** * Save settings to DB. * * Save settings to the database. * * @param array $settings Settings. * @param int $id Post ID. * @since 2.8.0 * @access protected * */ protected function save_settings_to_db( array $settings, $id ) { update_user_meta( get_current_user_id(), self::META_KEY, $settings ); } } settings/editor-preferences/model.php 0000644 00000014605 14717626151 0014022 0 ustar 00 <?php namespace Elementor\Core\Settings\EditorPreferences; use Elementor\Controls_Manager; use Elementor\Core\Settings\Base\Model as BaseModel; use Elementor\Modules\EditorAppBar\Module as AppBarModule; use Elementor\Modules\Checklist\Module as ChecklistModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Model extends BaseModel { /** * Get element name. * * Retrieve the element name. * * @return string The name. * @since 2.8.0 * @access public * */ public function get_name() { return 'editor-preferences'; } /** * Get panel page settings. * * Retrieve the page setting for the current panel. * * @since 2.8.0 * @access public */ public function get_panel_page_settings() { return [ 'title' => esc_html__( 'User Preferences', 'elementor' ), ]; } /** * @since 3.1.0 * @access protected */ protected function register_controls() { $this->start_controls_section( 'preferences', [ 'tab' => Controls_Manager::TAB_SETTINGS, 'label' => esc_html__( 'Preferences', 'elementor' ), ] ); $this->add_control( 'editor_heading', [ 'label' => esc_html__( 'Panel', 'elementor' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_control( 'ui_theme', [ 'label' => esc_html__( 'Display mode', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'light' => [ 'title' => esc_html__( 'Light mode', 'elementor' ), 'icon' => 'eicon-light-mode', ], 'dark' => [ 'title' => esc_html__( 'Dark mode', 'elementor' ), 'icon' => 'eicon-dark-mode', ], 'auto' => [ 'title' => esc_html__( 'Auto detect', 'elementor' ), 'icon' => 'eicon-header', ], ], 'default' => 'auto', 'description' => esc_html__( 'Set light or dark mode, or auto-detect to sync with your operating system settings.', 'elementor' ), ] ); $this->add_control( 'panel_width', [ 'label' => esc_html__( 'Width', 'elementor' ) . ' (px)', 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 200, 'max' => 680, ], ], 'default' => [ 'size' => 300, ], ] ); $this->add_control( 'preview_heading', [ 'label' => esc_html__( 'Canvas', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); if ( ! Plugin::$instance->experiments->is_feature_active( AppBarModule::EXPERIMENT_NAME ) ) { $this->add_control( 'default_device_view', [ 'label' => esc_html__( 'Default device view', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'default', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), 'mobile' => esc_html__( 'Mobile', 'elementor' ), 'tablet' => esc_html__( 'Tablet', 'elementor' ), 'desktop' => esc_html__( 'Desktop', 'elementor' ), ], 'description' => esc_html__( 'Choose which device to display when clicking the Responsive Mode icon.', 'elementor' ), ] ); } $this->add_control( 'edit_buttons', [ 'label' => esc_html__( 'Show quick edit options', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'description' => esc_html__( 'Show additional actions while hovering over the handle of an element.', 'elementor' ), ] ); $this->add_control( 'lightbox_in_editor', [ 'label' => esc_html__( 'Expand images in lightbox', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'description' => esc_html__( 'This only applies while you’re working in the editor. The front end won’t be affected.', 'elementor' ), ] ); $this->add_control( 'show_hidden_elements', [ 'label' => esc_html__( 'Show hidden elements', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'default' => 'yes', 'description' => esc_html__( 'This refers to elements you’ve hidden in the Responsive Visibility settings.', 'elementor' ), ] ); if ( ChecklistModule::should_display_checklist_toggle_control() ) { $this->add_control( 'get_started_heading', [ 'label' => esc_html__( 'Get Started', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( ChecklistModule::VISIBILITY_SWITCH_ID, [ 'label' => esc_html__( 'Launchpad Checklist', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'elementor' ), 'label_off' => esc_html__( 'Hide', 'elementor' ), 'default' => ChecklistModule::is_preference_switch_on() ? 'yes' : '', 'description' => esc_html__( 'Show a checklist to guide you through your first steps of website creation.', 'elementor' ), ] ); } $this->add_control( 'design_system_heading', [ 'label' => esc_html__( 'Design System', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'enable_styleguide_preview', [ 'label' => esc_html__( 'Show global settings', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'description' => esc_html__( 'Temporarily overlay the canvas with the style guide to preview your changes to global colors and fonts.', 'elementor' ), ] ); $this->add_control( 'navigation_heading', [ 'label' => esc_html__( 'Navigation', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'exit_to', [ 'label' => esc_html__( 'Exit to', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'this_post', 'options' => [ 'this_post' => esc_html__( 'This Post', 'elementor' ), 'all_posts' => esc_html__( 'All Posts', 'elementor' ), 'dashboard' => esc_html__( 'WP Dashboard', 'elementor' ), ], 'description' => esc_html__( 'Decide where you want to go when leaving the editor.', 'elementor' ), ] ); $this->end_controls_section(); } } settings/manager.php 0000644 00000011751 14717626151 0010546 0 ustar 00 <?php namespace Elementor\Core\Settings; use Elementor\Core\Settings\Base\CSS_Model; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor settings manager. * * Elementor settings manager handler class is responsible for registering and * managing Elementor settings managers. * * @since 1.6.0 */ class Manager { /** * Settings managers. * * Holds all the registered settings managers. * * @since 1.6.0 * @access private * * @var Base\Manager[] */ private static $settings_managers = []; /** * Builtin settings managers names. * * Holds the names for builtin Elementor settings managers. * * @since 1.6.0 * @access private * * @var array */ private static $builtin_settings_managers_names = [ 'page', 'editorPreferences' ]; /** * Add settings manager. * * Register a single settings manager to the registered settings managers. * * @since 1.6.0 * @access public * @static * * @param Base\Manager $manager Settings manager. */ public static function add_settings_manager( Base\Manager $manager ) { self::$settings_managers[ $manager->get_name() ] = $manager; } /** * Get settings managers. * * Retrieve registered settings manager(s). * * If no parameter passed, it will retrieve all the settings managers. For * any given parameter it will retrieve a single settings manager if one * exist, or `null` otherwise. * * @since 1.6.0 * @access public * @static * * @param string $manager_name Optional. Settings manager name. Default is * null. * * @return Base\Manager|Base\Manager[] Single settings manager, if it exists, * null if it doesn't exists, or the all * the settings managers if no parameter * defined. */ public static function get_settings_managers( $manager_name = null ) { if ( $manager_name ) { // Backwards compatibility for `general` manager, since 3.0.0. // Register the class only if needed. if ( 'general' === $manager_name ) { // TODO: _deprecated_argument( $manager_name, '3.0.0', 'Plugin::$instance->kits_manager->get_active_kit_for_frontend();' ); $manager_class = self::get_manager_class( $manager_name ); self::add_settings_manager( new $manager_class() ); } if ( isset( self::$settings_managers[ $manager_name ] ) ) { return self::$settings_managers[ $manager_name ]; } return null; } return self::$settings_managers; } /** * Register default settings managers. * * Register builtin Elementor settings managers. * * @since 1.6.0 * @access private * @static */ private static function register_default_settings_managers() { foreach ( self::$builtin_settings_managers_names as $manager_name ) { $manager_class = self::get_manager_class( $manager_name ); self::add_settings_manager( new $manager_class() ); } } /** * Get class path for default settings managers. * * @param $manager_name * * @return string * @since 3.0.0 * @access private * @static */ private static function get_manager_class( $manager_name ) { return __NAMESPACE__ . '\\' . ucfirst( $manager_name ) . '\Manager'; } /** * Get settings managers config. * * Retrieve the settings managers configuration. * * @since 1.6.0 * @access public * @static * * @return array The settings managers configuration. */ public static function get_settings_managers_config() { $config = []; $user_can = Plugin::instance()->role_manager->user_can( 'design' ); foreach ( self::$settings_managers as $name => $manager ) { $settings_model = $manager->get_model_for_config(); $tabs = $settings_model->get_tabs_controls(); if ( ! $user_can ) { unset( $tabs['style'] ); } $config[ $name ] = [ 'name' => $manager->get_name(), 'panelPage' => $settings_model->get_panel_page_settings(), 'controls' => $settings_model->get_controls(), 'tabs' => $tabs, 'settings' => $settings_model->get_settings(), ]; if ( $settings_model instanceof CSS_Model ) { $config[ $name ]['cssWrapperSelector'] = $settings_model->get_css_wrapper_selector(); } } return $config; } /** * Get settings frontend config. * * Retrieve the settings managers frontend configuration. * * @since 1.6.0 * @access public * @static * * @return array The settings managers frontend configuration. */ public static function get_settings_frontend_config() { $config = []; foreach ( self::$settings_managers as $name => $manager ) { $settings_model = $manager->get_model_for_config(); if ( $settings_model ) { $config[ $name ] = $settings_model->get_frontend_settings(); } } return $config; } /** * Run settings managers. * * Register builtin Elementor settings managers. * * @since 1.6.0 * @access public * @static */ public static function run() { self::register_default_settings_managers(); } } settings/base/css-manager.php 0000644 00000004561 14717626151 0012247 0 ustar 00 <?php namespace Elementor\Core\Settings\Base; use Elementor\Core\Files\CSS\Base as CSS_File; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class CSS_Manager extends Manager { /** * Get CSS file name. * * Retrieve CSS file name for the settings base css manager. * * @since 2.8.0 * @access protected * @abstract * * @return string CSS file name */ abstract protected function get_css_file_name(); /** * Get model for CSS file. * * Retrieve the model for the CSS file. * * @since 2.8.0 * @access protected * @abstract * * @param CSS_File $css_file The requested CSS file. * * @return CSS_Model * */ abstract protected function get_model_for_css_file( CSS_File $css_file ); /** * Get CSS file for update. * * Retrieve the CSS file before updating it. * * @since 2.8.0 * @access protected * @abstract * * @param int $id Post ID. * * @return CSS_File * */ abstract protected function get_css_file_for_update( $id ); /** * Settings base manager constructor. * * Initializing Elementor settings base css manager. * * @since 2.8.0 * @access public */ public function __construct() { parent::__construct(); $name = $this->get_css_file_name(); add_action( "elementor/css-file/{$name}/parse", [ $this, 'add_settings_css_rules' ] ); } /** * Save settings. * * Save settings to the database and update the CSS file. * * @since 2.8.0 * @access public * * @param array $settings Settings. * @param int $id Optional. Post ID. Default is `0`. */ public function save_settings( array $settings, $id = 0 ) { parent::save_settings( $settings, $id ); $css_file = $this->get_css_file_for_update( $id ); if ( $css_file ) { $css_file->update(); } } /** * Add settings CSS rules. * * Add new CSS rules to the settings manager. * * Fired by `elementor/css-file/{$name}/parse` action. * * @since 2.8.0 * @access public * * @param CSS_File $css_file The requested CSS file. * */ public function add_settings_css_rules( CSS_File $css_file ) { $model = $this->get_model_for_css_file( $css_file ); $css_file->add_controls_stack_style_rules( $model, $css_file->get_style_controls( $model, null, $model->get_settings() ), $model->get_settings(), [ '{{WRAPPER}}' ], [ $model->get_css_wrapper_selector() ] ); } } settings/base/manager.php 0000644 00000017156 14717626151 0011465 0 ustar 00 <?php namespace Elementor\Core\Settings\Base; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor settings base manager. * * Elementor settings base manager handler class is responsible for registering * and managing Elementor settings base managers. * * @since 1.6.0 * @abstract */ abstract class Manager { /** * Models cache. * * Holds all the models. * * @since 1.6.0 * @access private * * @var Model[] */ private $models_cache = []; /** * Settings base manager constructor. * * Initializing Elementor settings base manager. * * @since 1.6.0 * @access public */ public function __construct() { add_action( 'elementor/editor/init', [ $this, 'on_elementor_editor_init' ] ); add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); } /** * Register ajax actions. * * Add new actions to handle data after an ajax requests returned. * * Fired by `elementor/ajax/register_actions` action. * * @since 2.0.0 * @access public * * @param Ajax $ajax_manager */ public function register_ajax_actions( $ajax_manager ) { $name = $this->get_name(); $ajax_manager->register_ajax_action( "save_{$name}_settings", [ $this, 'ajax_save_settings' ] ); } /** * Get model for config. * * Retrieve the model for settings configuration. * * @since 1.6.0 * @access public * @abstract * * @return Model The model object. */ abstract public function get_model_for_config(); /** * Get manager name. * * Retrieve settings manager name. * * @since 1.6.0 * @access public * @abstract */ abstract public function get_name(); /** * Get model. * * Retrieve the model for any given model ID. * * @since 1.6.0 * @access public * * @param int $id Optional. Model ID. Default is `0`. * * @return Model The model. */ final public function get_model( $id = 0 ) { if ( ! isset( $this->models_cache[ $id ] ) ) { $this->create_model( $id ); } return $this->models_cache[ $id ]; } /** * Ajax request to save settings. * * Save settings using an ajax request. * * @since 1.6.0 * @access public * * @param array $request Ajax request. * * @return array Ajax response data. */ final public function ajax_save_settings( $request ) { $data = $request['data']; $id = 0; if ( ! empty( $request['id'] ) ) { $id = $request['id']; } $this->ajax_before_save_settings( $data, $id ); $this->save_settings( $data, $id ); $settings_name = $this->get_name(); $success_response_data = []; /** * Settings success response data. * * Filters the success response data when saving settings using ajax. * * The dynamic portion of the hook name, `$settings_name`, refers to the settings name. * * @since 2.0.0 * * @param array $success_response_data Success response data. * @param int $id Settings ID. * @param array $data Settings data. */ $success_response_data = apply_filters( "elementor/settings/{$settings_name}/success_response_data", $success_response_data, $id, $data ); return $success_response_data; } /** * Save settings. * * Save settings to the database. * * @since 1.6.0 * @access public * * @param array $settings Settings. * @param int $id Optional. Post ID. Default is `0`. */ public function save_settings( array $settings, $id = 0 ) { $special_settings = $this->get_special_settings_names(); $settings_to_save = $settings; foreach ( $special_settings as $special_setting ) { if ( isset( $settings_to_save[ $special_setting ] ) ) { unset( $settings_to_save[ $special_setting ] ); } } $this->save_settings_to_db( $settings_to_save, $id ); // Clear cache after save. if ( isset( $this->models_cache[ $id ] ) ) { unset( $this->models_cache[ $id ] ); } } /** * On Elementor init. * * Add editor template for the settings * * Fired by `elementor/init` action. * * @since 2.3.0 * @access public */ public function on_elementor_editor_init() { Plugin::$instance->common->add_template( $this->get_editor_template(), 'text' ); } /** * Get saved settings. * * Retrieve the saved settings from the database. * * @since 1.6.0 * @access protected * @abstract * * @param int $id Post ID. */ abstract protected function get_saved_settings( $id ); /** * Save settings to DB. * * Save settings to the database. * * @since 1.6.0 * @access protected * @abstract * * @param array $settings Settings. * @param int $id Post ID. */ abstract protected function save_settings_to_db( array $settings, $id ); /** * Get special settings names. * * Retrieve the names of the special settings that are not saved as regular * settings. Those settings have a separate saving process. * * @since 1.6.0 * @access protected * * @return array Special settings names. */ protected function get_special_settings_names() { return []; } /** * Ajax before saving settings. * * Validate the data before saving it and updating the data in the database. * * @since 1.6.0 * @access public * * @param array $data Post data. * @param int $id Post ID. */ public function ajax_before_save_settings( array $data, $id ) {} /** * Print the setting template content in the editor. * * Used to generate the control HTML in the editor using Underscore JS * template. The variables for the class are available using `data` JS * object. * * @since 1.6.0 * @access protected * * @param string $name Settings panel name. */ protected function print_editor_template_content( $name ) { ?> <# const tabs = elementor.config.settings.<?php // PHPCS - the variable $name does not contain a user input value. echo $name; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>.tabs; if ( Object.values( tabs ).length > 1 ) { #> <div class="elementor-panel-navigation"> <# _.each( tabs, function( tabTitle, tabSlug ) { $e.bc.ensureTab( 'panel/<?php // PHPCS - the variable $name does not contain a user input value. echo $name; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>-settings', tabSlug ); #> <button class="elementor-component-tab elementor-panel-navigation-tab elementor-tab-control-{{ tabSlug }}" data-tab="{{ tabSlug }}"> <span>{{{ tabTitle }}}</span> </button> <# } ); #> </div> <# } #> <div id="elementor-panel-<?php echo esc_attr( $name ); ?>-settings-controls"></div> <?php } /** * Create model. * * Create a new model object for any given model ID and store the object in * models cache property for later use. * * @since 1.6.0 * @access private * * @param int $id Model ID. */ private function create_model( $id ) { $class_parts = explode( '\\', get_called_class() ); array_splice( $class_parts, count( $class_parts ) - 1, 1, 'Model' ); $class_name = implode( '\\', $class_parts ); $this->models_cache[ $id ] = new $class_name( [ 'id' => $id, 'settings' => $this->get_saved_settings( $id ), ] ); } /** * Get editor template. * * Retrieve the final HTML for the editor. * * @since 1.6.0 * @access private * * @return string Settings editor template. */ private function get_editor_template() { $name = $this->get_name(); ob_start(); ?> <script type="text/template" id="tmpl-elementor-panel-<?php echo esc_attr( $name ); ?>-settings"> <?php $this->print_editor_template_content( $name ); ?> </script> <?php return ob_get_clean(); } } settings/base/model.php 0000644 00000001135 14717626151 0011141 0 ustar 00 <?php namespace Elementor\Core\Settings\Base; use Elementor\Controls_Stack; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor settings base model. * * Elementor settings base model handler class is responsible for registering * and managing Elementor settings base models. * * @since 1.6.0 * @abstract */ abstract class Model extends Controls_Stack { /** * Get panel page settings. * * Retrieve the page setting for the current panel. * * @since 1.6.0 * @access public * @abstract */ abstract public function get_panel_page_settings(); } settings/base/css-model.php 0000644 00000000564 14717626151 0011734 0 ustar 00 <?php namespace Elementor\Core\Settings\Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class CSS_Model extends Model { /** * Get CSS wrapper selector. * * Retrieve the wrapper selector for the current panel. * * @since 1.6.0 * @access public * @abstract */ abstract public function get_css_wrapper_selector(); } settings/general/manager.php 0000644 00000005050 14717626151 0012156 0 ustar 00 <?php namespace Elementor\Core\Settings\General; use Elementor\Core\Files\CSS\Base; use Elementor\Core\Settings\Base\CSS_Manager; use Elementor\Core\Settings\Base\Model as BaseModel; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * This class is deprecated, use Plugin::$instance->kits_manager->get_active_kit_for_frontend() instead. * it changed to support call like this: Manager::get_settings_managers( 'general' )->get_model()->get_settings( 'elementor_default_generic_fonts' ) * * @deprecated 3.0.0 Use `Plugin::$instance->kits_manager->get_active_kit_for_frontend()` instead. */ class Manager extends CSS_Manager { /** * Meta key for the general settings. * * @deprecated 3.0.0 */ const META_KEY = '_elementor_general_settings'; /** * General settings manager constructor. * * Initializing Elementor general settings manager. * * @since 1.6.0 * @deprecated 3.0.0 * @access public */ public function __construct() { parent::__construct(); _deprecated_file( __FILE__, '3.0.0', 'Plugin::$instance->kits_manager->get_active_kit_for_frontend()' ); $name = $this->get_css_file_name(); remove_action( "elementor/css-file/{$name}/parse", [ $this, 'add_settings_css_rules' ] ); } /** * Get manager name. * * Retrieve general settings manager name. * * @since 1.6.0 * @deprecated 3.0.0 * @access public * * @return string Manager name. */ public function get_name() { return 'general'; } /** * Get model for config. * * Retrieve the model for settings configuration. * * @since 1.6.0 * @deprecated 3.0.0 * @access public * * @return BaseModel The model object. */ public function get_model_for_config() { return $this->get_model(); } /** * @deprecated 3.0.0 */ protected function get_saved_settings( $id ) { return []; } /** * Get CSS file name. * * Retrieve CSS file name for the general settings manager. * * @since 1.6.0 * @deprecated 3.0.0 * @access protected * @return string * * @return string CSS file name. */ protected function get_css_file_name() { return 'global'; } /** * @deprecated 3.0.0 */ protected function save_settings_to_db( array $settings, $id ) { throw new \Exception( __CLASS__ . ' is deprecated. Use Plugin::$instance->kits_manager->get_active_kit_for_frontend() instead.' ); } /** * @deprecated 3.0.0 */ protected function get_model_for_css_file( Base $css_file ) { return false; } /** * @deprecated 3.0.0 */ protected function get_css_file_for_update( $id ) { return false; } } settings/general/model.php 0000644 00000002312 14717626151 0011642 0 ustar 00 <?php namespace Elementor\Core\Settings\General; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * This file is deprecated, use Plugin::$instance->kits_manager->get_active_kit_for_frontend() instead. * it changed to support call like this: Manager::get_settings_managers( 'general' )->get_model()->get_settings( 'elementor_default_generic_fonts' ) * * @deprecated 3.0.0 Use `Plugin::$instance->kits_manager->get_active_kit_for_frontend()` instead. */ class Model { /** * @deprecated 3.0.0 */ public function get_name() { return 'general-deprecated'; } /** * @deprecated 3.0.0 */ public function get_panel_page_settings() { return []; } /** * @deprecated 3.0.0 */ public function get_tabs_controls() { return []; } /** * @deprecated 3.0.0 */ public function get_frontend_settings() { return []; } /** * @deprecated 3.0.0 */ public function get_controls() { return []; } /** * @deprecated 3.0.0 */ public function get_settings( $setting = null ) { if ( $setting ) { $setting = str_replace( 'elementor_', '', $setting ); } return Plugin::$instance->kits_manager->get_current_settings( $setting ); } } isolation/wordpress-adapter.php 0000644 00000003352 14717626151 0012741 0 ustar 00 <?php namespace Elementor\Core\Isolation; class Wordpress_Adapter implements Wordpress_Adapter_Interface { public function get_plugins(): array { return get_plugins(); } public function is_plugin_active( $plugin_path ): bool { return is_plugin_active( $plugin_path ); } public function wp_nonce_url( $url, $action ): string { return wp_nonce_url( $url, $action ); } public function self_admin_url( $path ): string { return self_admin_url( $path ); } /** * Retrieves an array of pages (or hierarchical post type items). * * @return WP_Post[]|false Array of pages (or hierarchical post type items). Boolean false if the * specified post type is not hierarchical or the specified status is not * supported by the post type. */ public function get_pages( $args ) : ?array { return get_pages( $args ); } /** * Creates and returns a wp query instance. * * @return \WP_Query */ public function get_query( $args ) : ?\WP_Query { return new \WP_Query( $args ); } public function get_option( $option_key ) { return get_option( $option_key ); } public function update_option( $option_key, $option_value ) : void { update_option( $option_key, $option_value ); } public function add_option( $option_key, $option_value ) : void { add_option( $option_key, $option_value ); } public function add_query_arg( $args, $url ) : string { return add_query_arg( $args, $url ); } public function has_custom_logo() : bool { return has_custom_logo(); } public function current_user_can( $capability, $args ) : bool { return current_user_can( $capability, $args ); } public function get_post_status( $post_id ) : string { return get_post_status( $post_id ); } } isolation/kit-adapter-interface.php 0000644 00000000230 14717626151 0013426 0 ustar 00 <?php namespace Elementor\Core\Isolation; interface Kit_Adapter_Interface { public function get_kit_settings(); public function get_main_post(); } isolation/plugin-status-adapter-interface.php 0000644 00000000433 14717626151 0015463 0 ustar 00 <?php namespace Elementor\Core\Isolation; interface Plugin_Status_Adapter_Interface { public function is_plugin_installed( $plugin_path ): bool; public function get_install_plugin_url( $plugin_path ): string; public function get_activate_plugin_url( $plugin_path ): string; } isolation/elementor-adapter-interface.php 0000644 00000000223 14717626151 0014633 0 ustar 00 <?php namespace Elementor\Core\Isolation; interface Elementor_Adapter_Interface { public function get_template_type( $template_id ) : string; } isolation/kit-adapter.php 0000644 00000000557 14717626151 0011504 0 ustar 00 <?php namespace Elementor\Core\Isolation; use Elementor\Plugin; class Kit_Adapter implements Kit_Adapter_Interface { public function get_kit_settings() { return Plugin::$instance->kits_manager->get_kit_for_frontend()->get_settings(); } public function get_main_post() { return Plugin::$instance->kits_manager->get_kit_for_frontend()->get_main_post(); } } isolation/elementor-adapter.php 0000644 00000000567 14717626151 0012710 0 ustar 00 <?php namespace Elementor\Core\Isolation; use Elementor\Plugin; use Elementor\Modules\ElementorCounter\Module as Elementor_Counter_Module; use Elementor\TemplateLibrary\Source_Local; class Elementor_Adapter implements Elementor_Adapter_Interface { public function get_template_type( $template_id ): string { return Source_Local::get_template_type( $template_id ); } } isolation/plugin-status-adapter.php 0000644 00000002425 14717626151 0013530 0 ustar 00 <?php namespace Elementor\Core\Isolation; class Plugin_Status_Adapter implements Plugin_Status_Adapter_Interface { public Wordpress_Adapter_Interface $wordpress_adapter; public function __construct( Wordpress_Adapter_Interface $wordpress_adapter ) { $this->wordpress_adapter = $wordpress_adapter; } public function is_plugin_installed( $plugin_path ): bool { $installed_plugins = $this->wordpress_adapter->get_plugins(); return isset( $installed_plugins[ $plugin_path ] ); } public function get_install_plugin_url( $plugin_path ): string { $slug = dirname( $plugin_path ); $admin_base_url = $this->wordpress_adapter->self_admin_url( 'update.php' ); $admin_url = add_query_arg( [ 'action' => 'install-plugin', 'plugin' => $slug, ], $admin_base_url ); return $this->wordpress_adapter->wp_nonce_url( $admin_url, 'install-plugin_' . $slug ); } public function get_activate_plugin_url( $plugin_path ): string { $admin_base_url = $this->wordpress_adapter->self_admin_url( 'plugins.php' ); $admin_url = add_query_arg( [ 'action' => 'activate', 'plugin' => $plugin_path, 'plugin_status' => 'all', 'paged' => 1, 's' => '', ], $admin_base_url ); return $this->wordpress_adapter->wp_nonce_url( $admin_url, 'activate-plugin_' . $plugin_path ); } } isolation/wordpress-adapter-interface.php 0000644 00000001270 14717626151 0014674 0 ustar 00 <?php namespace Elementor\Core\Isolation; interface Wordpress_Adapter_Interface { public function get_plugins(); public function is_plugin_active( $plugin_path ); public function wp_nonce_url( $url, $action ); public function self_admin_url( $path ); public function get_pages( $args ); public function get_query( $args ); public function get_option( $option_key ); public function add_option( $option_key, $option_value ); public function update_option( $option_key, $option_value ); public function add_query_arg( $args, $url ); public function has_custom_logo(); public function current_user_can( $capability, $args ); public function get_post_status( $post_id ); }
| ver. 1.4 |
Github
|
.
| PHP 7.4.3-4ubuntu2.24 | Генерация страницы: 0.23 |
proxy
|
phpinfo
|
Настройка