File manager - Edit - /home/wwwwaterjetperth/public_html/core.tar
Back
breakpoints/breakpoint.php 0000644 00000005525 15230642235 0011745 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']; } } breakpoints/manager.php 0000644 00000036321 15230642235 0011217 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\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, ]; $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 ( 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', 'elementor' ), 'default_value' => 767, 'direction' => 'max', ], self::BREAKPOINT_KEY_MOBILE_EXTRA => [ 'label' => esc_html__( 'Mobile Extra', 'elementor' ), 'default_value' => 880, 'direction' => 'max', ], self::BREAKPOINT_KEY_TABLET => [ 'label' => esc_html__( 'Tablet', 'elementor' ), 'default_value' => 1024, 'direction' => 'max', ], self::BREAKPOINT_KEY_TABLET_EXTRA => [ 'label' => esc_html__( 'Tablet Extra', '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'; Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_hook( $deprecated_hook, '3.2.0', $replacement_hook ); // TODO: REMOVE THIS DEPRECATED HOOK IN ELEMENTOR v3.10.0/v4.0.0 $templates = apply_filters( $deprecated_hook, $templates ); return apply_filters( $replacement_hook, $templates ); } } files/base.php 0000644 00000013135 15230642235 0007276 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/file-types/base.php 0000644 00000002503 15230642235 0011354 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/svg.php 0000644 00000044060 15230642235 0011245 0 ustar 00 <?php namespace Elementor\Core\Files\File_Types; use Elementor\Core\Utils\Exceptions; 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'; /** * @var \DOMDocument */ private $svg_dom = null; /** * 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 ) { $original_content = 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->sanitizer( $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; } /** * 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 ) { // 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; } /** * 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 ) ) { return $svg; } $attachment_file = get_attached_file( $attachment_id ); if ( ! $attachment_file ) { return ''; } $svg = file_get_contents( $attachment_file ); if ( ! empty( $svg ) ) { update_post_meta( $attachment_id, self::META_KEY, $svg ); } return $svg; } /** * 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.5.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.5.0 * @access private * * @param $content * @return string */ private function encode_svg( $content ) { return gzencode( $content ); } /** * Decode SVG * * @since 3.5.0 * @access private * * @param $content * * @return string */ private function decode_svg( $content ) { return gzdecode( $content ); } /** * Is Allowed Tag * * @since 3.5.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.5.0 * @access private * * @param $element */ private function remove_element( $element ) { $element->parentNode->removeChild( $element ); // phpcs:ignore -- php DomDocument } /** * Is It An Attribute * * @since 3.5.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.5.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.5.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.5.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.5.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.5.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.5.0 * @access private * * @param \DOMElement $element */ private function strip_xlinks( $element ) { $xlinks = $element->getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ); if ( ! $xlinks ) { return; } $allowed_links = [ 'data:image/png', // PNG 'data:image/gif', // GIF 'data:image/jpg', // JPG 'data:image/jpe', // JPEG 'data:image/pjp', // PJPEG ]; if ( 1 === preg_match( self::SCRIPT_REGEX, $xlinks ) ) { if ( ! in_array( substr( $xlinks, 0, 14 ), $allowed_links, true ) ) { $element->removeAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ); } } } /** * Validate Use Tag * * @since 3.5.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.5.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.5.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.5.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.5.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.5.0 * @access private * * @param $string * @return string */ private function strip_line_breaks( $string ) { // Remove line breaks. return preg_replace( '/\r|\n/', '', $string ); } 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/json.php 0000644 00000001040 15230642235 0011406 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/zip.php 0000644 00000010766 15230642235 0011256 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 ); 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 Template 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/global-css.php 0000644 00000007670 15230642235 0011211 0 ustar 00 <?php 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/base.php 0000644 00000060364 15230642235 0010074 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\Kits\Documents\Tabs\Global_Typography; use Elementor\Element_Base; 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 = []; /** * 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; } // 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 ); } /** * 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(); 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 { $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 = ''; 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(); } } 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 ); } } /** * @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 ) { $control = $controls_stack[ $parser_control_name ]; $value = call_user_func( $value_callback, $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. 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() { $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 ); 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 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 = $values[ $control['name'] ]; 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 ) ) { $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/post-local-cache.php 0000644 00000001366 15230642235 0012275 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 00000015341 15230642235 0010142 0 ustar 00 <?php namespace Elementor\Core\Files\CSS; use Elementor\Base_Data_Control; use Elementor\Control_Repeater; use Elementor\Controls_Stack; 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 ); $element_settings = $element->get_settings(); $this->add_controls_stack_style_rules( $element, $this->get_style_controls( $element, null, $element->get_parsed_dynamic_settings() ), $element_settings, [ '{{ID}}', '{{WRAPPER}}' ], [ $element->get_id(), $this->get_element_unique_selector( $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 ); } } files/css/post-preview.php 0000644 00000003037 15230642235 0011620 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; } /** * @since 2.1.0 * @access public * @deprecated 3.0.0 Use `Post_Preview::get_post_id_for_data()` instead */ protected function get_preview_id() { _deprecated_function( __METHOD__, '3.0.0', __CLASS__ . '::get_post_id_for_data()' ); return $this->get_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/fonts/google-font.php 0000644 00000016761 15230642235 0011745 0 ustar 00 <?php namespace Elementor\Core\Files\Fonts; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Google_Font { const FOLDER_BASE = 'elementor/google-fonts'; const FOLDER_CSS = 'css'; const FOLDER_FONTS = 'fonts'; const AVAILABLE_FOLDERS = [ self::FOLDER_CSS, self::FOLDER_FONTS, ]; const UA_STRING = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'; const TYPE_DEFAULT = 'default'; const TYPE_EARLYACCESS = 'earlyaccess'; private static array $folders = []; public static function enqueue( string $font_name, string $font_type = self::TYPE_DEFAULT, $force_enqueue_from_cdn = false ): bool { if ( static::enqueue_style( $font_name ) ) { return true; } $is_local_gf_enabled = (bool) get_option( 'elementor_local_google_fonts', '0' ); if ( ! $is_local_gf_enabled ) { $force_enqueue_from_cdn = true; } if ( $force_enqueue_from_cdn || ! static::fetch_font_data( $font_name, $font_type ) ) { static::enqueue_from_cdn( $font_name, $font_type ); return false; } return static::enqueue_style( $font_name ); } private static function sanitize_font_name( string $font_name ): string { return sanitize_key( $font_name ); } private static function enqueue_style( string $font_name ): bool { $sanitize_font_name = static::sanitize_font_name( $font_name ); $font_data = static::get_font_data( $sanitize_font_name ); if ( empty( $font_data['url'] ) ) { return false; } wp_enqueue_style( 'elementor-gf-local-' . $sanitize_font_name, $font_data['url'], [], $font_data['version'] ); return true; } private static function get_font_data( string $font_name ) { $local_google_fonts = static::get_local_google_fonts(); return $local_google_fonts[ $font_name ] ?? null; } private static function get_local_google_fonts(): array { return (array) get_option( '_elementor_local_google_fonts', [] ); } private static function set_local_google_fonts( string $font_name, array $font_data ): void { $local_google_fonts = static::get_local_google_fonts(); $local_google_fonts[ $font_name ] = $font_data; update_option( '_elementor_local_google_fonts', $local_google_fonts ); } private static function fetch_font_data( string $font_name, string $font_type ): bool { $sanitize_font_name = static::sanitize_font_name( $font_name ); $fonts_folder = static::get_folder( static::FOLDER_FONTS ); $css_folder = static::get_folder( static::FOLDER_CSS ); if ( empty( $fonts_folder ) || empty( $css_folder ) ) { return false; } $css_content = static::get_css_content( $font_name, $font_type ); if ( empty( $css_content ) ) { return false; } $font_data = [ 'url' => $css_folder['url'] . $sanitize_font_name . '.css', 'version' => time(), ]; $css_folder_path = $css_folder['path'] . $sanitize_font_name . '.css'; $is_font_file_saved = file_put_contents( $css_folder_path, $css_content ); if ( ! $is_font_file_saved ) { return false; } static::set_local_google_fonts( $sanitize_font_name, $font_data ); return true; } private static function get_folder( string $folder ): array { $folders = static::get_folders(); return $folders[ $folder ] ?? []; } private static function get_folders(): array { static::init_folders(); return static::$folders; } private static function init_folders(): void { if ( ! empty( static::$folders ) ) { return; } static::$folders = []; $upload_dir = wp_upload_dir(); foreach ( static::AVAILABLE_FOLDERS as $folder ) { $folder_path = $upload_dir['basedir'] . '/' . static::FOLDER_BASE . '/' . $folder; $folder_url = $upload_dir['baseurl'] . '/' . static::FOLDER_BASE . '/' . $folder; if ( ! file_exists( $folder_path ) ) { wp_mkdir_p( $folder_path ); } static::$folders[ $folder ] = [ 'path' => trailingslashit( $folder_path ), 'url' => trailingslashit( $folder_url ), ]; } } private static function get_css_content( string $font_name, $font_type ): string { $css_content = static::get_raw_css_content( $font_name, $font_type ); if ( empty( $css_content ) ) { return ''; } return static::download_fonts( $font_name, $css_content ); } private static function get_raw_css_content( string $font_name, string $font_type ): string { $font_url = static::get_google_fonts_remote_url( $font_name, $font_type ); $css_content_response = wp_remote_get( $font_url, [ 'headers' => [ 'User-Agent' => static::UA_STRING, ], ] ); if ( is_wp_error( $css_content_response ) || 200 !== (int) wp_remote_retrieve_response_code( $css_content_response ) ) { return ''; } return wp_remote_retrieve_body( $css_content_response ); } private static function get_google_fonts_remote_url( string $font, string $font_type ): string { if ( self::TYPE_EARLYACCESS === $font_type ) { return static::get_earlyaccess_google_fonts_url( $font ); } return Plugin::$instance->frontend->get_stable_google_fonts_url( [ $font ] ); } private static function get_earlyaccess_google_fonts_url( string $font ): string { return sprintf( 'https://fonts.googleapis.com/earlyaccess/%s.css', strtolower( str_replace( ' ', '', $font ) ) ); } private static function download_fonts( string $font_name, string $css_content ): string { preg_match_all( '/url\(([^)]+)\)/', $css_content, $font_urls ); if ( ! function_exists( 'download_url' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } if ( ! empty( $font_urls[1] ) ) { $font_urls = $font_urls[1]; $fonts_folder = static::get_folder( static::FOLDER_FONTS ); $sanitize_font_name = static::sanitize_font_name( $font_name ); foreach ( $font_urls as $current_font_url ) { $original_font_url = trim( $current_font_url, '\'"' ); $cleaned_url = set_url_scheme( $original_font_url, 'https' ); $cleaned_url = strtok( $cleaned_url, '?#' ); $font_ext = pathinfo( $cleaned_url, PATHINFO_EXTENSION ); $tmp_font_file = download_url( $cleaned_url ); if ( is_wp_error( $tmp_font_file ) ) { return ''; } $unique_font_id = static::get_unique_font_id( $cleaned_url ); $current_font_basename = sprintf( '%s-%s.%s', $sanitize_font_name, strtolower( sanitize_file_name( $unique_font_id ) ), $font_ext ); $dest_file = $fonts_folder['path'] . $current_font_basename; $dest_file_url = $fonts_folder['url'] . $current_font_basename; // Use copy and unlink because rename breaks streams. // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $is_font_file_saved = @copy( $tmp_font_file, $dest_file ); @unlink( $tmp_font_file ); if ( ! $is_font_file_saved ) { return ''; } $css_content = str_replace( $original_font_url, $dest_file_url, $css_content ); } } return $css_content; } private static function get_unique_font_id( $font_url ): string { return substr( sha1( $font_url ), 0, 8 ); } private static function enqueue_from_cdn( string $font_name, string $font_type ): void { $font_url = static::get_google_fonts_remote_url( $font_name, $font_type ); $sanitize_font_name = static::sanitize_font_name( $font_name ); wp_enqueue_style( 'elementor-gf-' . $sanitize_font_name, $font_url, [], null ); } public static function clear_cache() { $folders = static::get_folders(); foreach ( $folders as $folder ) { $path = $folder['path'] . '*'; foreach ( glob( $path ) as $file_path ) { unlink( $file_path ); } } delete_option( '_elementor_local_google_fonts' ); } } files/manager.php 0000644 00000010065 15230642235 0007775 0 ustar 00 <?php namespace Elementor\Core\Files; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Files\CSS\Global_CSS; 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_option( Global_CSS::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' ); } /** * 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 ); } /** * 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 ); } } files/assets/files-upload-handler.php 0000644 00000007374 15230642235 0013675 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, use Elementor\Core\Files\Uploads_Manager instead. * * @deprecated 3.5.0 */ abstract class Files_Upload_Handler { const OPTION_KEY = 'elementor_unfiltered_files_upload'; abstract public function get_mime_type(); abstract public function get_file_type(); /** * is_elementor_media_upload * @deprecated 3.5.0 * @return bool */ private function is_elementor_media_upload() { return isset( $_POST['uploadTypeCaller'] ) && 'elementor-wp-media-upload' === $_POST['uploadTypeCaller']; // phpcs:ignore } /** * @return bool */ final public static function is_enabled() { $enabled = ! ! get_option( self::OPTION_KEY ) && Svg::file_sanitizer_can_run(); /** * @deprecated 3.0.0 Use `elementor/files/allow_unfiltered_upload` filter instead. */ $enabled = apply_filters( 'elementor/files/svg/enabled', $enabled ); /** * 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; } 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 * * @deprcated 3.5.0 * * @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 * * @deprcated 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 * * @deprcated 3.5.0 * * @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 * * @deprcated 3.5.0 * * @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 ); } } files/assets/svg/svg-handler.php 0000644 00000013777 15230642235 0012713 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, use Elementor\Core\Files\File_Types\Svg instead, accessed by calling: * `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );` * * @deprecated 3.5.0 */ class Svg_Handler extends Files_Upload_Handler { /** * Inline svg attachment meta key */ const META_KEY = '_elementor_inline_svg'; const SCRIPT_REGEX = '/(?:\w+script|data):/xi'; /** * Attachment ID. * * Holds the current attachment ID. * * @var int */ private $attachment_id; 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 ); } public function get_mime_type() { return 'image/svg+xml'; } public function get_file_type() { return 'svg'; } /** * delete_meta_cache * * @deprecated 3.5.0 */ 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 * * @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 * * @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 * * @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 * * @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 * * @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 * * @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/json/json-handler.php 0000644 00000001141 15230642235 0013215 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 { public static function get_name() { return 'json-handler'; } public function get_mime_type() { return 'application/json'; } public function get_file_type() { return 'json'; } } files/assets/manager.php 0000644 00000002325 15230642235 0011277 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/uploads-manager.php 0000644 00000040636 15230642235 0011451 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; 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 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 $file * @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 $file, $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( $file['fileData'] ) ) { $file = $this->save_base64_to_tmp_file( $file ); } $validation_result = $this->validate_file( $file, $allowed_file_extensions ); if ( is_wp_error( $validation_result ) ) { return $validation_result; } return $file; } /** * 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(); /** * 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 ); } else { 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 ) { $temp_filename = $this->create_unique_dir() . $file_name; 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(); $this->temp_dir = implode( DIRECTORY_SEPARATOR, [ $wp_upload_dir['basedir'], 'elementor', 'tmp' ] ) . DIRECTORY_SEPARATOR; 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 private * * @return bool */ private 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 * @return array|\WP_Error */ private function save_base64_to_tmp_file( $file ) { $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() ) { return new \WP_Error( Exceptions::FORBIDDEN, esc_html__( 'This file is not allowed for security reasons.', 'elementor' ) ); } // 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.' ); } return $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 ); } else { 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' ] ); } } debug/inspector.php 0000644 00000006467 15230642235 0010370 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/classes/theme-missing.php 0000644 00000001205 15230642235 0012551 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/shop-page-edit.php 0000644 00000001305 15230642235 0012607 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/htaccess.php 0000644 00000002324 15230642235 0011600 0 ustar 00 <?php namespace Elementor\Core\Debug\Classes; use Elementor\Modules\SafeMode\Module as Safe_Mode; 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( $_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/error_log 0000644 00000002140 15230642235 0011203 0 ustar 00 [07-May-2026 22:22:52 UTC] PHP Fatal error: Uncaught Error: Class 'Elementor\Core\Debug\Classes\Inspection_Base' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/debug/classes/htaccess.php:6 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/debug/classes/htaccess.php on line 6 [07-May-2026 22:22:54 UTC] PHP Fatal error: Uncaught Error: Class 'Elementor\Core\Debug\Classes\Inspection_Base' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/debug/classes/theme-missing.php:6 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/debug/classes/theme-missing.php on line 6 [07-May-2026 22:23:20 UTC] PHP Fatal error: Uncaught Error: Class 'Elementor\Core\Debug\Classes\Inspection_Base' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/debug/classes/shop-page-edit.php:4 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/debug/classes/shop-page-edit.php on line 4 debug/classes/inspection-base.php 0000644 00000000766 15230642235 0013076 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/loading-inspection-manager.php 0000644 00000002727 15230642235 0013553 0 ustar 00 <?php namespace Elementor\Core\Debug; use Elementor\Core\Debug\Classes\Inspection_Base; use Elementor\Core\Debug\Classes\Theme_Missing; use Elementor\Core\Debug\Classes\Htaccess; 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(); } /** * @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; } } schemes/typography.php 0000644 00000006057 15230642235 0011124 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 */ class Typography extends Base_UI { /** * 1st typography scheme. */ const TYPOGRAPHY_1 = '1'; /** * 2nd typography scheme. */ const TYPOGRAPHY_2 = '2'; /** * 3rd typography scheme. */ const TYPOGRAPHY_3 = '3'; /** * 4th typography scheme. */ const TYPOGRAPHY_4 = '4'; /** * Get typography scheme type. * * Retrieve the typography scheme type. * * @since 1.0.0 * @access public * @static * * @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 * * @return string Typography scheme title. */ public function get_title() { return esc_html__( 'Typography', 'elementor' ); } /** * Get typography scheme disabled title. * * Retrieve the typography scheme disabled title. * * @since 1.0.0 * @access public * * @return string Typography scheme disabled title. */ public function get_disabled_title() { return esc_html__( 'Default Fonts', 'elementor' ); } /** * Get typography scheme titles. * * Retrieve the typography scheme titles. * * @since 1.0.0 * @access public * * @return array Typography scheme titles. */ public function get_scheme_titles() { return [ self::TYPOGRAPHY_1 => esc_html__( 'Primary Headline', 'elementor' ), self::TYPOGRAPHY_2 => esc_html__( 'Secondary Headline', 'elementor' ), self::TYPOGRAPHY_3 => esc_html__( 'Body Text', 'elementor' ), self::TYPOGRAPHY_4 => esc_html__( 'Accent Text', 'elementor' ), ]; } /** * Get default typography scheme. * * Retrieve the default typography scheme. * * @since 1.0.0 * @access public * * @return array Default typography scheme. */ public function get_default_scheme() { return [ self::TYPOGRAPHY_1 => [ 'font_family' => 'Roboto', 'font_weight' => '600', ], self::TYPOGRAPHY_2 => [ 'font_family' => 'Roboto Slab', 'font_weight' => '400', ], self::TYPOGRAPHY_3 => [ 'font_family' => 'Roboto', 'font_weight' => '400', ], self::TYPOGRAPHY_4 => [ 'font_family' => 'Roboto', 'font_weight' => '500', ], ]; } /** * Init system typography schemes. * * Initialize the system typography schemes. * * @since 1.0.0 * @access protected * * @return array System typography schemes. */ protected function _init_system_schemes() { 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 */ public function print_template_content() { ?> <div class="elementor-panel-scheme-items"></div> <?php } } schemes/base.php 0000644 00000004261 15230642235 0007623 0 ustar 00 <?php namespace Elementor\Core\Schemes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor scheme base. * * An abstract class implementing the scheme interface, responsible for * creating new schemes. * * @since 1.0.0 * @abstract */ abstract class Base { /** * DB option name for the time when the scheme was last updated. */ const LAST_UPDATED_META = '_elementor_scheme_last_updated'; const SCHEME_OPTION_PREFIX = 'elementor_scheme_'; /** * Get scheme type. * * Retrieve the scheme type. * * @since 2.8.0 * @access public * @static */ public static function get_type() { return ''; } /** * Get default scheme. * * Retrieve the default scheme. * * @since 2.8.0 * @access public */ abstract public function get_default_scheme(); /** * Get description. * * Retrieve the scheme description. * * @since 1.0.0 * @access public * @static * * @return string Scheme description. */ public static function get_description() { return ''; } /** * Get scheme value. * * Retrieve the scheme value. * * @since 1.0.0 * @access public * * @return array Scheme value. */ public function get_scheme_value() { $scheme_value = get_option( self::SCHEME_OPTION_PREFIX . static::get_type() ); if ( ! $scheme_value ) { $scheme_value = $this->get_default_scheme(); update_option( self::SCHEME_OPTION_PREFIX . static::get_type(), $scheme_value ); } return $scheme_value; } /** * Save scheme. * * Update Elementor scheme in the database, and update the last updated * scheme time. * * @since 1.0.0 * @access public * * @param array $posted */ public function save_scheme( array $posted ) { update_option( self::SCHEME_OPTION_PREFIX . static::get_type(), $posted ); update_option( self::LAST_UPDATED_META, time() ); } /** * Get scheme. * * Retrieve the scheme. * * @since 1.0.0 * @access public * * @return array The scheme. */ public function get_scheme() { $scheme = []; foreach ( $this->get_scheme_value() as $scheme_key => $scheme_value ) { $scheme[ $scheme_key ] = [ 'value' => $scheme_value, ]; } return $scheme; } } schemes/color-picker.php 0000644 00000001707 15230642235 0011304 0 ustar 00 <?php namespace Elementor\Core\Schemes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor color picker scheme. * * Elementor color picker scheme class is responsible for initializing a scheme * for color pickers. * * @since 1.0.0 */ class Color_Picker extends Base { /** * Get color picker scheme type. * * Retrieve the color picker scheme type. * * @since 1.0.0 * @access public * @static * * @return string Color picker scheme type. */ public static function get_type() { return 'color-picker'; } /** * Get default color picker scheme. * * Retrieve the default color picker scheme. * * @since 1.0.0 * @access public * * @return array Default color picker scheme. */ public function get_default_scheme() { return [ 1 => '#6ec1e4', 2 => '#54595f', 3 => '#7a7a7a', 4 => '#61ce70', 5 => '#4054b2', 6 => '#23a455', 7 => '#000', 8 => '#fff', ]; } } schemes/base-ui.php 0000644 00000006334 15230642235 0010241 0 ustar 00 <?php namespace Elementor\Core\Schemes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Base_UI extends Base { /** * System schemes. * * Holds the list of all the system schemes. * * @since 2.8.0 * @access private * * @var array System schemes. */ private $_system_schemes; /** * Get scheme title. * * Retrieve the scheme title. * * @since 2.8.0 * @access public */ abstract public function get_title(); /** * Get scheme disabled title. * * Retrieve the scheme disabled title. * * @since 2.8.0 * @access public */ abstract public function get_disabled_title(); /** * Get scheme titles. * * Retrieve the scheme titles. * * @since 2.8.0 * @access public */ abstract public function get_scheme_titles(); /** * Print 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 2.8.0 * @access public */ abstract public function print_template_content(); /** * Init system schemes. * * Initialize the system schemes. * * @since 2.8.0 * @access protected * @abstract */ abstract protected function _init_system_schemes(); /** * Get system schemes. * * Retrieve the system schemes. * * @since 1.0.0 * @access public * * @return array System schemes. */ final public function get_system_schemes() { if ( null === $this->_system_schemes ) { $this->_system_schemes = $this->_init_system_schemes(); } return $this->_system_schemes; } /** * Print scheme template. * * Used to generate the scheme template on the editor using Underscore JS * template. * * @since 2.8.0 * @access public */ final public function print_template() { ?> <script type="text/template" id="tmpl-elementor-panel-schemes-<?php echo esc_attr( static::get_type() ); ?>"> <div class="elementor-panel-scheme-buttons"> <div class="elementor-panel-scheme-button-wrapper elementor-panel-scheme-reset"> <button class="elementor-button"> <i class="fa fa-undo" aria-hidden="true"></i> <?php echo esc_html__( 'Reset', 'elementor' ); ?> </button> </div> <div class="elementor-panel-scheme-button-wrapper elementor-panel-scheme-discard"> <button class="elementor-button"> <i class="eicon-close" aria-hidden="true"></i> <?php echo esc_html__( 'Discard', 'elementor' ); ?> </button> </div> <div class="elementor-panel-scheme-button-wrapper elementor-panel-scheme-save"> <button class="elementor-button elementor-button-success" disabled><?php echo esc_html__( 'Apply', 'elementor' ); ?></button> </div> </div> <?php $this->print_template_content(); ?> </script> <?php } /** * Get scheme. * * Retrieve the scheme. * * @since 2.8.0 * @access public * * @return array The scheme. */ public function get_scheme() { $scheme = []; $titles = $this->get_scheme_titles(); foreach ( $this->get_scheme_value() as $scheme_key => $scheme_value ) { $scheme[ $scheme_key ] = [ 'title' => isset( $titles[ $scheme_key ] ) ? $titles[ $scheme_key ] : '', 'value' => $scheme_value, ]; } return $scheme; } } schemes/color.php 0000644 00000015011 15230642235 0010022 0 ustar 00 <?php namespace Elementor\Core\Schemes; use Elementor\Plugin; 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 */ class Color extends Base_UI { /** * 1st color scheme. */ const COLOR_1 = '1'; /** * 2nd color scheme. */ const COLOR_2 = '2'; /** * 3rd color scheme. */ const COLOR_3 = '3'; /** * 4th color scheme. */ const COLOR_4 = '4'; /** * Get color scheme type. * * Retrieve the color scheme type. * * @since 1.0.0 * @access public * @static * * @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 * * @return string Color scheme title. */ public function get_title() { return esc_html__( 'Colors', 'elementor' ); } /** * Get color scheme disabled title. * * Retrieve the color scheme disabled title. * * @since 1.0.0 * @access public * * @return string Color scheme disabled title. */ public function get_disabled_title() { return esc_html__( 'Color Palettes', 'elementor' ); } /** * Get color scheme titles. * * Retrieve the color scheme titles. * * @since 1.0.0 * @access public * * @return array Color scheme titles. */ public function get_scheme_titles() { return [ self::COLOR_1 => esc_html__( 'Primary', 'elementor' ), self::COLOR_2 => esc_html__( 'Secondary', 'elementor' ), self::COLOR_3 => esc_html__( 'Text', 'elementor' ), self::COLOR_4 => esc_html__( 'Accent', 'elementor' ), ]; } /** * Get default color scheme. * * Retrieve the default color scheme. * * @since 1.0.0 * @access public * * @return array Default color scheme. */ public function get_default_scheme() { return [ self::COLOR_1 => '#6ec1e4', self::COLOR_2 => '#54595f', self::COLOR_3 => '#7a7a7a', self::COLOR_4 => '#61ce70', ]; } /** * 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 */ public function print_template_content() { ?> <div class="elementor-panel-scheme-content elementor-panel-box"> <div class="elementor-panel-heading"> <div class="elementor-panel-heading-title"><?php echo esc_html__( 'Color Palette', 'elementor' ); ?></div> </div> <?php $description = static::get_description(); if ( $description ) : ?> <div class="elementor-panel-scheme-description elementor-descriptor"><?php echo esc_html( $description ); ?></div> <?php endif; ?> <div class="elementor-panel-scheme-items elementor-panel-box-content"></div> </div> <div class="elementor-panel-scheme-colors-more-palettes elementor-panel-box"> <div class="elementor-panel-heading"> <div class="elementor-panel-heading-title"><?php echo esc_html__( 'More Palettes', 'elementor' ); ?></div> </div> <div class="elementor-panel-box-content"> <?php foreach ( $this->_get_system_schemes_to_print() as $scheme_name => $scheme ) : ?> <div class="elementor-panel-scheme-color-system-scheme" data-scheme-name="<?php echo esc_attr( $scheme_name ); ?>"> <div class="elementor-panel-scheme-color-system-items"> <?php foreach ( $scheme['items'] as $color_value ) : ?> <div class="elementor-panel-scheme-color-system-item" style="background-color: <?php echo esc_attr( $color_value ); ?>;"></div> <?php endforeach; ?> </div> <div class="elementor-title"><?php echo esc_html( $scheme['title'] ); ?></div> </div> <?php endforeach; ?> </div> </div> <?php } /** * Init system color schemes. * * Initialize the system color schemes. * * @since 1.0.0 * @access protected * * @return array System color schemes. */ protected function _init_system_schemes() { return [ 'joker' => [ 'title' => 'Joker', 'items' => [ self::COLOR_1 => '#202020', self::COLOR_2 => '#b7b4b4', self::COLOR_3 => '#707070', self::COLOR_4 => '#f6121c', ], ], 'ocean' => [ 'title' => 'Ocean', 'items' => [ self::COLOR_1 => '#1569ae', self::COLOR_2 => '#b6c9db', self::COLOR_3 => '#545454', self::COLOR_4 => '#fdd247', ], ], 'royal' => [ 'title' => 'Royal', 'items' => [ self::COLOR_1 => '#d5ba7f', self::COLOR_2 => '#902729', self::COLOR_3 => '#ae4848', self::COLOR_4 => '#302a8c', ], ], 'violet' => [ 'title' => 'Violet', 'items' => [ self::COLOR_1 => '#747476', self::COLOR_2 => '#ebca41', self::COLOR_3 => '#6f1683', self::COLOR_4 => '#a43cbd', ], ], 'sweet' => [ 'title' => 'Sweet', 'items' => [ self::COLOR_1 => '#6ccdd9', self::COLOR_2 => '#763572', self::COLOR_3 => '#919ca7', self::COLOR_4 => '#f12184', ], ], 'urban' => [ 'title' => 'Urban', 'items' => [ self::COLOR_1 => '#db6159', self::COLOR_2 => '#3b3b3b', self::COLOR_3 => '#7a7979', self::COLOR_4 => '#2abf64', ], ], 'earth' => [ 'title' => 'Earth', 'items' => [ self::COLOR_1 => '#882021', self::COLOR_2 => '#c48e4c', self::COLOR_3 => '#825e24', self::COLOR_4 => '#e8c12f', ], ], 'river' => [ 'title' => 'River', 'items' => [ self::COLOR_1 => '#8dcfc8', self::COLOR_2 => '#565656', self::COLOR_3 => '#50656e', self::COLOR_4 => '#dc5049', ], ], 'pastel' => [ 'title' => 'Pastel', 'items' => [ self::COLOR_1 => '#f27f6f', self::COLOR_2 => '#f4cd78', self::COLOR_3 => '#a5b3c1', self::COLOR_4 => '#aac9c3', ], ], ]; } /** * Get system color schemes to print. * * Retrieve the system color schemes * * @since 1.0.0 * @access protected * * @return array The system color schemes. */ protected function _get_system_schemes_to_print() { return $this->get_system_schemes(); } /** * Get current color scheme title. * * Retrieve the current color scheme title. * * @since 1.0.0 * @access protected * @deprecated 3.3.0 * * @return string The current color scheme title. */ protected function _get_current_scheme_title() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_funtion( __METHOD__, '3.3.0' ); return esc_html__( 'Color Palette', 'elementor' ); } } schemes/manager.php 0000644 00000017426 15230642235 0010332 0 ustar 00 <?php namespace Elementor\Core\Schemes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\TemplateLibrary\Source_Local; use Elementor\User; /** * 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 * * @param string $scheme_class Scheme class name. */ public function register_scheme( $scheme_class ) { /** @var Base $scheme_instance */ $scheme_instance = new $scheme_class(); $this->_registered_schemes[ $scheme_instance::get_type() ] = $scheme_instance; } /** * Unregister scheme. * * Removes a scheme from the list of registered schemes. * * @since 1.0.0 * @access public * * @param string $id Scheme ID. * * @return bool True if the scheme was removed, False otherwise. */ public function unregister_scheme( $id ) { if ( ! isset( $this->_registered_schemes[ $id ] ) ) { return false; } unset( $this->_registered_schemes[ $id ] ); return true; } /** * Get registered schemes. * * Retrieve the registered schemes list from the current instance. * * @since 1.0.0 * @access public * * @return Base[] Registered schemes. */ public function get_registered_schemes() { return $this->_registered_schemes; } /** * Get schemes data. * * Retrieve all the registered schemes with data for each scheme. * * @since 1.0.0 * @access public * * @return array Registered schemes with each scheme data. */ public function get_registered_schemes_data() { $data = []; foreach ( $this->get_registered_schemes() as $scheme ) { $type = $scheme::get_type(); $data[ $type ] = [ 'items' => $scheme->get_scheme(), ]; if ( $scheme instanceof Base_UI ) { $data[ $type ]['title'] = $scheme->get_title(); $data[ $type ]['disabled_title'] = $scheme->get_disabled_title(); } } return $data; } /** * Get default schemes. * * Retrieve all the registered schemes with default scheme for each scheme. * * @since 1.0.0 * @access public * * @return array Registered schemes with with default scheme for each scheme. */ public function get_schemes_defaults() { $data = []; foreach ( $this->get_registered_schemes() as $scheme ) { $type = $scheme::get_type(); $data[ $type ] = [ 'items' => $scheme->get_default_scheme(), ]; if ( $scheme instanceof Base_UI ) { $data[ $type ]['title'] = $scheme->get_title(); } } return $data; } /** * Get system schemes. * * Retrieve all the registered ui schemes with system schemes for each scheme. * * @since 1.0.0 * @access public * * @return array Registered ui schemes with with system scheme for each scheme. */ public function get_system_schemes() { $data = []; foreach ( $this->get_registered_schemes() as $scheme ) { if ( $scheme instanceof Base_UI ) { $data[ $scheme::get_type() ] = $scheme->get_system_schemes(); } } return $data; } /** * Get scheme. * * Retrieve a single scheme from the list of all the registered schemes in * the current instance. * * @since 1.0.0 * @access public * * @param string $id Scheme ID. * * @return false|Base Scheme instance if scheme exist, False otherwise. */ public function get_scheme( $id ) { $schemes = $this->get_registered_schemes(); if ( ! isset( $schemes[ $id ] ) ) { return false; } return $schemes[ $id ]; } /** * 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 * * @param string $scheme_type Scheme type. * @param string $scheme_value Scheme value. * * @return false|string Scheme value if scheme exist, False otherwise. */ public function get_scheme_value( $scheme_type, $scheme_value ) { $scheme = $this->get_scheme( $scheme_type ); if ( ! $scheme ) { return false; } return $scheme->get_scheme_value()[ $scheme_value ]; } /** * Ajax apply scheme. * * Ajax handler for Elementor apply_scheme. * * Fired by `wp_ajax_elementor_apply_scheme` action. * * @since 1.0.0 * @access public * * @param array $data * * @return bool */ public function ajax_apply_scheme( array $data ) { if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) { return false; } if ( ! isset( $data['scheme_name'] ) ) { return false; } $scheme_obj = $this->get_scheme( $data['scheme_name'] ); if ( ! $scheme_obj ) { return false; } $posted = json_decode( $data['data'], true ); $scheme_obj->save_scheme( $posted ); return true; } /** * 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 */ public function print_schemes_templates() { foreach ( $this->get_registered_schemes() as $scheme ) { if ( $scheme instanceof Base_UI ) { $scheme->print_template(); } } } /** * @param Ajax $ajax * * @since 2.3.0 * @access public */ public function register_ajax_actions( Ajax $ajax ) { $ajax->register_ajax_action( 'apply_scheme', [ $this, 'ajax_apply_scheme' ] ); } /** * Get enabled schemes. * * Retrieve all enabled schemes from the list of the registered schemes in * the current instance. * * @since 1.0.0 * @access public * @static * * @return array Enabled schemes. */ public static function get_enabled_schemes() { if ( null === self::$_enabled_schemes ) { $enabled_schemes = []; foreach ( self::$_schemes_types as $schemes_type ) { if ( 'yes' === get_option( 'elementor_disable_' . $schemes_type . '_schemes' ) ) { continue; } $enabled_schemes[] = $schemes_type; } /** * Enabled schemes. * * Filters the list of enabled schemes. * * @since 1.0.0 * * @param array $enabled_schemes The list of enabled schemes. */ $enabled_schemes = apply_filters( 'elementor/schemes/enabled_schemes', $enabled_schemes ); self::$_enabled_schemes = $enabled_schemes; } return self::$_enabled_schemes; } /** * Register default schemes. * * Add a default schemes to the register schemes list. * * This method is used to set initial schemes when initializing the class. * * @since 1.7.12 * @access private */ private function register_default_schemes() { foreach ( self::$_schemes_types as $scheme_type ) { $this->register_scheme( __NAMESPACE__ . '\\' . str_replace( '-', '_', ucwords( $scheme_type, '-' ) ) ); } } /** * Schemes manager constructor. * * Initializing Elementor schemes manager and register default schemes. * * @since 1.0.0 * @access public */ public function __construct() { $this->register_default_schemes(); add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); } } document-types/page-base.php 0000644 00000011163 15230642235 0012065 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 { /** * @since 2.0.8 * @access public * @static */ 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, ], ] ); } /** * @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(); self::register_hide_title_control( $this ); self::register_post_fields_control( $this ); self::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' => esc_html__( 'Not working? You can set a different selector for the title in Site Settings > Layout', 'elementor' ), '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_group_control( Group_Control_Background::get_type(), [ 'name' => 'background', 'fields_options' => [ 'image' => [ // Currently isn't supported. 'dynamic' => [ 'active' => false, ], ], ], ] ); $document->add_responsive_control( 'padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', 'em', '%' ], 'selectors' => [ '{{WRAPPER}}' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}', ], ] ); $document->end_controls_section(); Plugin::$instance->controls_manager->add_custom_css_controls( $document ); } /** * @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, ] ); } 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 ), ], ] ); } $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'] = 'page'; $config['default_route'] = 'templates/pages'; return $config; } } document-types/page.php 0000644 00000001344 15230642235 0011155 0 ustar 00 <?php namespace Elementor\Core\DocumentTypes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Page extends PageBase { /** * Get Properties * * Return Document Configuration Properties. * * @return array $properties */ public static function get_properties() { $properties = parent::get_properties(); $properties['cpt'] = [ 'page' ]; $properties['support_kit'] = true; return $properties; } public static function get_type() { return 'wp-page'; } /** * Get Title * * @access public * @static */ public static function get_title() { return esc_html__( 'Page', 'elementor' ); } public static function get_plural_title() { return __( 'Pages', 'elementor' ); } } document-types/post.php 0000644 00000001141 15230642235 0011221 0 ustar 00 <?php namespace Elementor\Core\DocumentTypes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Post extends PageBase { public static function get_properties() { $properties = parent::get_properties(); $properties['support_kit'] = true; $properties['cpt'] = [ 'post' ]; return $properties; } public static function get_type() { return 'wp-post'; } /** * @access public * @static */ public static function get_title() { return esc_html__( 'Post', 'elementor' ); } public static function get_plural_title() { return __( 'Posts', 'elementor' ); } } documents-manager.php 0000644 00000042776 15230642235 0010710 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 ] ) ) { if ( wp_is_post_autosave( $post_id ) ) { $post_type = get_post_type( wp_get_post_parent_id( $post_id ) ); } else { $post_type = get_post_type( $post_id ); } $doc_type = 'post'; if ( isset( $this->cpt[ $post_type ] ) ) { $doc_type = $this->cpt[ $post_type ]; } $meta_type = get_post_meta( $post_id, Document::TYPE_META_KEY, true ); if ( $meta_type && isset( $this->types[ $meta_type ] ) ) { $doc_type = $meta_type; } $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 ]; } /** * 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 ) { if ( is_preview() || 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 ); // Refresh after save. $document = $this->get( $document->get_post()->ID, false ); $return_data = [ 'status' => $document->get_post()->post_status, 'config' => [ 'document' => [ 'last_edited' => $document->get_last_edited(), 'urls' => [ 'wp_preview' => $document->get_wp_preview_url(), ], ], ], ]; /** * 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. * * @since 2.0.0 * @access public * * @param $request * * @return bool True if changes discarded, False otherwise. */ public function ajax_discard_changes( $request ) { $document = $this->get( $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; } } utils/version.php 0000644 00000007211 15230642236 0010106 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/exceptions.php 0000644 00000001304 15230642236 0010577 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/constants.php 0000644 00000000616 15230642236 0010437 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'; } utils/ab-test.php 0000644 00000004711 15230642236 0007762 0 ustar 00 <?php namespace Elementor\Core\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Core A/B Testing utility class * * Provides A/B testing functionality for Core features. * Uses WordPress transients for caching and user-specific variation assignment. */ class Ab_Test { const PREFIX_CACHE_KEY = '_elementor_ab_test_'; const CACHE_TTL = 90 * DAY_IN_SECONDS; /** * Get variation for a specific test * * @param string $test_name The name of the A/B test * @param int $user_id Optional user ID, defaults to current user * @return int Returns 1 or 2 for variation assignment */ public static function get_variation( $test_name, $user_id = null ): int { if ( null === $user_id ) { $user_id = get_current_user_id(); } $variation_id = self::get_variation_id_from_cache( $test_name, $user_id ); if ( false === $variation_id ) { $variation_id = self::get_random_variation(); self::set_variation_id_from_cache( $test_name, $user_id, $variation_id ); } return absint( $variation_id ); } /** * Check if user should see the feature (50% probability) * * @param string $test_name The name of the A/B test * @param int $user_id Optional user ID, defaults to current user * @return bool True if user should see the feature */ public static function should_show_feature( $test_name, $user_id = null ): bool { $variation = self::get_variation( $test_name, $user_id ); return 1 === $variation; // Only variation 1 sees the feature } /** * Get variation ID from cache * * @param string $test_name The name of the A/B test * @param int $user_id User ID * @return int|false Variation ID or false if not cached */ private static function get_variation_id_from_cache( $test_name, $user_id ) { $cache_key = self::PREFIX_CACHE_KEY . $test_name . '_' . $user_id; return get_transient( $cache_key ); } /** * Set variation ID in cache * * @param string $test_name The name of the A/B test * @param int $user_id User ID * @param int $variation_id Variation ID to cache */ private static function set_variation_id_from_cache( $test_name, $user_id, $variation_id ): void { $cache_key = self::PREFIX_CACHE_KEY . $test_name . '_' . $user_id; set_transient( $cache_key, $variation_id, self::CACHE_TTL ); } /** * Generate random variation (1 or 2) * * @return int Random variation ID */ private static function get_random_variation(): int { return mt_rand( 1, 2 ); } } utils/assets-translation-loader.php 0000644 00000004774 15230642236 0013536 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/str.php 0000644 00000001103 15230642236 0007223 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 ) { return $matches[1] . \Requests_IDNAEncoder::encode( $matches[2] ); }, $url ); } } utils/svg/svg-sanitizer.php 0000644 00000034403 15230642236 0012030 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/plugins-manager.php 0000644 00000005532 15230642236 0011516 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/force-locale.php 0000644 00000007070 15230642236 0010757 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/promotions/filtered-promotions-manager.php 0000644 00000004143 15230642236 0016250 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 15230642236 0013024 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/collection.php 0000644 00000020160 15230642236 0010552 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 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 ) ); } /** * @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; } /** * 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; } /** * @param mixed $key * * @return bool */ public function offsetExists( $key ) { return isset( $this->items[ $key ] ); } /** * @param mixed $key * * @return mixed */ public function offsetGet( $key ) { return $this->items[ $key ]; } /** * @param mixed $key * @param mixed $value */ public function offsetSet( $key, $value ) { if ( is_null( $key ) ) { $this->items[] = $value; } else { $this->items[ $key ] = $value; } } /** * @param mixed $key */ public function offsetUnset( $key ) { unset( $this->items[ $key ] ); } /** * @return \ArrayIterator|\Traversable */ public function getIterator() { return new \ArrayIterator( $this->items ); } /** * @return int|void */ 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/import-export/parsers/wxr-parser.php 0000644 00000002557 15230642236 0015053 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-xml.php 0000644 00000016656 15230642236 0015656 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-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, 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-simple-xml.php 0000644 00000020443 15230642236 0017132 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-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( 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/parsers/wxr-parser-regex.php 0000644 00000026572 15230642236 0016166 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/wp-exporter.php 0000644 00000064776 15230642236 0013571 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; /** * 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'] ) ) { $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 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 && false === array_search( $thumbnail_id, $post_ids, true ) ) { $post_ids [] = $thumbnail_id; } } } /* * Get the requested terms ready, empty unless posts filtered by category * or all content. */ $cats = []; $tags = []; $terms = []; if ( isset( $term ) && $term ) { $cat = get_term( $term['term_id'], 'category' ); $cats = [ $cat->term_id => $cat ]; unset( $term, $cat ); } elseif ( 'all' === $this->args['content'] ) { $categories = (array) get_categories( [ 'get' => 'all' ] ); $tags = (array) get_tags( array( 'get' => 'all' ) ); $custom_taxonomies = get_taxonomies( [ '_builtin' => false ] ); $custom_terms = (array) get_terms( [ 'taxonomy' => $custom_taxonomies, 'get' => 'all', ] ); // Put categories in order with no child going before its parent. while ( $cat = array_shift( $categories ) ) { if ( 0 == $cat->parent || isset( $cats[ $cat->parent ] ) ) { $cats[ $cat->term_id ] = $cat; } else { $categories[] = $cat; } } // Put terms in order with no child going before its parent. while ( $t = array_shift( $custom_terms ) ) { if ( 0 == $t->parent || isset( $terms[ $t->parent ] ) ) { $terms[ $t->term_id ] = $t; } else { $custom_terms[] = $t; } } unset( $categories, $custom_taxonomies, $custom_terms ); } return [ 'ids' => $post_ids, 'xml' => $this->get_xml_export( $post_ids, $cats, $tags, $terms ), ]; } /** * 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", wxr_cdata( $meta->meta_key ), 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_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 ) { $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 .= wxr_term_name( $menu ); $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 * @param $cats * @param $tags * @param $terms * * @return string */ private function get_xml_export( array $post_ids, array $cats, array $tags, array $terms ) { $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 ) . $this->wxr_categories_list( $cats ) . $this->wxr_tags_list( $tags ) . $this->wxr_terms_list( $terms ); 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'] ) { $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 15230642236 0012052 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/import-export/wp-import.php 0000644 00000125610 15230642236 0013214 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 $categories = []; private $tags = []; private $base_url = ''; private $page_on_front; // Mappings from old information to new. private $processed_authors = []; private $author_mapping = []; private $processed_terms = []; private $processed_posts = []; private $post_orphans = []; private $processed_menu_items = []; private $menu_item_orphans = []; private $missing_menu_items = []; private $fetch_attachments = false; private $url_remap = []; private $featured_images = []; /** * 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 = [ 'categories' => $this->process_categories(), 'tags' => $this->process_tags(), '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->categories = $import_data['categories']; $this->tags = $import_data['tags']; $this->base_url = esc_url( $import_data['base_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 ) ) { /* translators: %s: Post author. */ $this->output['errors'][] = sprintf( 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. $santized_old_login = sanitize_user( $old_login, true ); $old_id = isset( $this->authors[ $old_login ]['author_id'] ) ? intval( $this->authors[ $old_login ]['author_id'] ) : false; if ( ! empty( $this->args['user_map'][ $i ] ) ) { $user = get_userdata( intval( $this->args['user_map'][ $i ] ) ); if ( isset( $user->ID ) ) { if ( $old_id ) { $this->processed_authors[ $old_id ] = $user->ID; } $this->author_mapping[ $santized_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[ $santized_old_login ] = $user_id; } else { /* translators: %s: Author display name. */ $error = sprintf( 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[ $santized_old_login ] ) ) { if ( $old_id ) { $this->processed_authors[ $old_id ] = (int) get_current_user_id(); } $this->author_mapping[ $santized_old_login ] = (int) get_current_user_id(); } } } /** * Create new categories based on import information * * Doesn't create a new category if its slug already exists * * @return int number of imported categories. */ private function process_categories() { $result = 0; $this->categories = apply_filters( 'wp_import_categories', $this->categories ); if ( empty( $this->categories ) ) { return $result; } foreach ( $this->categories as $cat ) { // if the category already exists leave it alone $term_id = term_exists( $cat['category_nicename'], 'category' ); if ( $term_id ) { if ( is_array( $term_id ) ) { $term_id = $term_id['term_id']; } if ( isset( $cat['term_id'] ) ) { $this->processed_terms[ intval( $cat['term_id'] ) ] = (int) $term_id; } continue; } $parent = empty( $cat['category_parent'] ) ? 0 : category_exists( $cat['category_parent'] ); $description = isset( $cat['category_description'] ) ? $cat['category_description'] : ''; $data = [ 'category_nicename' => $cat['category_nicename'], 'category_parent' => $parent, 'cat_name' => wp_slash( $cat['cat_name'] ), 'category_description' => wp_slash( $description ), ]; $id = wp_insert_category( $data ); if ( ! is_wp_error( $id ) && $id > 0 ) { if ( isset( $cat['term_id'] ) ) { $this->processed_terms[ intval( $cat['term_id'] ) ] = $id; } $result++; } else { /* translators: %s: Category name. */ $error = sprintf( esc_html__( 'Failed to import category %s', 'elementor' ), $cat['category_nicename'] ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { $error .= PHP_EOL . $id->get_error_message(); } $this->output['errors'][] = $error; continue; } $this->process_termmeta( $cat, $id ); } unset( $this->categories ); return $result; } /** * Create new post tags based on import information * * Doesn't create a tag if its slug already exists * * @return int number of imported tags. */ private function process_tags() { $result = 0; $this->tags = apply_filters( 'wp_import_tags', $this->tags ); if ( empty( $this->tags ) ) { return $result; } foreach ( $this->tags as $tag ) { // if the tag already exists leave it alone $term_id = term_exists( $tag['tag_slug'], 'post_tag' ); if ( $term_id ) { if ( is_array( $term_id ) ) { $term_id = $term_id['term_id']; } if ( isset( $tag['term_id'] ) ) { $this->processed_terms[ intval( $tag['term_id'] ) ] = (int) $term_id; } continue; } $description = isset( $tag['tag_description'] ) ? $tag['tag_description'] : ''; $args = [ 'slug' => $tag['tag_slug'], 'description' => wp_slash( $description ), ]; $id = wp_insert_term( wp_slash( $tag['tag_name'] ), 'post_tag', $args ); if ( ! is_wp_error( $id ) ) { if ( isset( $tag['term_id'] ) ) { $this->processed_terms[ intval( $tag['term_id'] ) ] = $id['term_id']; } $result++; } else { /* translators: %s: Tag name. */ $error = sprintf( esc_html__( 'Failed to import post tag %s', 'elementor' ), $tag['tag_name'] ); if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) { $error .= PHP_EOL . $id->get_error_message(); } $this->output['errors'][] = $error; continue; } $this->process_termmeta( $tag, $id['term_id'] ); } unset( $this->tags ); return $result; } /** * Create new terms based on import information * * Doesn't create a term its slug already exists * * @return int number of imported terms. */ private function process_terms() { $result = 0; $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'] ) ) { $this->processed_terms[ intval( $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[ intval( $term['term_id'] ) ] = $id['term_id']; } $result++; } 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(); } $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'] ) { $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[ intval( $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 = [ 'import_id' => $post['post_id'], '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 ); $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[ intval( $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']; 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 ][] = intval( $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[ intval( $meta['value'] ) ] ) ) { $value = $this->processed_authors[ intval( $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 ) { // 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; } $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; } 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_object_id = $post_meta_key_value['menu_item_object_id']; if ( 'taxonomy' === $post_meta_key_value['_menu_item_type'] && isset( $this->processed_terms[ intval( $_menu_item_object_id ) ] ) ) { $_menu_item_object_id = $this->processed_terms[ intval( $_menu_item_object_id ) ]; } elseif ( 'post_type' === $post_meta_key_value['_menu_item_type'] && isset( $this->processed_posts[ intval( $_menu_item_object_id ) ] ) ) { $_menu_item_object_id = $this->processed_posts[ intval( $_menu_item_object_id ) ]; } elseif ( 'custom' !== $post_meta_key_value['_menu_item_type'] ) { // Associated object is missing or not imported yet, we'll retry later. $this->missing_menu_items[] = $item; return; } $_menu_item_menu_item_parent = $post_meta_key_value['menu_item_menu_item_parent']; if ( isset( $this->processed_menu_items[ intval( $_menu_item_menu_item_parent ) ] ) ) { $_menu_item_menu_item_parent = $this->processed_menu_items[ intval( $_menu_item_menu_item_parent ) ]; } elseif ( $_menu_item_menu_item_parent ) { $this->menu_item_orphans[ intval( $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' => intval( $item['menu_order'] ), 'menu-item-type' => $post_meta_key_value['_menu_item_type'], 'menu-item-title' => $item['post_title'], 'menu-item-url' => $post_meta_key_value['_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[ intval( $item['post_id'] ) ] = (int) $id; } } /** * 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'] ); 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 ); } } // All other posts/terms are imported, retry menu items with missing associated object. $missing_menu_items = $this->missing_menu_items; foreach ( $missing_menu_items as $item ) { $this->process_menu_item( $item ); } // 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; } public function run() { $this->import( $this->requested_file_path ); return $this->output; } public function __construct( $file, $args = [] ) { $this->requested_file_path = $file; $this->args = $args; if ( ! empty( $this->args['fetch_attachments'] ) ) { $this->fetch_attachments = true; } } } utils/http.php 0000644 00000001725 15230642236 0007404 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/error_log 0000644 00000001455 15230642236 0007631 0 ustar 00 [07-May-2026 22:22:28 UTC] PHP Warning: Use of undefined constant ABSPATH - assumed 'ABSPATH' (this will throw an Error in a future version of PHP) in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/utils/plugins-manager.php on line 5 [07-May-2026 22:22:28 UTC] PHP Warning: require_once(ABSPATHwp-admin/includes/plugin-install.php): failed to open stream: No such file or directory in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/utils/plugins-manager.php on line 5 [07-May-2026 22:22:28 UTC] PHP Fatal error: require_once(): Failed opening required 'ABSPATHwp-admin/includes/plugin-install.php' (include_path='.:/opt/cpanel/ea-php74/root/usr/share/pear') in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/utils/plugins-manager.php on line 5 utils/hints.php 0000644 00000023266 15230642236 0007556 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/static-collection.php 0000644 00000002127 15230642236 0012042 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/api/parse-errors.php 0000644 00000001777 15230642236 0011631 0 ustar 00 <?php namespace Elementor\Core\Utils\Api; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Parse_Errors { /** * @var array<array{key: string, error: string}> */ private array $errors = []; public static function make() { return new static(); } public function add( string $key, string $error ): self { $this->errors[] = [ 'key' => $key, 'error' => $error, ]; return $this; } public function is_empty(): bool { return empty( $this->errors ); } public function all(): array { return $this->errors; } public function to_string(): string { $errors = []; foreach ( $this->errors as $error ) { $errors[] = $error['key'] . ': ' . $error['error']; } return implode( ', ', $errors ); } public function merge( Parse_Errors $errors, ?string $prefix = null ): self { foreach ( $errors->all() as $error ) { $new_key = $prefix ? "{$prefix}.{$error['key']}" : $error['key']; $this->add( $new_key, $error['error'] ); } return $this; } } utils/api/response-builder.php 0000644 00000001527 15230642236 0012460 0 ustar 00 <?php namespace Elementor\Core\Utils\Api; class Response_Builder { private $data; private int $status; private array $meta = []; const NO_CONTENT = 204; private function __construct( $data, $status ) { $this->data = $data; $this->status = $status; } public static function make( $data = null, $status = 200 ) { return new self( $data, $status ); } public function set_meta( array $meta ) { $this->meta = $meta; return $this; } public function set_status( int $status ) { $this->status = $status; return $this; } public function no_content() { return $this->set_status( static::NO_CONTENT ); } public function build() { $res_data = static::NO_CONTENT === $this->status ? null : [ 'data' => $this->data, 'meta' => $this->meta, ]; return new \WP_REST_Response( $res_data, $this->status ); } } utils/api/parse-result.php 0000644 00000001144 15230642236 0011617 0 ustar 00 <?php namespace Elementor\Core\Utils\Api; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Parse_Result { private Parse_Errors $errors; private $value; public static function make() { return new static(); } public function __construct() { $this->errors = Parse_Errors::make(); } public function wrap( $value ): self { $this->value = $value; return $this; } public function unwrap() { return $this->value; } public function is_valid(): bool { return $this->errors->is_empty(); } public function errors(): Parse_Errors { return $this->errors; } } utils/api/error-builder.php 0000644 00000001510 15230642236 0011743 0 ustar 00 <?php namespace Elementor\Core\Utils\Api; class Error_Builder { private string $message; private int $status; private string $code; private array $meta; private function __construct( $code, $status = 500 ) { $this->code = $code; $this->status = $status; $this->message = ''; $this->meta = []; } public static function make( $code, $status = 500 ) { return new self( $code, $status ); } public function set_status( int $status ) { $this->status = $status; return $this; } public function set_message( string $message ) { $this->message = $message; return $this; } public function set_meta( array $meta ) { $this->meta = $meta; return $this; } public function build() { return new \WP_Error( $this->code, $this->message, [ 'status' => $this->status, 'meta' => $this->meta, ], ); } } kits/controls/repeater.php 0000644 00000003243 15230642236 0011706 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; $settings['item_actions']['sort'] = 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-button-default 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/tabs/theme-style-images.php 0000644 00000010716 15230642236 0014674 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', '%' ], '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', '%' ], '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' => [ '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/settings-layout.php 0000644 00000024576 15230642236 0014355 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' ) . ' (px)', 'type' => Controls_Manager::SLIDER, '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: 1140)', 'elementor' ), 'selectors' => [ '.elementor-section.elementor-section-boxed > .elementor-container' => 'max-width: {{SIZE}}{{UNIT}}', ], ] ); $this->add_control( 'space_between_widgets', [ 'label' => esc_html__( 'Widgets Space', 'elementor' ) . ' (px)', 'type' => Controls_Manager::SLIDER, 'default' => [ 'size' => 20, ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 40, ], ], 'placeholder' => '20', 'description' => esc_html__( 'Sets the default space between widgets (Default: 20)', 'elementor' ), 'selectors' => [ '.elementor-widget:not(:last-child)' => 'margin-bottom: {{SIZE}}{{UNIT}}', ], ] ); $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, '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, ] ); /** * @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, '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/settings-background.php 0000644 00000003035 15230642236 0015142 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->end_controls_section(); } } kits/documents/tabs/theme-style-buttons.php 0000644 00000013103 15230642236 0015116 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(), [ 'label' => esc_html__( 'Typography', 'elementor' ), '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', '%' ], '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', '%' ], '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', '%' ], 'selectors' => [ $button_selector => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); $this->end_controls_section(); } } kits/documents/tabs/tab-base.php 0000644 00000004267 15230642236 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::RAW_HTML, 'raw' => 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="' . esc_url( Settings::get_url() ) . '" target="_blank">', '</a>' ), 'content_classes' => 'elementor-panel-alert elementor-panel-alert-warning', 'render_type' => 'ui', ] ); } } } kits/documents/tabs/settings-lightbox.php 0000644 00000011211 15230642236 0014636 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, '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, 'selectors' => [ '.elementor-lightbox' => '--lightbox-navigation-icons-size: {{SIZE}}{{UNIT}}', ], 'separator' => 'before', ] ); $this->end_controls_section(); } } kits/documents/tabs/global-typography.php 0000644 00000011236 15230642236 0014633 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}}', ], ], ], ] ); $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( 'system_typography', [ 'type' => Global_Style_Repeater::CONTROL_TYPE, 'fields' => $repeater->get_controls(), 'default' => $default_typography, 'item_actions' => [ 'add' => false, 'remove' => false, ], ] ); $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', ] ); $this->end_controls_section(); } } kits/documents/tabs/theme-style-form-fields.php 0000644 00000012140 15230642236 0015627 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(), [ 'label' => esc_html__( 'Typography', 'elementor' ), '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(), [ 'label' => esc_html__( 'Typography', 'elementor' ), '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, ], ], ] ); $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', '%' ], '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 . '_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', '%' ], 'selectors' => [ $selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); } } kits/documents/tabs/global-colors.php 0000644 00000004645 15230642236 0013734 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, 'dynamic' => [], '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( 'system_colors', [ 'type' => Global_Style_Repeater::CONTROL_TYPE, 'fields' => $repeater->get_controls(), 'default' => $default_colors, 'item_actions' => [ 'add' => false, 'remove' => false, ], ] ); $this->add_control( 'custom_colors', [ 'type' => Global_Style_Repeater::CONTROL_TYPE, 'fields' => $repeater->get_controls(), ] ); $this->end_controls_section(); } } kits/documents/tabs/settings-site-identity.php 0000644 00000007713 15230642236 0015625 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::RAW_HTML, 'raw' => esc_html__( 'Changes will be reflected in the preview only after the page reloads.', 'elementor' ), 'content_classes' => 'elementor-panel-alert elementor-panel-alert-info', ] ); $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' => esc_html__( 'Suggested image dimensions: 350 × 100 pixels.', 'elementor' ), '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'] ); } } } kits/documents/tabs/settings-page-transitions.php 0000644 00000001362 15230642236 0016313 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 __( '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-typography.php 0000644 00000011575 15230642236 0015641 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(), [ 'label' => esc_html__( 'Typography', 'elementor' ), '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' => [ 'min' => 0, 'max' => 100, ], 'em' => [ 'min' => 0.1, 'max' => 20, ], 'vh' => [ 'min' => 0, 'max' => 100, ], ], 'size_units' => [ 'px', 'em', 'vh' ], ] ); //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(), [ 'label' => esc_html__( 'Typography', 'elementor' ), '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(), [ 'label' => esc_html__( 'Typography', 'elementor' ), 'name' => 'link_hover_typography', 'selector' => $link_hover_selectors, ] ); $this->end_controls_tab(); $this->end_controls_tabs(); // Headings. $this->add_element_controls( esc_html__( 'H1', 'elementor' ), 'h1', '{{WRAPPER}} h1' ); $this->add_element_controls( esc_html__( 'H2', 'elementor' ), 'h2', '{{WRAPPER}} h2' ); $this->add_element_controls( esc_html__( 'H3', 'elementor' ), 'h3', '{{WRAPPER}} h3' ); $this->add_element_controls( esc_html__( 'H4', 'elementor' ), 'h4', '{{WRAPPER}} h4' ); $this->add_element_controls( esc_html__( 'H5', 'elementor' ), 'h5', '{{WRAPPER}} h5' ); $this->add_element_controls( esc_html__( 'H6', 'elementor' ), '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(), [ 'label' => esc_html__( 'Typography', 'elementor' ), 'name' => $prefix . '_typography', 'selector' => $selector, ] ); } } kits/documents/tabs/settings-custom-css.php 0000644 00000001277 15230642236 0015131 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/kit.php 0000644 00000014207 15230642236 0011026 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 ] = [ '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() { $is_edit_mode = Plugin::$instance->editor->is_edit_mode(); if ( ! $is_edit_mode ) { // In the Front End, the Kit is initialized before CSS is generated, so we always duplicate controls in // the kit. $initial_responsive_controls_duplication_mode = Plugin::$instance->breakpoints->get_responsive_control_duplication_mode(); Plugin::$instance->breakpoints->set_responsive_control_duplication_mode( 'on' ); } $this->register_document_controls(); foreach ( $this->tabs as $tab ) { $tab->register_controls(); } if ( ! $is_edit_mode ) { Plugin::$instance->breakpoints->set_responsive_control_duplication_mode( $initial_responsive_controls_duplication_mode ); } } 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, ]; // Add the Page Transitions tab only if the experiment exists. if ( Plugin::instance()->experiments->get_features( 'page-transitions' ) ) { $tabs['settings-page-transitions'] = Tabs\Settings_Page_Transitions::class; } // Add the Custom CSS tab at last. $tabs['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/manager.php 0000644 00000026620 15230642236 0007652 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 E_HASH_COMMAND_OPEN_SITE_SETTINGS = 'e:run:panel/global/open'; public function get_active_id() { return get_option( self::OPTION_ACTIVE ); } public function get_active_kit() { $kit = Plugin::$instance->documents->get( $this->get_active_id() ); if ( ! $this->is_valid_kit( $kit ) ) { return $this->get_empty_kit_instance(); } return $kit; } 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 ); return $kit->get_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' => __( '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 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 ( '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 ) { $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 ) { $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->get_edit_url() . '#' . self::E_HASH_COMMAND_OPEN_SITE_SETTINGS, '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/panel.php 0000644 00000002520 15230642236 0010465 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' } #> <div 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> <# } #> </div> <div class="elementor-repeater-row-controls"></div> </script> kits/views/trash-kit-confirmation.php 0000644 00000003157 15230642237 0013772 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/error_log 0000644 00000000515 15230642237 0010575 0 ustar 00 [07-May-2026 22:27:15 UTC] PHP Fatal error: Uncaught Error: Call to undefined function esc_html__() in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/kits/views/panel.php:12 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/kits/views/panel.php on line 12 editor/data/globals/controller.php 0000644 00000002023 15230642237 0013263 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/error_log 0000644 00000000563 15230642237 0012313 0 ustar 00 [07-May-2026 22:33:56 UTC] PHP Fatal error: Uncaught Error: Class 'Elementor\Data\V2\Base\Controller' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/editor/data/globals/controller.php:8 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/editor/data/globals/controller.php on line 8 editor/data/globals/endpoints/typography.php 0000644 00000003123 15230642237 0015313 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/data/globals/endpoints/base.php 0000644 00000003160 15230642237 0014020 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( __( '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( __( '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 00000001777 15230642237 0014423 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/error_log 0000644 00000002237 15230642237 0014316 0 ustar 00 [07-May-2026 22:34:08 UTC] PHP Fatal error: Uncaught Error: Class 'Elementor\Core\Editor\Data\Globals\Endpoints\Base' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/editor/data/globals/endpoints/colors.php:6 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/editor/data/globals/endpoints/colors.php on line 6 [07-May-2026 22:34:10 UTC] PHP Fatal error: Uncaught Error: Class 'Elementor\Data\V2\Base\Endpoint' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/editor/data/globals/endpoints/base.php:9 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/editor/data/globals/endpoints/base.php on line 9 [07-May-2026 22:34:16 UTC] PHP Fatal error: Uncaught Error: Class 'Elementor\Core\Editor\Data\Globals\Endpoints\Base' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/editor/data/globals/endpoints/typography.php:6 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/editor/data/globals/endpoints/typography.php on line 6 editor/loader/editor-base-loader.php 0000644 00000013200 15230642237 0013453 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/v1/templates/editor-body-v1.view.php 0000644 00000003102 15230642237 0016053 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/templates/editor-body-v1-view.php 0000644 00000003142 15230642237 0016056 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 printf( /* translators: %s: Page title. */ 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 15230642237 0013655 0 ustar 00 window.elementor.start(); editor/loader/v1/editor-v1-loader.php 0000644 00000004073 15230642237 0013425 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/editor-loader-factory.php 0000644 00000002450 15230642237 0014215 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 15230642237 0016635 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 15230642237 0016061 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/templates/editor-body-v2-view.php 0000644 00000003220 15230642237 0016055 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 printf( /* translators: %s: Page title. */ 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-loader-v2.js 0000644 00000000625 15230642237 0013667 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/js/editor-environment-v2.js 0000644 00000000251 15230642237 0014760 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/editor-v2-loader.php 0000644 00000011661 15230642237 0013430 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 15230642237 0017417 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/loader/editor-loader-interface.php 0000644 00000002162 15230642237 0014506 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/promotion.php 0000644 00000001433 15230642237 0010576 0 ustar 00 <?php namespace Elementor\Core\Editor; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Promotion { public function get_elements_promotion() { // For BC $has_pro = Utils::has_pro(); 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' => [ 'text' => $has_pro ? __( 'Connect & Activate', 'elementor' ) : __( 'Upgrade', 'elementor' ), 'url' => $has_pro ? admin_url( 'admin.php?page=elementor-license' ) : 'https://go.elementor.com/go-pro-%s', ], ]; } } editor/editor.php 0000644 00000060354 15230642237 0010045 0 ustar 00 <?php namespace Elementor\Core\Editor; use Elementor\Api; use Elementor\Core\Breakpoints\Breakpoint; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Core\Common\Modules\Ajax\Module; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Debug\Loading_Inspection_Manager; use Elementor\Core\Files\Uploads_Manager; use Elementor\Core\Schemes\Manager as Schemes_Manager; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Icons_Manager; use Elementor\Plugin; use Elementor\Settings; use Elementor\Shapes; use Elementor\TemplateLibrary\Source_Local; use Elementor\Tools; use Elementor\User; use Elementor\Utils; use Elementor\Core\Editor\Data; 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'; /** * 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; /** * 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->print_editor_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 ); } /** * 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; $plugin = Plugin::$instance; // 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 $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || defined( 'ELEMENTOR_TESTS' ) && ELEMENTOR_TESTS ) ? '' : '.min'; wp_register_script( 'elementor-editor-modules', ELEMENTOR_ASSETS_URL . 'js/editor-modules' . $suffix . '.js', [ 'elementor-common-modules', ], ELEMENTOR_VERSION, true ); wp_register_script( 'elementor-editor-document', ELEMENTOR_ASSETS_URL . 'js/editor-document' . $suffix . '.js', [ 'elementor-common-modules', ], ELEMENTOR_VERSION, true ); // Hack for waypoint with editor mode. wp_register_script( 'elementor-waypoints', ELEMENTOR_ASSETS_URL . 'lib/waypoints/waypoints-for-editor.js', [ 'jquery', ], '4.0.2', true ); wp_register_script( 'perfect-scrollbar', ELEMENTOR_ASSETS_URL . 'lib/perfect-scrollbar/js/perfect-scrollbar' . $suffix . '.js', [], '1.4.0', true ); wp_register_script( 'jquery-easing', ELEMENTOR_ASSETS_URL . 'lib/jquery-easing/jquery-easing' . $suffix . '.js', [ 'jquery', ], '1.3.2', true ); wp_register_script( 'nprogress', ELEMENTOR_ASSETS_URL . 'lib/nprogress/nprogress' . $suffix . '.js', [], '0.2.0', true ); wp_register_script( 'tipsy', ELEMENTOR_ASSETS_URL . 'lib/tipsy/tipsy' . $suffix . '.js', [ 'jquery', ], '1.0.0', true ); wp_register_script( 'jquery-elementor-select2', ELEMENTOR_ASSETS_URL . 'lib/e-select2/js/e-select2.full' . $suffix . '.js', [ 'jquery', ], '4.0.6-rc.1', true ); wp_register_script( 'flatpickr', ELEMENTOR_ASSETS_URL . 'lib/flatpickr/flatpickr' . $suffix . '.js', [ 'jquery', ], '1.12.0', 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', ELEMENTOR_ASSETS_URL . 'lib/jquery-hover-intent/jquery-hover-intent' . $suffix . '.js', [], '1.0.0', true ); wp_register_script( 'nouislider', ELEMENTOR_ASSETS_URL . 'lib/nouislider/nouislider' . $suffix . '.js', [], '13.0.0', true ); wp_register_script( 'pickr', ELEMENTOR_ASSETS_URL . 'lib/pickr/pickr.min.js', [], '1.5.0', true ); wp_register_script( 'elementor-editor', ELEMENTOR_ASSETS_URL . 'js/editor' . $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 ); /** * 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' ); $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( $this->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'; $config = [ 'initial_document' => $document->get_config(), 'version' => ELEMENTOR_VERSION, 'home_url' => home_url(), 'admin_settings_url' => admin_url( 'admin.php?page=' . Settings::PAGE_ID ), 'admin_tools_url' => admin_url( 'admin.php?page=' . Tools::PAGE_ID ), 'autosave_interval' => AUTOSAVE_INTERVAL, 'tabs' => $plugin->controls_manager->get_tabs(), 'controls' => $plugin->controls_manager->get_controls_data(), 'elements' => $plugin->elements_manager->get_element_types_config(), 'schemes' => [ 'items' => $plugin->schemes_manager->get_registered_schemes_data(), 'enabled_schemes' => Schemes_Manager::get_enabled_schemes(), ], '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' => Utils::get_pro_link( 'https://elementor.com/pro/?utm_source=icon-library&utm_campaign=gopro&utm_medium=wp-dash' ), ], 'filesUpload' => [ 'unfilteredFiles' => Uploads_Manager::are_unfiltered_uploads_enabled(), ], 'fa4_to_fa5_mapping_url' => ELEMENTOR_ASSETS_URL . 'lib/font-awesome/migration/mapping.js', 'default_schemes' => $plugin->schemes_manager->get_schemes_defaults(), 'settings' => $settings, 'system_schemes' => $plugin->schemes_manager->get_system_schemes(), 'wp_editor' => $this->get_wp_editor_config(), 'settings_page_link' => Settings::get_url(), 'tools_page_link' => Tools::get_url(), '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->role_manager->get_user_restrictions_array(), 'is_administrator' => current_user_can( 'manage_options' ), 'introduction' => User::get_introduction_meta(), ], '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' => [ 'darkModeStylesheetURL' => ELEMENTOR_ASSETS_URL . 'css/editor-dark-mode' . $suffix . '.css', '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(), ], ]; if ( ! Utils::has_pro() && current_user_can( 'manage_options' ) ) { $config['promotionWidgets'] = Api::get_promotion_widgets(); } $this->bc_move_document_filters(); /** * Localize editor settings. * * Filters the editor localized settings. * * @since 1.0.0 * * @param array $config Editor configuration. * @param int $post_id The ID of the current post being edited. */ $config = apply_filters( 'elementor/editor/localize_settings', $config ); Utils::print_js_config( 'elementor-editor', 'ElementorConfig', $config ); wp_enqueue_script( 'elementor-editor' ); wp_set_script_translations( 'elementor-editor', 'elementor' ); $plugin->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' ); $suffix = Utils::is_script_debug() ? '' : '.min'; $direction_suffix = is_rtl() ? '-rtl' : ''; wp_register_style( 'font-awesome', ELEMENTOR_ASSETS_URL . 'lib/font-awesome/css/font-awesome' . $suffix . '.css', [], '4.7.0' ); wp_register_style( 'elementor-select2', ELEMENTOR_ASSETS_URL . 'lib/e-select2/css/e-select2' . $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', ELEMENTOR_ASSETS_URL . 'lib/flatpickr/flatpickr' . $suffix . '.css', [], '1.12.0' ); wp_register_style( 'pickr', ELEMENTOR_ASSETS_URL . 'lib/pickr/themes/monolith.min.css', [], '1.5.0' ); wp_register_style( 'elementor-editor', ELEMENTOR_ASSETS_URL . 'css/editor' . $direction_suffix . $suffix . '.css', [ 'elementor-common', 'elementor-select2', 'elementor-icons', 'wp-auth-check', 'google-font-roboto', 'flatpickr', 'pickr', ], ELEMENTOR_VERSION ); wp_enqueue_style( 'elementor-editor' ); $ui_theme = SettingsManager::get_settings_managers( 'editorPreferences' )->get_model()->get_settings( 'ui_theme' ); if ( 'light' !== $ui_theme ) { $ui_theme_media_queries = 'all'; if ( 'auto' === $ui_theme ) { $ui_theme_media_queries = '(prefers-color-scheme: dark)'; } wp_enqueue_style( 'elementor-editor-dark-mode', ELEMENTOR_ASSETS_URL . 'css/editor-dark-mode' . $suffix . '.css', [ 'elementor-editor', ], ELEMENTOR_VERSION, $ui_theme_media_queries ); } $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' ); } /** * Get WordPress editor config. * * Config the default WordPress editor with custom settings for Elementor use. * * @since 1.9.0 * @access private */ private 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 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->schemes_manager->print_schemes_templates(); $plugin->dynamic_tags->print_templates(); $this->init_editor_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(); 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; } /** * Init editor templates. * * Initialize default elementor templates used in the editor panel. * * @since 1.7.0 * @access private */ private function init_editor_templates() { $template_names = [ 'global', 'panel', 'panel-elements', 'repeater', 'templates', 'navigator', 'hotkeys', 'responsive-bar', ]; foreach ( $template_names as $template_name ) { Plugin::$instance->common->add_template( ELEMENTOR_PATH . "includes/editor-templates/$template_name.php" ); } } private 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 ); // TODO: Hard deprecation // _deprecated_hook( '`' . $old_tag . ` is no longer using post_id', '2.9.0', $new_tag' ); } } } } public function set_post_id( $post_id ) { $this->post_id = $post_id; } } editor/notice-bar.php 0000644 00000003444 15230642237 0010577 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\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( '-30 days' ) ) { return []; } return [ 'muted_period' => 90, 'option_key' => '_elementor_editor_upgrade_notice_dismissed', 'message' => sprintf( /* translators: 1: Link open tag, 2: Link close tag. */ esc_html__( 'Love using Elementor? %1$sLearn how you can build better sites with Elementor Pro.%2$s', 'elementor' ), '<a href="%s">', '</a>' ), 'action_title' => esc_html__( 'Go Pro', 'elementor' ), 'action_url' => Utils::get_pro_link( 'https://elementor.com/pro/?utm_source=editor-notice-bar&utm_campaign=gopro&utm_medium=wp-dash' ), ]; } final public function get_notice() { if ( ! current_user_can( 'manage_options' ) ) { 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 $settings; } public function __construct() { add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); } public function set_notice_dismissed() { 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' ] ); } } experiments/non-existing-dependency.php 0000644 00000001014 15230642237 0014356 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/experiments-reporter.php 0000644 00000005661 15230642237 0014037 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', ]; foreach ( $experiments_manager->get_features() as $feature_name => $feature_data ) { $data_to_collect = []; // Extract only tracking keys. foreach ( $tracking_keys as $tracking_key ) { $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"; } $output .= $experiment['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 ); $output .= '<tr><td>' . esc_html( $experiment['title'] ) . ': </td>'; $output .= '<td>' . esc_html( $state ) . '</td>'; $output .= '</tr>'; } return [ 'value' => $output, ]; } } experiments/manager.php 0000644 00000047554 15230642237 0011255 0 ustar 00 <?php namespace Elementor\Core\Experiments; use Elementor\Core\Base\Base_Object; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Core\Upgrade\Manager as Upgrade_Manager; use Elementor\Modules\System_Info\Module as System_Info; use Elementor\Plugin; use Elementor\Settings; use Elementor\Tracker; 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'; 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 $description * @type string $release_status * @type string $default * @type callable $on_state_change * } * * @return array|null */ public function add_feature( array $options ) { if ( isset( $this->features[ $options['name'] ] ) ) { return null; } $default_experimental_data = [ 'description' => '', 'release_status' => self::RELEASE_STATUS_ALPHA, 'default' => self::STATE_INACTIVE, 'new_site' => [ 'default_active' => false, 'always_active' => false, 'minimum_installation_version' => null, ], 'on_state_change' => null, ]; $allowed_options = [ 'name', 'title', 'description', 'release_status', 'default', 'new_site', 'on_state_change' ]; $experimental_data = $this->merge_properties( $default_experimental_data, $options, $allowed_options ); $new_site = $experimental_data['new_site']; $feature_is_mutable = true; if ( $new_site['default_active'] || $new_site['always_active'] ) { $is_new_installation = Upgrade_Manager::install_compare( $new_site['minimum_installation_version'], '>=' ); if ( $is_new_installation ) { if ( $new_site['always_active'] ) { $experimental_data['state'] = self::STATE_ACTIVE; $feature_is_mutable = false; } elseif ( $new_site['default_active'] ) { $experimental_data['default'] = self::STATE_ACTIVE; } } } $experimental_data['mutable'] = $feature_is_mutable; if ( $feature_is_mutable ) { $state = $this->get_saved_feature_state( $options['name'] ); if ( ! $state ) { $state = self::STATE_DEFAULT; } $experimental_data['state'] = $state; } $this->features[ $options['name'] ] = $experimental_data; if ( $feature_is_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 ) { $this->on_feature_state_change( $experimental_data, $new_state ); }; 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; } /** * 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 ) { $feature = $this->get_features( $feature_name ); if ( ! $feature ) { return false; } return self::STATE_ACTIVE === $this->get_feature_actual_state( $feature ); } /** * Set Feature Default State * * @since 3.1.0 * @access public * * @param string $feature_name * @param int $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 private * * @param string $feature_name * * @return string */ private function get_feature_option_key( $feature_name ) { return 'elementor_experiment-' . $feature_name; } private function add_default_features() { $this->add_feature( [ 'name' => 'e_dom_optimization', 'title' => esc_html__( 'Optimized DOM Output', 'elementor' ), 'description' => esc_html__( 'Developers, Please Note! This experiment includes some markup changes. If you\'ve used custom code in Elementor, you might have experienced a snippet of code not running. Turning this experiment off allows you to keep prior Elementor markup output settings, and have that lovely code running again.', 'elementor' ) . ' <a href="https://go.elementor.com/wp-dash-legacy-optimized-dom" target="_blank">' . esc_html__( 'Learn More', 'elementor' ) . '</a>', 'release_status' => self::RELEASE_STATUS_STABLE, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '3.1.0-beta', ], ] ); $this->add_feature( [ 'name' => 'e_optimized_assets_loading', 'title' => esc_html__( 'Improved Asset Loading', 'elementor' ), 'description' => esc_html__( 'Please Note! The "Improved Asset Loading" mode reduces the amount of code that is loaded on the page by default. When activated, parts of the infrastructure code will be loaded dynamically, only when needed. Keep in mind that activating this experiment may cause conflicts with incompatible plugins.', 'elementor' ) . ' <a href="https://go.elementor.com/wp-dash-improved-asset-loading/" target="_blank">' . esc_html__( 'Learn More', 'elementor' ) . '</a>', 'release_status' => self::RELEASE_STATUS_BETA, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '3.2.0-beta', ], ] ); $this->add_feature( [ 'name' => 'e_optimized_css_loading', 'title' => esc_html__( 'Improved CSS Loading', 'elementor' ), 'description' => 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' ) . ' <a href="https://go.elementor.com/wp-dash-improved-css-loading/" target="_blank">' . esc_html__( 'Learn More', 'elementor' ) . '</a>', 'release_status' => self::RELEASE_STATUS_BETA, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '3.3.0-beta', ], ] ); $this->add_feature( [ 'name' => 'e_font_icon_svg', 'title' => esc_html__( 'Inline Font Icons', 'elementor' ), 'description' => esc_html__( 'The “Inline Font Icons” will render the icons as inline SVG without loading the Font-Awsome and the eicons libraries and its related CSS files and fonts. Learn More.', 'elementor' ) . ' <a href="https://go.elementor.com/wp-dash-inline-font-awesome/" target="_blank">' . esc_html__( 'Learn More', 'elementor' ) . '</a>', 'release_status' => self::RELEASE_STATUS_ALPHA, ] ); $this->add_feature( [ 'name' => 'a11y_improvements', 'title' => esc_html__( 'Accessibility Improvements', 'elementor' ), 'description' => esc_html__( 'An array of accessibility enhancements in Elementor pages.', 'elementor' ) . '<br><strong>' . esc_html__( 'Please note!', 'elementor' ) . '</strong> ' . esc_html__( 'These enhancements may include some markup changes to existing elementor widgets', 'elementor' ) . ' <a href="https://go.elementor.com/wp-dash-a11y-improvements" target="_blank">' . esc_html__( 'Learn More', 'elementor' ) . '</a>', 'release_status' => self::RELEASE_STATUS_STABLE, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '3.1.0-beta', ], ] ); $this->add_feature( [ 'name' => 'e_import_export', 'title' => esc_html__( 'Import Export Template Kit', 'elementor' ), 'description' => esc_html__( 'Design sites faster with a template kit that contains some or all components of a complete site, like templates, content & site settings.', 'elementor' ) . '<br>' . esc_html__( 'You can import a kit and apply it to your site, or export the elements from this site to be used anywhere else.', 'elementor' ), 'release_status' => self::RELEASE_STATUS_BETA, 'default' => self::STATE_ACTIVE, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '3.2.0-beta', ], ] ); $this->add_feature( [ 'name' => 'additional_custom_breakpoints', 'title' => esc_html__( 'Additional Custom Breakpoints', 'elementor' ), 'description' => 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' ) . '<br /><strong>' . esc_html__( 'Please note! Conditioning controls on values of responsive controls is not supported when this mode is active.', 'elementor' ) . '</strong>' . ' <a href="https://go.elementor.com/wp-dash-additional-custom-breakpoints/" target="_blank">' . esc_html__( 'Learn More', 'elementor' ) . '</a>', 'release_status' => self::RELEASE_STATUS_BETA, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '3.4.0-beta', ], ] ); $this->add_feature( [ 'name' => 'e_hidden_wordpress_widgets', 'title' => esc_html__( 'Hide native WordPress widgets from search results', 'elementor' ), 'description' => esc_html__( 'WordPress widgets will not be shown when searching in the editor panel. Instead, these widgets can be found in the “WordPress” dropdown at the bottom of the panel.', 'elementor' ), 'release_status' => self::RELEASE_STATUS_BETA, 'default' => self::STATE_ACTIVE, ] ); } /** * 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 ) { if ( ! $feature['mutable'] ) { 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__( 'Experiments', '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__( 'Elementor Experiments', 'elementor' ); ?> </h2> <p class="e-experiment__description"> <?php printf( /* translators: %1$s Link open tag, %2$s: Link close tag. */ esc_html__( 'Access new and experimental features from Elementor before they\'re officially released. As these features are still in development, they are likely to change, evolve or even be removed altogether. %1$sLearn More.%2$s', 'elementor' ), '<a href="https://go.elementor.com/wp-dash-experiments/" target="_blank">', '</a>' ); ?> </p> <p class="e-experiment__description"> <?php echo esc_html__( 'To use an experiment on your site, simply click on the dropdown next to it and switch to Active. You can always deactivate them at any time.', 'elementor' ); ?> </p> <p class="e-experiment__description"> <?php printf( /* translators: %1$s Link open tag, %2$s: Link close tag. */ esc_html__( 'Your feedback is important - %1$shelp us%2$s improve these features by sharing your thoughts and inputs.', 'elementor' ), '<a href="https://go.elementor.com/wp-dash-experiments-report-an-issue/" target="_blank">', '</a>' ); ?> </p> <?php if ( $this->get_features() ) { ?> <button type="button" class="button e-experiment__button" value="active">Activate All Experiments</button> <button type="button" class="button e-experiment__button" value="inactive">Deactivate All Experiments</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 ) { ?> <div class="e-experiment__content"> <select id="e-experiment-<?php echo $feature['name']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" class="e-experiment__select" name="<?php echo $this->get_feature_option_key( $feature['name'] ); ?>"> <?php foreach ( $this->states as $state_key => $state_title ) { ?> <option value="<?php echo $state_key; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" <?php selected( $state_key, $feature['state'] ); ?>><?php echo $state_title; ?></option> <?php } ?> </select> <p class="description"><?php echo $feature['description']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p> <?php if ( 'stable' !== $feature['release_status'] ) { ?> <div class="e-experiment__status"><?php echo sprintf( esc_html__( 'Status: %s', 'elementor' ), $this->release_statuses[ $feature['release_status'] ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div> <?php } ?> </div> <?php } /** * 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> </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 ( 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 */ private function on_feature_state_change( array $old_feature_data, $new_state ) { $this->features[ $old_feature_data['name'] ]['state'] = $new_state; $new_feature_data = $this->get_features( $old_feature_data['name'] ); $actual_old_state = $this->get_feature_actual_state( $old_feature_data ); $actual_new_state = $this->get_feature_actual_state( $new_feature_data ); if ( $actual_old_state === $actual_new_state ) { return; } Plugin::$instance->files_manager->clear_cache(); if ( $new_feature_data['on_state_change'] ) { $new_feature_data['on_state_change']( $actual_old_state, $actual_new_state ); } } 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 ); } } } experiments/wrap-core-dependency.php 0000644 00000001102 15230642237 0013631 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/error_log 0000644 00000000604 15230642237 0011030 0 ustar 00 [07-May-2026 22:18:23 UTC] PHP Fatal error: Uncaught Error: Class 'Elementor\Modules\System_Info\Reporters\Base' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/experiments/experiments-reporter.php:13 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/experiments/experiments-reporter.php on line 13 experiments/exceptions/dependency-exception.php 0000644 00000000261 15230642237 0016116 0 ustar 00 <?php namespace Elementor\Core\Experiments\Exceptions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Dependency_Exception extends \Exception { } experiments/wp-cli.php 0000644 00000006463 15230642237 0011030 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 ); // Activate experiment callback. $activate = function ( $site = '', $id = null ) use ( $args, $is_network ) { $success = 'Experiment activated successfully'; $error = 'Cannot activate experiment'; if ( $is_network ) { $success .= " for site {$site}"; $error .= " for site {$site}"; } $experiments_manager = Plugin::instance()->experiments; $option = $experiments_manager->get_feature_option_key( $args[0] ); update_option( $option, Experiments_Manager::STATE_ACTIVE ); try { \WP_CLI::success( $success ); } catch ( \Exception $e ) { \WP_CLI::error( $error ); } }; if ( $is_network ) { $this->foreach_sites( $activate ); } else { $activate(); } } /** * 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 ); // Activate experiment callback. $activate = function ( $site = '' ) use ( $args, $is_network ) { $success = 'Experiment deactivated successfully'; $error = 'Cannot deactivate experiment'; if ( $is_network ) { $success .= " for site {$site}"; $error .= " for site {$site}"; } $experiments_manager = Plugin::instance()->experiments; $option = $experiments_manager->get_feature_option_key( $args[0] ); update_option( $option, Experiments_Manager::STATE_INACTIVE ); try { \WP_CLI::success( $success ); } catch ( \Exception $e ) { \WP_CLI::error( $error ); } }; if ( $is_network ) { $this->foreach_sites( $activate ); } else { $activate(); } } /** * 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 ) { /** @var \WP_Site[] $sites */ $sites = get_sites(); foreach ( $sites as $keys => $site ) { // Cast $blog as an array instead of object $blog_id = $site->blog_id; switch_to_blog( $blog_id ); $callback( get_option( 'home' ), $blog_id ); restore_current_blog(); } } } logger/log-reporter.php 0000644 00000005720 15230642237 0011165 0 ustar 00 <?php namespace Elementor\Core\Logger; use Elementor\Modules\System_Info\Reporters\Base; 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 __( '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 ] ) ) { if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 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/items/file.php 0000644 00000001720 15230642237 0010600 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']; } 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'; } } logger/items/base.php 0000644 00000012243 15230642237 0010575 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 ); } 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/log-item-interface.php 0000644 00000002307 15230642237 0013336 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 15230642237 0010445 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/js.php 0000644 00000001504 15230642237 0010275 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'] ); } 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/manager.php 0000644 00000014431 15230642237 0010155 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\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; 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 ) { if ( ! $last_error ) { $last_error = error_get_last(); } if ( ! $last_error ) { return; } if ( empty( $last_error['file'] ) ) { return; } $error_path = ( wp_normalize_path( $last_error['file'] ) ); // `untrailingslashit` in order to include other plugins prefixed with elementor. $elementor_path = untrailingslashit( wp_normalize_path( ELEMENTOR_PATH ) ); if ( false === strpos( $error_path, $elementor_path ) ) { 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 ); } public function rest_error_handler( $error_number, $error_message, $error_file, $error_line ) { $error = new \WP_Error( $error_number, $error_message, [ 'type' => $error_number, 'message' => $error_message, 'file' => $error_file, 'line' => $error_line, ] ); // Notify $e.data. if ( ! 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->get_error_data() ); } else { echo wp_json_encode( [ 'message' => 'Server error, see Elementor => System Info', ] ); } } $this->shutdown( $error->get_error_data() ); exit; } 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(); } // PHPCS - See comment above. array_walk_recursive( $_POST['data'], function( &$value ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $value = sanitize_text_field( $value ); } ); // PHPCS - See comment above. foreach ( $_POST['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/logger-interface.php 0000644 00000002364 15230642237 0013424 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/loggers/base.php 0000644 00000004520 15230642237 0011115 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 15230642237 0010565 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; } } behaviors/interfaces/lock-behavior.php 0000644 00000001174 15230642237 0014116 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(); } base/elements-iteration-actions/base.php 0000644 00000003307 15230642237 0014356 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 00000012056 15230642237 0014747 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 ); 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 ( ! $this->is_active_page_assets_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 ( $new_assets[ $assets_type ] as $asset_name ) { if ( ! isset( $this->page_assets[ $assets_type ][ $asset_name ] ) ) { $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 is_active_page_assets_mode() { $is_optimized_mode = Plugin::$instance->experiments->is_feature_active( 'e_optimized_assets_loading' ); return ! Plugin::$instance->preview->is_preview_mode() && $is_optimized_mode; } 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 ( ! $this->is_active_page_assets_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 ); } } } base/background-task-manager.php 0000644 00000004744 15230642237 0012673 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/providers/social-network-provider.php 0000644 00000016233 15230642237 0015006 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/module.php 0000644 00000015224 15230642237 0007464 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 Module 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() { // Cloning instances of the class is forbidden _doing_it_wrong( __FUNCTION__, esc_html__( 'Something went wrong.', 'elementor' ), '1.0.0' ); } /** * Wakeup. * * Disable unserializing of the class. * * @since 1.7.0 * @access public */ public function __wakeup() { // Unserializing instances of the class is forbidden _doing_it_wrong( __FUNCTION__, esc_html__( 'Something went wrong.', 'elementor' ), '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 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/app.php 0000644 00000002344 15230642237 0006756 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/traits/shared-widget-controls-trait.php 0000644 00000015766 15230642237 0015231 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-process/wp-background-process.php 0000644 00000025474 15230642237 0016221 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, /* translators: %d: Interval in minutes. */ 'display' => sprintf( 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 15230642237 0015223 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/db-upgrades-manager.php 0000644 00000013620 15230642237 0012002 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(), ], ] ); Plugin::$instance->files_manager->clear_cache(); $this->update_db_version(); if ( $did_tasks ) { $this->add_flag( 'completed' ); } } 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; } 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/background-task.php 0000644 00000021556 15230642237 0011263 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, /* translators: %d: Interval in minutes. */ 'display' => sprintf( 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/document.php 0000644 00000120042 15230642237 0010010 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\Files\CSS\Post as Post_CSS; use Elementor\Core\Settings\Page\Model as Page_Model; use Elementor\Core\Utils\Exceptions; 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; 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'; /** * 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'; 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; /** * @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, ]; } /** * @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' => static::get_editor_panel_categories(), 'default_route' => $default_route, 'has_elements' => static::get_property( 'has_elements' ), 'support_kit' => static::get_property( 'support_kit' ), 'messages' => [ /* translators: %s: Document title. */ 'publish_notification' => sprintf( esc_html__( 'Hurray! Your %s is live.', 'elementor' ), static::get_title() ), ], ]; } /** * 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( 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; } /** * @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 { $attributes['data-elementor-settings'] = wp_json_encode( $this->get_frontend_settings() ); } return $attributes; } /** * @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. // PHPCS - only reading the value from $_POST['initial_document_id']. if ( ! empty( $_POST['initial_document_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing // PHPCS - only reading the value from $_POST['initial_document_id']. $document = Plugin::$instance->documents->get( $_POST['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 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_type_object = get_post_type_object( $this->get_main_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(), '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(), ], ]; do_action( 'elementor/document/before_get_config', $this ); if ( static::get_property( 'has_elements' ) ) { $config['elements'] = $this->get_elements_raw_data( null, true ); $config['widgets'] = 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 ) { /** * 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(); /** * 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(); 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; } /** * @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; } $editor_data[] = $element->get_raw_data( $with_html_content ); } // 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 return [ [ 'id' => Utils::generate_random_string(), 'elType' => 'section', 'elements' => [ [ 'id' => Utils::generate_random_string(), 'elType' => 'column', 'elements' => [ [ 'id' => Utils::generate_random_string(), 'elType' => $widget_type::get_type(), 'widgetType' => $widget_type->get_name(), 'settings' => $settings, ], ], ], ], ], ]; } /** * @since 2.1.3 * @access public */ public function print_elements_with_wrapper( $elements_data = null ) { if ( ! $elements_data ) { $elements_data = $this->get_elements_data(); } $is_dom_optimization_active = Plugin::$instance->experiments->is_feature_active( 'e_dom_optimization' ); ?> <div <?php Utils::print_html_attributes( $this->get_container_attributes() ); ?>> <?php if ( ! $is_dom_optimization_active ) { ?> <div class="elementor-inner"> <?php } ?> <div class="elementor-section-wrap"> <?php $this->print_elements( $elements_data ); ?> </div> <?php if ( ! $is_dom_optimization_active ) { ?> </div> <?php } ?> </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 [ /* translators: %s: Document title. */ 'title' => sprintf( 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 ); } /** * 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 string $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 ) { /* translators: 1: Saving date, 2: Author display name. */ $last_edited = sprintf( esc_html__( 'Draft saved on %1$s by %2$s', 'elementor' ), '<time>' . $date . '</time>', $display_name ); } else { /* translators: 1: Editing date, 2: Author display name. */ $last_edited = sprintf( 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 isn't 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 ); } } } private function process_element_import_export( Controls_Stack $element, $method ) { $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; } if ( method_exists( $control_class, $method ) ) { $element_data['settings'][ $control['name'] ] = $control_class->{$method}( $element->get_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 ) { // 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(); } } 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, 'separator' => 'none', ] ); $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/base-object.php 0000644 00000013064 15230642240 0010347 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; } } role-manager/editor-one-role-manager-menu.php 0000644 00000002426 15230642240 0015206 0 ustar 00 <?php namespace Elementor\Core\RoleManager; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Third_Level_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Role_Manager_Menu implements Menu_Item_Third_Level_Interface, Admin_Menu_Item_With_Page { public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_group_id(): string { return Menu_Config::EDITOR_GROUP_ID; } public function get_label(): string { return esc_html__( 'Role Manager', 'elementor' ); } public function get_position(): int { return 40; } public function get_slug(): string { return Role_Manager::PAGE_ID; } public function get_icon(): string { return 'users'; } public function has_children(): bool { return false; } public function get_page_title() { return $this->get_label(); } public function render() { $role_manager = Plugin::$instance->role_manager; if ( $role_manager ) { $role_manager->display_settings_page(); } } } role-manager/role-manager-menu-item.php 0000644 00000001527 15230642240 0014100 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 00000014447 15230642240 0012207 0 ustar 00 <?php namespace Elementor\Core\RoleManager; use Elementor\Settings_Page; use Elementor\Settings; use Elementor\Utils; 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'; /** * @since 2.0.0 * @access public */ public function get_role_manager_options() { return get_option( 'elementor_' . self::ROLE_MANAGER_OPTION_NAME, [] ); } /** * @since 2.0.0 * @access protected */ protected function get_page_title() { return __( 'Role Manager', 'elementor' ); } /** * @since 2.0.0 * @access public */ public function register_admin_menu() { $sanitized_page_title = esc_html( $this->get_page_title() ); add_submenu_page( Settings::PAGE_ID, $sanitized_page_title, $sanitized_page_title, 'manage_options', self::PAGE_ID, [ $this, 'display_settings_page' ] ); } /** * @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' ], ], ], ], ], ], ], ]; } /** * @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( $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> <?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 } /** * @since 2.0.0 * @access public */ public function get_go_pro_link_html() { $pro_link = Utils::get_pro_link( 'https://elementor.com/pro/?utm_source=wp-role-manager&utm_campaign=gopro&utm_medium=wp-dash' ); ?> <div class="elementor-role-go-pro"> <div class="elementor-role-go-pro__desc"><?php echo esc_html__( 'Want to give access only to content?', 'elementor' ); ?></div> <div class="elementor-role-go-pro__link"><a class="elementor-button elementor-button-default elementor-button-go-pro" target="_blank" href="<?php echo esc_url( $pro_link ); ?>"><?php echo esc_html__( 'Go Pro', 'elementor' ); ?></a></div> </div> <?php } /** * @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( 'admin_menu', [ $this, 'register_admin_menu' ], 100 ); add_action( 'elementor/role/restrictions/controls', [ $this, 'get_go_pro_link_html' ] ); } } responsive/files/frontend.php 0000644 00000011516 15230642240 0012375 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; 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 = 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. $value = -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 00000007302 15230642240 0011647 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. */ const BREAKPOINT_OPTION_PREFIX = 'viewport_'; /** * Default breakpoints. * * Holds the default responsive breakpoints. * * @since 1.0.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 * @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 * @access public * @static * * @return array Default breakpoints. */ public static function get_default_breakpoints() { return self::$default_breakpoints; } /** * Get editable breakpoints. * * Retrieve the editable breakpoints. * * @since 1.0.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 * @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 * @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 * @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 * @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(); } } modules-manager.php 0000644 00000005222 15230642240 0010334 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', 'usage', 'dev-tools', 'landing-pages', 'compatibility-tag', 'elements-color-picker', 'shapes', 'favorites', 'admin-top-bar', ]; } /** * 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'; } } common/app.php 0000644 00000013044 15230642240 0007325 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\Plugin; 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() ); } /** * 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-dialog', 'wp-api-request', ], 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/' ), [], '5.14.0' ); wp_enqueue_style( 'elementor-common', $this->get_css_assets_url( 'common', null, 'default', true ), [ 'elementor-icons', ], 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 ); return [ '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(), ], ]; } /** * 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/events-manager/module.php 0000644 00000004412 15230642240 0014415 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\EventsManager; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\Connect\Apps\Base_App; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Utils; use Elementor\Plugin; use Elementor\Tracker; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const EXPERIMENT_NAME = 'editor_events'; public function get_name() { return 'events-manager'; } public static function get_editor_events_config() { $can_send_events = ! empty( ELEMENTOR_EDITOR_EVENTS_MIXPANEL_TOKEN ) && Tracker::is_allow_track() && ! Tracker::has_terms_changed( '2025-07-07' ) && Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ); $settings = [ 'can_send_events' => $can_send_events, 'elementor_version' => ELEMENTOR_VERSION, 'site_url' => hash( 'sha256', get_site_url() ), 'wp_version' => get_bloginfo( 'version' ), 'user_agent' => esc_html( Utils::get_super_global_value( $_SERVER, 'HTTP_USER_AGENT' ) ), 'site_language' => get_locale(), 'site_key' => get_option( Base_App::OPTION_CONNECT_SITE_KEY ), 'subscription_id' => self::get_subscription_id(), 'subscription' => self::get_subscription(), 'token' => ELEMENTOR_EDITOR_EVENTS_MIXPANEL_TOKEN, ]; return $settings; } public static function get_experimental_data(): array { return [ 'name' => static::EXPERIMENT_NAME, 'title' => esc_html__( 'Elementor Editor Events', 'elementor' ), 'description' => esc_html__( 'Editor events processing', 'elementor' ), 'hidden' => true, 'release_status' => Experiments_Manager::RELEASE_STATUS_ALPHA, 'default' => Experiments_Manager::STATE_INACTIVE, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '3.32.0', ], ]; } private static function get_subscription_id() { $subscription = self::get_subscription(); return $subscription['subscription_id'] ?? null; } private static function get_subscription() { if ( ! Utils::has_pro() ) { return null; } $license_data = get_option( '_elementor_pro_license_v2_data' ); if ( ! isset( $license_data['value'] ) ) { return null; } return json_decode( $license_data['value'], true ); } } common/modules/connect/admin.php 0000644 00000004406 15230642240 0012740 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect; use Elementor\Plugin; use Elementor\Settings; 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() { $submenu_page = add_submenu_page( Settings::PAGE_ID, __( 'Connect', 'elementor' ), __( 'Connect', 'elementor' ), 'edit_posts', self::PAGE_ID, [ $this, 'render_page' ] ); add_action( 'load-' . $submenu_page, [ $this, 'on_load_page' ] ); } /** * @since 2.3.0 * @access public */ public function hide_menu_item() { remove_submenu_page( Settings::PAGE_ID, self::PAGE_ID ); } /** * @since 2.3.0 * @access public */ public function on_load_page() { if ( isset( $_GET['action'], $_GET['app'] ) ) { $manager = Plugin::$instance->common->get_component( 'connect' ); $app_slug = $_GET['app']; $app = $manager->get_app( $app_slug ); $nonce_action = $_GET['app'] . $_GET['action']; if ( ! $app ) { wp_die( 'Unknown app: ' . esc_attr( $app_slug ) ); } if ( empty( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], $nonce_action ) ) { wp_die( 'Invalid Nonce', 'Invalid Nonce', [ 'back_link' => true, ] ); } $method = 'action_' . $_GET['action']; if ( method_exists( $app, $method ) ) { call_user_func( [ $app, $method ] ); } } } /** * @since 2.3.0 * @access public */ public function render_page() { $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 \Elementor\Core\Common\Modules\Connect\Apps\Base_App $app */ foreach ( $apps as $app ) { echo '<div class="elementor-connect-app-wrapper">'; $app->render_admin_widget(); echo '</div>'; } ?> </div><!-- /.wrap --> <?php } /** * @since 2.3.0 * @access public */ public function __construct() { self::$url = admin_url( 'admin.php?page=' . self::PAGE_ID ); add_action( 'admin_menu', [ $this, 'register_admin_menu' ], 206 ); add_action( 'admin_head', [ $this, 'hide_menu_item' ] ); } } common/modules/connect/module.php 0000644 00000012171 15230642240 0013133 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; /** * @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(); } } /** * @deprecated 3.1.0 */ public function localize_settings() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0' ); return []; } /** * 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 = '' ) { return [ static::ACCESS_LEVEL_CORE => [ 'label' => null, 'promotion_url' => null, 'color' => null, ], static::ACCESS_LEVEL_PRO => [ 'label' => 'Pro', 'promotion_url' => Utils::get_pro_link( "https://elementor.com/pro/?utm_source={$context}&utm_medium=wp-dash&utm_campaign=gopro" ), 'color' => '#92003B', ], static::ACCESS_LEVEL_EXPERT => [ 'label' => 'Expert', 'promotion_url' => Utils::get_pro_link( "https://elementor.com/pro/?utm_source={$context}&utm_medium=wp-dash&utm_campaign=goexpert" ), 'color' => '#010051', ], ]; } 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/rest/rest-api.php 0000644 00000010230 15230642240 0014341 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect\Rest; use Elementor\Plugin; use WP_Http; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Library Connect REST API. * * REST API controller for handling library connect operations. */ class Rest_Api { /** * REST API namespace. */ const REST_NAMESPACE = 'elementor/v1'; /** * REST API base. */ const REST_BASE = 'library'; /** * Authentication mode. */ const AUTH_MODE = 'rest'; /** * Register REST API routes. * * @access public * @return void */ public function register_routes() { register_rest_route( self::REST_NAMESPACE, self::REST_BASE . '/connect', [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'connect' ], 'permission_callback' => [ $this, 'connect_permissions_check' ], 'args' => [ 'token' => [ 'required' => true, 'type' => 'string', 'description' => 'Connect CLI token', ], ], ], ] ); register_rest_route( self::REST_NAMESPACE, self::REST_BASE . '/connect', [ [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'disconnect' ], 'permission_callback' => [ $this, 'connect_permissions_check' ], ], ] ); } public function connect( \WP_REST_Request $request ) { $app = $this->get_connect_app(); if ( ! $app ) { return $this->elementor_library_app_not_available(); } $app->set_auth_mode( self::AUTH_MODE ); $_REQUEST['mode'] = self::AUTH_MODE; $_REQUEST['token'] = $request->get_param( 'token' ); try { $app->action_authorize(); $app->action_get_token(); if ( $app->is_connected() ) { return $this->success_response( [ 'message' => __( 'Connected successfully.', 'elementor' ) ], WP_Http::CREATED ); } else { return $this->error_response( 'elementor_library_not_connected', __( 'Failed to connect to Elementor Library.', 'elementor' ), WP_Http::INTERNAL_SERVER_ERROR ); } } catch ( \Exception $e ) { return $this->error_response( 'elementor_library_connect_error', $e->getMessage(), WP_Http::INTERNAL_SERVER_ERROR ); } } public function disconnect( \WP_REST_Request $request ) { $app = $this->get_connect_app(); if ( ! $app ) { return $this->elementor_library_app_not_available(); } $app->set_auth_mode( self::AUTH_MODE ); $_REQUEST['mode'] = self::AUTH_MODE; try { $app->action_disconnect(); return $this->success_response( [ 'message' => __( 'Disconnected successfully.', 'elementor' ) ], WP_Http::OK ); } catch ( \Exception $e ) { return $this->error_response( 'elementor_library_disconnect_error', $e->getMessage(), WP_Http::INTERNAL_SERVER_ERROR ); } } public function connect_permissions_check( \WP_REST_Request $request ) { return current_user_can( 'manage_options' ); } private function route_wrapper( callable $cb ) { try { $response = $cb(); } catch ( \Exception $e ) { return $this->error_response( 'unexpected_error', __( 'Something went wrong', 'elementor' ), WP_Http::INTERNAL_SERVER_ERROR ); } return $response; } private function error_response( $code, $message, $status = WP_Http::BAD_REQUEST ) { return new \WP_Error( $code, $message, [ 'status' => $status ] ); } private function success_response( $data = [], $status = WP_Http::OK ) { $response = rest_ensure_response( array_merge( [ 'success' => true ], $data ) ); $response->set_status( $status ); return $response; } private function elementor_library_app_not_available() { return $this->error_response( 'elementor_library_app_not_available', __( 'Elementor Library app is not available.', 'elementor' ), WP_Http::INTERNAL_SERVER_ERROR ); } /** * Get the connect app. * * @return \Elementor\Core\Common\Modules\Connect\Apps\Library|null */ private function get_connect_app() { $connect = Plugin::$instance->common->get_component( 'connect' ); if ( ! $connect ) { return null; } $app = $connect->get_app( 'library' ); if ( ! $app ) { $connect->init(); $app = $connect->get_app( 'library' ); } return $app; } } common/modules/connect/rest/readme.md 0000644 00000004524 15230642240 0013674 0 ustar 00 # Elementor Library Connect REST API This module provides REST API endpoints for connecting and disconnecting your WordPress site to the Elementor Library, similar in purpose to the [Elementor CLI Library Connect command](https://developers.elementor.com/docs/cli/library-connect/). ## Overview The REST API allows programmatic connection and disconnection to the Elementor Library, which is useful for automation, integrations, and testing. **Note:** The REST API is intended for internal and advanced use, mirroring the functionality of the CLI command. ## Endpoints ### 1. Connect to Elementor Library - **URL:** `/index.php?rest_route=/elementor/v1/library/connect` - **Method:** `POST` - **Permissions:** Requires the `manage_options` capability (typically administrators). - **Body Parameters:** - `token` (string, required): The connect token from your Elementor account dashboard. #### Example Request ```http POST /index.php?rest_route=/elementor/v1/library/connect Content-Type: application/json Authorization: Basic {{encoded_wp_credentials}} { "token": "YOUR_CLI_TOKEN" } ``` #### Example Success Response ```json { "success": true, "message": "Connected successfully." } ``` #### Example Error Response ```json { "code": "elementor_library_not_connected", "message": "Failed to connect to Elementor Library.", "data": { "status": 500 } } ``` --- ### 2. Disconnect from Elementor Library - **URL:** `/index.php?rest_route=/elementor/v1/library/connect` - **Method:** `DELETE` - **Permissions:** Requires the `manage_options` capability. #### Example Request ```http DELETE /index.php?rest_route=/elementor/v1/library/connect Authorization: Basic {{encoded_wp_credentials}} ``` #### Example Success Response ```json { "success": true, "message": "Disconnected successfully." } ``` #### Example Error Response ```json { "code": "elementor_library_disconnect_error", "message": "Error message here", "data": { "status": 500 } } ``` --- ## Permissions All endpoints require the user to have the `manage_options` capability. ## Error Handling Errors are returned as standard WordPress REST API error objects, with a `code`, `message`, and HTTP status. ## Reference - For CLI usage and more context, see the [Elementor CLI Library Connect documentation](https://developers.elementor.com/docs/cli/library-connect/). common/modules/connect/admin-menu-items/editor-one-connect-menu.php 0000644 00000003024 15230642240 0021452 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Core\Common\Modules\Connect\Apps\Base_App; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Connect_Menu implements Menu_Item_Interface, Admin_Menu_Item_With_Page { public function get_capability(): string { return 'edit_posts'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return false; } public function get_label(): string { return esc_html__( 'Connect', 'elementor' ); } public function get_position(): int { return 999; } public function get_slug(): string { return 'elementor-connect'; } public function get_group_id(): string { return Menu_Config::SYSTEM_GROUP_ID; } public function get_page_title() { return esc_html__( 'Connect', 'elementor' ); } 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 15230642240 0014246 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/library.php 0000644 00000006302 15230642240 0014254 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect\Apps; 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, __( '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( 'panel-library' ), 'base_access_level' => ConnectModule::ACCESS_LEVEL_CORE, 'current_access_level' => ConnectModule::ACCESS_LEVEL_CORE, ], ] ); } 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' ] ); } 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, ]; } protected function init() { add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] ); add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); } } common/modules/connect/apps/common-app.php 0000644 00000001614 15230642240 0014657 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/feedback.php 0000644 00000004750 15230642240 0014341 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Connect\Apps; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Feedback extends Common_App { const EXPERIMENT_NAME = 'in_editor_feedback'; const FEEDBACK_ENDPOINT = 'https://my.elementor.com/feedback/api/v1'; public function __construct() { parent::__construct(); Plugin::$instance->experiments->add_feature([ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'In-Editor Feedback', 'elementor' ), 'description' => esc_html__( 'Enable in-editor feedback submission.', 'elementor' ), 'hidden' => true, 'release_status' => Plugin::$instance->experiments::RELEASE_STATUS_BETA, 'default' => Plugin::$instance->experiments::STATE_INACTIVE, 'new_site' => [ 'default_active' => false, 'minimum_installation_version' => '3.35.0', ], ]); } public function get_title() { return esc_html__( 'Product Feedback', 'elementor' ); } /** * @since 2.3.0 * @access protected */ protected function get_slug() { return 'product-feedback'; } protected function get_base_connect_info() { return [ 'app' => 'editor', 'access_token' => $this->get( 'access_token' ), 'client_id' => $this->get( 'client_id' ), 'endpoint' => 'taxonomies', 'local_id' => get_current_user_id(), 'site_key' => $this->get_site_key(), 'home_url' => trailingslashit( home_url() ), ]; } protected function get_api_url() { return static::FEEDBACK_ENDPOINT . '/' . $this->get_slug(); } protected function get_generated_urls( $endpoint ) { return [ $this->get_api_url() ]; } public function submit( $body ) { $is_active = Plugin::instance()->experiments->is_feature_active( self::EXPERIMENT_NAME ); if ( ! $is_active ) { return [ 'success' => false, 'data' => [ 'message' => 'In-Editor Feedback is not active.', ], ]; } $connect_info = $this->get_base_connect_info(); $merged_body = array_merge( $connect_info, $body ); $signature = $this->generate_signature( $merged_body ); $headers = [ 'access-token' => $connect_info['access_token'], 'app' => 'library', 'client-id' => $connect_info['client_id'], 'endpoint' => 'taxonomies', 'home-url' => $connect_info['home_url'], 'local-id' => $connect_info['local_id'], 'site-key' => $this->get_site_key(), 'X-Elementor-Signature' => $signature, ]; $response = wp_remote_post( $this->get_api_url(), [ 'headers' => $headers, 'body' => $body, ]); return $response; } } common/modules/connect/apps/base-user-app.php 0000644 00000001060 15230642240 0015250 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/base-app.php 0000644 00000044750 15230642240 0014311 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; 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' ); /* translators: %s: Remote user. */ $title = sprintf( 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(); } if ( empty( $_REQUEST['state'] ) || $_REQUEST['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' => $_REQUEST['code'], '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(); } if ( ! empty( $response->data_share_opted_in ) && current_user_can( 'manage_options' ) ) { Tracker::set_opt_in( true ); } $this->delete( 'state' ); $this->set( (array) $response ); $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 all the connect information * * @return array */ protected function get_connect_info() { $connect_info = [ '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() ), ]; $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 ) ) { // 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 ( 401 === $code ) { $this->delete(); if ( 'xhr' !== $this->auth_mode ) { $this->action_authorize(); } } 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', ]; $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() { if ( $this->get( 'client_id' ) ) { return; } $response = $this->request( 'get_client_id', [ // phpcs:ignore WordPress.Security.NonceVerification 'source' => isset( $_REQUEST['source'] ) ? esc_attr( $_REQUEST['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( $_REQUEST['callback_id'] ); ?>', <?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( $_REQUEST['token'] ); return; default: wp_redirect( $this->get_remote_authorize_url() ); 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( $_REQUEST['callback_id'] ), ], $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' ); } if ( isset( $_REQUEST['mode'] ) ) { // phpcs:ignore -- nonce validation is not require here. $allowed_auth_modes = [ 'popup', ]; if ( defined( 'WP_CLI' ) && WP_CLI ) { $allowed_auth_modes[] = 'cli'; } $mode = $_REQUEST['mode']; // phpcs:ignore -- nonce validation is not require here. 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/connect-menu-item.php 0000644 00000002236 15230642240 0015176 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/finder/categories-manager.php 0000644 00000007027 15230642240 0015225 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 * @access public * * @param string $category_name * @param Base_Category $category * * @deprecated 3.5.0 Use `$this->register()` instead. */ public function add_category( $category_name, Base_Category $category ) { // TODO: Uncomment when Pro uses the new hook. //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; } /** * 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. */ // TODO: Uncomment when Pro uses the new hook. //Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->do_deprecated_action( // 'elementor/finder/categories/init', // [ $this ], // '3.5.0', // 'elementor/finder/register' //); do_action( 'elementor/finder/categories/init', $this ); /** * 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/template.php 0000644 00000003331 15230642240 0013275 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"></i> <input id="elementor-finder__search__input" placeholder="<?php echo esc_html__( '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 }}}"></i> </div> <div class="elementor-finder__results__item__title">{{{ title }}}</div> <# if ( description ) { #> <div class="elementor-finder__results__item__description">- {{{ description }}}</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 00000004717 15230642240 0012760 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 ) { $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/categories/general.php 0000644 00000004030 15230642240 0015221 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' => _x( 'Saved Templates', 'Template Library', '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' ], ], ]; } } common/modules/finder/categories/site.php 0000644 00000004046 15230642240 0014557 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/settings.php 0000644 00000002575 15230642240 0015460 0 ustar 00 <?php namespace Elementor\Core\Common\Modules\Finder\Categories; use Elementor\Core\Common\Modules\Finder\Base_Category; 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 = [] ) { $settings_url = ElementorSettings::get_url(); return [ 'general-settings' => [ 'title' => esc_html__( 'General Settings', 'elementor' ), 'url' => $settings_url, 'keywords' => [ 'general', 'settings', 'elementor' ], ], 'advanced' => [ 'title' => esc_html__( 'Advanced', 'elementor' ), 'url' => $settings_url . '#tab-advanced', 'keywords' => [ 'advanced', 'settings', 'elementor' ], ], 'experiments' => [ 'title' => esc_html__( 'Experiments', 'elementor' ), 'url' => $settings_url . '#tab-experiments', 'keywords' => [ 'settings', 'elementor', 'experiments' ], ], ]; } } common/modules/finder/categories/create.php 0000644 00000005163 15230642240 0015057 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 ) { return $this->get_create_new_template( $document_class::get_add_new_title(), $document_class::get_create_url() ); } 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/edit.php 0000644 00000005270 15230642240 0014540 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/finder/categories/tools.php 0000644 00000003465 15230642240 0014757 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' ], ], ]; 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/base-category.php 0000644 00000003100 15230642240 0014201 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 * @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/event-tracker/data/controller.php 0000644 00000003513 15230642240 0016063 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/module.php 0000644 00000001642 15230642240 0014255 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/db.php 0000644 00000011405 15230642240 0013353 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/personal-data.php 0000644 00000003711 15230642240 0015521 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/ajax/module.php 0000644 00000015707 15230642240 0012435 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; 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 ); $this->requests = json_decode( stripslashes( $_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 { $results = call_user_func( $this->ajax_actions[ $action_data['action'] ]['callback'], $action_data['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 ! empty( $_REQUEST['_nonce'] ) && wp_verify_nonce( $_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; } } database/base-migration.php 0000644 00000000261 15230642240 0011717 0 ustar 00 <?php namespace Elementor\Core\Database; abstract class Base_Migration { /** * Runs when upgrading the database * * @return void */ abstract public function up(); } database/base-database-updater.php 0000644 00000002467 15230642240 0013146 0 ustar 00 <?php namespace Elementor\Core\Database; use Elementor\Core\Utils\Collection; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Database_Updater { public function up( $force = false ) { $installed_version = $this->get_installed_version(); if ( ! $force && $this->get_db_version() <= $installed_version ) { return; } $migrations = new Collection( $this->get_migrations() ); if ( ! $force ) { $migrations = $migrations->filter( function ( $_, $version ) use ( $installed_version ) { return $version > $installed_version; } ); } $migrations->map( function ( Base_Migration $migration, $version ) { $migration->up(); $this->update_db_version_option( $version ); } ); $this->update_db_version_option( $this->get_db_version() ); } public function register() { add_action( 'admin_init', function () { $this->up(); } ); } protected function update_db_version_option( $version ) { update_option( $this->get_db_version_option_name(), $version ); } protected function get_installed_version() { return intval( get_option( $this->get_db_version_option_name() ) ); } abstract protected function get_db_version(); abstract protected function get_db_version_option_name(): string; abstract protected function get_migrations(): array; } app/app.php 0000644 00000014020 15230642240 0006610 0 ustar 00 <?php namespace Elementor\Core\App; use Elementor\Core\Base\App as BaseApp; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class App extends BaseApp { const PAGE_ID = 'elementor-app'; /** * Get module name. * * Retrieve the module name. * * @since 3.0.0 * @access public * * @return string Module name. */ public function get_name() { return 'app'; } public function get_base_url() { return admin_url( 'admin.php?page=' . self::PAGE_ID . '&ver=' . ELEMENTOR_VERSION ); } public function register_admin_menu() { add_submenu_page( Source_Local::ADMIN_MENU_SLUG, esc_html__( 'Theme Builder', 'elementor' ), esc_html__( 'Theme Builder', 'elementor' ), 'manage_options', self::PAGE_ID ); } public function fix_submenu( $menu ) { global $submenu; if ( is_multisite() && is_network_admin() ) { return $menu; } // Non admin role / custom wp menu. if ( empty( $submenu[ Source_Local::ADMIN_MENU_SLUG ] ) ) { return $menu; } // Hack to add a link to sub menu. foreach ( $submenu[ Source_Local::ADMIN_MENU_SLUG ] as &$item ) { if ( self::PAGE_ID === $item[2] ) { $item[2] = $this->get_settings( 'menu_url' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $item[4] = 'elementor-app-link'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } } return $menu; } public function is_current() { return ( ! empty( $_GET['page'] ) && self::PAGE_ID === $_GET['page'] ); } public function admin_init() { do_action( 'elementor/app/init', $this ); $this->enqueue_assets(); // Setup default heartbeat options // TODO: Enable heartbeat. add_filter( 'heartbeat_settings', function( $settings ) { $settings['interval'] = 15; return $settings; } ); $this->render(); die; } protected function get_init_settings() { $referer = wp_get_referer(); return [ 'menu_url' => $this->get_base_url() . '#site-editor/promotion', 'assets_url' => ELEMENTOR_ASSETS_URL, 'return_url' => $referer ? $referer : admin_url(), 'hasPro' => Utils::has_pro(), 'admin_url' => admin_url(), 'login_url' => wp_login_url(), ]; } private function render() { require __DIR__ . '/view.php'; } /** * Get Elementor UI theme preference. * * Retrieve the user UI theme preference as defined by editor preferences manager. * * @since 3.0.0 * @access private * * @return string Preferred UI theme. */ private function get_elementor_ui_theme_preference() { $editor_preferences = SettingsManager::get_settings_managers( 'editorPreferences' ); return $editor_preferences->get_model()->get_settings( 'ui_theme' ); } /** * Enqueue dark theme detection script. * * Enqueues an inline script that detects user-agent settings for dark mode and adds a complimentary class to the body tag. * * @since 3.0.0 * @access private */ private function enqueue_dark_theme_detection_script() { if ( 'auto' === $this->get_elementor_ui_theme_preference() ) { wp_add_inline_script( 'elementor-app', 'if ( window.matchMedia && window.matchMedia( `(prefers-color-scheme: dark)` ).matches ) { document.body.classList.add( `eps-theme-dark` ); }' ); } } private function enqueue_assets() { Plugin::$instance->init_common(); Plugin::$instance->common->register_scripts(); wp_register_style( 'select2', $this->get_css_assets_url( 'e-select2', 'assets/lib/e-select2/css/' ), [], '4.0.6-rc.1' ); wp_register_style( 'elementor-icons', $this->get_css_assets_url( 'elementor-icons', 'assets/lib/eicons/css/' ), [], '5.14.0' ); wp_register_style( 'elementor-common', $this->get_css_assets_url( 'common', null, 'default', true ), [], ELEMENTOR_VERSION ); wp_register_style( 'select2', ELEMENTOR_ASSETS_URL . 'lib/e-select2/css/e-select2.css', [], '4.0.6-rc.1' ); wp_enqueue_style( 'elementor-app', $this->get_css_assets_url( 'app', null, 'default', true ), [ 'select2', 'elementor-icons', 'elementor-common', 'select2', ], ELEMENTOR_VERSION ); wp_enqueue_script( 'elementor-app-packages', $this->get_js_assets_url( 'app-packages' ), [ 'wp-i18n', 'react', ], ELEMENTOR_VERSION, true ); wp_register_script( 'select2', $this->get_js_assets_url( 'e-select2.full', 'assets/lib/e-select2/js/' ), [ 'jquery', ], '4.0.6-rc.1', true ); wp_enqueue_script( 'elementor-app', $this->get_js_assets_url( 'app' ), [ 'wp-url', 'wp-i18n', 'react', 'react-dom', 'select2', ], ELEMENTOR_VERSION, true ); $this->enqueue_dark_theme_detection_script(); wp_set_script_translations( 'elementor-app-packages', 'elementor' ); wp_set_script_translations( 'elementor-app', 'elementor' ); $this->print_config(); } public function enqueue_app_loader() { wp_enqueue_script( 'elementor-app-loader', $this->get_js_assets_url( 'app-loader' ), [ 'elementor-common', ], ELEMENTOR_VERSION, true ); $this->print_config( 'elementor-app-loader' ); } public function __construct() { $this->add_component( 'site-editor', new Modules\SiteEditor\Module() ); if ( current_user_can( 'manage_options' ) && Plugin::$instance->experiments->is_feature_active( 'e_import_export' ) || Utils::is_wp_cli() ) { $this->add_component( 'import-export', new Modules\ImportExport\Module() ); // Kit library is depended on import-export $this->add_component( 'kit-library', new Modules\KitLibrary\Module() ); } add_action( 'admin_menu', [ $this, 'register_admin_menu' ], 21 /* after Elementor page */ ); // Happens after WP plugin page validation. add_filter( 'add_menu_classes', [ $this, 'fix_submenu' ] ); if ( $this->is_current() ) { add_action( 'admin_init', [ $this, 'admin_init' ], 0 ); } else { add_action( 'elementor/common/after_register_scripts', [ $this, 'enqueue_app_loader' ] ); } } } app/modules/onboarding/module.php 0000644 00000033015 15230642240 0013114 0 ustar 00 <?php namespace Elementor\Core\App\Modules\Onboarding; use Automatic_Upgrader_Skin; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Common\Modules\Connect\Apps\Library; use Elementor\Core\Files\Uploads_Manager; use Elementor\Plugin; use Elementor\Tracker; use Plugin_Upgrader; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Onboarding Module * * Responsible for initializing Elementor App functionality * * @since 3.6.0 */ class Module extends BaseModule { const VERSION = '1.0.0'; const ONBOARDING_OPTION = 'elementor_onboarded'; /** * Get name. * * @since 3.6.0 * @access public * * @return string */ public function get_name() { return 'onboarding'; } /** * Set Onboarding Settings * * Creates an array of module settings that is localized into the JS App config. * * @since 3.6.0 */ private function set_onboarding_settings() { if ( ! Plugin::$instance->common ) { return; } // Get the published pages and posts $pages_and_posts = new \WP_Query( [ 'post_type' => [ 'page', 'post' ], 'post_status' => 'publish', 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'no_found_rows' => true, ] ); $custom_site_logo_id = get_theme_mod( 'custom_logo' ); $custom_logo_src = wp_get_attachment_image_src( $custom_site_logo_id, 'full' ); $site_name = get_option( 'blogname', '' ); $hello_theme = wp_get_theme( 'hello-elementor' ); $hello_theme_errors = is_object( $hello_theme->errors() ) ? $hello_theme->errors()->errors : []; /** @var Library $library */ $library = Plugin::$instance->common->get_component( 'connect' )->get_app( 'library' ); Plugin::$instance->app->set_settings( 'onboarding', [ 'eventPlacement' => 'Onboarding wizard', 'onboardingAlreadyRan' => get_option( self::ONBOARDING_OPTION ), 'onboardingVersion' => self::VERSION, 'isLibraryConnected' => $library->is_connected(), // Used to check if the Hello Elementor theme is installed but not activated. 'helloInstalled' => empty( $hello_theme_errors['theme_not_found'] ), 'helloActivated' => 'hello-elementor' === get_option( 'template' ), // The "Use Hello theme on my site" checkbox should be checked by default only if this condition is met. 'helloOptOut' => count( $pages_and_posts->posts ) < 5, 'siteName' => esc_html( $site_name ), 'isUnfilteredFilesEnabled' => Uploads_Manager::are_unfiltered_uploads_enabled(), 'urls' => [ 'kitLibrary' => Plugin::$instance->app->get_base_url() . '#/kit-library?order[direction]=desc&order[by]=featuredIndex', 'createNewPage' => Plugin::$instance->documents->get_create_new_post_url(), 'connect' => $library->get_admin_url( 'authorize', [ 'utm_source' => 'onboarding-wizard', 'utm_campaign' => 'connect-account', 'utm_medium' => 'wp-dash', 'utm_term' => self::VERSION, 'source' => 'generic', ] ), 'signUp' => $library->get_admin_url( 'authorize', [ 'utm_source' => 'onboarding-wizard', 'utm_campaign' => 'connect-account', 'utm_medium' => 'wp-dash', 'utm_term' => self::VERSION, 'source' => 'generic', 'screen_hint' => 'signup', ] ), 'uploadPro' => Plugin::$instance->app->get_base_url() . '#/onboarding/uploadAndInstallPro?mode=popup', ], 'siteLogo' => [ 'id' => $custom_site_logo_id, 'url' => $custom_logo_src ? $custom_logo_src[0] : '', ], 'utms' => [ 'connectTopBar' => '&utm_content=top-bar', 'connectCta' => '&utm_content=cta-button', 'connectCtaLink' => '&utm_content=cta-link', 'downloadPro' => '?utm_source=onboarding-wizard&utm_campaign=my-account-subscriptions&utm_medium=wp-dash&utm_content=import-pro-plugin&utm_term=' . self::VERSION, ], 'nonce' => wp_create_nonce( 'onboarding' ), ] ); } /** * Get Permission Error Response * * Returns the response that is returned when the user's capabilities are not sufficient for performing an action. * * @since 3.6.4 * * @return array */ private function get_permission_error_response() { return [ 'status' => 'error', 'payload' => [ 'error_message' => __( 'you are not allowed to perform this action', 'elementor' ), ], ]; } /** * Set Opt In * * This method opts the user into sharing non-sensitive usage data with Elementor. * * @since 3.6.0 * * @return array */ private function set_usage_data_opt_in() { Tracker::set_opt_in( true ); return [ 'status' => 'success', 'payload' => [ 'usageDataShared' => true, ], ]; } /** * Maybe Update Site Logo * * If a new name is provided, it will be updated as the Site Name. * * @since 3.6.0 * * @return array */ private function maybe_update_site_name() { $problem_error = [ 'status' => 'error', 'payload' => [ 'error_message' => 'There was a problem setting your site name', ], ]; // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( empty( $_POST['data'] ) ) { return $problem_error; } // phpcs:ignore WordPress.Security.NonceVerification.Missing $data = json_decode( stripslashes( $_POST['data'] ), true ); if ( ! isset( $data['siteName'] ) ) { return $problem_error; } /** * Onboarding Site Name * * Filters the new site name passed by the user to update in Elementor's onboarding process. * Elementor runs `esc_html()` on the Site Name passed by the user for security reasons. If a user wants to * include special characters in their site name, they can use this filter to override it. * * @since 3.6.0 * * @param string Escaped new site name */ $new_site_name = apply_filters( 'elementor/onboarding/site-name', $data['siteName'] ); // The site name is sanitized in `update_options()` update_option( 'blogname', $new_site_name ); return [ 'status' => 'success', 'payload' => [ 'siteNameUpdated' => true, ], ]; } /** * Maybe Update Site Logo * * If an image attachment ID is provided, it will be updated as the Site Logo Theme Mod. * * @since 3.6.0 * * @return array */ private function maybe_update_site_logo() { if ( ! current_user_can( 'edit_theme_options' ) ) { return $this->get_permission_error_response(); } $data_error = [ 'status' => 'error', 'payload' => [ 'error_message' => esc_html__( 'There was a problem setting your site logo', 'elementor' ), ], ]; // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( empty( $_POST['data'] ) ) { return $data_error; } // phpcs:ignore WordPress.Security.NonceVerification.Missing $data = json_decode( stripslashes( $_POST['data'] ), true ); // If there is no attachment ID passed or it is not a valid ID, exit here. if ( empty( $data['attachmentId'] ) ) { return $data_error; } $absint_attachment_id = absint( $data['attachmentId'] ); if ( 0 === $absint_attachment_id ) { return $data_error; } $attachment_url = wp_get_attachment_url( $data['attachmentId'] ); // Check if the attachment exists. If it does not, exit here. if ( ! $attachment_url ) { return $data_error; } set_theme_mod( 'custom_logo', $absint_attachment_id ); return [ 'status' => 'success', 'payload' => [ 'siteLogoUpdated' => true, ], ]; } /** * Maybe Upload Logo Image * * If an image file upload is provided, and it passes validation, it will be uploaded to the site's Media Library. * * @since 3.6.0 * * @return array */ private function maybe_upload_logo_image() { $error_message = __( 'There was a problem uploading your file', 'elementor' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( empty( $_FILES['fileToUpload'] ) || ! is_array( $_FILES['fileToUpload'] ) ) { return [ 'status' => 'error', 'payload' => [ 'error_message' => $error_message, ], ]; } // If the user has allowed it, set the Request's state as an "Elementor Upload" request, in order to add // support for non-standard file uploads. if ( 'image/svg+xml' === $_FILES['fileToUpload']['type'] ) { if ( Uploads_Manager::are_unfiltered_uploads_enabled() ) { Plugin::$instance->uploads_manager->set_elementor_upload_state( true ); } else { wp_send_json_error( 'To upload SVG files, you must allow uploading unfiltered files.' ); } } // If the image is an SVG file, sanitation is performed during the import (upload) process. $image_attachment = Plugin::$instance->templates_manager->get_import_images_instance()->import( $_FILES['fileToUpload'] ); if ( 'image/svg+xml' === $_FILES['fileToUpload']['type'] && Uploads_Manager::are_unfiltered_uploads_enabled() ) { // Reset Upload state. Plugin::$instance->uploads_manager->set_elementor_upload_state( false ); } if ( $image_attachment && ! is_wp_error( $image_attachment ) ) { $result = [ 'status' => 'success', 'payload' => [ 'imageAttachment' => $image_attachment, ], ]; } else { $result = [ 'status' => 'error', 'payload' => [ 'error_message' => $error_message, ], ]; } return $result; } /** * Activate Hello Theme * * @since 3.6.0 * * @return array */ private function maybe_activate_hello_theme() { if ( ! current_user_can( 'switch_themes' ) ) { return $this->get_permission_error_response(); } switch_theme( 'hello-elementor' ); return [ 'status' => 'success', 'payload' => [ 'helloThemeActivated' => true, ], ]; } /** * Upload and Install Elementor Pro * * @since 3.6.0 * * @return array */ private function upload_and_install_pro() { if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) { return $this->get_permission_error_response(); } $error_message = __( 'There was a problem uploading your file', 'elementor' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( empty( $_FILES['fileToUpload'] ) || ! is_array( $_FILES['fileToUpload'] ) ) { return [ 'status' => 'error', 'payload' => [ 'error_message' => $error_message, ], ]; } $result = []; if ( ! class_exists( 'Automatic_Upgrader_Skin' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } $skin = new Automatic_Upgrader_Skin(); $upgrader = new Plugin_Upgrader( $skin ); $upload_result = $upgrader->install( $_FILES['fileToUpload']['tmp_name'], [ 'overwrite_package' => false ] ); if ( ! $upload_result || is_wp_error( $upload_result ) ) { $result = [ 'status' => 'error', 'payload' => [ 'error_message' => $error_message, ], ]; } else { $activated = activate_plugin( WP_PLUGIN_DIR . '/elementor-pro/elementor-pro.php', false, false, true ); if ( ! is_wp_error( $activated ) ) { $result = [ 'status' => 'success', 'payload' => [ 'elementorProInstalled' => true, ], ]; } else { $result = [ 'status' => 'error', 'payload' => [ 'error_message' => $error_message, 'elementorProInstalled' => false, ], ]; } } return $result; } private function maybe_update_onboarding_db_option() { $db_option = get_option( self::ONBOARDING_OPTION ); if ( ! $db_option ) { update_option( self::ONBOARDING_OPTION, true ); } return [ 'status' => 'success', 'payload' => 'onboarding DB', ]; } /** * Maybe Handle Ajax * * This method checks if there are any AJAX actions being * @since 3.6.0 * * @return array|null */ private function maybe_handle_ajax() { $result = []; // phpcs:ignore WordPress.Security.NonceVerification.Missing switch ( $_POST['action'] ) { case 'elementor_update_site_name': // If no value is passed for any reason, no need ot update the site name. $result = $this->maybe_update_site_name(); break; case 'elementor_update_site_logo': $result = $this->maybe_update_site_logo(); break; case 'elementor_upload_site_logo': $result = $this->maybe_upload_logo_image(); break; case 'elementor_update_data_sharing': $result = $this->set_usage_data_opt_in(); break; case 'elementor_activate_hello_theme': $result = $this->maybe_activate_hello_theme(); break; case 'elementor_upload_and_install_pro': $result = $this->upload_and_install_pro(); break; case 'elementor_update_onboarding_option': $result = $this->maybe_update_onboarding_db_option(); } if ( ! empty( $result ) ) { if ( 'success' === $result['status'] ) { wp_send_json_success( $result['payload'] ); } else { wp_send_json_error( $result['payload'] ); } } } public function __construct() { add_action( 'elementor/init', function() { // Only load when viewing the onboarding app. if ( Plugin::$instance->app->is_current() ) { $this->set_onboarding_settings(); // Needed for installing the Hello Elementor theme. wp_enqueue_script( 'updates' ); // Needed for uploading Logo from WP Media Library. wp_enqueue_media(); Plugin::$instance->app->set_settings( 'disable_dark_theme', true ); } }, 12 ); // Needed for uploading Logo from WP Media Library. The 'admin_menu' hook is used because it runs before // 'admin_init', and the App triggers printing footer scripts on 'admin_init' at priority 0. add_action( 'admin_menu', function() { add_action( 'wp_print_footer_scripts', 'wp_print_media_templates' ); } ); add_action( 'admin_init', function() { if ( wp_doing_ajax() && isset( $_POST['action'] ) && isset( $_POST['_nonce'] ) && wp_verify_nonce( $_POST['_nonce'], Ajax::NONCE_KEY ) && current_user_can( 'manage_options' ) ) { $this->maybe_handle_ajax(); } } ); } } app/modules/import-export/import.php 0000644 00000003231 15230642240 0013645 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport; use Elementor\Core\App\Modules\ImportExport\Compatibility\Base_Adapter; use Elementor\Core\App\Modules\ImportExport\Compatibility\Envato; use Elementor\Core\App\Modules\ImportExport\Compatibility\Kit_Library; use Elementor\Core\App\Modules\ImportExport\Directories\Root; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Import extends Iterator { /** * @var Base_Adapter[] */ private $adapters = []; final public function run() { $this->temp_dir = $this->get_settings( 'session' ); $manifest_data = $this->read_json_file( 'manifest' ); $manifest_data = $this->adapt_manifest_structure( $manifest_data ); $root_directory = new Root( $this ); return $root_directory->run_import( $manifest_data ); } final public function read_json_file( $name ) { $name = $this->get_archive_file_full_path( $name . '.json' ); return json_decode( file_get_contents( $name, true ), true ); } final public function get_adapters() { return $this->adapters; } final public function adapt_manifest_structure( array $manifest_data ) { $this->init_adapters( $manifest_data ); foreach ( $this->adapters as $adapter ) { $manifest_data = $adapter->get_manifest_data( $manifest_data ); } return $manifest_data; } private function init_adapters( array $manifest_data ) { /** @var Base_Adapter[] $adapter_types */ $adapter_types = [ Envato::class, Kit_Library::class ]; foreach ( $adapter_types as $adapter_type ) { if ( $adapter_type::is_compatibility_needed( $manifest_data, $this->get_settings() ) ) { $this->adapters[] = new $adapter_type( $this ); } } } } app/modules/import-export/module.php 0000644 00000023501 15230642240 0013622 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport; use Elementor\Core\Base\Document; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Files\Uploads_Manager; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Tools; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Import Export Module * * Responsible for initializing Elementor App functionality */ class Module extends BaseModule { const FORMAT_VERSION = '1.0'; const EXPORT_TRIGGER_KEY = 'elementor_export_kit'; const IMPORT_TRIGGER_KEY = 'elementor_import_kit'; const MANIFEST_ERROR_KEY = 'manifest-error'; /** * @var Export */ public $export; /** * @var Import */ public $import; /** * Get name. * * @access public * * @return string */ public function get_name() { return 'import-export'; } public function get_init_settings() { if ( ! Plugin::$instance->app->is_current() ) { return []; } $export_nonce = wp_create_nonce( 'elementor_export' ); $export_url = add_query_arg( [ '_nonce' => $export_nonce ], Plugin::$instance->app->get_base_url() ); return [ 'exportURL' => $export_url, 'summaryTitles' => $this->get_summary_titles(), 'isUnfilteredFilesEnabled' => Uploads_Manager::are_unfiltered_uploads_enabled(), ]; } public function get_summary_titles() { $summary_titles = []; $document_types = Plugin::$instance->documents->get_document_types(); foreach ( $document_types as $name => $document_type ) { $summary_titles['templates'][ $name ] = [ 'single' => $document_type::get_title(), 'plural' => $document_type::get_plural_title(), ]; } $post_types = get_post_types_by_support( 'elementor' ); foreach ( $post_types as $post_type ) { if ( Source_Local::CPT === $post_type ) { continue; } $post_type_object = get_post_type_object( $post_type ); $summary_titles['content'][ $post_type ] = [ 'single' => $post_type_object->labels->singular_name, 'plural' => $post_type_object->label, ]; } $active_kit = Plugin::$instance->kits_manager->get_active_kit(); foreach ( $active_kit->get_tabs() as $key => $tab ) { $summary_titles['site-settings'][ $key ] = $tab->get_title(); } return $summary_titles; } private function import_stage_1() { // PHPCS - Already validated in caller function. if ( ! empty( $_POST['e_import_file'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $file_url = $_POST['e_import_file']; // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! filter_var( $file_url, FILTER_VALIDATE_URL ) || 0 !== strpos( $file_url, 'http' ) ) { throw new \Error( __( 'Invalid URL', 'elementor' ) ); } $remote_zip_request = wp_remote_get( $file_url ); if ( is_wp_error( $remote_zip_request ) ) { throw new \Error( $remote_zip_request->get_error_message() ); } if ( 200 !== $remote_zip_request['response']['code'] ) { throw new \Error( $remote_zip_request['response']['message'] ); } $file_name = Plugin::$instance->uploads_manager->create_temp_file( $remote_zip_request['body'], 'kit.zip' ); } else { // PHPCS - Already validated in caller function. $file_name = $_FILES['e_import_file']['tmp_name']; // phpcs:ignore WordPress.Security.NonceVerification.Missing } $extraction_result = Plugin::$instance->uploads_manager->extract_and_validate_zip( $file_name, [ 'json', 'xml' ] ); if ( ! empty( $file_url ) ) { Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $file_name ) ); } $session_dir = $extraction_result['extraction_directory']; $manifest_file_content = file_get_contents( $session_dir . 'manifest.json', true ); if ( ! $manifest_file_content ) { throw new \Error( self::MANIFEST_ERROR_KEY ); } $manifest_data = json_decode( $manifest_file_content, true ); // In case that the manifest content is not a valid JSON or empty. if ( ! $manifest_data ) { throw new \Error( self::MANIFEST_ERROR_KEY ); } $manifest_data = $this->import->adapt_manifest_structure( $manifest_data ); $result = [ 'session' => $session_dir, 'manifest' => $manifest_data, ]; $result = apply_filters( 'elementor/import/stage_1/result', $result ); return $result; } private function import_stage_2( $settings_directory ) { set_time_limit( 0 ); $result = $this->import->run(); Plugin::$instance->uploads_manager->remove_file_or_dir( $settings_directory ); return $result; } private function on_admin_init() { if ( ! isset( $_POST['action'] ) || self::IMPORT_TRIGGER_KEY !== $_POST['action'] || ! wp_verify_nonce( $_POST['_nonce'], Ajax::NONCE_KEY ) ) { return; } $import_settings = json_decode( stripslashes( $_POST['data'] ), true ); // Set the Request's state as an Elementor upload request, in order to support unfiltered file uploads. Plugin::$instance->uploads_manager->set_elementor_upload_state( true ); try { $this->import = new Import( $import_settings ); if ( 1 === $import_settings['stage'] ) { $result = $this->import_stage_1(); } elseif ( 2 === $import_settings['stage'] ) { $result = $this->import_stage_2( $import_settings['session'] ); } wp_send_json_success( $result ); } catch ( \Error $error ) { wp_send_json_error( $error->getMessage() ); } } private function on_init() { if ( ! isset( $_GET[ self::EXPORT_TRIGGER_KEY ] ) || ! wp_verify_nonce( $_GET['_nonce'], 'elementor_export' ) ) { return; } $export_settings = $_GET[ self::EXPORT_TRIGGER_KEY ]; try { $this->export = new Export( self::merge_properties( [], $export_settings, [ 'include', 'kitInfo' ] ) ); $export_result = $this->export->run(); $file_name = $export_result['file_name']; $file = file_get_contents( $file_name, true ); Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $file_name ) ); wp_send_json_success( [ 'manifest' => $export_result['manifest'], 'file' => base64_encode( $file ), ] ); } catch ( \Error $error ) { wp_send_json_error( $error->getMessage() ); } } private function render_import_export_tab_content() { $intro_text_link = sprintf( '<a href="https://go.elementor.com/wp-dash-import-export-general" target="_blank">%s</a>', esc_html__( 'Learn more', 'elementor' ) ); $intro_text = sprintf( /* translators: 1: New line break, 2: Learn More link. */ __( 'Design sites faster with a template kit that contains some or all components of a complete site, like templates, content & site settings.%1$sYou can import a kit and apply it to your site, or export the elements from this site to be used anywhere else. %2$s', 'elementor' ), '<br>', $intro_text_link ); $content_data = [ 'export' => [ 'title' => esc_html__( 'Export a Template Kit', 'elementor' ), 'button' => [ 'url' => Plugin::$instance->app->get_base_url() . '#/export', 'text' => esc_html__( 'Start Export', 'elementor' ), ], 'description' => esc_html__( 'Bundle your whole site - or just some of its elements - to be used for another website.', 'elementor' ), 'link' => [ 'url' => 'https://go.elementor.com/wp-dash-import-export-export-flow', 'text' => esc_html__( 'Learn More', 'elementor' ), ], ], 'import' => [ 'title' => esc_html__( 'Import a Template Kit', 'elementor' ), 'button' => [ 'url' => Plugin::$instance->app->get_base_url() . '#/import', 'text' => esc_html__( 'Start Import', 'elementor' ), ], 'description' => esc_html__( 'Apply the design and settings of another site to this one.', 'elementor' ), 'link' => [ 'url' => 'https://go.elementor.com/wp-dash-import-export-import-flow', 'text' => esc_html__( 'Learn More', 'elementor' ), ], ], ]; $info_text = esc_html__( 'Even after you import and apply a Template Kit, you can undo it by restoring a previous version of your site.', 'elementor' ) . '<br>' . esc_html__( 'Open Site Settings > History > Revisions.', 'elementor' ); ?> <div class="tab-import-export-kit__content"> <p class="tab-import-export-kit__info"><?php Utils::print_unescaped_internal_string( $intro_text ); ?></p> <div class="tab-import-export-kit__wrapper"> <?php foreach ( $content_data as $data ) { ?> <div class="tab-import-export-kit__container"> <div class="tab-import-export-kit__box"> <h2><?php Utils::print_unescaped_internal_string( $data['title'] ); ?></h2> <a href="<?php Utils::print_unescaped_internal_string( $data['button']['url'] ); ?>" class="elementor-button elementor-button-success"> <?php Utils::print_unescaped_internal_string( $data['button']['text'] ); ?> </a> </div> <p><?php Utils::print_unescaped_internal_string( $data['description'] ); ?></p> <a href="<?php Utils::print_unescaped_internal_string( $data['link']['url'] ); ?>" target="_blank"><?php Utils::print_unescaped_internal_string( $data['link']['text'] ); ?></a> </div> <?php } ?> </div> <p class="tab-import-export-kit__info"><?php Utils::print_unescaped_internal_string( $info_text ); ?></p> </div> <?php } public function register_settings_tab( Tools $tools ) { $tools->add_tab( 'import-export-kit', [ 'label' => esc_html__( 'Import / Export Kit', 'elementor' ), 'sections' => [ 'intro' => [ 'label' => esc_html__( 'Template Kits', 'elementor' ), 'callback' => function() { $this->render_import_export_tab_content(); }, 'fields' => [], ], ], ] ); } public function __construct() { add_action( 'init', function() { $this->on_init(); } ); add_action( 'admin_init', function() { $this->on_admin_init(); } ); $page_id = Tools::PAGE_ID; add_action( "elementor/admin/after_create_settings/{$page_id}", [ $this, 'register_settings_tab' ] ); if ( Utils::is_wp_cli() ) { \WP_CLI::add_command( 'elementor kit', WP_CLI::class ); } } } app/modules/import-export/directories/base.php 0000644 00000005513 15230642240 0015566 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Directories; use Elementor\Core\App\Modules\ImportExport\Export; use Elementor\Core\App\Modules\ImportExport\Import; use Elementor\Core\App\Modules\ImportExport\Iterator; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Base { /** * @var Base[] */ private $sub_directories = []; /** * @var Base */ private $parent; /** * @var Iterator */ protected $iterator; /** * @var Export */ protected $exporter; /** * @var Import */ protected $importer; abstract protected function get_name(); public function __construct( Iterator $iterator, Base $parent = null ) { $this->iterator = $iterator; if ( $iterator instanceof Export ) { $this->exporter = $iterator; } else { $this->importer = $iterator; } $this->parent = $parent; $this->register_directories(); } final public function get_path() { $path = $this->get_name(); if ( $this->parent ) { $parent_name = $this->parent->get_name(); if ( $parent_name ) { $parent_name .= '/'; } $path = $parent_name . $path; } return $path; } final public function run_export() { $this->exporter->set_current_archive_path( $this->get_path() ); $manifest_data = $this->export(); foreach ( $this->sub_directories as $sub_directory ) { $manifest_data[ $sub_directory->get_name() ] = $sub_directory->run_export(); } return $manifest_data; } final public function run_import( array $settings ) { $this->importer->set_current_archive_path( $this->get_path() ); $meta_data = $this->import( $settings ); foreach ( $this->sub_directories as $sub_directory ) { $sub_directory_name = $sub_directory->get_name(); if ( ! isset( $settings[ $sub_directory_name ] ) ) { continue; } $meta_data[ $sub_directory_name ] = $sub_directory->run_import( $settings[ $sub_directory_name ] ); } return $meta_data; } /** * @return array */ protected function export() { return []; } /** * @param array $import_settings * * @return array */ protected function import( array $import_settings ) { return []; } protected function get_default_sub_directories() { return []; } private function register_directories() { $sub_directories = $this->get_default_sub_directories(); $path = $this->get_path(); /** * Kit sub directories. * * Filters sub directories when importing/exporting kits. * * The dynamic portion of the hook name, `$path`, refers to the directory path. * * @param array $sub_directories A list of sub directories. * @param Elementor\Core\App\Modules\ImportExport\Directories\Base $this The base class instance. */ $sub_directories = apply_filters( "elementor/kit/import-export/directory/{$path}", $sub_directories, $this ); $this->sub_directories = $sub_directories; } } app/modules/import-export/directories/content.php 0000644 00000001170 15230642240 0016321 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Directories; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Content extends Base { protected function get_name() { return 'content'; } protected function get_default_sub_directories() { $post_types = get_post_types_by_support( 'elementor' ); $sub_directories = []; foreach ( $post_types as $post_type ) { if ( Source_Local::CPT === $post_type ) { continue; } $sub_directories[] = new Post_Type( $this->iterator, $this, $post_type ); } return $sub_directories; } } app/modules/import-export/directories/wp-custom-post-type-title.php 0000644 00000001303 15230642240 0021664 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Directories; use Elementor\Core\App\Modules\ImportExport\Iterator; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class WP_Custom_Post_Type_Title extends Base { private $post_type; public function __construct( Iterator $iterator, Base $parent, $post_type ) { parent::__construct( $iterator, $parent ); $this->post_type = $post_type; } public function export() { $post_type_object = get_post_type_object( $this->post_type ); return [ 'name' => $post_type_object->name, 'label' => $post_type_object->label, ]; } protected function get_name() { return $this->post_type; } } app/modules/import-export/directories/plugins.php 0000644 00000001032 15230642240 0016325 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Directories; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } class Plugins extends Base { protected function get_name() { return 'plugins'; } protected function export() { $included_plugins = $this->iterator->get_settings( 'plugins' ); return $included_plugins; } protected function import( array $import_settings ) { return null; } } app/modules/import-export/directories/templates.php 0000644 00000004661 15230642240 0016655 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Directories; use Elementor\Core\Base\Document; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Templates extends Base { protected function get_name() { return 'templates'; } protected function export() { $template_types = array_values( Source_Local::get_template_types() ); $query_args = [ 'post_type' => Source_Local::CPT, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => Document::TYPE_META_KEY, 'value' => $template_types, ], ], ]; $templates_query = new \WP_Query( $query_args ); $manifest_data = []; foreach ( $templates_query->posts as $template_post ) { $template_id = $template_post->ID; $template_document = Plugin::$instance->documents->get( $template_id ); $template_export_data = $template_document->get_export_data(); $this->exporter->add_json_file( $template_id, $template_export_data ); $manifest_data[ $template_id ] = $template_document->get_export_summary(); } return $manifest_data; } protected function import( array $import_settings ) { $result = [ 'succeed' => [], 'failed' => [], ]; foreach ( $import_settings as $id => $template_settings ) { try { $import = $this->import_template( $id, $template_settings ); if ( is_wp_error( $import ) ) { $result['failed'][ $id ] = $import->get_error_message(); continue; } $result['succeed'][ $id ] = $import; } catch ( \Error $error ) { $result['failed'][ $id ] = $error->getMessage(); } } return $result; } private function import_template( $id, array $template_settings ) { $template_data = $this->importer->read_json_file( $id ); $doc_type = $template_settings['doc_type']; $new_document = Plugin::$instance->documents->create( $doc_type, [ 'post_title' => $template_settings['title'], 'post_type' => Source_Local::CPT, 'post_status' => 'publish', ] ); if ( is_wp_error( $new_document ) ) { return $new_document; } $template_data['import_settings'] = $template_settings; $template_data['id'] = $id; foreach ( $this->importer->get_adapters() as $adapter ) { $template_data = $adapter->get_template_data( $template_data, $template_settings ); } $new_document->import( $template_data ); return $new_document->get_main_id(); } } app/modules/import-export/directories/wp-content.php 0000644 00000001436 15230642240 0016752 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Directories; use Elementor\Modules\LandingPages\Module as Landing_Pages_Module; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class WP_Content extends Base { protected function get_name() { return 'wp-content'; } protected function get_default_sub_directories() { $post_types = get_post_types( [ 'public' => true, 'can_export' => true, ] ); unset( $post_types['attachment'], $post_types[ Landing_Pages_Module::CPT ], $post_types[ Source_Local::CPT ] ); $sub_directories = []; foreach ( $post_types as $post_type ) { $sub_directories[] = new WP_Post_Type( $this->iterator, $this, $post_type ); } return $sub_directories; } } app/modules/import-export/directories/wp-post-type.php 0000644 00000002545 15230642240 0017246 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Directories; use Elementor\Core\App\Modules\ImportExport\Iterator; use Elementor\Core\Utils\ImportExport\WP_Exporter; use Elementor\Core\Utils\ImportExport\WP_Import; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class WP_Post_Type extends Base { private $post_type; protected function get_name() { return $this->post_type; } public function __construct( Iterator $iterator, Base $parent, $post_type ) { parent::__construct( $iterator, $parent ); $this->post_type = $post_type; } public function export() { $wp_exporter = new WP_Exporter( [ 'content' => $this->post_type, 'status' => 'publish', 'limit' => 20, 'meta_query' => [ [ 'key' => '_elementor_edit_mode', 'compare' => 'NOT EXISTS', ], ], 'include_post_featured_image_as_attachment' => true, // Will export 'featured_image' as attachment. ] ); $export_result = $wp_exporter->run(); $this->exporter->add_file( $this->post_type . '.xml', $export_result['xml'] ); return $export_result['ids']; } protected function import( array $import_settings ) { $wp_importer = new WP_Import( $this->importer->get_archive_file_full_path( $this->post_type . '.xml' ), [ 'fetch_attachments' => true, ] ); $result = $wp_importer->run(); return $result['summary']['posts']; } } app/modules/import-export/directories/post-type.php 0000644 00000006402 15230642240 0016616 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Directories; use Elementor\Core\App\Modules\ImportExport\Iterator; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Post_Type extends Base { private $post_type; private $show_page_on_front; private $page_on_front_id; public function __construct( Iterator $iterator, Base $parent, $post_type ) { parent::__construct( $iterator, $parent ); $this->post_type = $post_type; if ( 'page' === $post_type ) { $this->init_page_on_front_data(); } } public function export() { $query_args = [ 'post_type' => $this->post_type, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => '_elementor_data', 'compare' => 'EXISTS', ], [ 'key' => '_elementor_data', 'compare' => '!=', 'value' => '[]', ], ], ]; $query = new \WP_Query( $query_args ); $manifest_data = []; foreach ( $query->posts as $post ) { $document = Plugin::$instance->documents->get( $post->ID ); $post_manifest_data = [ 'title' => $post->post_title, 'excerpt' => $post->post_excerpt, 'doc_type' => $document->get_name(), 'thumbnail' => get_the_post_thumbnail_url( $post ), 'url' => get_permalink( $post ), ]; if ( $post->ID === $this->page_on_front_id ) { $post_manifest_data['show_on_front'] = true; } $manifest_data[ $post->ID ] = $post_manifest_data; $this->exporter->add_json_file( $post->ID, $document->get_export_data() ); } return $manifest_data; } public function import_post( $id, array $post_settings ) { $post_data = $this->importer->read_json_file( $id ); $post_attributes = [ 'post_title' => $post_settings['title'], 'post_type' => $this->post_type, 'post_status' => 'publish', ]; if ( ! empty( $post_settings['excerpt'] ) ) { $post_attributes['post_excerpt'] = $post_settings['excerpt']; } $new_document = Plugin::$instance->documents->create( $post_settings['doc_type'], $post_attributes ); if ( is_wp_error( $new_document ) ) { return $new_document; } $post_data['import_settings'] = $post_settings; $new_document->import( $post_data ); $new_id = $new_document->get_main_id(); if ( ! empty( $post_settings['show_on_front'] ) ) { update_option( 'page_on_front', $new_id ); if ( ! $this->show_page_on_front ) { update_option( 'show_on_front', 'page' ); } } return $new_id; } protected function get_name() { return $this->post_type; } protected function import( array $import_settings ) { $result = [ 'succeed' => [], 'failed' => [], ]; foreach ( $import_settings as $id => $post_settings ) { try { $import = $this->import_post( $id, $post_settings ); if ( is_wp_error( $import ) ) { $result['failed'][ $id ] = $import->get_error_message(); continue; } $result['succeed'][ $id ] = $import; } catch ( \Error $error ) { $result['failed'][ $id ] = $error->getMessage(); } } return $result; } private function init_page_on_front_data() { $this->show_page_on_front = 'page' === get_option( 'show_on_front' ); if ( $this->show_page_on_front && $this->exporter ) { $this->page_on_front_id = (int) get_option( 'page_on_front' ); } } } app/modules/import-export/directories/custom-post-type-title.php 0000644 00000002145 15230642240 0021245 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Directories; use Elementor\Core\App\Modules\ImportExport\Iterator; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Adds CPT name and label values to manifest.json. * * @since 3.6.0 */ class Custom_Post_Type_Title extends Base { public $custom_post_types; public function __construct( Iterator $iterator, Base $parent, $custom_post_types ) { $this->custom_post_types = $custom_post_types; parent::__construct( $iterator, $parent ); } protected function get_name() { return 'custom-post-type-title'; } protected function get_default_sub_directories() { $post_types = get_post_types( [ 'public' => true, 'can_export' => true, ] ); foreach ( $post_types as $post_type ) { if ( ! in_array( $post_type, $this->custom_post_types ) ) { unset( $post_types[ $post_type ] ); } } $sub_directories = []; foreach ( $post_types as $post_type ) { $sub_directories[] = new WP_Custom_Post_Type_Title( $this->iterator, $this, $post_type ); } return $sub_directories; } } app/modules/import-export/directories/root.php 0000644 00000006170 15230642240 0015637 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Directories; use Elementor\Core\App\Modules\ImportExport\Module; use Elementor\Core\Settings\Page\Manager as PageManager; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Root extends Base { protected function get_name() { return ''; } protected function export() { $kit = Plugin::$instance->kits_manager->get_active_kit(); $exporter_settings = $this->exporter->get_settings(); $include = $exporter_settings['include']; $include_site_settings = in_array( 'settings', $include, true ); if ( $include_site_settings ) { $kit_data = $kit->get_export_data(); $excluded_kit_settings_keys = [ 'site_name', 'site_description', 'site_logo', 'site_favicon', ]; foreach ( $excluded_kit_settings_keys as $setting_key ) { unset( $kit_data['settings'][ $setting_key ] ); } $this->exporter->add_json_file( 'site-settings', $kit_data ); } $kit_post = $kit->get_post(); $manifest_data = [ 'name' => sanitize_title( $exporter_settings['kitInfo']['title'] ), 'title' => $exporter_settings['kitInfo']['title'], 'description' => $exporter_settings['kitInfo']['description'], 'author' => get_the_author_meta( 'display_name', $kit_post->post_author ), 'version' => Module::FORMAT_VERSION, 'elementor_version' => ELEMENTOR_VERSION, 'created' => gmdate( 'Y-m-d H:i:s' ), 'thumbnail' => get_the_post_thumbnail_url( $kit_post ), 'site' => get_site_url(), ]; if ( $include_site_settings ) { $kit_tabs = $kit->get_tabs(); unset( $kit_tabs['settings-site-identity'] ); $manifest_data['site-settings'] = array_keys( $kit_tabs ); } return $manifest_data; } protected function import( array $import_settings ) { $include = $this->importer->get_settings( 'include' ); if ( ! in_array( 'settings', $include, true ) ) { return; } $kit = Plugin::$instance->kits_manager->get_active_kit(); $old_settings = $kit->get_meta( PageManager::META_KEY ); if ( ! $old_settings ) { $old_settings = []; } $new_settings = $this->importer->read_json_file( 'site-settings' ); $new_settings = $new_settings['settings']; if ( ! empty( $old_settings['custom_colors'] ) ) { $new_settings['custom_colors'] = array_merge( $old_settings['custom_colors'], $new_settings['custom_colors'] ); } if ( ! empty( $old_settings['custom_typography'] ) ) { $new_settings['custom_typography'] = array_merge( $old_settings['custom_typography'], $new_settings['custom_typography'] ); } $new_settings = array_replace_recursive( $old_settings, $new_settings ); $kit->save( [ 'settings' => $new_settings ] ); } protected function get_default_sub_directories() { $sub_directories = []; $include = $this->iterator->get_settings( 'include' ); if ( in_array( 'templates', $include, true ) ) { $sub_directories[] = new Templates( $this->iterator, $this ); } if ( in_array( 'content', $include, true ) ) { $sub_directories[] = new Content( $this->iterator, $this ); $sub_directories[] = new WP_Content( $this->iterator, $this ); } return $sub_directories; } } app/modules/import-export/iterator.php 0000644 00000002551 15230642240 0014170 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport; use Elementor\Core\Base\Base_Object; use Elementor\Modules\System_Info\Reporters\Server; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Iterator extends Base_Object { const ZIP_ARCHIVE_MODULE_NOT_INSTALLED_KEY = 'zip-archive-module-not-installed'; const NO_WRITE_PERMISSIONS_KEY = 'no-write-permissions'; protected $temp_dir; private $current_archive_path = ''; abstract public function run(); protected function get_archive_file_path( $file_name ) { return $this->get_current_archive_path() . $file_name; } public function get_archive_file_full_path( $file_name ) { return $this->temp_dir . $this->get_archive_file_path( $file_name ); } public function get_current_archive_path() { return $this->current_archive_path; } public function set_current_archive_path( $path ) { if ( $path ) { $path .= '/'; } $this->current_archive_path = $path; } public function __construct( array $settings ) { if ( ! class_exists( '\ZipArchive' ) ) { throw new \Error( self::ZIP_ARCHIVE_MODULE_NOT_INSTALLED_KEY ); } $server = new Server(); $server_write_permissions = $server->get_write_permissions(); if ( $server_write_permissions['warning'] ) { throw new \Error( self::NO_WRITE_PERMISSIONS_KEY ); } $this->set_settings( $settings ); } } app/modules/import-export/export.php 0000644 00000003237 15230642240 0013662 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport; use Elementor\Core\App\Modules\ImportExport\Directories\Root; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Export extends Iterator { /** * @var \ZipArchive */ private $zip_archive; private $archive_file_name; final public function run() { ob_start(); $this->init_zip_archive(); $root_directory = new Root( $this ); $manifest_data = $root_directory->run_export(); /** * Manifest data from exported kit. * * Filters the manifest data of any exported kit. * * @param array $manifest_data Manifest data. * @param Export $this The export instance. */ $manifest_data = apply_filters( 'elementor/kit/export/manifest-data', $manifest_data, $this ); $this->set_current_archive_path( '' ); $this->add_json_file( 'manifest', $manifest_data ); $this->zip_archive->close(); return [ 'manifest' => $manifest_data, 'file_name' => $this->archive_file_name, ]; } public function add_json_file( $name, $content, $json_flags = null ) { $this->add_file( $name . '.json', wp_json_encode( $content, $json_flags ) ); } public function add_file( $file_name, $content ) { $this->zip_archive->addFromString( $this->get_archive_file_path( $file_name ), $content ); } private function init_zip_archive() { $zip_archive = new \ZipArchive(); $this->temp_dir = Plugin::$instance->uploads_manager->create_unique_dir(); $this->archive_file_name = $this->temp_dir . 'kit.zip'; $zip_archive->open( $this->archive_file_name, \ZipArchive::CREATE | \ZipArchive::OVERWRITE ); $this->zip_archive = $zip_archive; } } app/modules/import-export/wp-cli.php 0000644 00000013113 15230642240 0013526 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport; use Elementor\Plugin; use Elementor\Core\App\Modules\KitLibrary\Connect\Kit_Library; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Wp_Cli extends \WP_CLI_Command { /** * Export a Kit * * [--include] * Which type of content to include. Possible values are 'content', 'templates', 'site-settings'. * if this parameter won't be specified, All data types will be included. * * ## EXAMPLES * * 1. wp elementor kit export path/to/export-file-name.zip * - This will export all site data to the specified file name. * * 2. wp elementor kit export path/to/export-file-name.zip --include=kit-settings,content * - This will export only site settings and content. * * @param array $args * @param array $assoc_args */ public function export( $args, $assoc_args ) { if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please specify a file name' ); } \WP_CLI::line( 'Kit export started.' ); $export_settings = [ 'include' => [ 'content', 'templates', 'settings' ], ]; foreach ( $assoc_args as $key => $value ) { $import_settings[ $key ] = explode( ',', $value ); } $export_settings = array_merge( $export_settings, $assoc_args ); try { $exporter = new Export( $export_settings ); $result = $exporter->run(); rename( $result['file_name'], $args[0] ); } catch ( \Error $error ) { \WP_CLI::error( $error->getMessage() ); } \WP_CLI::success( 'Kit exported successfully.' ); } /** * Import a Kit * * [--include] * Which type of content to include. Possible values are 'content', 'templates', 'site-settings'. * if this parameter won't be specified, All data types will be included. * * [--overrideConditions] * Templates ids to override conditions for. * * [--sourceType] * Which source type is used in the current session. Available values are 'local', 'remote', 'library'. * The default value is 'local' * * ## EXAMPLES * * 1. wp elementor kit import path/to/elementor-kit.zip * - This will import the whole kit file content. * * 2. wp elementor kit import path/to/elementor-kit.zip --include=site-settings,content * - This will import only site settings and content. * * 3. wp elementor kit import path/to/elementor-kit.zip --overrideConditions=3478,4520 * - This will import all content and will override conditions for the given template ids. * * @param array $args * @param array $assoc_args */ public function import( array $args, array $assoc_args ) { if ( ! current_user_can( 'administrator' ) ) { \WP_CLI::error( 'You must run this command as an admin user' ); } if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please specify a file to import' ); } \WP_CLI::line( 'Kit export started' ); \WP_CLI::line( 'Extracting zip archive...' ); $assoc_args = wp_parse_args( $assoc_args, [ 'sourceType' => 'local', ] ); $url = null; $file_path = $args[0]; if ( 'library' === $assoc_args['sourceType'] ) { $url = $this->get_url_from_library( $args[0] ); } elseif ( 'remote' === $assoc_args['sourceType'] ) { $url = $args[0]; } if ( $url ) { $file_path = $this->create_temp_file_from_url( $url ); } $extraction_result = Plugin::$instance->uploads_manager->extract_and_validate_zip( $file_path, [ 'json', 'xml' ] ); if ( is_wp_error( $extraction_result ) ) { \WP_CLI::error( $extraction_result->get_error_message() ); } $import_settings = [ 'include' => [ 'templates', 'content', 'site-settings' ], 'directory' => $extraction_result['extraction_directory'], ]; foreach ( $assoc_args as $key => $value ) { $import_settings[ $key ] = explode( ',', $value ); } try { \WP_CLI::line( 'Importing data...' ); $import = new Import( $import_settings ); Plugin::$instance->app->get_component( 'import-export' )->import = $import; $import->run(); \WP_CLI::line( 'Removing temp files...' ); Plugin::$instance->uploads_manager->remove_file_or_dir( $import_settings['directory'] ); // The file was created from remote or library request and it should be removed. if ( $url ) { Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $file_path ) ); } \WP_CLI::success( 'Kit imported successfully' ); } catch ( \Error $error ) { Plugin::$instance->uploads_manager->remove_file_or_dir( $import_settings['directory'] ); \WP_CLI::error( $error->getMessage() ); } } /** * Helper to get kit url by the kit id * TODO: Maybe extract it. * * @param $kit_id * * @return string */ private function get_url_from_library( $kit_id ) { /** @var Kit_Library $app */ $app = Plugin::$instance->common->get_component( 'connect' )->get_app( 'kit-library' ); if ( ! $app ) { \WP_CLI::error( 'Kit library app not found' ); } $response = $app->download_link( $kit_id ); if ( is_wp_error( $response ) ) { \WP_CLI::error( "Library Response: {$response->get_error_message()}" ); } return $response->download_link; } /** * Helper to get kit zip file path by the kit url * TODO: Maybe extract it. * * @param $url * * @return string */ private function create_temp_file_from_url( $url ) { $response = wp_remote_get( $url ); if ( is_wp_error( $response ) ) { \WP_CLI::error( "Download file url: {$response->get_error_message()}" ); } if ( 200 !== $response['response']['code'] ) { \WP_CLI::error( "Download file url: {$response['response']['message']}" ); } return Plugin::$instance->uploads_manager->create_temp_file( $response['body'], 'kit.zip' ); } } app/modules/import-export/compatibility/base-adapter.php 0000644 00000001317 15230642240 0017537 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Compatibility; use Elementor\Core\App\Modules\ImportExport\Import; use Elementor\Core\Base\Base_Object; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Base_Adapter extends Base_Object { protected $importer; public static function is_compatibility_needed( array $manifest_data, array $import_settings ) { return false; } public function get_manifest_data( array $manifest_data ) { return $manifest_data; } public function get_template_data( array $template_data, array $template_settings ) { return $template_data; } public function __construct( Import $importer ) { $this->importer = $importer; } } app/modules/import-export/compatibility/kit-library.php 0000644 00000001432 15230642240 0017436 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Compatibility; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Kit_Library extends Base_Adapter { public static function is_compatibility_needed( array $manifest_data, array $import_settings ) { return ! empty( $import_settings['referrer'] ) && 'kit-library' === $import_settings['referrer']; } public function get_manifest_data( array $manifest_data ) { if ( ! empty( $manifest_data['content']['page'] ) ) { foreach ( $manifest_data['content']['page'] as & $page ) { $page['thumbnail'] = false; } } if ( ! empty( $manifest_data['templates'] ) ) { foreach ( $manifest_data['templates'] as & $template ) { $template['thumbnail'] = false; } } return $manifest_data; } } app/modules/import-export/compatibility/envato.php 0000644 00000005076 15230642241 0016512 0 ustar 00 <?php namespace Elementor\Core\App\Modules\ImportExport\Compatibility; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Envato extends Base_Adapter { public static function is_compatibility_needed( array $manifest_data, array $import_settings ) { return ! empty( $manifest_data['manifest_version'] ); } public function get_manifest_data( array $manifest_data ) { $templates = $manifest_data['templates']; $manifest_data['templates'] = []; foreach ( $templates as $template ) { // Envato store their global kit styles as a 'global.json' template file, this needs to be converted to 'site-settings.json' file if ( ! empty( $template['metadata']['template_type'] ) && 'global-styles' === $template['metadata']['template_type'] ) { $global_file_data = $this->importer->read_json_file( str_replace( '.json', '', $template['source'] ) ); $site_settings = [ 'settings' => $global_file_data['page_settings'] ]; $site_settings_file_destination = $this->importer->get_archive_file_full_path( 'site-settings.json' ); file_put_contents( $site_settings_file_destination, wp_json_encode( $site_settings ) ); // Getting the site-settings because Envato stores them in one of the posts. $kit = Plugin::$instance->kits_manager->get_active_kit(); $kit_tabs = $kit->get_tabs(); unset( $kit_tabs['settings-site-identity'] ); $manifest_data['site-settings'] = array_keys( $kit_tabs ); continue; } // Evanto uses "type" instead of "doc_type" $template['doc_type'] = $template['type']; // Evanto uses for "name" instead of "title" $template['title'] = $template['name']; // Envato specifying an exact path to the template rather than using its "ID" as an index. // This extracts the "file name" part out of our exact source list and we treat that as an ID. $file_name_without_extension = str_replace( '.json', '', basename( $template['source'] ) ); // Append the template to the global list: $manifest_data['templates'][ $file_name_without_extension ] = $template; } return $manifest_data; } public function get_template_data( array $template_data, array $template_settings ) { if ( ! empty( $template_data['metadata']['elementor_pro_conditions'] ) ) { foreach ( $template_data['metadata']['elementor_pro_conditions'] as $condition ) { list ( $type, $name, $sub_name, $sub_id ) = array_pad( explode( '/', $condition ), 4, '' ); $template_data['import_settings']['conditions'][] = compact( 'type', 'name', 'sub_name', 'sub_id' ); } } return $template_data; } } app/modules/kit-library/data/kits/controller.php 0000644 00000002766 15230642241 0016016 0 ustar 00 <?php namespace Elementor\Core\App\Modules\KitLibrary\Data\Kits; use Elementor\Core\App\Modules\KitLibrary\Data\Base_Controller; use Elementor\Data\V2\Base\Exceptions\Error_404; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Base_Controller { public function get_name() { return 'kits'; } public function get_items( $request ) { $data = $this->get_repository()->get_all( $request->get_param( 'force' ) ); return [ 'data' => $data->values(), ]; } public function get_item( $request ) { $data = $this->get_repository()->find( $request->get_param( 'id' ) ); if ( ! $data ) { return new Error_404( __( 'Kit not exists.', 'elementor' ), 'kit_not_exists' ); } return [ 'data' => $data, ]; } public function get_collection_params() { return [ 'force' => [ 'description' => 'Force an API request and skip the cache.', 'required' => false, 'default' => false, 'type' => 'boolean', ], ]; } public function register_endpoints() { $this->index_endpoint->register_item_route( \WP_REST_Server::READABLE, [ 'id' => [ 'description' => 'Unique identifier for the object.', 'type' => 'string', 'required' => true, ], 'id_arg_type_regex' => '[\w]+', ] ); $this->register_endpoint( new Endpoints\Download_Link( $this ) ); $this->register_endpoint( new Endpoints\Favorites( $this ) ); } public function get_permission_callback( $request ) { return current_user_can( 'administrator' ); } } app/modules/kit-library/data/kits/endpoints/favorites.php 0000644 00000002044 15230642241 0017625 0 ustar 00 <?php namespace Elementor\Core\App\Modules\KitLibrary\Data\Kits\Endpoints; use Elementor\Core\App\Modules\KitLibrary\Data\Kits\Controller; use Elementor\Data\V2\Base\Endpoint; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @property Controller $controller */ class Favorites extends Endpoint { public function get_name() { return 'favorites'; } public function get_format() { return 'kits/favorites/{id}'; } protected function register() { $args = [ 'id_arg_type_regex' => '[\w]+', ]; $this->register_item_route( \WP_REST_Server::CREATABLE, $args ); $this->register_item_route( \WP_REST_Server::DELETABLE, $args ); } public function create_item( $id, $request ) { $repository = $this->controller->get_repository(); $kit = $repository->add_to_favorites( $id ); return [ 'data' => $kit, ]; } public function delete_item( $id, $request ) { $repository = $this->controller->get_repository(); $kit = $repository->remove_from_favorites( $id ); return [ 'data' => $kit, ]; } } app/modules/kit-library/data/kits/endpoints/download-link.php 0000644 00000001545 15230642241 0020372 0 ustar 00 <?php namespace Elementor\Core\App\Modules\KitLibrary\Data\Kits\Endpoints; use Elementor\Data\V2\Base\Endpoint; use Elementor\Core\App\Modules\KitLibrary\Data\Kits\Controller; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @property Controller $controller */ class Download_Link extends Endpoint { public function get_name() { return 'download-link'; } public function get_format() { return 'kits/download-link/{id}'; } protected function register() { $this->register_item_route( \WP_REST_Server::READABLE, [ 'id_arg_type_regex' => '[\w]+', ] ); } public function get_item( $id, $request ) { $repository = $this->controller->get_repository(); $data = $repository->get_download_link( $id ); return [ 'data' => $data, 'meta' => [ 'nonce' => wp_create_nonce( 'kit-library-import' ), ], ]; } } app/modules/kit-library/data/repository.php 0000644 00000017161 15230642241 0015073 0 ustar 00 <?php namespace Elementor\Core\App\Modules\KitLibrary\Data; use Elementor\Core\Utils\Collection; use Elementor\Data\V2\Base\Exceptions\Error_404; use Elementor\Data\V2\Base\Exceptions\WP_Error_Exception; use Elementor\Modules\Library\User_Favorites; use Elementor\Core\App\Modules\KitLibrary\Connect\Kit_Library; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Repository { /** * There is no label for subscription plan with access_level=0 + it should not * be translated. */ const SUBSCRIPTION_PLAN_FREE_TAG = 'Free'; const TAXONOMIES_KEYS = [ 'tags', 'categories', 'features', 'types' ]; const KITS_CACHE_KEY = 'elementor_remote_kits'; const KITS_TAXONOMIES_CACHE_KEY = 'elementor_remote_kits_taxonomies'; const KITS_CACHE_TTL_HOURS = 12; const KITS_TAXONOMIES_CACHE_TTL_HOURS = 12; /** * @var Kit_Library */ protected $api; /** * @var User_Favorites */ protected $user_favorites; /** * @var Collection */ protected $subscription_plans; /** * Get all kits. * * @param false $force_api_request * * @return Collection */ public function get_all( $force_api_request = false ) { return $this->get_kits_data( $force_api_request ) ->map( function ( $kit ) { return $this->transform_kit_api_response( $kit ); } ); } /** * Get specific kit. * * @param $id * @param array $options * * @return array|null */ public function find( $id, $options = [] ) { $options = wp_parse_args( $options, [ 'manifest_included' => true, ] ); $item = $this->get_kits_data() ->find( function ( $kit ) use ( $id ) { return $kit->_id === $id; } ); if ( ! $item ) { return null; } $manifest = null; if ( $options['manifest_included'] ) { $manifest = $this->api->get_manifest( $id ); if ( is_wp_error( $manifest ) ) { throw new WP_Error_Exception( $manifest ); } } return $this->transform_kit_api_response( $item, $manifest ); } /** * @param false $force_api_request * * @return Collection */ public function get_taxonomies( $force_api_request = false ) { return $this->get_taxonomies_data( $force_api_request ) ->only( static::TAXONOMIES_KEYS ) ->reduce( function ( Collection $carry, $taxonomies, $type ) { return $carry->merge( array_map( function ( $taxonomy ) use ( $type ) { return [ 'text' => $taxonomy->name, 'type' => $type, ]; }, $taxonomies ) ); }, new Collection( [] ) ) ->merge( $this->subscription_plans->map( function ( $label ) { return [ 'text' => $label ? $label : self::SUBSCRIPTION_PLAN_FREE_TAG, 'type' => 'subscription_plans', ]; } ) ) ->unique( [ 'text', 'type' ] ); } /** * @param $id * * @return array */ public function get_download_link( $id ) { $response = $this->api->download_link( $id ); if ( is_wp_error( $response ) ) { throw new WP_Error_Exception( $response ); } return [ 'download_link' => $response->download_link ]; } /** * @param $id * * @return array * @throws \Exception */ public function add_to_favorites( $id ) { $kit = $this->find( $id, [ 'manifest_included' => false ] ); if ( ! $kit ) { throw new Error_404( __( 'Kit not found', 'elementor' ), 'kit_not_found' ); } $this->user_favorites->add( 'elementor', 'kits', $kit['id'] ); $kit['is_favorite'] = true; return $kit; } /** * @param $id * * @return array * @throws \Exception */ public function remove_from_favorites( $id ) { $kit = $this->find( $id, [ 'manifest_included' => false ] ); if ( ! $kit ) { throw new Error_404( __( 'Kit not found', 'elementor' ), 'kit_not_found' ); } $this->user_favorites->remove( 'elementor', 'kits', $kit['id'] ); $kit['is_favorite'] = false; return $kit; } /** * @param bool $force_api_request * * @return Collection */ private function get_kits_data( $force_api_request = false ) { $data = get_transient( static::KITS_CACHE_KEY ); if ( ! $data || $force_api_request ) { $data = $this->api->get_all(); if ( is_wp_error( $data ) ) { throw new WP_Error_Exception( $data ); } set_transient( static::KITS_CACHE_KEY, $data, static::KITS_CACHE_TTL_HOURS * HOUR_IN_SECONDS ); } return new Collection( $data ); } /** * @param bool $force_api_request * * @return Collection */ private function get_taxonomies_data( $force_api_request = false ) { $data = get_transient( static::KITS_TAXONOMIES_CACHE_KEY ); if ( ! $data || $force_api_request ) { $data = $this->api->get_taxonomies(); if ( is_wp_error( $data ) ) { throw new WP_Error_Exception( $data ); } set_transient( static::KITS_TAXONOMIES_CACHE_KEY, $data, static::KITS_TAXONOMIES_CACHE_TTL_HOURS * HOUR_IN_SECONDS ); } return new Collection( (array) $data ); } /** * @param $kit * @param null $manifest * * @return array */ private function transform_kit_api_response( $kit, $manifest = null ) { $subscription_plan_tag = $this->subscription_plans->get( $kit->access_level ); $taxonomies = ( new Collection( (array) $kit ) ) ->only( static::TAXONOMIES_KEYS ) ->flatten() ->pluck( 'name' ) ->push( $subscription_plan_tag ? $subscription_plan_tag : self::SUBSCRIPTION_PLAN_FREE_TAG ); return array_merge( [ 'id' => $kit->_id, 'title' => $kit->title, 'thumbnail_url' => $kit->thumbnail, 'access_level' => $kit->access_level, 'keywords' => $kit->keywords, 'taxonomies' => $taxonomies->values(), 'is_favorite' => $this->user_favorites->exists( 'elementor', 'kits', $kit->_id ), // TODO: Remove all the isset when the API stable. 'trend_index' => isset( $kit->trend_index ) ? $kit->trend_index : 0, 'featured_index' => isset( $kit->featured_index ) ? $kit->featured_index : 0, 'popularity_index' => isset( $kit->popularity_index ) ? $kit->popularity_index : 0, 'created_at' => isset( $kit->created_at ) ? $kit->created_at : null, 'updated_at' => isset( $kit->updated_at ) ? $kit->updated_at : null, // ], $manifest ? $this->transform_manifest_api_response( $manifest ) : [] ); } /** * @param $manifest * * @return array */ private function transform_manifest_api_response( $manifest ) { $manifest_content = ( new Collection( (array) $manifest->content ) ) ->reduce( function ( $carry, $content, $type ) { $mapped_documents = array_map( function ( $document ) use ( $type ) { // TODO: Fix it! // Hack to override a bug when a document with type of 'wp-page' is declared as 'wp-post'. if ( 'page' === $type ) { $document->doc_type = 'wp-page'; } return $document; }, (array) $content ); return $carry + $mapped_documents; }, [] ); $content = ( new Collection( (array) $manifest->templates ) ) ->union( $manifest_content ) ->map( function ( $manifest_item, $key ) { return [ 'id' => isset( $manifest_item->id ) ? $manifest_item->id : $key, 'title' => $manifest_item->title, 'doc_type' => $manifest_item->doc_type, 'thumbnail_url' => $manifest_item->thumbnail, 'preview_url' => isset( $manifest_item->url ) ? $manifest_item->url : null, ]; } ); return [ 'description' => $manifest->description, 'preview_url' => isset( $manifest->site ) ? $manifest->site : '', 'documents' => $content->values(), ]; } /** * @param Kit_Library $kit_library * @param User_Favorites $user_favorites * @param Collection $subscription_plans */ public function __construct( Kit_Library $kit_library, User_Favorites $user_favorites, Collection $subscription_plans ) { $this->api = $kit_library; $this->user_favorites = $user_favorites; $this->subscription_plans = $subscription_plans; } } app/modules/kit-library/data/base-controller.php 0000644 00000001715 15230642241 0015745 0 ustar 00 <?php namespace Elementor\Core\App\Modules\KitLibrary\Data; use Elementor\Plugin; use Elementor\Data\V2\Base\Controller; use Elementor\Core\Utils\Collection; use Elementor\Modules\Library\User_Favorites; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Controller extends Controller { /** * @var Repository */ private $repository; /** * @return Repository */ public function get_repository() { if ( ! $this->repository ) { /** @var \Elementor\Core\Common\Modules\Connect\Module $connect */ $connect = Plugin::$instance->common->get_component( 'connect' ); $subscription_plans = ( new Collection( $connect->get_subscription_plans() ) ) ->map( function ( $value ) { return $value['label']; } ); $this->repository = new Repository( $connect->get_app( 'kit-library' ), new User_Favorites( get_current_user_id() ), $subscription_plans ); } return $this->repository; } } app/modules/kit-library/data/taxonomies/controller.php 0000644 00000001465 15230642241 0017225 0 ustar 00 <?php namespace Elementor\Core\App\Modules\KitLibrary\Data\Taxonomies; use Elementor\Core\App\Modules\KitLibrary\Data\Base_Controller; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Base_Controller { public function get_name() { return 'kit-taxonomies'; } public function get_collection_params() { return [ 'force' => [ 'description' => 'Force an API request and skip the cache.', 'required' => false, 'default' => false, 'type' => 'boolean', ], ]; } public function get_items( $request ) { $data = $this->get_repository()->get_taxonomies( $request->get_param( 'force' ) ); return [ 'data' => $data->values(), ]; } public function get_permission_callback( $request ) { return current_user_can( 'administrator' ); } } app/modules/kit-library/module.php 0000644 00000005063 15230642241 0013226 0 ustar 00 <?php namespace Elementor\Core\App\Modules\KitLibrary; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\App\Modules\KitLibrary\Connect\Kit_Library; use Elementor\Core\Common\Modules\Connect\Module as ConnectModule; use Elementor\Core\App\Modules\KitLibrary\Data\Kits\Controller as Kits_Controller; use Elementor\Core\App\Modules\KitLibrary\Data\Taxonomies\Controller as Taxonomies_Controller; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Module extends BaseModule { /** * Get name. * * @access public * * @return string */ public function get_name() { return 'kit-library'; } /** * Register the admin menu. */ private function register_admin_menu() { add_submenu_page( Source_Local::ADMIN_MENU_SLUG, __( 'Kit Library', 'elementor' ), __( 'Kit Library', 'elementor' ), 'manage_options', Plugin::$instance->app->get_base_url() . '#/kit-library' ); } private function set_kit_library_settings() { if ( ! Plugin::$instance->common ) { return; } /** @var ConnectModule $connect */ $connect = Plugin::$instance->common->get_component( 'connect' ); /** @var Kit_Library $kit_library */ $kit_library = $connect->get_app( 'kit-library' ); Plugin::$instance->app->set_settings( 'kit-library', [ 'has_access_to_module' => current_user_can( 'administrator' ), 'subscription_plans' => $connect->get_subscription_plans( 'wp-kit-library' ), 'is_pro' => false, 'is_library_connected' => $kit_library->is_connected(), 'library_connect_url' => $kit_library->get_admin_url( 'authorize', [ 'utm_source' => 'kit-library', 'utm_medium' => 'wp-dash', 'utm_campaign' => 'library-connect', 'utm_term' => '%%page%%', // Will be replaced in the frontend. ] ), 'access_level' => ConnectModule::ACCESS_LEVEL_CORE, ] ); } /** * Module constructor. */ public function __construct() { Plugin::$instance->data_manager_v2->register_controller( new Kits_Controller() ); Plugin::$instance->data_manager_v2->register_controller( new Taxonomies_Controller() ); add_action( 'admin_menu', function () { $this->register_admin_menu(); }, 50 /* after Elementor page */ ); add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) { $connect_module->register_app( 'kit-library', Kit_Library::get_class_name() ); } ); add_action( 'elementor/init', function () { $this->set_kit_library_settings(); }, 12 /** after the initiation of the connect kit library */ ); } } app/modules/kit-library/connect/kit-library.php 0000644 00000002034 15230642241 0015616 0 ustar 00 <?php namespace Elementor\Core\App\Modules\KitLibrary\Connect; use Elementor\Core\Common\Modules\Connect\Apps\Library; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Kit_Library extends Library { const DEFAULT_BASE_ENDPOINT = 'https://my.elementor.com/api/v1/kits-library'; const FALLBACK_BASE_ENDPOINT = 'https://ms-8874.elementor.com/api/v1/kits-library'; public function get_title() { return __( 'Kit Library', 'elementor' ); } public function get_all() { return $this->http_request( 'GET', 'kits' ); } public function get_taxonomies() { return $this->http_request( 'GET', 'taxonomies' ); } public function get_manifest( $id ) { return $this->http_request( 'GET', "kits/{$id}/manifest" ); } public function download_link( $id ) { return $this->http_request( 'GET', "kits/{$id}/download-link" ); } protected function get_api_url() { return [ static::DEFAULT_BASE_ENDPOINT, static::FALLBACK_BASE_ENDPOINT, ]; } protected function init() { // Remove parent init actions. } } app/modules/site-editor/module.php 0000644 00000002061 15230642241 0013220 0 ustar 00 <?php namespace Elementor\Core\App\Modules\SiteEditor; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Site Editor Module * * Responsible for initializing Elementor App functionality */ class Module extends BaseModule { /** * Get name. * * @access public * * @return string */ public function get_name() { return 'site-editor'; } public function add_menu_in_admin_bar( $admin_bar_config ) { $admin_bar_config['elementor_edit_page']['children'][] = [ 'id' => 'elementor_app_site_editor', 'title' => esc_html__( 'Theme Builder', 'elementor' ), 'sub_title' => esc_html__( 'Site', 'elementor' ), 'href' => Plugin::$instance->app->get_settings( 'menu_url' ), 'class' => 'elementor-app-link', 'parent_class' => 'elementor-second-section', ]; return $admin_bar_config; } public function __construct() { add_filter( 'elementor/frontend/admin_bar/settings', [ $this, 'add_menu_in_admin_bar' ] ); // After kit (Site settings) } } app/view.php 0000644 00000001331 15230642241 0007004 0 ustar 00 <?php namespace Elementor\Core\App; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @var App $this */ $theme_class = 'dark' === $this->get_elementor_ui_theme_preference() ? 'eps-theme-dark' : ''; ?> <!DOCTYPE html> <html <?php language_attributes(); ?>> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title><?php echo esc_html__( 'Elementor', 'elementor' ) . ' ... '; ?></title> <base target="_parent"> <?php wp_print_styles(); ?> </head> <body class="<?php Utils::print_unescaped_internal_string( $theme_class ); ?>"> <div id="e-app"></div> <?php wp_print_footer_scripts(); ?> </body> </html> page-assets/data-managers/base.php 0000644 00000015657 15230642241 0013124 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 */ 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 (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 ( 'content' === $data_type ) { $data = 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/widgets-css.php 0000644 00000001600 15230642241 0014425 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_size = $this->get_file_data( 'size' ); $widget_css = ''; if ( $asset_css_file_size ) { // If the file size is larger than 8KB then calling the external CSS file, otherwise, printing inline CSS. if ( $asset_css_file_size > 8000 ) { $asset_url = $this->get_config_data( 'file_url' ); $widget_css = sprintf( '<link rel="stylesheet" href="%s">', $asset_url ); } else { $widget_css = $this->get_file_data( 'content' ); $widget_css = sprintf( '<style>%s</style>', $widget_css ); } } return $widget_css; } } page-assets/data-managers/font-icon-svg/font-awesome.php 0000644 00000002417 15230642241 0017275 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_URL . '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/font-icon-svg/base.php 0000644 00000000673 15230642241 0015605 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 15230642241 0016301 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 00000002010 15230642241 0016213 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_URL . '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/responsive-widgets.php 0000644 00000001153 15230642241 0016035 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/loader.php 0000644 00000005546 15230642241 0010750 0 ustar 00 <?php namespace Elementor\Core\Page_Assets; use Elementor\Core\Base\Module; use Elementor\Plugin; 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 $assets; public function get_name() { return 'assets-loader'; } private function init_assets() { $this->assets = [ 'styles' => [ 'e-animations' => [ 'src' => $this->get_css_assets_url( 'animations', 'assets/lib/animations/', true ), 'version' => ELEMENTOR_VERSION, 'dependencies' => [], ], ], 'scripts' => [], ]; } public function get_assets() { 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 ) { 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; } } } /** * @param array $assets { * @type array 'styles' * @type array 'scripts' * } */ public function add_assets( array $assets ) { if ( ! $this->assets ) { $this->init_assets(); } $this->assets = array_replace_recursive( $this->assets, $assets ); } public function enqueue_assets() { $assets = $this->get_assets(); $is_preview_mode = Plugin::$instance->preview->is_preview_mode(); $is_optimized_assets_loading = Plugin::$instance->experiments->is_feature_active( 'e_optimized_assets_loading' ); foreach ( $assets as $assets_type => $assets_type_data ) { foreach ( $assets_type_data as $asset_name => $asset_data ) { if ( ! empty( $asset_data['enabled'] ) || $is_preview_mode || ! $is_optimized_assets_loading ) { if ( 'scripts' === $assets_type ) { wp_enqueue_script( $asset_name, $asset_data['src'], $asset_data['dependencies'], $asset_data['version'], true ); } else { wp_enqueue_style( $asset_name, $asset_data['src'], $asset_data['dependencies'], $asset_data['version'] ); } } } } } private function register_assets() { $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(); } } dynamic-tags/dynamic-css.php 0000644 00000005617 15230642241 0012057 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() { 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 ); } } } } dynamic-tags/base-tag.php 0000644 00000007643 15230642241 0011331 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( '(' + 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/tag.php 0000644 00000004103 15230642241 0010405 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 = $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' ), ] ); $this->add_control( 'after', [ 'label' => esc_html__( 'After', 'elementor' ), ] ); $this->add_control( 'fallback', [ 'label' => esc_html__( 'Fallback', 'elementor' ), ] ); $this->end_controls_section(); } } dynamic-tags/manager.php 0000644 00000026573 15230642241 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(); } /** * @deprecated 3.1.0 */ public function localize_settings() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0' ); return []; } /** * 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. */ // TODO: Uncomment when Pro uses the new hook. //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' //); do_action( 'elementor/dynamic_tags/register_tags', $this ); } 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 `$this->register()` instead. * * @param string $class */ public function register_tag( $class ) { // TODO: Uncomment when Pro uses the new hook. //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 `$this->unregister()` instead. * * @param string $tag_name */ public function unregister_tag( $tag_name ) { // TODO: Uncomment when Pro uses the new hook. //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 ) { $css_file = Dynamic_CSS::create( $css_file->get_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/data-tag.php 0000644 00000001363 15230642241 0011321 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 ); } } frontend/render-modes/render-mode-base.php 0000644 00000004147 15230642241 0014576 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-modes/render-mode-normal.php 0000644 00000000632 15230642241 0015147 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/performance.php 0000644 00000001435 15230642241 0011377 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-mode-manager.php 0000644 00000007117 15230642241 0012712 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 extends '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 */ private function add_current_actions() { if ( ! $this->current->get_permissions_callback() ) { 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(); } } admin/notices/elementor-dev-notice.php 0000644 00000010353 15230642241 0014037 0 ustar 00 <?php namespace Elementor\Core\Admin\Notices; use Elementor\User; use Elementor\Plugin; use Elementor\Settings; use Elementor\Core\Experiments\Manager as Experiments_Manager; 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->has_at_least_one_active_experiment() && $this->is_elementor_setting_page() ) || $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 as at least one active experiment (The state must be "active" and not default-active). * * @return bool */ private function has_at_least_one_active_experiment() { foreach ( Plugin::$instance->experiments->get_features() as $feature_name => $feature ) { if ( Experiments_Manager::STATE_ACTIVE === $feature['state'] ) { return true; } } return 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/update-php-notice.php 0000644 00000002073 15230642241 0013340 0 ustar 00 <?php namespace Elementor\Core\Admin\Notices; use Elementor\Core\Admin\Notices\Base_Notice; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Update_Php_Notice extends Base_Notice { /** * Notice ID. */ const ID = 'elementor_update_php_warning'; /** * @inheritDoc */ public function should_print() { return ! is_php_version_compatible( '7.0' ); } /** * @inheritDoc */ public function get_config() { return [ 'id' => static::ID, 'title' => esc_html__( 'Update your PHP to keep getting Elementor updates', 'elementor' ), 'description' => esc_html__( 'Elementor\'s upcoming v3.7 will include a deprecation of PHP v5.6 that prevents you from receiving future updates. Make sure you update now - either manually or with your hosting provider.', 'elementor' ), 'button' => [ 'text' => '<i class="dashicons dashicons-update" aria-hidden="true"></i>' . esc_html__( 'Show me how', 'elementor' ), 'url' => 'https://go.elementor.com/wp-dash-php-deprecated/', 'new_tab' => true, 'type' => 'cta', ], ]; } } admin/notices/base-notice.php 0000644 00000001003 15230642241 0012173 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/admin.php 0000644 00000055442 15230642241 0007446 0 ustar 00 <?php namespace Elementor\Core\Admin; use Elementor\Api; use Elementor\Beta_Testers; use Elementor\Core\Base\App; use Elementor\Plugin; use Elementor\Settings; use Elementor\User; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin extends App { /** * Get module name. * * Retrieve the module name. * * @since 2.3.0 * @access public * * @return string Module name. */ public function get_name() { return 'admin'; } /** * @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; } global $wpdb; $has_elementor_page = ! ! $wpdb->get_var( "SELECT `post_id` FROM `{$wpdb->postmeta}` WHERE `meta_key` = '_elementor_edit_mode' LIMIT 1;" ); if ( $has_elementor_page ) { return; } wp_safe_redirect( admin_url( 'admin.php?page=elementor-getting-started' ) ); exit; } /** * 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', $this->get_js_assets_url( 'admin' ), [ 'elementor-common', ], ELEMENTOR_VERSION, true ); wp_enqueue_script( 'elementor-admin' ); $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 ( ! isset( $_POST['_elementor_edit_mode_nonce'] ) || ! wp_verify_nonce( $_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 ); $links['go_pro'] = sprintf( '<a href="%1$s" target="_blank" class="elementor-plugins-gopro">%2$s</a>', Utils::get_pro_link( 'https://elementor.com/pro/?utm_source=wp-plugins&utm_campaign=gopro&utm_medium=wp-dash' ), esc_html__( 'Go Pro', 'elementor' ) ); 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 } /** * Elementor dashboard widget. * * Displays the Elementor dashboard widget. * * Fired by `wp_add_dashboard_widget` function. * * @since 1.9.0 * @access public */ public function elementor_dashboard_overview_widget() { $elementor_feed = Api::get_feed_data(); $recently_edited_query = Utils::get_recently_edited_posts_query(); 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-dashboard-widget"> <div class="e-overview__header"> <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 if ( ! empty( $create_new_post_type ) ) : ?> <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 endif; ?> </div> <?php if ( $recently_edited_query->have_posts() ) : ?> <div class="e-overview__recently-edited"> <h3 class="e-heading e-divider_bottom"><?php echo esc_html__( 'Recently Edited', 'elementor' ); ?></h3> <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_attr( $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_time(); ?></span> </li> <?php endwhile; ?> </ul> </div> <?php endif; ?> <?php if ( ! empty( $elementor_feed ) ) : ?> <div class="e-overview__feed"> <h3 class="e-heading e-divider_bottom"><?php echo esc_html__( 'News & Updates', 'elementor' ); ?></h3> <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 endif; ?> <?php echo esc_html( $feed_item['title'] ); ?> </a> <p class="e-overview__post-description"><?php echo esc_html( $feed_item['excerpt'] ); ?></p> </li> <?php endforeach; ?> </ul> </div> <?php endif; ?> <div class="e-overview__footer e-divider_top"> <ul> <?php foreach ( $this->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_attr( $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 endforeach; ?> </ul> </div> </div> <?php } /** * 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() { $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 = [ 'go-pro' => [ 'title' => esc_html__( 'Go Pro', 'elementor' ), 'link' => Utils::get_pro_link( 'https://elementor.com/pro/?utm_source=wp-overview-widget&utm_campaign=gopro&utm_medium=wp-dash' ), ], ]; // Visible to all core users when Elementor Pro is not installed. $additions_actions['find_an_expert'] = [ 'title' => esc_html__( 'Find an Expert', 'elementor' ), 'link' => 'https://go.elementor.com/go-pro-find-an-expert', ]; /** * 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; } /** * 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' ); if ( empty( $_GET['post_type'] ) ) { $post_type = 'post'; } else { $post_type = $_GET['post_type']; } if ( ! User::is_current_user_can_edit_post_type( $post_type ) ) { return; } if ( empty( $_GET['template_type'] ) ) { $type = 'post'; } else { $type = sanitize_text_field( $_GET['template_type'] ); } $post_data = isset( $_GET['post_data'] ) ? $_GET['post_data'] : []; $meta = []; /** * 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 = 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; } /** * @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' ); } /** * @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 ); } /** * @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 ); } /** * @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( 'canary-deployment', new Canary_Deployment() ); $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_beta_tester' ] ); add_action( 'in_plugin_update_message-' . ELEMENTOR_PLUGIN_BASE, function( $plugin_data ) { $this->version_update_warning( ELEMENTOR_VERSION, $plugin_data['new_version'] ); } ); } /** * @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(), ], '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, ], ]; /** * 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; } } admin/admin-notices.php 0000644 00000042621 15230642241 0011103 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\Plugin; 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 EXCLUDE_PAGES = [ 'plugins.php', 'plugin-install.php', 'plugin-editor.php' ]; private $plain_notices = [ 'api_notice', 'api_upgrade_plugin', 'tracker', 'rate_us_feedback', 'woocommerce_promote', 'cf7_promote', 'mc4wp_promote', 'popup_maker_promote', 'role_manager_promote', ]; 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 addons. * * @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 if have any 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 have upgrade notices to show. 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 Elementor 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_woocommerce_promote() { $notice_id = 'woocommerce_promote'; if ( Utils::has_pro() || ! function_exists( 'WC' ) ) { return false; } if ( ! current_user_can( 'install_plugins' ) ) { return false; } if ( ! in_array( $this->current_screen_id, [ 'edit-product', 'woocommerce_page_wc-settings' ], true ) || User::is_user_notice_viewed( $notice_id ) ) { return false; } if ( strtotime( '2019-08-01' ) > $this->get_install_time() ) { return false; } if ( strtotime( '+24 hours', $this->get_install_time() ) > time() ) { return false; } $options = [ 'title' => esc_html__( 'Using WooCommerce?', 'elementor' ), 'description' => esc_html__( 'With Elementor Pro’s WooCommerce Builder, you’ll be able to design your store without coding!', 'elementor' ), 'id' => $notice_id, 'button' => [ 'text' => esc_html__( 'Learn More', 'elementor' ), 'url' => 'https://go.elementor.com/plugin-promotion-woocommerce/', 'new_tab' => true, 'type' => 'cta', ], ]; $this->print_admin_notice( $options ); return true; } private function notice_cf7_promote() { $notice_id = 'cf7_promote'; if ( Utils::has_pro() || ! defined( 'WPCF7_VERSION' ) ) { return false; } if ( ! current_user_can( 'install_plugins' ) ) { return false; } if ( ! in_array( $this->current_screen_id, [ 'toplevel_page_wpcf7', 'contact_page_wpcf7-integration' ], true ) || User::is_user_notice_viewed( $notice_id ) ) { return false; } if ( strtotime( '2019-08-01' ) > $this->get_install_time() ) { return false; } if ( strtotime( '+24 hours', $this->get_install_time() ) > time() ) { return false; } $options = [ 'title' => esc_html__( 'Using Elementor & Contact Form 7?', 'elementor' ), 'description' => esc_html__( 'Try out Elementor Pro and design your forms visually with one powerful tool.', 'elementor' ), 'id' => $notice_id, 'button' => [ 'text' => esc_html__( 'Learn More', 'elementor' ), 'url' => 'https://go.elementor.com/plugin-promotion-contactform7/', 'new_tab' => true, 'type' => 'cta', ], ]; $this->print_admin_notice( $options ); return true; } private function notice_mc4wp_promote() { $notice_id = 'mc4wp_promote'; if ( Utils::has_pro() || ! defined( 'MC4WP_VERSION' ) ) { return false; } if ( ! current_user_can( 'install_plugins' ) ) { return false; } if ( ! in_array( $this->current_screen_id, [ 'toplevel_page_mailchimp-for-wp', 'mc4wp_page_mailchimp-for-wp-forms', 'mc4wp_page_mailchimp-for-wp-integrations', 'mc4wp_page_mailchimp-for-wp-other', 'mc4wp_page_mailchimp-for-wp-extensions' ], true ) || User::is_user_notice_viewed( $notice_id ) ) { return false; } if ( strtotime( '2019-08-01' ) > $this->get_install_time() ) { return false; } if ( strtotime( '+24 hours', $this->get_install_time() ) > time() ) { return false; } $options = [ 'title' => esc_html__( 'Want to design better MailChimp forms?', 'elementor' ), 'description' => esc_html__( 'Use Elementor Pro and enjoy unlimited integrations, visual design, templates and more.', 'elementor' ), 'dismissible' => true, 'id' => $notice_id, 'button' => [ 'text' => esc_html__( 'Learn More', 'elementor' ), 'url' => 'https://go.elementor.com/plugin-promotion-mc4wp/', 'new_tab' => true, 'type' => 'cta', ], ]; $this->print_admin_notice( $options ); return true; } private function notice_popup_maker_promote() { $notice_id = 'popup_maker_promote'; if ( Utils::has_pro() || ! class_exists( 'Popup_Maker' ) ) { return false; } if ( ! current_user_can( 'install_plugins' ) ) { return false; } if ( ! in_array( $this->current_screen_id, [ 'edit-popup', 'popup_page_pum-settings' ], true ) || User::is_user_notice_viewed( $notice_id ) ) { return false; } if ( strtotime( '2019-08-01' ) > $this->get_install_time() ) { return false; } if ( strtotime( '+24 hours', $this->get_install_time() ) > time() ) { return false; } $options = [ 'title' => esc_html__( 'Using popups on your site?', 'elementor' ), 'description' => esc_html__( 'Build outstanding popups using Elementor Pro and get more leads, sales and subscribers.', 'elementor' ), 'dismissible' => true, 'id' => $notice_id, 'button' => [ 'text' => esc_html__( 'Learn More', 'elementor' ), 'url' => 'https://go.elementor.com/plugin-promotion-popupmaker/', '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', ], ]; $this->print_admin_notice( $options ); return true; } public function print_admin_notice( array $options ) { global $pagenow; if ( in_array( $pagenow, self::EXCLUDE_PAGES ) ) { 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', '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/feedback.php 0000644 00000013677 15230642241 0010106 0 ustar 00 <?php namespace Elementor\Core\Admin; use Elementor\Api; use Elementor\Core\Base\Module; use Elementor\Plugin; use Elementor\Tracker; 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', ], ELEMENTOR_VERSION, true ); wp_enqueue_script( 'elementor-admin-feedback' ); } /** * @since 2.3.0 * @deprecated 3.1.0 */ public function localize_feedback_dialog_settings() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0' ); return []; } /** * 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() { if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], '_elementor_deactivate_feedback_nonce' ) ) { wp_send_json_error(); } $reason_text = ''; $reason_key = ''; if ( ! empty( $_POST['reason_key'] ) ) { $reason_key = $_POST['reason_key']; } if ( ! empty( $_POST[ "reason_{$reason_key}" ] ) ) { $reason_text = $_POST[ "reason_{$reason_key}" ]; } Api::send_feedback( $reason_key, $reason_text ); wp_send_json_success(); } /** * @since 2.3.0 * @access protected */ protected function get_init_settings() { if ( ! $this->is_plugins_screen() ) { return []; } return [ 'is_tracker_opted_in' => Tracker::is_allow_track() ]; } /** * @since 2.3.0 * @access private */ private function is_plugins_screen() { return in_array( get_current_screen()->id, [ 'plugins', 'plugins-network' ] ); } } admin/canary-deployment.php 0000644 00000011655 15230642241 0012007 0 ustar 00 <?php namespace Elementor\Core\Admin; use Elementor\Api; use Elementor\Core\Base\Module; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } 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/menu/main.php 0000644 00000004011 15230642241 0010230 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/base.php 0000644 00000007040 15230642241 0010223 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 00000005032 15230642241 0012752 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_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 ); $page_title = $has_page ? $item->get_page_title() : ''; $callback = $has_page ? [ $item, 'render' ] : ''; return add_menu_page( $page_title, $item->get_label(), $item->get_capability(), $item_slug, $callback ); } 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/interfaces/admin-menu-item-has-position.php 0000644 00000000313 15230642241 0017031 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 15230642241 0014423 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/menu/interfaces/admin-menu-item-with-page.php 0000644 00000000376 15230642241 0016312 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/ui/components/button.php 0000644 00000004565 15230642241 0012473 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/editor-one-menu/elementor-one-menu-manager.php 0000644 00000023146 15230642241 0016504 0 ustar 00 <?php namespace Elementor\Core\Admin\EditorOneMenu; use Elementor\Core\Admin\EditorOneMenu\Menu\Editor_One_Custom_Elements_Menu; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Legacy_Submenu_Interceptor; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\Modules\EditorOne\Classes\Slug_Normalizer; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Elementor_One_Menu_Manager { private Menu_Data_Provider $menu_data_provider; private bool $is_pro_module_enabled = false; private Legacy_Submenu_Interceptor $legacy_submenu_interceptor; public function __construct() { $this->menu_data_provider = Menu_Data_Provider::instance(); $this->legacy_submenu_interceptor = new Legacy_Submenu_Interceptor( $this->menu_data_provider, new Slug_Normalizer() ); $this->register_actions(); } private function register_actions(): void { add_action( 'init', [ $this, 'check_if_pro_module_is_enabled' ] ); add_action( 'admin_menu', [ $this, 'register_elementor_home_submenus' ], 9 ); add_action( 'admin_menu', function () { do_action( 'elementor/editor-one/menu/register', $this->menu_data_provider ); } ); add_action( 'admin_menu', [ $this, 'register_pro_submenus' ], 100 ); add_action( 'admin_menu', [ $this, 'intercept_legacy_submenus' ], 10003 ); add_action( 'admin_menu', [ $this, 'register_flyout_items_as_hidden_submenus' ], 10004 ); add_action( 'admin_menu', [ $this, 'remove_all_submenus_for_edit_posts_users' ], 10005 ); add_action( 'admin_menu', [ $this, 'override_elementor_page_for_edit_posts_users' ], 1006 ); add_filter( 'add_menu_classes', [ $this, 'fix_theme_builder_submenu_url' ] ); add_action( 'admin_head', [ $this, 'hide_flyout_items_from_wp_menu' ] ); add_action( 'admin_head', [ $this, 'hide_legacy_templates_menu' ] ); add_action( 'admin_head', [ $this, 'hide_old_elementor_menu' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_menu_assets' ] ); add_action( 'admin_print_scripts-elementor_page_elementor-editor', [ $this, 'enqueue_home_screen_on_editor_page' ] ); } public function check_if_pro_module_is_enabled(): void { $this->is_pro_module_enabled = apply_filters( 'elementor/modules/editor-one/is_pro_module_enabled', false ); if ( ! $this->is_pro_module_enabled && Utils::has_pro() ) { $this->menu_data_provider->register_menu( new Editor_One_Custom_Elements_Menu() ); } } public function register_elementor_home_submenus(): void { add_submenu_page( Menu_Config::ELEMENTOR_HOME_MENU_SLUG, esc_html__( 'Editor', 'elementor' ), esc_html__( 'Editor', 'elementor' ), Menu_Config::CAPABILITY_EDIT_POSTS, Menu_Config::ELEMENTOR_MENU_SLUG, [ $this, 'render_editor_page' ], 20 ); do_action( 'elementor/editor-one/menu/register_submenus' ); } public function register_pro_submenus(): void { if ( ! $this->is_pro_module_enabled && Utils::has_pro() && class_exists( '\ElementorPro\License\API' ) && \ElementorPro\License\API::is_license_active() ) { add_submenu_page( Menu_Config::ELEMENTOR_HOME_MENU_SLUG, esc_html__( 'Theme Builder', 'elementor' ), esc_html__( 'Theme Builder', 'elementor' ), Menu_Config::CAPABILITY_EDIT_POSTS, 'elementor-theme-builder', '', 70 ); add_submenu_page( Menu_Config::ELEMENTOR_HOME_MENU_SLUG, esc_html__( 'Submissions', 'elementor' ), esc_html__( 'Submissions', 'elementor' ), 'edit_posts', 'e-form-submissions', '', 80 ); } } public function remove_all_submenus_for_edit_posts_users(): void { $user_capabilities = Menu_Data_Provider::get_current_user_capabilities(); if ( ! $user_capabilities['is_edit_posts_user'] ) { return; } global $submenu; if ( empty( $submenu[ Menu_Config::ELEMENTOR_MENU_SLUG ] ) ) { return; } $submenu_items = $submenu[ Menu_Config::ELEMENTOR_MENU_SLUG ]; foreach ( $submenu_items as $index => $submenu_item ) { if ( 0 === $index ) { continue; } $submenu_slug = $submenu_item[2] ?? ''; if ( ! empty( $submenu_slug ) ) { remove_submenu_page( Menu_Config::ELEMENTOR_MENU_SLUG, $submenu_slug ); } } } public function render_editor_page(): void { Plugin::instance()->settings->display_home_screen(); } public function override_elementor_page_for_edit_posts_users(): void { $user_capabilities = Menu_Data_Provider::get_current_user_capabilities(); if ( ! $user_capabilities['is_edit_posts_user'] ) { return; } $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? ''; if ( Menu_Config::ELEMENTOR_MENU_SLUG !== $page ) { return; } $templates_url = admin_url( 'edit.php?post_type=elementor_library&tabs_group=library' ); wp_safe_redirect( $templates_url ); exit; } public function enqueue_home_screen_on_editor_page(): void { $home_module = Plugin::instance()->modules_manager->get_modules( 'home' ); if ( $home_module && method_exists( $home_module, 'enqueue_home_screen_scripts' ) ) { $home_module->enqueue_home_screen_scripts(); } } public function fix_theme_builder_submenu_url( $menu ) { global $submenu; $menu_slugs = [ Menu_Config::ELEMENTOR_HOME_MENU_SLUG ]; foreach ( $menu_slugs as $menu_slug ) { if ( empty( $submenu[ $menu_slug ] ) ) { continue; } foreach ( $submenu[ $menu_slug ] as &$item ) { if ( 'elementor-theme-builder' === $item[2] ) { $item[2] = $this->get_theme_builder_url(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited break; } } } return $menu; } private function get_theme_builder_url(): string { return $this->menu_data_provider->get_theme_builder_url(); } public function hide_legacy_templates_menu(): void { ?> <style type="text/css"> #menu-posts-elementor_library { display: none !important; } </style> <?php } public function hide_old_elementor_menu(): void { ?> <style type="text/css"> #toplevel_page_elementor { display: none !important; } </style> <?php } public function register_flyout_items_as_hidden_submenus(): void { $hooks = []; $this->iterate_all_flyout_items( function( string $item_slug, Menu_Item_Interface $item ) use ( &$hooks ) { $hook = $this->register_hidden_submenu( $item_slug, $item ); if ( $hook ) { $hooks[ $item_slug ] = $hook; } } ); do_action( 'elementor/editor-one/menu/after_register_hidden_submenus', $hooks ); } private function register_hidden_submenu( string $item_slug, Menu_Item_Interface $item ) { $original_parent = $this->get_original_parent_slug( $item ); $parent_slug = $this->resolve_hidden_submenu_parent( $original_parent ); $has_page = method_exists( $item, 'render' ); $page_title = $has_page ? $item->get_page_title() : ''; $callback = $has_page ? [ $item, 'render' ] : ''; $capability = $item->get_capability(); $position = $item->get_position(); return add_submenu_page( $parent_slug, $page_title, $item->get_label(), $capability, $item_slug, $callback, $position ); } private function resolve_hidden_submenu_parent( ?string $parent_slug ): string { $default_parent_slug = Menu_Config::ELEMENTOR_HOME_MENU_SLUG; if ( empty( $parent_slug ) ) { return $default_parent_slug; } $elementor_parent_slugs = [ Menu_Config::EDITOR_GROUP_ID => true, Menu_Config::EDITOR_MENU_SLUG => true, Menu_Config::TEMPLATES_GROUP_ID => true, Menu_Config::LEGACY_TEMPLATES_SLUG => true, Menu_Config::SETTINGS_GROUP_ID => true, Menu_Config::CUSTOM_ELEMENTS_GROUP_ID => true, Menu_Config::SYSTEM_GROUP_ID => true, ]; if ( isset( $elementor_parent_slugs[ $parent_slug ] ) ) { return $default_parent_slug; } return $parent_slug; } private function iterate_all_flyout_items( callable $callback ): void { $level3_items = $this->menu_data_provider->get_level3_items(); $level4_items = $this->menu_data_provider->get_level4_items(); $all_items = array_merge_recursive( $level3_items, $level4_items ); foreach ( $all_items as $group_items ) { foreach ( $group_items as $item_slug => $item ) { $callback( $item_slug, $item ); } } } private function get_original_parent_slug( $item ): ?string { return $item->get_parent_slug(); } public function hide_flyout_items_from_wp_menu(): void { $protected_wp_menu_slugs = [ Menu_Config::EDITOR_MENU_SLUG, 'elementor-theme-builder', 'e-form-submissions', ]; $this->iterate_all_flyout_items( function( string $item_slug, Menu_Item_Interface $item ) use ( $protected_wp_menu_slugs ) { if ( in_array( $item_slug, $protected_wp_menu_slugs, true ) ) { return; } $original_parent = $this->get_original_parent_slug( $item ); $parent_slug = $this->resolve_hidden_submenu_parent( $original_parent ); remove_submenu_page( $parent_slug, $item_slug ); } ); } public function intercept_legacy_submenus(): void { $this->legacy_submenu_interceptor->intercept_all( $this->is_pro_module_enabled ); } public function enqueue_admin_menu_assets(): void { $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_style( 'elementor-admin-menu', ELEMENTOR_ASSETS_URL . 'css/modules/editor-one/admin-menu' . $min_suffix . '.css', [], ELEMENTOR_VERSION ); $config = [ 'editorFlyout' => $this->menu_data_provider->get_third_level_data( Menu_Data_Provider::THIRD_LEVEL_FLYOUT_MENU ), ]; wp_enqueue_script( 'editor-one-menu', ELEMENTOR_ASSETS_URL . 'js/editor-one-menu' . $min_suffix . '.js', [], ELEMENTOR_VERSION, true ); wp_localize_script( 'editor-one-menu', 'editorOneMenuConfig', $config ); } } admin/editor-one-menu/menu/legacy-submenu-item-not-mapped.php 0000644 00000002427 15230642241 0020242 0 ustar 00 <?php namespace Elementor\Core\Admin\EditorOneMenu\Menu; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Legacy_Submenu_Item_Not_Mapped implements Menu_Item_Interface { private $submenu_data; private $parent_slug; private $position; public function __construct( array $submenu_data, ?string $parent_slug = null, ?int $position = 100 ) { $this->submenu_data = $submenu_data; $this->parent_slug = $parent_slug ?? Menu_Config::ELEMENTOR_MENU_SLUG; $this->position = $position; } public function get_label(): string { return $this->submenu_data[0] ?? ''; } public function get_capability(): string { return $this->submenu_data[1] ?? Menu_Config::CAPABILITY_MANAGE_OPTIONS; } public function get_slug(): string { return $this->submenu_data[2] ?? ''; } public function get_parent_slug(): string { return $this->parent_slug; } public function is_visible(): bool { return true; } public function get_page_title(): string { return $this->submenu_data[3] ?? $this->get_label(); } public function get_position(): int { return $this->position; } public function get_group_id(): string { return Menu_Config::THIRD_PARTY_GROUP_ID; } } admin/editor-one-menu/menu/abstract-level4-menu-item.php 0000644 00000001346 15230642241 0017215 0 ustar 00 <?php namespace Elementor\Core\Admin\EditorOneMenu\Menu; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } abstract class Abstract_Level4_Menu_Item implements Menu_Item_Interface { public function get_capability(): string { return Menu_Config::CAPABILITY_MANAGE_OPTIONS; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } abstract public function get_label(): string; abstract public function get_position(): int; abstract public function get_slug(): string; abstract public function get_group_id(): string; } admin/editor-one-menu/menu/abstract-menu-item.php 0000644 00000001604 15230642241 0016021 0 ustar 00 <?php namespace Elementor\Core\Admin\EditorOneMenu\Menu; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Third_Level_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } abstract class Abstract_Menu_Item implements Menu_Item_Third_Level_Interface { public function get_capability(): string { return Menu_Config::CAPABILITY_MANAGE_OPTIONS; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_group_id(): string { return Menu_Config::EDITOR_GROUP_ID; } public function has_children(): bool { return false; } abstract public function get_label(): string; abstract public function get_position(): int; abstract public function get_slug(): string; abstract public function get_icon(): string; } admin/editor-one-menu/menu/legacy-submenu-item.php 0000644 00000003053 15230642241 0016174 0 ustar 00 <?php namespace Elementor\Core\Admin\EditorOneMenu\Menu; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Third_Level_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Legacy_Submenu_Item implements Menu_Item_Third_Level_Interface { private $submenu_data; private $parent_slug; private $position; public function __construct( array $submenu_data, ?string $parent_slug = null, ?int $position = 100 ) { $this->submenu_data = $submenu_data; $this->parent_slug = $parent_slug ?? Menu_Config::ELEMENTOR_MENU_SLUG; $this->position = $position; } public function get_label(): string { return $this->submenu_data[0] ?? ''; } public function get_capability(): string { return $this->submenu_data[1] ?? Menu_Config::CAPABILITY_MANAGE_OPTIONS; } public function get_slug(): string { return $this->submenu_data[2] ?? ''; } public function get_parent_slug(): string { return $this->parent_slug; } public function is_visible(): bool { return true; } public function get_page_title(): string { return $this->submenu_data[3] ?? $this->get_label(); } public function get_position(): int { return $this->position; } public function get_group_id(): string { return $this->submenu_data[4] ?? Menu_Config::EDITOR_GROUP_ID; } public function get_icon(): string { $item_slug = $this->get_slug(); $icon = Menu_Config::get_attribute_mapping()[ $item_slug ]['icon'] ?? 'admin-generic'; return $icon; } public function has_children(): bool { return false; } } admin/editor-one-menu/menu/editor-one-custom-elements-menu.php 0000644 00000001776 15230642241 0020463 0 ustar 00 <?php namespace Elementor\Core\Admin\EditorOneMenu\Menu; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Third_Level_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Custom_Elements_Menu implements Menu_Item_Third_Level_Interface { public function get_capability(): string { return Menu_Config::CAPABILITY_MANAGE_OPTIONS; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'Custom Elements', 'elementor' ); } public function get_position(): int { return 70; } public function get_slug(): string { return 'elementor-custom-elements'; } public function get_icon(): string { return 'adjustments'; } public function get_group_id(): string { return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID; } public function has_children(): bool { return true; } } admin/editor-one-menu/menu/third-party-pages-menu.php 0000644 00000001752 15230642241 0016632 0 ustar 00 <?php namespace Elementor\Core\Admin\EditorOneMenu\Menu; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Third_Level_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Third_Party_Pages_Menu implements Menu_Item_Third_Level_Interface { public function get_capability(): string { return Menu_Config::CAPABILITY_EDIT_POSTS; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_HOME_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'Addons', 'elementor' ); } public function get_position(): int { return 200; } public function get_slug(): string { return 'elementor-third-party-pages'; } public function get_icon(): string { return 'extension'; } public function get_group_id(): string { return Menu_Config::THIRD_PARTY_GROUP_ID; } public function has_children(): bool { return true; } } admin/editor-one-menu/interfaces/menu-item-third-level-interface.php 0000644 00000000403 15230642241 0021546 0 ustar 00 <?php namespace Elementor\Core\Admin\EditorOneMenu\Interfaces; if ( ! defined( 'ABSPATH' ) ) { exit; } interface Menu_Item_Third_Level_Interface extends Menu_Item_Interface { public function get_icon(): string; public function has_children(): bool; } admin/editor-one-menu/interfaces/menu-item-interface.php 0000644 00000000652 15230642241 0017337 0 ustar 00 <?php namespace Elementor\Core\Admin\EditorOneMenu\Interfaces; if ( ! defined( 'ABSPATH' ) ) { exit; } interface Menu_Item_Interface { public function get_capability(): string; public function get_label(): string; public function get_parent_slug(): string; public function is_visible(): bool; public function get_position(): int; public function get_slug(): string; public function get_group_id(): string; } admin/editor-one-menu/interfaces/menu-item-with-custom-url-interface.php 0000644 00000000307 15230642241 0022415 0 ustar 00 <?php namespace Elementor\Core\Admin\EditorOneMenu\Interfaces; if ( ! defined( 'ABSPATH' ) ) { exit; } interface Menu_Item_With_Custom_Url_Interface { public function get_menu_url(): string; } upgrade/upgrades.php 0000644 00000065462 15230642241 0010532 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\Schemes\Base; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Core\Settings\Page\Manager as SettingsPageManager; use Elementor\Icons_Manager; use Elementor\Modules\Usage\Module; use Elementor\Plugin; use Elementor\Tracker; use Elementor\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 ); } /** * Move default colors settings to active kit and all it's revisions. * * @param Updater $updater * * @return bool */ public static function _v_3_0_0_move_default_colors_to_kit( $updater, $include_revisions = true ) { $callback = function( $kit_id ) { if ( ! Plugin::$instance->kits_manager->is_custom_colors_enabled() ) { self::notice( 'System colors are disabled. nothing to do.' ); return; } $kit = Plugin::$instance->documents->get( $kit_id ); // Already exist. use raw settings that doesn't have default values. $meta_key = SettingsPageManager::META_KEY; $kit_raw_settings = $kit->get_meta( $meta_key ); if ( isset( $kit_raw_settings['system_colors'] ) ) { self::notice( 'System colors already exist. nothing to do.' ); return; } $scheme_obj = Plugin::$instance->schemes_manager->get_scheme( 'color' ); $default_colors = $scheme_obj->get_scheme(); $new_ids = [ 'primary', 'secondary', 'text', 'accent', ]; foreach ( $default_colors as $index => $color ) { $kit->add_repeater_row( 'system_colors', [ '_id' => $new_ids[ $index - 1 ], // $default_colors starts from 1. 'title' => $color['title'], 'color' => strtoupper( $color['value'] ), ] ); } }; return self::move_settings_to_kit( $callback, $updater, $include_revisions ); } /** * Move saved colors settings to active kit and all it's revisions. * * @param Updater $updater * * @return bool */ public static function _v_3_0_0_move_saved_colors_to_kit( $updater, $include_revisions = true ) { $callback = function( $kit_id ) { $kit = Plugin::$instance->documents->get( $kit_id ); // Already exist. use raw settings that doesn't have default values. $meta_key = SettingsPageManager::META_KEY; $kit_raw_settings = $kit->get_meta( $meta_key ); if ( isset( $kit_raw_settings['custom_colors'] ) ) { self::notice( 'Custom colors already exist. nothing to do.' ); return; } $system_colors_rows = $kit->get_settings( 'system_colors' ); if ( ! $system_colors_rows ) { $system_colors_rows = []; } $system_colors = []; foreach ( $system_colors_rows as $color_row ) { $system_colors[] = strtoupper( $color_row['color'] ); } $saved_scheme_obj = Plugin::$instance->schemes_manager->get_scheme( 'color-picker' ); $current_saved_colors_rows = $saved_scheme_obj->get_scheme(); $current_saved_colors = []; foreach ( $current_saved_colors_rows as $color_row ) { $current_saved_colors[] = strtoupper( $color_row['value'] ); } $colors_to_save = array_diff( $current_saved_colors, $system_colors ); if ( empty( $colors_to_save ) ) { self::notice( 'Saved colors not found. nothing to do.' ); return; } foreach ( $colors_to_save as $index => $color ) { $kit->add_repeater_row( 'custom_colors', [ '_id' => Utils::generate_random_string(), 'title' => esc_html__( 'Saved Color', 'elementor' ) . ' #' . ( $index + 1 ), 'color' => $color, ] ); } }; return self::move_settings_to_kit( $callback, $updater, $include_revisions ); } /** * Move default typography settings to active kit and all it's revisions. * * @param Updater $updater * * @return bool */ public static function _v_3_0_0_move_default_typography_to_kit( $updater, $include_revisions = true ) { $callback = function( $kit_id ) { if ( ! Plugin::$instance->kits_manager->is_custom_typography_enabled() ) { self::notice( 'System typography is disabled. nothing to do.' ); return; } $kit = Plugin::$instance->documents->get( $kit_id ); // Already exist. use raw settings that doesn't have default values. $meta_key = SettingsPageManager::META_KEY; $kit_raw_settings = $kit->get_meta( $meta_key ); if ( isset( $kit_raw_settings['system_typography'] ) ) { self::notice( 'System typography already exist. nothing to do.' ); return; } $scheme_obj = Plugin::$instance->schemes_manager->get_scheme( 'typography' ); $default_typography = $scheme_obj->get_scheme(); $new_ids = [ 'primary', 'secondary', 'text', 'accent', ]; foreach ( $default_typography as $index => $typography ) { $kit->add_repeater_row( 'system_typography', [ '_id' => $new_ids[ $index - 1 ], // $default_typography starts from 1. 'title' => $typography['title'], 'typography_typography' => 'custom', 'typography_font_family' => $typography['value']['font_family'], 'typography_font_weight' => $typography['value']['font_weight'], ] ); } }; return self::move_settings_to_kit( $callback, $updater, $include_revisions ); } public static function v_3_1_0_move_optimized_dom_output_to_experiments() { $saved_option = get_option( 'elementor_optimized_dom_output' ); if ( $saved_option ) { $new_option = 'enabled' === $saved_option ? Experiments_Manager::STATE_ACTIVE : Experiments_Manager::STATE_INACTIVE; add_option( 'elementor_experiment-e_dom_optimization', $new_option ); } } 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; $key = Base::SCHEME_OPTION_PREFIX; $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '{$key}%';" ); // phpcs:ignore } 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 ); } } upgrade/custom-tasks.php 0000644 00000000564 15230642241 0011345 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 15230642241 0011477 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/custom-tasks-manager.php 0000644 00000004663 15230642241 0012761 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 15230642241 0007642 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/elementor-3-re-migrate-globals.php 0000644 00000011001 15230642242 0014502 0 ustar 00 <?php namespace Elementor\Core\Upgrade; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Core\Settings\Page\Manager as SettingsPageManager; use Elementor\Plugin; use Elementor\Tools; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Elementor_3_Re_Migrate_Globals { public function register_ajax_actions( Ajax $ajax ) { $ajax->register_ajax_action( 're_migrate_globals', [ $this, 'ajax_re_migrate_globals' ] ); } public function ajax_re_migrate_globals() { $this->run(); return true; } /** * @deprecated 3.1.0 */ public function admin_localize_settings() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0' ); return []; } public function register_admin_tools_fields( Tools $tools ) { // Add the re-migrate button only if needed. if ( ! $this->has_typography() && ! $this->has_colors() && ! $this->has_saved_colors() ) { return; } // Add the button to the rollback screen. $tools->add_fields( 'versions', 'rollback', [ 're_migrate_globals_separator' => [ 'field_args' => [ 'type' => 'raw_html', 'html' => '<hr>', ], ], 're_migrate_globals' => [ 'label' => esc_html__( 'Rerun Update Script', 'elementor' ), 'field_args' => [ 'type' => 'raw_html', 'html' => sprintf( '<a href="#" data-nonce="%s" class="button elementor-button-spinner elementor-re-migrate-globals-button">%s</a>', wp_create_nonce( 'elementor_re_migrate_globals' ), __( 'Migrate to v3.0', 'elementor' ) ), 'desc' => '<span style="color: red;">' . esc_html__( 'Warning: This will reset your current Global Fonts and Colors, and will migrate your previous settings from v2.x versions.', 'elementor' ) . '</span>', ], ], ] ); } private function run() { $this->notice( 'Re-migrate 3.0.0 globals: Start' ); $callbacks = []; $kit = Plugin::$instance->kits_manager->get_active_kit(); // Already exist. use raw settings that doesn't have default values. $meta_key = SettingsPageManager::META_KEY; $kit_raw_settings = $kit->get_meta( $meta_key ); // Apply a default value for $kit_raw_settings in case it's not defined. if ( empty( $kit_raw_settings ) ) { $kit_raw_settings = []; } if ( $this->has_typography() ) { $callbacks[] = [ Upgrades::class, '_v_3_0_0_move_default_typography_to_kit' ]; unset( $kit_raw_settings['system_typography'] ); } else { $this->notice( 'Typography not found' ); } if ( $this->has_colors() ) { $callbacks[] = [ Upgrades::class, '_v_3_0_0_move_default_colors_to_kit' ]; unset( $kit_raw_settings['system_colors'] ); } else { $this->notice( 'Colors not found' ); } if ( $this->has_saved_colors() ) { $callbacks[] = [ Upgrades::class, '_v_3_0_0_move_saved_colors_to_kit' ]; unset( $kit_raw_settings['custom_colors'] ); } else { $this->notice( 'Saved Colors not found' ); } if ( ! empty( $callbacks ) ) { $page_settings_manager = SettingsManager::get_settings_managers( 'page' ); $page_settings_manager->save_settings( $kit_raw_settings, $active_kit_id ); $this->run_callbacks( $callbacks ); } Plugin::$instance->files_manager->clear_cache(); $this->notice( 'Re-migrate 3.0.0 globals: End' ); } private function run_callbacks( $callbacks ) { foreach ( $callbacks as $callback ) { $updater = $this->create_updater(); $this->notice( 'Re-migrate 3.0.0 globals: ' . $callback[1] ); // Run upgrade. call_user_func( $callback, $updater, false ); } } /** * @return Updater */ private function create_updater() { $upgrades_manager = new Manager(); /** @var Updater $updater */ $updater = $upgrades_manager->get_task_runner(); $updater->set_current_item( [ 'iterate_num' => 1, ] ); return $updater; } private function has_typography() { return ! ! get_option( 'elementor_scheme_typography' ); } private function has_colors() { return ! ! get_option( 'elementor_scheme_color' ); } private function has_saved_colors() { return ! ! get_option( 'elementor_scheme_color-picker' ); } private function notice( $message ) { $logger = Plugin::$instance->logger->get_logger(); $logger->notice( $message ); } public function __construct() { add_action( 'elementor/admin/after_create_settings/' . Tools::PAGE_ID, [ $this, 'register_admin_tools_fields' ], 60 /* After plugins rollback */ ); add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); } } upgrade/manager.php 0000644 00000004055 15230642242 0010322 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 { const INSTALLS_HISTORY_META = '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( self::INSTALLS_HISTORY_META, [] ); } public static function install_compare( $version, $operator ) { $installs_history = self::get_installs_history(); return version_compare( key( $installs_history ), $version, $operator ); } protected function update_db_version() { parent::update_db_version(); $installs_history = self::get_installs_history(); $time = time(); $installs_history[ ELEMENTOR_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[ ELEMENTOR_VERSION ] - 1; } uksort( $installs_history, 'version_compare' ); update_option( self::INSTALLS_HISTORY_META, $installs_history ); } } upgrade/updater.php 0000644 00000000753 15230642242 0010355 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 ); } } isolation/kit-adapter-interface.php 0000644 00000000230 15230642242 0013414 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.php 0000644 00000002425 15230642242 0013516 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/kit-adapter.php 0000644 00000000557 15230642242 0011472 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/plugin-status-adapter-interface.php 0000644 00000000433 15230642242 0015451 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-counter-adapter-interface.php 0000644 00000000504 15230642242 0016300 0 ustar 00 <?php namespace Elementor\Core\Isolation; if ( ! defined( 'ABSPATH' ) ) { exit; } interface Elementor_Counter_Adapter_Interface { public function get_count( $key ): ?int; public function set_count( $key, $count = 0 ): void; public function increment( $key ): void; public function is_key_allowed( $key ): bool; } isolation/elementor-adapter-interface.php 0000644 00000000223 15230642242 0014621 0 ustar 00 <?php namespace Elementor\Core\Isolation; interface Elementor_Adapter_Interface { public function get_template_type( $template_id ) : string; } isolation/elementor-adapter.php 0000644 00000000567 15230642242 0012676 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/error_log 0000644 00000003012 15230642242 0010456 0 ustar 00 [07-May-2026 22:27:32 UTC] PHP Fatal error: Uncaught Error: Interface 'Elementor\Core\Isolation\Kit_Adapter_Interface' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/isolation/kit-adapter.php:6 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/isolation/kit-adapter.php on line 6 [07-May-2026 22:27:35 UTC] PHP Fatal error: Uncaught Error: Interface 'Elementor\Core\Isolation\Wordpress_Adapter_Interface' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/isolation/wordpress-adapter.php:4 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/isolation/wordpress-adapter.php on line 4 [07-May-2026 22:27:37 UTC] PHP Fatal error: Uncaught Error: Interface 'Elementor\Core\Isolation\Plugin_Status_Adapter_Interface' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/isolation/plugin-status-adapter.php:4 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/isolation/plugin-status-adapter.php on line 4 [07-May-2026 22:27:52 UTC] PHP Fatal error: Uncaught Error: Interface 'Elementor\Core\Isolation\Elementor_Adapter_Interface' not found in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/isolation/elementor-adapter.php:9 Stack trace: #0 {main} thrown in /home/wwwwaterjetperth/public_html/wp-content/plugins/elementor/core/isolation/elementor-adapter.php on line 9 isolation/wordpress-adapter-interface.php 0000644 00000001270 15230642242 0014662 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 ); } isolation/wordpress-adapter.php 0000644 00000003352 15230642242 0012727 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 ); } } wp-api.php 0000644 00000001401 15230642242 0006446 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' ) ); } } settings/editor-preferences/model.php 0000644 00000006074 15230642242 0014011 0 ustar 00 <?php namespace Elementor\Core\Settings\EditorPreferences; use Elementor\Controls_Manager; use Elementor\Core\Settings\Base\Model as BaseModel; 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( 'ui_theme', [ 'label' => esc_html__( 'UI Theme', 'elementor' ), 'type' => Controls_Manager::SELECT, 'description' => esc_html__( 'Set light or dark mode, or use Auto Detect to sync it with your OS setting.', 'elementor' ), 'default' => 'auto', 'options' => [ 'auto' => esc_html__( 'Auto Detect', 'elementor' ), 'light' => esc_html__( 'Light', 'elementor' ), 'dark' => esc_html__( 'Dark', 'elementor' ), ], ] ); $this->add_control( 'panel_width', [ 'label' => esc_html__( 'Panel Width', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 200, 'max' => 680, ], ], 'default' => [ 'size' => 300, ], ] ); $this->add_control( 'edit_buttons', [ 'label' => esc_html__( 'Editing Handles', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'description' => esc_html__( 'Show editing handles when hovering over the element edit button.', 'elementor' ), ] ); $this->add_control( 'lightbox_in_editor', [ 'label' => esc_html__( 'Enable Lightbox In Editor', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', ] ); $this->add_control( 'responsive_heading', [ 'label' => __( 'Responsive Preview', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'show_hidden_elements', [ 'label' => __( 'Hidden Elements', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => 'Show', 'label_off' => 'Hide', 'default' => 'yes', ] ); $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' ), ], ] ); $this->end_controls_section(); } } settings/editor-preferences/manager.php 0000644 00000002662 15230642242 0014322 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/page/model.php 0000644 00000007562 15230642242 0011143 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 [ /* translators: %s: Document title. */ 'title' => sprintf( 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/page/manager.php 0000644 00000020041 15230642242 0011440 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['post_status'] ) ) { $this->save_post_status( $id, $data['post_status'] ); unset( $post->post_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' ) ) { 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 ); #> <div class="elementor-component-tab elementor-panel-navigation-tab elementor-tab-control-{{ tabSlug }}" data-tab="{{ tabSlug }}"> <a href="#">{{{ tabTitle }}}</a> </div> <# } ); #> </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', ]; } /** * @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 ( 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 ); } } settings/base/model.php 0000644 00000001135 15230642242 0011127 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-manager.php 0000644 00000004561 15230642242 0012235 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/css-model.php 0000644 00000000564 15230642242 0011722 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/base/manager.php 0000644 00000017153 15230642242 0011450 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 ); #> <div class="elementor-component-tab elementor-panel-navigation-tab elementor-tab-control-{{ tabSlug }}" data-tab="{{ tabSlug }}"> <a href="#">{{{ tabTitle }}}</a> </div> <# } ); #> </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/general/model.php 0000644 00000001677 15230642242 0011645 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 since 3.0.0 */ class Model { public function get_name() { return 'general-deprecated'; } public function get_panel_page_settings() { return []; } public function get_tabs_controls() { return []; } public function get_frontend_settings() { return []; } public function get_controls() { return []; } public function get_settings( $setting = null ) { if ( $setting ) { $setting = str_replace( 'elementor_', '', $setting ); } return Plugin::$instance->kits_manager->get_current_settings( $setting ); } } settings/general/manager.php 0000644 00000004523 15230642242 0012150 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 since 3.0.0 */ class Manager extends CSS_Manager { /** * Meta key for the general settings. */ const META_KEY = '_elementor_general_settings'; /** * General settings manager constructor. * * Initializing Elementor general settings manager. * * @since 1.6.0 * @access public */ public function __construct() { parent::__construct(); // TODO: _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 * @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 * @access public * * @return BaseModel The model object. */ public function get_model_for_config() { return $this->get_model(); } /** * @deprecated */ protected function get_saved_settings( $id ) { return []; } /** * Get CSS file name. * * Retrieve CSS file name for the general settings manager. * * @since 1.6.0 * @access protected * @return string * * @return string CSS file name. */ protected function get_css_file_name() { return 'global'; } /** * @deprecated */ 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()' ); } /** * @deprecated */ protected function get_model_for_css_file( Base $css_file ) { return false; } /** * @deprecated */ protected function get_css_file_for_update( $id ) { return false; } } settings/manager.php 0000644 00000011752 15230642242 0010535 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(); } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings