����JFIF���������
__ __ __ __ _____ _ _ _____ _ _ _ | \/ | \ \ / / | __ \ (_) | | / ____| | | | | | \ / |_ __\ V / | |__) | __ ___ ____ _| |_ ___ | (___ | |__ ___| | | | |\/| | '__|> < | ___/ '__| \ \ / / _` | __/ _ \ \___ \| '_ \ / _ \ | | | | | | |_ / . \ | | | | | |\ V / (_| | || __/ ____) | | | | __/ | | |_| |_|_(_)_/ \_\ |_| |_| |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1 if you need WebShell for Seo everyday contact me on Telegram Telegram Address : @jackleetFor_More_Tools:
includes/class-orbit-fox-i18n.php 0000666 00000001530 15216560560 0012665 0 ustar 00 <?php
/**
* Define the internationalization functionality
*
* Loads and defines the internationalization files for this plugin
* so that it is ready for translation.
*
* @link https://themeisle.com
* @since 1.0.0
*
* @package Orbit_Fox
* @subpackage Orbit_Fox/includes
*/
/**
* Define the internationalization functionality.
*
* Loads and defines the internationalization files for this plugin
* so that it is ready for translation.
*
* @since 1.0.0
* @package Orbit_Fox
* @subpackage Orbit_Fox/includes
* @author Themeisle <friends@themeisle.com>
*/
class Orbit_Fox_I18n {
/**
* Load the plugin text domain for translation.
*
* @since 1.0.0
*/
public function load_plugin_textdomain() {
load_plugin_textdomain(
'themeisle-companion',
false,
OBX_PATH . '/languages/'
);
}
}
app/class-orbit-fox-plugin-install.php 0000666 00000005565 15216560560 0014036 0 ustar 00 <?php
/**
* Class that handles plugins installation.
*/
/**
* Class Orbit_Fox_Plugin_Install
*/
class Orbit_Fox_Plugin_Install {
/**
* Get info from wordpress.org api.
*
* @param string $slug Plugin slug.
*
* @return array|mixed|object|WP_Error
*/
public function call_plugin_api( $slug ) {
include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
$call_api = get_transient( 'ti_plugin_info_' . $slug );
if ( false === $call_api ) {
$call_api = plugins_api(
'plugin_information',
array(
'slug' => $slug,
'fields' => array(
'downloaded' => false,
'rating' => false,
'description' => false,
'short_description' => true,
'donate_link' => false,
'tags' => false,
'sections' => true,
'homepage' => true,
'added' => false,
'last_updated' => false,
'compatibility' => false,
'tested' => false,
'requires' => false,
'downloadlink' => false,
'icons' => true,
'banners' => true,
),
)
);
set_transient( 'ti_plugin_info_' . $slug, $call_api, 1 * DAY_IN_SECONDS );
}
return $call_api;
}
/**
* Check plugin state.
*
* @param string $slug plugin slug.
*
* @return bool
*/
public function check_plugin_state( $slug ) {
$plugin_link_suffix = self::get_plugin_path( $slug );
if ( file_exists( ABSPATH . 'wp-content/plugins/' . $plugin_link_suffix ) ) {
return is_plugin_active( $plugin_link_suffix ) ? 'deactivate' : 'activate';
}
return 'install';
}
/**
* Get plugin path based on plugin slug.
*
* @param string $slug Plugin slug.
*
* @return string
*/
public static function get_plugin_path( $slug ) {
switch ( $slug ) {
case 'translatepress-multilingual':
return $slug . '/index.php';
case 'feedzy-rss-feeds':
return $slug . '/feedzy-rss-feed.php';
case 'wp-cloudflare-page-cache':
return $slug . '/wp-cloudflare-super-page-cache.php';
case 'multiple-pages-generator-by-porthas':
return $slug . '/porthas-multi-pages-generator.php';
default:
return $slug . '/' . $slug . '.php';
}
}
/**
* Get Plugin Action link.
*
* @param string $slug plugin slug.
* @param string $action action [activate, deactivate].
* @return string
*/
public function get_plugin_action_link( $slug, $action = 'activate' ) {
if ( ! in_array( $action, array( 'activate', 'deactivate' ) ) ) {
return '';
}
return add_query_arg(
array(
'action' => $action,
'plugin' => rawurlencode( $this->get_plugin_path( $slug ) ),
'plugin_status' => 'all',
'paged' => '1',
'_wpnonce' => wp_create_nonce( $action . '-plugin_' . $this->get_plugin_path( $slug ) ),
),
esc_url( network_admin_url( 'plugins.php' ) )
);
}
}
app/views/partials/empty-tpl.php 0000666 00000002042 15216560560 0012754 0 ustar 00 <?php
/**
* Empty modules template.
* Imported via the Orbit_Fox_Render_Helper.
*
* @link https://themeisle.com
* @since 1.0.0
*
* @package Orbit_Fox
* @subpackage Orbit_Fox/app/views/partials
*/
if ( ! isset( $title ) ) {
$title = __( 'There are no modules for the Fox!', 'themeisle-companion' ); //phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited
}
if ( ! isset( $btn_text ) ) {
$btn_text = __( 'Contact support', 'themeisle-companion' );
}
if ( ! isset( $show_btn ) ) {
$show_btn = true;
}
?>
<div class="empty">
<div class="empty-icon">
<i class="dashicons dashicons-warning" style="width: 48px; height: 48px; font-size: 48px; "></i>
</div>
<h4 class="empty-title"><?php echo wp_kses_post( $title ); ?></h4>
<?php echo ( isset( $sub_title ) ) ? '<p class="empty-subtitle">' . wp_kses_post( $sub_title ) . '</p>' : ''; ?>
<?php
if ( $show_btn ) {
?>
<div class="empty-action">
<button class="btn btn-primary"><?php echo wp_kses_post( $btn_text ); ?></button>
</div>
<?php
}
?>
</div>
app/views/modules-page.php 0000666 00000006003 15216560560 0011565 0 ustar 00 <?php
/**
* The View Page for Orbit Fox Modules.
*
* @link https://themeisle.com
* @since 1.0.0
*
* @package Orbit_Fox
* @subpackage Orbit_Fox/app/views
* @codeCoverageIgnore
*/
if ( ! isset( $no_modules ) ) {
$no_modules = true;
}
if ( ! isset( $empty_tpl ) ) {
$empty_tpl = '';
}
if ( ! isset( $count_modules ) ) {
$count_modules = 0;
}
if ( ! isset( $tiles ) ) {
$tiles = '';
}
if ( ! isset( $toasts ) ) {
$toasts = '';
}
if ( ! isset( $panels ) ) {
$panels = '';
}
$current_tab = 'modules';
if ( isset( $_GET['show_plugins'] ) && $_GET['show_plugins'] === 'yes' ) {
$current_tab = 'plugins';
}
?>
<div class="obfx-wrapper obfx-header">
<div class="obfx-header-content">
<img src="<?php echo esc_url( OBFX_URL ); ?>/images/orbit-fox.png" title="Orbit Fox" class="obfx-logo"/>
<h1><?php esc_html_e( 'Orbit Fox', 'themeisle-companion' ); ?></h1><span class="powered"> by <a
href="https://themeisle.com" target="_blank"><b>ThemeIsle</b></a></span>
</div>
</div>
<div id="obfx-wrapper" style="padding: 0; margin-top: 10px; margin-bottom: 5px;">
<?php
echo wp_kses_post( $toasts );
?>
</div>
<div class="obfx-full-page-container">
<div class="obfx-wrapper" id="obfx-modules-wrapper">
<?php
if ( $no_modules ) {
echo wp_kses_post( $empty_tpl );
} else {
?>
<div class="panel">
<div class="panel-header text-center">
<div class="panel-title mt-10">
<ul class="obfx-menu-tabs">
<li class="<?php echo $current_tab === 'modules' ? 'obfx-tab-active' : ''; ?>"><a
href="
<?php
echo esc_url( admin_url( 'admin.php?page=obfx_companion' ) );
?>
"><?php esc_html_e( 'Available Modules', 'themeisle-companion' ); ?></a></li>
<li class="<?php echo $current_tab === 'plugins' ? 'obfx-tab-active' : ''; ?>">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=obfx_companion&show_plugins=yes' ) ); ?>"><?php esc_html_e( 'Recommended Plugins', 'themeisle-companion' ); ?></a>
</li>
</ul>
</div>
</div>
<div class="panel-body">
<?php if ( $current_tab === 'modules' ) { ?>
<?php echo ( $tiles ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php } ?>
<?php if ( $current_tab === 'plugins' ) { ?>
<?php do_action( 'obfx_recommended_plugins' ); ?>
<?php } ?>
</div>
<div class="panel-footer" <?php echo $current_tab === 'plugins' ? 'style="display:none"' : ''; ?>>
<!-- buttons or inputs -->
</div>
</div>
<div class="panel" <?php echo $current_tab === 'plugins' ? 'style="display:none"' : ''; ?>>
<div class="panel-header text-center">
<div class="panel-title mt-10"><?php echo esc_html__( 'Activated Modules Options', 'themeisle-companion' ); ?></div>
</div>
<?php echo ( $panels === '' ) ? '<p class="text-center">' . esc_html__( 'No modules activated.', 'themeisle-companion' ) . '</p>' : ( $panels ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<?php
}
?>
</div>
</div>
types/class-hestia-customizer-section.php 0000666 00000001565 15220572725 0014667 0 ustar 00 <?php
/**
* Customizer Section type Enforcing.
*
* @package Hestia
*/
/**
* Class Hestia_Customizer_Section
*/
class Hestia_Customizer_Section {
/**
* ID of section
*
* @var string the control ID.
*/
public $id;
/**
* Args for section instance.
*
* @var array args passed into section.
*/
public $args = array();
/**
* Custom section ( string of class name | null)
*
* @var null|string
*/
public $custom_section = null;
/**
* Hestia_Customizer_Section constructor.
*
* @param string $id the control id.
* @param array $args the add_section array.
* @param string $custom_section [optional] this should be added if the section is a custom section.
*/
public function __construct( $id, $args, $custom_section = null ) {
$this->id = $id;
$this->args = $args;
$this->custom_section = $custom_section;
}
}
types/class-hestia-customizer-partial.php 0000666 00000001341 15220572725 0014647 0 ustar 00 <?php
/**
* Customizer partial type enforcing.
*
* @package Hestia
*/
/**
* Class Hestia_Customizer_Partial
*/
class Hestia_Customizer_Partial {
/**
* ID of control that will be attached to. Also ID of the partial itself.
*
* @var string the control ID.
*/
public $control_id;
/**
* Args for the partial.
*
* @var array args passed into partial.
*/
public $args = array();
/**
* Control id
*
* @var string $id Control id.
*/
public $id = '';
/**
* Hestia_Customizer_Partial constructor.
*
* @param string $control_id the control id.
* @param array $args the partial args.
*/
public function __construct( $control_id, $args ) {
$this->id = $control_id;
$this->args = $args;
}
}
abstract/class-hestia-abstract-metabox.php 0000666 00000002543 15220572725 0014715 0 ustar 00 <?php
/**
* Abstract class of metaboxes.
*
* @package Hestia
*/
/**
* Class Hesita_Abstract_Metabox
*/
abstract class Hestia_Abstract_Metabox {
/**
* Init function.
*/
public function init() {
add_action( 'add_meta_boxes', array( $this, 'add' ) );
add_action( 'save_post', array( $this, 'save' ) );
}
/**
* Add metabox fuction.
*/
public abstract function add();
/**
* Save metabox function.
*/
public abstract function save( $post_id );
/**
* Display metabox function.
*/
public abstract function html();
/**
* Create a control inside the metabox.
*
* @param string $type Control type.
* @param string $id Control id.
* @param array $settings Control settings.
*
* @return WP_Error | Hestia_Meta_Radio_Buttons
*/
protected function create_control( $type, $id, $settings = array() ) {
if ( empty( $type ) ) {
return new WP_Error( 'missing-type', 'No control type.' );
}
if ( empty( $id ) ) {
return new WP_Error( 'missing-id', 'No control id.' );
}
$feature_words = explode( '-', $type );
$feature_words = array_map( 'ucfirst', $feature_words );
$feature_name = implode( '_', $feature_words );
$class = 'Hestia_Meta_' . $feature_name;
if ( class_exists( $class ) ) {
return new $class( $id, $settings );
}
return new WP_Error( 'missing-class', 'Class does not exist' );
}
}
abstract/class-hestia-abstract-module.php 0000666 00000002341 15220572725 0014537 0 ustar 00 <?php
/**
* Abstract class that all modules should implement.
*
* @package Inc/Core/Abstract
*/
/**
* Class Hestia_Abstract_Module
*/
abstract class Hestia_Abstract_Module {
/**
* Classes to load.
*
* @var array
*/
protected $classes_to_load = array();
/**
* Check if module should load.
*
* @return bool
*/
abstract protected function should_load();
/**
* Run module's functions.
*
* @return void
*/
abstract function run_module();
/**
* Register customizer classes.
*/
private function register_customizer_classes() {
if ( empty( $this->classes_to_load ) ) {
return false;
}
foreach ( $this->classes_to_load as $class_name => $class_path ) {
$filename = 'class-' . str_replace( '_', '-', strtolower( $class_name ) ) . '.php';
$full_path = trailingslashit( $class_path ) . $filename;
if ( is_file( $full_path ) ) {
require $full_path;
$instance = new $class_name();
if ( method_exists( $instance, 'init' ) ) {
$instance->init();
}
}
}
return true;
}
/**
* Init module function.
*
* @return void
*/
public function init() {
if ( ! $this->should_load() ) {
return;
}
$this->register_customizer_classes();
$this->run_module();
}
}
abstract/class-hestia-register-customizer-controls.php 0000666 00000015677 15220572725 0017360 0 ustar 00 <?php
/**
* Customizer control registering abstract class.
*
* @package Hestia
*/
/**
* Class Hestia_Register_Customizer_Controls
*/
abstract class Hestia_Register_Customizer_Controls extends Hestia_Abstract_Main {
/**
* WP_Customize object
*
* @var $wp_customize object
*/
private $wpc;
/**
* Selective refresh.
*
* @var string transport or postMessage
*/
protected $selective_refresh;
/**
* Controls to register.
*
* @var array Controls that will be registered.
*/
private $controls_to_register = array();
/**
* Sections to register
*
* @var array Controls that will be registered.
*/
private $sections_to_register = array();
/**
* Panels to register
*
* @var array Panels that will be registered.
*/
private $panels_to_register = array();
/**
* Partials to register.
*
* @var array Partials that will be registered.
*/
private $partials_to_register = array();
/**
* Control types to regiister.
*
* @var array Control types that will be registered for use with the content_template Underscores template.
*/
private $types_to_register = array();
/**
* Initialize the control. Add all the hooks necessary.
*/
public function init() {
add_action( 'customize_register', array( $this, 'register_controls_callback' ) );
}
/**
* The function tied to customize_register.
*
* @param object $wp_customize the customizer manager.
*/
public function register_controls_callback( $wp_customize ) {
$this->wpc = $wp_customize;
$this->set_selective_refresh();
$this->before_add_controls();
$this->add_controls();
$this->after_add_controls();
$this->register_controls();
$this->register_panels();
$this->register_sections();
$this->register_types();
$this->change_controls();
$this->register_partials();
}
/**
* Function that should be extended to add customizer controls.
*
* @return void
*/
abstract public function add_controls();
/**
* Hook before controls are defined.
*
* @return void
*/
public function before_add_controls() {
}
/**
* Hook after controls are defined.
*
* @return void
*/
public function after_add_controls() {
}
/**
* Change controls function.
*
* @return void
*/
public function change_controls() {
return;
}
/**
* Check selective refresh.
*/
private function set_selective_refresh() {
$this->selective_refresh = isset( $this->wpc->selective_refresh ) ? 'postMessage' : 'refresh';
}
/**
* Register all the defined sections.
*/
private function register_panels() {
$panels = $this->panels_to_register;
foreach ( $panels as $index => $panel ) {
$this->wpc->add_panel( $panel->id, $panel->args );
}
}
/**
* Register all the defined sections.
*/
private function register_sections() {
$sections = $this->sections_to_register;
foreach ( $sections as $index => $section ) {
if ( $section->custom_section !== null && class_exists( $section->custom_section ) ) {
$this->wpc->add_section( new $section->custom_section( $this->wpc, $section->id, $section->args ) );
} else {
$this->wpc->add_section( $section->id, $section->args );
}
}
}
/**
* Register all the defined controls.
*/
private function register_controls() {
$controls = $this->controls_to_register;
foreach ( $controls as $index => $control ) {
$this->wpc->add_setting( $control->id, $control->setting_args );
if ( $control->custom_control !== null && class_exists( $control->custom_control ) ) {
$this->wpc->add_control( new $control->custom_control( $this->wpc, $control->id, $control->control_args ) );
} else {
$this->wpc->add_control( $control->id, $control->control_args );
}
if ( $control->partial !== null ) {
$this->add_partial( new Hestia_Customizer_Partial( $control->id, $control->partial ) );
}
}
}
/**
* Register control types defined to work with Underscores template.
*/
private function register_types() {
$types = $this->types_to_register;
foreach ( $types as $object => $type ) {
call_user_func_array( array( $this->wpc, 'register_' . $type . '_type' ), array( $object ) );
}
}
/**
* Register all the defined controls.
*/
private function register_partials() {
$partials = $this->partials_to_register;
foreach ( $partials as $index => $partial ) {
$this->wpc->selective_refresh->add_partial( $partial->id, $partial->args );
}
}
/**
* Add the controls to load.
*
* @param Hestia_Customizer_Control $control Hestia_Customizer_Control $control control to add.
*/
public function add_control( Hestia_Customizer_Control $control ) {
array_push( $this->controls_to_register, $control );
}
/**
* Add the sections to load.
*
* @param Hestia_Customizer_Section $section section to add.
*/
public function add_section( Hestia_Customizer_Section $section ) {
array_push( $this->sections_to_register, $section );
}
/**
* Add the panels to load.
*
* @param Hestia_Customizer_Panel $panel panel to add.
*/
public function add_panel( Hestia_Customizer_Panel $panel ) {
array_push( $this->panels_to_register, $panel );
}
/**
* Add types that will be registered for .
*
* @param string $object_name the object name that will be registered.
* @param string $type the type of object to register [panel, section, control].
*/
public function register_type( $object_name, $type ) {
$accepted_types = array( 'panel', 'section', 'control' );
if ( ! in_array( $type, $accepted_types, true ) ) {
return;
}
$this->types_to_register[ $object_name ] = $type;
}
/**
* Add the partials to load.
*
* @param Hestia_Customizer_Partial $partial partial to add.
*/
public function add_partial( Hestia_Customizer_Partial $partial ) {
array_push( $this->partials_to_register, $partial );
}
/**
* Get customizer object.
*
* @param string $type object type [ section, control, setting, panel ].
* @param string $id the id of the customizer object.
*
* @return mixed|null
*/
public function get_customizer_object( $type, $id ) {
$accepted_types = array( 'setting', 'control', 'section', 'panel' );
if ( ! in_array( $type, $accepted_types, true ) ) {
return null;
}
$object = call_user_func_array( array( $this->wpc, 'get_' . $type ), array( $id ) );
if ( empty( $object ) ) {
return null;
}
return $object;
}
/**
* Change a customizer object.
*
* @param string $type object type [ section, control, setting, panel ].
* @param string $id id of object.
* @param string $property property to change.
* @param string|integer|array $value the value.
*/
public function change_customizer_object( $type, $id, $property, $value ) {
$accepted_types = array( 'setting', 'control', 'section', 'panel' );
if ( ! in_array( $type, $accepted_types, true ) ) {
return;
}
$object = call_user_func_array( array( $this->wpc, 'get_' . $type ), array( $id ) );
if ( empty( $object ) ) {
return;
}
$object->$property = $value;
}
}
experiments/wrap-core-dependency.php 0000666 00000001103 15220772103 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/non-existing-dependency.php 0000666 00000001015 15220772103 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/wp-cli.php 0000666 00000012602 15220772103 0011017 0 ustar 00 <?php
namespace Elementor\Core\Experiments;
use Elementor\Core\Experiments\Manager as Experiments_Manager;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Wp_Cli extends \WP_CLI_Command {
/**
* Activate an Experiment
*
* ## EXAMPLES
*
* 1. wp elementor experiments activate container
* - This will activate the Container experiment.
*
* @param array $args
* @param array|null $assoc_args - Arguments from WP CLI command.
*/
public function activate( $args, $assoc_args ) {
if ( empty( $args[0] ) ) {
\WP_CLI::error( 'Please specify an experiment.' );
}
$is_network = $this->is_network( $assoc_args );
$experiments = $this->parse_experiments( $args[0] );
$plural = $this->get_plural( $experiments );
$success = 'Experiment' . $plural . ' activated successfully';
$error = 'Cannot activate experiment' . $plural;
if ( $is_network ) {
$success .= " for site {$site}";
$error .= " for site {$site}";
}
$experiments_manager = Plugin::instance()->experiments;
if ( ! $this->check_experiments_exist( $experiments_manager, $experiments ) ) {
\WP_CLI::error( 'Experiments do not exist' . $args[0] );
}
if ( $is_network ) {
$this->foreach_sites( $this->update_experiment_state, $experiments, Experiments_Manager::STATE_ACTIVE, $is_network, $success, $error );
} else {
$this->update_experiment_state( $experiments, Experiments_Manager::STATE_ACTIVE, $is_network, $success, $error );
}
}
/**
* Deactivate an Experiment
*
* ## EXAMPLES
*
* 1. wp elementor experiments deactivate container
* - This will deactivate the Container experiment.
*
* @param array $args
* @param array|null $assoc_args - Arguments from WP CLI command.
*/
public function deactivate( $args, $assoc_args ) {
if ( empty( $args[0] ) ) {
\WP_CLI::error( 'Please specify an experiment.' );
}
$is_network = $this->is_network( $assoc_args );
$experiments = $this->parse_experiments( $args[0] );
$plural = $this->get_plural( $experiments );
$success = 'Experiment' . $plural . ' deactivated successfully';
$error = 'Cannot deactivate experiment' . $plural;
$experiments_manager = Plugin::instance()->experiments;
if ( ! $this->check_experiments_exist( $experiments_manager, $experiments ) ) {
\WP_CLI::error( 'Experiments do not exist' );
}
if ( $is_network ) {
$this->foreach_sites( $this->update_experiment_state, $experiments, Experiments_Manager::STATE_INACTIVE, $is_network, $success, $error );
} else {
$this->update_experiment_state( $experiments, Experiments_Manager::STATE_INACTIVE, $is_network, $success, $error );
}
}
/**
* Experiment Status
*
* ## EXAMPLES
*
* 1. wp elementor experiments status container
* - This will return the status of Container experiment. (active/inactive)
*
* @param array $args
*/
public function status( $args ) {
if ( empty( $args[0] ) ) {
\WP_CLI::error( 'Please specify an experiment.' );
}
$experiments_manager = Plugin::$instance->experiments;
$experiments_status = $experiments_manager->is_feature_active( $args[0] ) ? 'active' : 'inactive';
\WP_CLI::line( $experiments_status );
}
/**
* Determine if the current website is a multisite.
*
* @param array|null $assoc_args - Arguments from WP CLI command.
*
* @return bool
*/
private function is_network( $assoc_args ) {
return ! empty( $assoc_args['network'] ) && is_multisite();
}
/**
* Iterate over network sites and execute a callback.
*
* @param callable $callback - Callback to execute. Gets the site name & id as parameters.
*
* @return void
*/
private function foreach_sites( callable $callback, $experiments, $state, $is_network, $success, $error ) {
$blog_ids = get_sites( [
'fields' => 'ids',
'number' => 0,
] );
foreach ( $blog_ids as $blog_id ) {
switch_to_blog( $blog_id );
$callback( get_option( 'home' ), $experiments, $state, $is_network, $success, $error );
restore_current_blog();
}
}
/**
* @param string $experiments_str comma delimited string of experiments.
*
* @return array array of experiments
*/
private function parse_experiments( $experiments_str ) {
return explode( ',', $experiments_str );
}
/**
* @param array $experiments experiments.
*
* @return string plural
*/
private function get_plural( $experiments ) {
return count( $experiments ) > 0 ? 's' : '';
}
/**
* @param Experiments_Manager $experiments_manager manager.
* @param array $experiments experiments.
*
* @return bool true when all experiments exist, otherwise false
*/
private function check_experiments_exist( $experiments_manager, $experiments ) {
foreach ( $experiments as $experiment ) {
$feature = $experiments_manager->get_features( $experiment );
if ( ! $feature ) {
return false;
}
}
return true;
}
private function update_experiment_state( $experiments, $state, $is_network, $success_message, $error_message, $site_id = '' ) {
if ( $is_network ) {
$success_message .= " for site {$site}";
$error_message .= " for site {$site}";
}
$experiments_manager = Plugin::instance()->experiments;
foreach ( $experiments as $experiment ) {
$option = $experiments_manager->get_feature_option_key( $experiment );
update_option( $option, $state );
}
try {
\WP_CLI::success( $success_message );
} catch ( \Exception $e ) {
\WP_CLI::error( $error_message );
}
}
}
experiments/experiments-reporter.php 0000666 00000006235 15220772103 0014034 0 ustar 00 <?php
namespace Elementor\Core\Experiments;
use Elementor\Modules\System_Info\Reporters\Base;
use Elementor\Plugin;
/**
* Elementor experiments report.
*
* Elementor experiment report handler class responsible for generating a report for
* the experiments included in Elementor and their status.
*/
class Experiments_Reporter extends Base {
/**
* Get experiments reporter title.
*
* @return string Reporter title.
*/
public function get_title() {
return esc_html__( 'Elementor Experiments', 'elementor' );
}
/**
* Get experiments report fields.
*
* @return array Required report fields with field ID and field label.
*/
public function get_fields() {
return [
'experiments' => '',
];
}
/**
* Get Experiments.
*/
public function get_experiments() {
$result = [];
$experiments_manager = Plugin::$instance->experiments;
// TODO: Those keys should be at `$experiments_manager`.
$tracking_keys = [
'default',
'state',
'tags',
];
foreach ( $experiments_manager->get_features() as $feature_name => $feature_data ) {
$data_to_collect = [];
// Extract only tracking keys.
foreach ( $tracking_keys as $tracking_key ) {
if ( empty( $feature_data[ $tracking_key ] ) ) {
continue;
}
$data_to_collect[ $tracking_key ] = $feature_data[ $tracking_key ];
}
$result[ $feature_name ] = $data_to_collect;
}
return [
'value' => $result,
];
}
/**
* Get Raw Experiments.
*
* Retrieve a string containing the list of Elementor experiments and each experiment's status (active/inactive).
* The string is formatted in a non-table structure, and it is meant for export/download of the system info reports.
*
* @return array
*/
public function get_raw_experiments() {
$experiments = Plugin::$instance->experiments->get_features();
$output = '';
$is_first_item = true;
foreach ( $experiments as $experiment ) {
// If the state is default, add the default state to the string.
$state = Plugin::$instance->experiments->get_feature_state_label( $experiment );
// The first item automatically has a tab character before it. Add tabs only to the rest of the items.
if ( ! $is_first_item ) {
$output .= "\t";
}
$title = isset( $experiment['title'] ) ? $experiment['title'] : $experiment['name'];
$output .= $title . ': ' . $state . PHP_EOL;
$is_first_item = false;
}
return [
'value' => $output,
];
}
/**
* Get HTML Experiments.
*
* Retrieve the list of Elementor experiments and each experiment's status (active/inactive), in HTML table format.
*
* @return array
*/
public function get_html_experiments() {
$experiments = Plugin::$instance->experiments->get_features();
$output = '';
foreach ( $experiments as $experiment ) {
// If the state is default, add the default state to the string.
$state = Plugin::$instance->experiments->get_feature_state_label( $experiment );
$title = isset( $experiment['title'] ) ? $experiment['title'] : $experiment['name'];
$output .= '<tr><td>' . esc_html( $title ) . ': </td>';
$output .= '<td>' . esc_html( $state ) . '</td>';
$output .= '</tr>';
}
return [
'value' => $output,
];
}
}
debug/loading-inspection-manager.php 0000666 00000003370 15220772103 0013547 0 ustar 00 <?php
namespace Elementor\Core\Debug;
use Elementor\Core\Debug\Classes\Inspection_Base;
use Elementor\Core\Debug\Classes\Shop_Page_Edit;
use Elementor\Core\Debug\Classes\Theme_Missing;
use Elementor\Core\Debug\Classes\Htaccess;
use Elementor\Utils;
class Loading_Inspection_Manager {
public static $_instance = null;
public static function instance() {
if ( null === self::$_instance ) {
self::$_instance = new Loading_Inspection_Manager();
}
return self::$_instance;
}
/** @var Inspection_Base[] */
private $inspections = [];
public function register_inspections() {
$this->inspections['theme-missing'] = new Theme_Missing();
$this->inspections['htaccess'] = new Htaccess();
$is_editing_shop_page = Utils::get_super_global_value( $_GET, 'post' ) == get_option( 'woocommerce_shop_page_id' );
if ( $is_editing_shop_page ) {
$this->inspections['shop-page-edit'] = new Shop_Page_Edit();
}
}
/**
* @param Inspection_Base $inspection
*/
public function register_inspection( $inspection ) {
$this->inspections[ $inspection->get_name() ] = $inspection;
}
public function run_inspections() {
$debug_data = [
'message' => esc_html__( "We’re sorry, but something went wrong. Click on 'Learn more' and follow each of the steps to quickly solve it.", 'elementor' ),
'header' => esc_html__( 'The preview could not be loaded', 'elementor' ),
'doc_url' => 'https://go.elementor.com/preview-not-loaded/',
];
foreach ( $this->inspections as $inspection ) {
if ( ! $inspection->run() ) {
$debug_data = [
'message' => $inspection->get_message(),
'header' => $inspection->get_header_message(),
'doc_url' => $inspection->get_help_doc_url(),
'error' => true,
];
break;
}
}
return $debug_data;
}
}
debug/classes/htaccess.php 0000666 00000002412 15220772103 0011577 0 ustar 00 <?php
namespace Elementor\Core\Debug\Classes;
use Elementor\Modules\SafeMode\Module as Safe_Mode;
use Elementor\Utils;
class Htaccess extends Inspection_Base {
private $message = '';
public function __construct() {
$this->message = esc_html__( 'Your site\'s .htaccess file appears to be missing.', 'elementor' );
}
public function run() {
$safe_mode_enabled = get_option( Safe_Mode::OPTION_ENABLED, '' );
if ( empty( $safe_mode_enabled ) || is_multisite() ) {
return true;
}
$permalink_structure = get_option( 'permalink_structure' );
if ( empty( $permalink_structure ) || empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
return true;
}
$server = strtoupper( Utils::get_super_global_value( $_SERVER, 'SERVER_SOFTWARE' ) );
if ( strstr( $server, 'APACHE' ) ) {
$htaccess_file = get_home_path() . '.htaccess';
/* translators: %s: Path to .htaccess file. */
$this->message .= ' ' . sprintf( esc_html__( 'File Path: %s', 'elementor' ), $htaccess_file ) . ' ';
return file_exists( $htaccess_file );
}
return true;
}
public function get_name() {
return 'apache-htaccess';
}
public function get_message() {
return $this->message;
}
public function get_help_doc_url() {
return 'https://go.elementor.com/preview-not-loaded/#htaccess';
}
}
debug/classes/shop-page-edit.php 0000666 00000001305 15220772103 0012610 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' );
}
}
responsive/responsive.php 0000666 00000010602 15220772103 0011651 0 ustar 00 <?php
namespace Elementor\Core\Responsive;
use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
use Elementor\Modules\DevTools\Deprecation;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor responsive.
*
* Elementor responsive handler class is responsible for setting up Elementor
* responsive breakpoints.
*
* @since 1.0.0
* @deprecated 3.2.0
*/
class Responsive {
/**
* The Elementor breakpoint prefix.
*
* @deprecated 3.2.0
*/
const BREAKPOINT_OPTION_PREFIX = 'viewport_';
/**
* Default breakpoints.
*
* Holds the default responsive breakpoints.
*
* @since 1.0.0
* @deprecated 3.2.0
* @access private
* @static
*
* @var array Default breakpoints.
*/
private static $default_breakpoints = [
'xs' => 0,
'sm' => 480,
'md' => 768,
'lg' => 1025,
'xl' => 1440,
'xxl' => 1600,
];
/**
* Editable breakpoint keys.
*
* Holds the editable breakpoint keys.
*
* @since 1.0.0
* @deprecated 3.2.0
* @access private
* @static
*
* @var array Editable breakpoint keys.
*/
private static $editable_breakpoints_keys = [
'md',
'lg',
];
/**
* Get default breakpoints.
*
* Retrieve the default responsive breakpoints.
*
* @since 1.0.0
* @deprecated 3.2.0 Use `Elementor\Core\Breakpoints\Manager::get_default_config()` instead.
* @access public
* @static
*
* @return array Default breakpoints.
*/
public static function get_default_breakpoints() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.2.0', 'Elementor\Core\Breakpoints\Manager::get_default_config()' );
return self::$default_breakpoints;
}
/**
* Get editable breakpoints.
*
* Retrieve the editable breakpoints.
*
* @since 1.0.0
* @deprecated 3.2.0
* @access public
* @static
*
* @return array Editable breakpoints.
*/
public static function get_editable_breakpoints() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.2.0' );
return array_intersect_key( self::get_breakpoints(), array_flip( self::$editable_breakpoints_keys ) );
}
/**
* Get breakpoints.
*
* Retrieve the responsive breakpoints.
*
* @since 1.0.0
* @deprecated 3.2.0
* @access public
* @static
*
* @return array Responsive breakpoints.
*/
public static function get_breakpoints() {
return array_reduce(
array_keys( self::$default_breakpoints ), function( $new_array, $breakpoint_key ) {
if ( ! in_array( $breakpoint_key, self::$editable_breakpoints_keys, true ) ) {
$new_array[ $breakpoint_key ] = self::$default_breakpoints[ $breakpoint_key ];
} else {
$saved_option = Plugin::$instance->kits_manager->get_current_settings( self::BREAKPOINT_OPTION_PREFIX . $breakpoint_key );
$new_array[ $breakpoint_key ] = $saved_option ? (int) $saved_option : self::$default_breakpoints[ $breakpoint_key ];
}
return $new_array;
}, []
);
}
/**
* @since 2.1.0
* @deprecated 3.2.0 Use `Plugin::$instance->breakpoints->has_custom_breakpoints()` instead.
* @access public
* @static
*/
public static function has_custom_breakpoints() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.2.0', 'Plugin::$instance->breakpoints->has_custom_breakpoints()' );
return (bool) array_diff( self::$default_breakpoints, self::get_breakpoints() );
}
/**
* @since 2.1.0
* @deprecated 3.2.0 Use `Elementor\Core\Breakpoints\Manager::get_stylesheet_templates_path()` instead.
* @access public
* @static
*/
public static function get_stylesheet_templates_path() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.2.0', 'Elementor\Core\Breakpoints\Manager::get_stylesheet_templates_path()' );
return Breakpoints_Manager::get_stylesheet_templates_path();
}
/**
* @since 2.1.0
* @deprecated 3.2.0 Use `Elementor\Core\Breakpoints\Manager::compile_stylesheet_templates()` instead.
* @access public
* @static
*/
public static function compile_stylesheet_templates() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.2.0', 'Elementor\Core\Breakpoints\Manager::compile_stylesheet_templates()' );
Breakpoints_Manager::compile_stylesheet_templates();
}
}
responsive/files/frontend.php 0000666 00000011466 15220772103 0012406 0 ustar 00 <?php
namespace Elementor\Core\Responsive\Files;
use Elementor\Core\Breakpoints\Breakpoint;
use Elementor\Core\Files\Base;
use Elementor\Core\Responsive\Responsive;
use Elementor\Plugin;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Frontend extends Base {
const META_KEY = 'elementor-custom-breakpoints-files';
private $template_file;
/**
* @since 2.1.0
* @access public
*/
public function __construct( $file_name, $template_file = null ) {
$this->template_file = $template_file;
parent::__construct( $file_name );
}
/**
* @since 2.1.0
* @access public
*/
public function parse_content() {
$breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints();
$breakpoints_keys = array_keys( $breakpoints );
$file_content = Utils::file_get_contents( $this->template_file );
// The regex pattern parses placeholders located in the frontend _templates.scss file.
$file_content = preg_replace_callback( '/ELEMENTOR_SCREEN_([A-Z_]+)(?:_(MIN|MAX|NEXT))/', function ( $placeholder_data ) use ( $breakpoints_keys, $breakpoints ) {
// Handle BC for legacy template files and Elementor Pro builds.
$placeholder_data = $this->maybe_convert_placeholder_data( $placeholder_data );
$breakpoint_index = array_search( strtolower( $placeholder_data[1] ), $breakpoints_keys, true );
if ( 'DESKTOP' === $placeholder_data[1] ) {
if ( 'MIN' === $placeholder_data[2] ) {
$value = Plugin::$instance->breakpoints->get_desktop_min_point();
} elseif ( isset( $breakpoints['widescreen'] ) ) {
// If the 'widescreen' breakpoint is active, the Desktop's max value is the Widescreen breakpoint - 1px.
$value = $breakpoints['widescreen']->get_value() - 1;
} else {
// If the 'widescreen' breakpoint is not active, the Desktop device should not have a max value.
$value = 99999;
}
} elseif ( false === $breakpoint_index ) {
// If the breakpoint in the placeholder is not active - use a -1 value for the media query, to make
// sure the setting is printed (to avoid a PHP error) but doesn't apply.
return -1;
} elseif ( 'WIDESCREEN' === $placeholder_data[1] ) {
$value = $breakpoints['widescreen']->get_value();
} else {
$breakpoint_index = array_search( strtolower( $placeholder_data[1] ), $breakpoints_keys, true );
$is_max_point = 'MAX' === $placeholder_data[2];
// If the placeholder capture is `MOBILE_NEXT` or `TABLET_NEXT`, the original breakpoint value is used.
if ( ! $is_max_point && 'NEXT' !== $placeholder_data[2] ) {
$breakpoint_index--;
}
$value = $breakpoints[ $breakpoints_keys[ $breakpoint_index ] ]->get_value();
if ( ! $is_max_point ) {
$value++;
}
}
return $value . 'px';
}, $file_content );
return $file_content;
}
/**
* Load meta.
*
* Retrieve the file meta data.
*
* @since 2.1.0
* @access protected
*/
protected function load_meta() {
$option = $this->load_meta_option();
$file_meta_key = $this->get_file_meta_key();
if ( empty( $option[ $file_meta_key ] ) ) {
return [];
}
return $option[ $file_meta_key ];
}
/**
* Update meta.
*
* Update the file meta data.
*
* @since 2.1.0
* @access protected
*
* @param array $meta New meta data.
*/
protected function update_meta( $meta ) {
$option = $this->load_meta_option();
$option[ $this->get_file_meta_key() ] = $meta;
update_option( static::META_KEY, $option );
}
/**
* Delete meta.
*
* Delete the file meta data.
*
* @since 2.1.0
* @access protected
*/
protected function delete_meta() {
$option = $this->load_meta_option();
$file_meta_key = $this->get_file_meta_key();
if ( isset( $option[ $file_meta_key ] ) ) {
unset( $option[ $file_meta_key ] );
}
if ( $option ) {
update_option( static::META_KEY, $option );
} else {
delete_option( static::META_KEY );
}
}
/**
* @since 2.1.0
* @access private
*/
private function get_file_meta_key() {
return pathinfo( $this->get_file_name(), PATHINFO_FILENAME );
}
/**
* @since 2.1.0
* @access private
*/
private function load_meta_option() {
$option = get_option( static::META_KEY );
if ( ! $option ) {
$option = [];
}
return $option;
}
/**
* Maybe Convert Placeholder Data
*
* Converts responsive placeholders in Elementor CSS template files from the legacy format into the new format.
* Used for backwards compatibility for old Pro versions that were built with an Elementor Core version <3.2.0.
*
* @since 3.2.3
*/
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;
}
}
kits/documents/tabs/settings-custom-css.php 0000666 00000001301 15220772103 0015115 0 ustar 00 <?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Settings_Custom_CSS extends Tab_Base {
public function get_id() {
return 'settings-custom-css';
}
public function get_title() {
return esc_html__( 'Custom CSS', 'elementor' );
}
public function get_group() {
return 'settings';
}
public function get_icon() {
return 'eicon-custom-css';
}
public function get_help_url() {
return 'https://go.elementor.com/global-custom-css/';
}
protected function register_tab_controls() {
Plugin::$instance->controls_manager->add_custom_css_controls( $this->parent, $this->get_id() );
}
}
kits/documents/tabs/theme-style-images.php 0000666 00000011020 15220772103 0014661 0 ustar 00 <?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Border;
use Elementor\Group_Control_Box_Shadow;
use Elementor\Group_Control_Css_Filter;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Theme_Style_Images extends Tab_Base {
public function get_id() {
return 'theme-style-images';
}
public function get_title() {
return esc_html__( 'Images', 'elementor' );
}
public function get_group() {
return 'theme-style';
}
public function get_icon() {
return 'eicon-image';
}
public function get_help_url() {
return 'https://go.elementor.com/global-theme-style-images/';
}
protected function register_tab_controls() {
$image_selectors = [
'{{WRAPPER}} img',
];
$image_hover_selectors = [
'{{WRAPPER}} img:hover',
];
$image_selectors = implode( ',', $image_selectors );
$image_hover_selectors = implode( ',', $image_hover_selectors );
$this->start_controls_section(
'section_images',
[
'label' => esc_html__( 'Images', 'elementor' ),
'tab' => $this->get_id(),
]
);
$this->add_default_globals_notice();
$this->start_controls_tabs( 'tabs_image_style' );
$this->start_controls_tab(
'tab_image_normal',
[
'label' => esc_html__( 'Normal', 'elementor' ),
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => 'image_border',
'selector' => $image_selectors,
'fields_options' => [
'color' => [
'dynamic' => [],
],
],
]
);
$this->add_responsive_control(
'image_border_radius',
[
'label' => esc_html__( 'Border Radius', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
'selectors' => [
$image_selectors => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->add_control(
'image_opacity',
[
'label' => esc_html__( 'Opacity', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'range' => [
'px' => [
'max' => 1,
'min' => 0.10,
'step' => 0.01,
],
],
'selectors' => [
$image_selectors => 'opacity: {{SIZE}};',
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => 'image_box_shadow',
'exclude' => [
'box_shadow_position',
],
'selector' => $image_selectors,
]
);
$this->add_group_control(
Group_Control_Css_Filter::get_type(),
[
'name' => 'image_css_filters',
'selector' => '{{WRAPPER}} img',
]
);
$this->end_controls_tab();
$this->start_controls_tab(
'tab_image_hover',
[
'label' => esc_html__( 'Hover', 'elementor' ),
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => 'image_hover_border',
'selector' => '{{WRAPPER}} img:hover',
'fields_options' => [
'color' => [
'dynamic' => [],
],
],
]
);
$this->add_responsive_control(
'image_hover_border_radius',
[
'label' => esc_html__( 'Border Radius', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
'selectors' => [
$image_hover_selectors => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->add_control(
'image_hover_opacity',
[
'label' => esc_html__( 'Opacity', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'range' => [
'px' => [
'max' => 1,
'min' => 0.10,
'step' => 0.01,
],
],
'selectors' => [
$image_hover_selectors => 'opacity: {{SIZE}};',
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => 'image_hover_box_shadow',
'exclude' => [
'box_shadow_position',
],
'selector' => $image_hover_selectors,
]
);
$this->add_group_control(
Group_Control_Css_Filter::get_type(),
[
'name' => 'image_hover_css_filters',
'selector' => $image_hover_selectors,
]
);
$this->add_control(
'image_hover_transition',
[
'label' => esc_html__( 'Transition Duration', 'elementor' ) . ' (s)',
'type' => Controls_Manager::SLIDER,
'range' => [
'px' => [
'min' => 0,
'max' => 3,
'step' => 0.1,
],
],
'selectors' => [
$image_selectors => 'transition-duration: {{SIZE}}s',
],
]
);
$this->end_controls_tab();
$this->end_controls_tabs();
$this->end_controls_section();
}
}
kits/documents/tabs/settings-layout.php 0000666 00000030376 15220772103 0014350 0 ustar 00 <?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Core\Breakpoints\Breakpoint;
use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
use Elementor\Plugin;
use Elementor\Controls_Manager;
use Elementor\Core\Base\Document;
use Elementor\Modules\PageTemplates\Module as PageTemplatesModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Settings_Layout extends Tab_Base {
const ACTIVE_BREAKPOINTS_CONTROL_ID = 'active_breakpoints';
public function get_id() {
return 'settings-layout';
}
public function get_title() {
return esc_html__( 'Layout', 'elementor' );
}
public function get_group() {
return 'settings';
}
public function get_icon() {
return 'eicon-layout-settings';
}
public function get_help_url() {
return 'https://go.elementor.com/global-layout/';
}
protected function register_tab_controls() {
$breakpoints_default_config = Breakpoints_Manager::get_default_config();
$breakpoint_key_mobile = Breakpoints_Manager::BREAKPOINT_KEY_MOBILE;
$breakpoint_key_tablet = Breakpoints_Manager::BREAKPOINT_KEY_TABLET;
$this->start_controls_section(
'section_' . $this->get_id(),
[
'label' => esc_html__( 'Layout Settings', 'elementor' ),
'tab' => $this->get_id(),
]
);
$this->add_responsive_control(
'container_width',
[
'label' => esc_html__( 'Content Width', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'default' => [
'size' => 1140,
],
'tablet_default' => [
'size' => $breakpoints_default_config[ $breakpoint_key_tablet ]['default_value'],
],
'mobile_default' => [
'size' => $breakpoints_default_config[ $breakpoint_key_mobile ]['default_value'],
],
'range' => [
'px' => [
'min' => 300,
'max' => 1500,
'step' => 10,
],
],
'description' => esc_html__( 'Sets the default width of the content area (Default: 1140px)', 'elementor' ),
'selectors' => [
'.elementor-section.elementor-section-boxed > .elementor-container' => 'max-width: {{SIZE}}{{UNIT}}',
'.e-con' => '--container-max-width: {{SIZE}}{{UNIT}}',
],
]
);
$is_container_active = Plugin::instance()->experiments->is_feature_active( 'container' );
if ( $is_container_active ) {
$this->add_responsive_control(
'container_padding',
[
'label' => esc_html__( 'Container Padding', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'description' => esc_html__( 'Sets the default space inside the container (Default is 10px)', 'elementor' ),
'selectors' => [
'.e-con' => '--container-default-padding-top: {{TOP}}{{UNIT}}; --container-default-padding-right: {{RIGHT}}{{UNIT}}; --container-default-padding-bottom: {{BOTTOM}}{{UNIT}}; --container-default-padding-left: {{LEFT}}{{UNIT}};',
],
]
);
}
$widgets_space_label = $is_container_active
? esc_html__( 'Gaps', 'elementor' )
: esc_html__( 'Widgets Space', 'elementor' );
$this->add_control(
'space_between_widgets',
[
'label' => $widgets_space_label,
'type' => Controls_Manager::GAPS,
'default' => [
'row' => '20',
'column' => '20',
'unit' => 'px',
],
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'placeholder' => [
'row' => '20',
'column' => '20',
],
'description' => esc_html__( 'Sets the default space between widgets (Default: 20px)', 'elementor' ),
'selectors' => $this->get_spacing_selectors(),
'conversion_map' => [
'old_key' => 'size',
'new_key' => 'column',
],
'upgrade_conversion_map' => [
'old_key' => 'size',
'new_keys' => [ 'column', 'row' ],
],
'validators' => [
'Number' => [
'min' => 0,
],
],
]
);
$this->add_control(
'page_title_selector',
[
'label' => esc_html__( 'Page Title Selector', 'elementor' ),
'type' => Controls_Manager::TEXT,
'default' => 'h1.entry-title',
'placeholder' => 'h1.entry-title',
'description' => esc_html__( 'Elementor lets you hide the page title. This works for themes that have "h1.entry-title" selector. If your theme\'s selector is different, please enter it above.', 'elementor' ),
'label_block' => true,
'ai' => [
'active' => false,
],
'selectors' => [
// Hack to convert the value into a CSS selector.
'' => '}{{VALUE}}{display: var(--page-title-display)',
],
]
);
$this->add_control(
'stretched_section_container',
[
'label' => esc_html__( 'Stretched Section Fit To', 'elementor' ),
'type' => Controls_Manager::TEXT,
'placeholder' => 'body',
'description' => esc_html__( 'Enter parent element selector to which stretched sections will fit to (e.g. #primary / .wrapper / main etc). Leave blank to fit to page width.', 'elementor' ),
'label_block' => true,
'frontend_available' => true,
'ai' => [
'active' => false,
],
]
);
/**
* @var PageTemplatesModule $page_templates_module
*/
$page_templates_module = Plugin::$instance->modules_manager->get_modules( 'page-templates' );
$page_templates = $page_templates_module->add_page_templates( [], null, null );
// Removes the Theme option from the templates because 'default' is already handled.
unset( $page_templates[ PageTemplatesModule::TEMPLATE_THEME ] );
$page_template_control_options = [
'label' => esc_html__( 'Default Page Layout', 'elementor' ),
'options' => [
// This is here because the "Theme" string is different than the default option's string.
'default' => esc_html__( 'Theme', 'elementor' ),
] + $page_templates,
];
$page_templates_module->add_template_controls( $this->parent, 'default_page_template', $page_template_control_options );
$this->end_controls_section();
$this->start_controls_section(
'section_breakpoints',
[
'label' => esc_html__( 'Breakpoints', 'elementor' ),
'tab' => $this->get_id(),
]
);
$prefix = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX;
$options = [];
foreach ( $breakpoints_default_config as $breakpoint_key => $breakpoint ) {
$options[ $prefix . $breakpoint_key ] = $breakpoint['label'];
}
if ( Plugin::$instance->experiments->is_feature_active( 'additional_custom_breakpoints' ) ) {
$active_breakpoints_control_type = Controls_Manager::SELECT2;
} else {
$active_breakpoints_control_type = Controls_Manager::HIDDEN;
}
$this->add_control(
self::ACTIVE_BREAKPOINTS_CONTROL_ID,
[
'label' => esc_html__( 'Active Breakpoints', 'elementor' ),
'type' => $active_breakpoints_control_type,
'description' => esc_html__( 'Mobile and Tablet options cannot be deleted.', 'elementor' ),
'options' => $options,
'default' => [
$prefix . $breakpoint_key_mobile,
$prefix . $breakpoint_key_tablet,
],
'select2options' => [
'allowClear' => false,
],
'lockedOptions' => [
$prefix . $breakpoint_key_mobile,
$prefix . $breakpoint_key_tablet,
],
'label_block' => true,
'render_type' => 'none',
'frontend_available' => true,
'multiple' => true,
]
);
$this->add_breakpoints_controls();
// Include the old mobile and tablet breakpoint controls as hidden for backwards compatibility.
$this->add_control( 'viewport_md', [ 'type' => Controls_Manager::HIDDEN ] );
$this->add_control( 'viewport_lg', [ 'type' => Controls_Manager::HIDDEN ] );
$this->end_controls_section();
}
private function get_spacing_selectors(): array {
$optimized_markup = Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' );
$sections_widget_spacing = $optimized_markup
? '--kit-widget-spacing: {{ROW}}{{UNIT}}'
: 'margin-block-end: {{ROW}}{{UNIT}}';
return [
'.elementor-widget:not(:last-child)' => $sections_widget_spacing,
'.elementor-element' => '--widgets-spacing: {{ROW}}{{UNIT}} {{COLUMN}}{{UNIT}};--widgets-spacing-row: {{ROW}}{{UNIT}};--widgets-spacing-column: {{COLUMN}}{{UNIT}};',
];
}
/**
* Before Save
*
* Runs Before the Kit document is saved.
*
* For backwards compatibility, when the mobile and tablet breakpoints are updated, we also update the
* old breakpoint settings ('viewport_md', 'viewport_lg' ) with the saved values + 1px. The reason 1px
* is added is because the old breakpoints system was min-width based, and the new system introduced in
* Elementor v3.2.0 is max-width based.
*
* @since 3.2.0
*
* @param array $data
* @return array $data
*/
public function before_save( array $data ) {
// When creating a default kit, $data['settings'] is empty and should remain empty, so settings.
if ( empty( $data['settings'] ) ) {
return $data;
}
$prefix = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX;
$mobile_breakpoint_key = $prefix . Breakpoints_Manager::BREAKPOINT_KEY_MOBILE;
$tablet_breakpoint_key = $prefix . Breakpoints_Manager::BREAKPOINT_KEY_TABLET;
$default_breakpoint_config = Breakpoints_Manager::get_default_config();
// Update the old mobile breakpoint. If the setting is empty, use the default value.
$data['settings'][ $prefix . 'md' ] = empty( $data['settings'][ $mobile_breakpoint_key ] )
? $default_breakpoint_config[ Breakpoints_Manager::BREAKPOINT_KEY_MOBILE ]['default_value'] + 1
: $data['settings'][ $mobile_breakpoint_key ] + 1;
// Update the old tablet breakpoint. If the setting is empty, use the default value.
$data['settings'][ $prefix . 'lg' ] = empty( $data['settings'][ $tablet_breakpoint_key ] )
? $default_breakpoint_config[ Breakpoints_Manager::BREAKPOINT_KEY_TABLET ]['default_value'] + 1
: $data['settings'][ $tablet_breakpoint_key ] + 1;
return $data;
}
public function on_save( $data ) {
if ( ! isset( $data['settings'] ) || ( isset( $data['settings']['post_status'] ) && Document::STATUS_PUBLISH !== $data['settings']['post_status'] ) ) {
return;
}
$should_compile_css = false;
$breakpoints_default_config = Breakpoints_Manager::get_default_config();
foreach ( $breakpoints_default_config as $breakpoint_key => $default_config ) {
$breakpoint_setting_key = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX . $breakpoint_key;
if ( isset( $data['settings'][ $breakpoint_setting_key ] ) ) {
$should_compile_css = true;
}
}
if ( $should_compile_css ) {
Breakpoints_Manager::compile_stylesheet_templates();
}
}
private function add_breakpoints_controls() {
$default_breakpoints_config = Breakpoints_Manager::get_default_config();
$prefix = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX;
// If the ACB experiment is inactive, only add the mobile and tablet controls.
if ( ! Plugin::$instance->experiments->is_feature_active( 'additional_custom_breakpoints' ) ) {
$default_breakpoints_config = array_intersect_key( $default_breakpoints_config, array_flip( [ Breakpoints_Manager::BREAKPOINT_KEY_MOBILE, Breakpoints_Manager::BREAKPOINT_KEY_TABLET ] ) );
}
// Add a control for each of the **default** breakpoints.
foreach ( $default_breakpoints_config as $breakpoint_key => $default_breakpoint_config ) {
$this->add_control(
'breakpoint_' . $breakpoint_key . '_heading',
[
'label' => $default_breakpoint_config['label'],
'type' => Controls_Manager::HEADING,
'separator' => 'before',
'conditions' => [
'terms' => [
[
'name' => 'active_breakpoints',
'operator' => 'contains',
'value' => $prefix . $breakpoint_key,
],
],
],
]
);
$control_config = [
'label' => esc_html__( 'Breakpoint', 'elementor' ) . ' (px)',
'type' => Controls_Manager::NUMBER,
'placeholder' => $default_breakpoint_config['default_value'],
'frontend_available' => true,
'validators' => [
'Breakpoint' => [
'breakpointName' => $breakpoint_key,
],
],
'conditions' => [
'terms' => [
[
'name' => 'active_breakpoints',
'operator' => 'contains',
'value' => $prefix . $breakpoint_key,
],
],
],
];
if ( Breakpoints_Manager::BREAKPOINT_KEY_WIDESCREEN === $breakpoint_key ) {
$control_config['description'] = esc_html__(
'Widescreen breakpoint settings will apply from the selected value and up.',
'elementor'
);
}
// Add the breakpoint Control itself.
$this->add_control( $prefix . $breakpoint_key, $control_config );
}
}
}
kits/documents/tabs/tab-base.php 0000666 00000004225 15220772103 0012645 0 ustar 00 <?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Core\Kits\Documents\Kit;
use Elementor\Core\Kits\Manager;
use Elementor\Plugin;
use Elementor\Settings;
use Elementor\Sub_Controls_Stack;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
abstract class Tab_Base extends Sub_Controls_Stack {
/**
* @var Kit
*/
protected $parent;
abstract protected function register_tab_controls();
public function get_group() {
return 'settings';
}
public function get_icon() {
return '';
}
public function get_help_url() {
return '';
}
public function get_additional_tab_content() {
return '';
}
public function register_controls() {
$this->register_tab();
$this->register_tab_controls();
}
public function on_save( $data ) {}
/**
* Before Save
*
* Allows for modifying the kit data before it is saved to the database.
*
* @param array $data
* @return array
*/
public function before_save( array $data ) {
return $data;
}
protected function register_tab() {
Controls_Manager::add_tab( $this->get_id(), $this->get_title() );
}
protected function add_default_globals_notice() {
// Get the current section config (array - section id and tab) to use for creating a unique control ID and name
$current_section = $this->parent->get_current_section();
/** @var Manager $module */
$kits_manager = Plugin::$instance->kits_manager;
if ( $kits_manager->is_custom_colors_enabled() || $kits_manager->is_custom_typography_enabled() ) {
$this->add_control(
$current_section['section'] . '_schemes_notice',
[
'name' => $current_section['section'] . '_schemes_notice',
'type' => Controls_Manager::ALERT,
'alert_type' => 'warning',
'content' => sprintf(
/* translators: 1: Link open tag, 2: Link close tag. */
esc_html__( 'In order for Theme Style to affect all relevant Elementor elements, please disable Default Colors and Fonts from the %1$sSettings Page%2$s.', 'elementor' ),
'<a href="' . Settings::get_settings_tab_url( 'general' ) . '" target="_blank">',
'</a>'
),
'render_type' => 'ui',
]
);
}
}
}
kits/documents/tabs/theme-style-buttons.php 0000666 00000013122 15220772103 0015117 0 ustar 00 <?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Background;
use Elementor\Group_Control_Border;
use Elementor\Group_Control_Box_Shadow;
use Elementor\Group_Control_Text_Shadow;
use Elementor\Group_Control_Typography;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Theme_Style_Buttons extends Tab_Base {
public function get_id() {
return 'theme-style-buttons';
}
public function get_title() {
return esc_html__( 'Buttons', 'elementor' );
}
public function get_group() {
return 'theme-style';
}
public function get_icon() {
return 'eicon-button';
}
public function get_help_url() {
return 'https://go.elementor.com/global-theme-style-buttons/';
}
protected function register_tab_controls() {
$button_selectors = [
'{{WRAPPER}} button',
'{{WRAPPER}} input[type="button"]',
'{{WRAPPER}} input[type="submit"]',
'{{WRAPPER}} .elementor-button',
];
$button_hover_selectors = [
'{{WRAPPER}} button:hover',
'{{WRAPPER}} button:focus',
'{{WRAPPER}} input[type="button"]:hover',
'{{WRAPPER}} input[type="button"]:focus',
'{{WRAPPER}} input[type="submit"]:hover',
'{{WRAPPER}} input[type="submit"]:focus',
'{{WRAPPER}} .elementor-button:hover',
'{{WRAPPER}} .elementor-button:focus',
];
$button_selector = implode( ',', $button_selectors );
$button_hover_selector = implode( ',', $button_hover_selectors );
$this->start_controls_section(
'section_buttons',
[
'label' => esc_html__( 'Buttons', 'elementor' ),
'tab' => $this->get_id(),
]
);
$this->add_default_globals_notice();
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'button_typography',
'selector' => $button_selector,
]
);
$this->add_group_control(
Group_Control_Text_Shadow::get_type(),
[
'name' => 'button_text_shadow',
'selector' => $button_selector,
]
);
$this->start_controls_tabs( 'tabs_button_style' );
$this->start_controls_tab(
'tab_button_normal',
[
'label' => esc_html__( 'Normal', 'elementor' ),
]
);
$this->add_control(
'button_text_color',
[
'label' => esc_html__( 'Text Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$button_selector => 'color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Background::get_type(),
[
'name' => 'button_background',
'types' => [ 'classic', 'gradient' ],
'exclude' => [ 'image' ],
'selector' => $button_selector,
'fields_options' => [
'background' => [
'default' => 'classic',
],
'color' => [
'dynamic' => [],
],
'color_b' => [
'dynamic' => [],
],
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => 'button_box_shadow',
'selector' => $button_selector,
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => 'button_border',
'selector' => $button_selector,
'fields_options' => [
'color' => [
'dynamic' => [],
],
],
]
);
$this->add_control(
'button_border_radius',
[
'label' => esc_html__( 'Border Radius', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
'selectors' => [
$button_selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->end_controls_tab();
$this->start_controls_tab(
'tab_button_hover',
[
'label' => esc_html__( 'Hover', 'elementor' ),
]
);
$this->add_control(
'button_hover_text_color',
[
'label' => esc_html__( 'Text Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$button_hover_selector => 'color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Background::get_type(),
[
'name' => 'button_hover_background',
'types' => [ 'classic', 'gradient' ],
'exclude' => [ 'image' ],
'selector' => $button_hover_selector,
'fields_options' => [
'background' => [
'default' => 'classic',
],
'color' => [
'dynamic' => [],
],
'color_b' => [
'dynamic' => [],
],
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => 'button_hover_box_shadow',
'selector' => $button_hover_selector,
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => 'button_hover_border',
'selector' => $button_hover_selector,
'fields_options' => [
'color' => [
'dynamic' => [],
],
],
]
);
$this->add_control(
'button_hover_border_radius',
[
'label' => esc_html__( 'Border Radius', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
'selectors' => [
$button_hover_selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->end_controls_tab();
$this->end_controls_tabs();
$this->add_responsive_control(
'button_padding',
[
'label' => esc_html__( 'Padding', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'selectors' => [
$button_selector => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
'separator' => 'before',
]
);
$this->end_controls_section();
}
}
kits/documents/tabs/settings-background.php 0000666 00000004046 15220772103 0015145 0 ustar 00 <?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Background;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Settings_Background extends Tab_Base {
public function get_id() {
return 'settings-background';
}
public function get_title() {
return esc_html__( 'Background', 'elementor' );
}
public function get_group() {
return 'settings';
}
public function get_icon() {
return 'eicon-background';
}
public function get_help_url() {
return 'https://go.elementor.com/global-background/';
}
protected function register_tab_controls() {
$this->start_controls_section(
'section_background',
[
'label' => $this->get_title(),
'tab' => $this->get_id(),
]
);
$this->add_group_control(
Group_Control_Background::get_type(),
[
'name' => 'body_background',
'types' => [ 'classic', 'gradient' ],
'selector' => '{{WRAPPER}}',
'fields_options' => [
'background' => [
'frontend_available' => true,
],
'color' => [
'dynamic' => [],
],
'color_b' => [
'dynamic' => [],
],
],
]
);
$this->add_control(
'mobile_browser_background',
[
'label' => esc_html__( 'Mobile Browser Background', 'elementor' ),
'type' => Controls_Manager::COLOR,
'description' => esc_html__( 'The `theme-color` meta tag will only be available in supported browsers and devices.', 'elementor' ),
'separator' => 'before',
]
);
$this->add_control(
'body_overscroll_behavior',
[
'label' => esc_html__( 'Overscroll Behavior', 'elementor' ),
'type' => Controls_Manager::SELECT,
'options' => [
'' => esc_html__( 'Default', 'elementor' ),
'none' => esc_html__( 'None', 'elementor' ),
'auto' => esc_html__( 'Auto', 'elementor' ),
'contain' => esc_html__( 'Contain', 'elementor' ),
],
'separator' => 'before',
'selectors' => [
'{{WRAPPER}}' => 'overscroll-behavior: {{VALUE}};',
],
]
);
$this->end_controls_section();
}
}
kits/documents/tabs/global-typography.php 0000666 00000012344 15220772103 0014634 0 ustar 00 <?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Core\Kits\Controls\Repeater as Global_Style_Repeater;
use Elementor\Group_Control_Typography;
use Elementor\Repeater;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Global_Typography extends Tab_Base {
const TYPOGRAPHY_PRIMARY = 'globals/typography?id=primary';
const TYPOGRAPHY_SECONDARY = 'globals/typography?id=secondary';
const TYPOGRAPHY_TEXT = 'globals/typography?id=text';
const TYPOGRAPHY_ACCENT = 'globals/typography?id=accent';
const TYPOGRAPHY_NAME = 'typography';
const TYPOGRAPHY_GROUP_PREFIX = self::TYPOGRAPHY_NAME . '_';
public function get_id() {
return 'global-typography';
}
public function get_title() {
return esc_html__( 'Global Fonts', 'elementor' );
}
public function get_group() {
return 'global';
}
public function get_icon() {
return 'eicon-t-letter';
}
public function get_help_url() {
return 'https://go.elementor.com/global-fonts/';
}
protected function register_tab_controls() {
$this->start_controls_section(
'section_text_style',
[
'label' => esc_html__( 'Global Fonts', 'elementor' ),
'tab' => $this->get_id(),
]
);
$repeater = new Repeater();
$repeater->add_control(
'title',
[
'type' => Controls_Manager::TEXT,
'label_block' => true,
'required' => true,
]
);
$repeater->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => self::TYPOGRAPHY_NAME,
'label' => '',
'global' => [
'active' => false,
],
'fields_options' => [
'font_family' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-family: "{{VALUE}}"',
],
],
'font_size' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-size: {{SIZE}}{{UNIT}}',
],
],
'font_weight' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-weight: {{VALUE}}',
],
],
'text_transform' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-text-transform: {{VALUE}}',
],
],
'font_style' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-style: {{VALUE}}',
],
],
'text_decoration' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-text-decoration: {{VALUE}}',
],
],
'line_height' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-line-height: {{SIZE}}{{UNIT}}',
],
],
'letter_spacing' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-letter-spacing: {{SIZE}}{{UNIT}}',
],
],
'word_spacing' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-word-spacing: {{SIZE}}{{UNIT}}',
],
],
],
]
);
$typography_key = self::TYPOGRAPHY_GROUP_PREFIX . 'typography';
$font_family_key = self::TYPOGRAPHY_GROUP_PREFIX . 'font_family';
$font_weight_key = self::TYPOGRAPHY_GROUP_PREFIX . 'font_weight';
$default_typography = [
[
'_id' => 'primary',
'title' => esc_html__( 'Primary', 'elementor' ),
$typography_key => 'custom',
$font_family_key => 'Roboto',
$font_weight_key => '600',
],
[
'_id' => 'secondary',
'title' => esc_html__( 'Secondary', 'elementor' ),
$typography_key => 'custom',
$font_family_key => 'Roboto Slab',
$font_weight_key => '400',
],
[
'_id' => 'text',
'title' => esc_html__( 'Text', 'elementor' ),
$typography_key => 'custom',
$font_family_key => 'Roboto',
$font_weight_key => '400',
],
[
'_id' => 'accent',
'title' => esc_html__( 'Accent', 'elementor' ),
$typography_key => 'custom',
$font_family_key => 'Roboto',
$font_weight_key => '500',
],
];
$this->add_control(
'heading_system_typography',
[
'type' => Controls_Manager::HEADING,
'label' => esc_html__( 'System Fonts', 'elementor' ),
]
);
$this->add_control(
'system_typography',
[
'type' => Global_Style_Repeater::CONTROL_TYPE,
'fields' => $repeater->get_controls(),
'default' => $default_typography,
'item_actions' => [
'add' => false,
'remove' => false,
],
'separator' => 'after',
]
);
$this->add_control(
'heading_custom_typography',
[
'type' => Controls_Manager::HEADING,
'label' => esc_html__( 'Custom Fonts', 'elementor' ),
]
);
$this->add_control(
'custom_typography',
[
'type' => Global_Style_Repeater::CONTROL_TYPE,
'fields' => $repeater->get_controls(),
]
);
$this->add_control(
'default_generic_fonts',
[
'label' => esc_html__( 'Fallback Font Family', 'elementor' ),
'type' => Controls_Manager::TEXT,
'default' => 'Sans-serif',
'description' => esc_html__( 'The list of fonts used if the chosen font is not available.', 'elementor' ),
'label_block' => true,
'separator' => 'before',
'ai' => [
'active' => false,
],
]
);
$this->end_controls_section();
}
}
kits/documents/tabs/global-colors.php 0000666 00000005372 15220772103 0013732 0 ustar 00 <?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Core\Kits\Controls\Repeater as Global_Style_Repeater;
use Elementor\Repeater;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Global_Colors extends Tab_Base {
const COLOR_PRIMARY = 'globals/colors?id=primary';
const COLOR_SECONDARY = 'globals/colors?id=secondary';
const COLOR_TEXT = 'globals/colors?id=text';
const COLOR_ACCENT = 'globals/colors?id=accent';
public function get_id() {
return 'global-colors';
}
public function get_title() {
return esc_html__( 'Global Colors', 'elementor' );
}
public function get_group() {
return 'global';
}
public function get_icon() {
return 'eicon-global-colors';
}
public function get_help_url() {
return 'https://go.elementor.com/global-colors/';
}
protected function register_tab_controls() {
$this->start_controls_section(
'section_global_colors',
[
'label' => esc_html__( 'Global Colors', 'elementor' ),
'tab' => $this->get_id(),
]
);
$repeater = new Repeater();
$repeater->add_control(
'title',
[
'type' => Controls_Manager::TEXT,
'label_block' => true,
'required' => true,
]
);
// Color Value
$repeater->add_control(
'color',
[
'type' => Controls_Manager::COLOR,
'label_block' => true,
'selectors' => [
'{{WRAPPER}}' => '--e-global-color-{{_id.VALUE}}: {{VALUE}}',
],
'global' => [
'active' => false,
],
]
);
$default_colors = [
[
'_id' => 'primary',
'title' => esc_html__( 'Primary', 'elementor' ),
'color' => '#6EC1E4',
],
[
'_id' => 'secondary',
'title' => esc_html__( 'Secondary', 'elementor' ),
'color' => '#54595F',
],
[
'_id' => 'text',
'title' => esc_html__( 'Text', 'elementor' ),
'color' => '#7A7A7A',
],
[
'_id' => 'accent',
'title' => esc_html__( 'Accent', 'elementor' ),
'color' => '#61CE70',
],
];
$this->add_control(
'heading_system_colors',
[
'type' => Controls_Manager::HEADING,
'label' => esc_html__( 'System Colors', 'elementor' ),
]
);
$this->add_control(
'system_colors',
[
'type' => Global_Style_Repeater::CONTROL_TYPE,
'fields' => $repeater->get_controls(),
'default' => $default_colors,
'item_actions' => [
'add' => false,
'remove' => false,
],
'separator' => 'after',
]
);
$this->add_control(
'heading_custom_colors',
[
'type' => Controls_Manager::HEADING,
'label' => esc_html__( 'Custom Colors', 'elementor' ),
]
);
$this->add_control(
'custom_colors',
[
'type' => Global_Style_Repeater::CONTROL_TYPE,
'fields' => $repeater->get_controls(),
]
);
$this->end_controls_section();
}
}
kits/documents/tabs/settings-lightbox.php 0000666 00000011401 15220772103 0014637 0 ustar 00 <?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Settings_Lightbox extends Tab_Base {
public function get_id() {
return 'settings-lightbox';
}
public function get_title() {
return esc_html__( 'Lightbox', 'elementor' );
}
public function get_group() {
return 'settings';
}
public function get_icon() {
return 'eicon-lightbox-expand';
}
public function get_help_url() {
return 'https://go.elementor.com/global-lightbox/';
}
protected function register_tab_controls() {
$this->start_controls_section(
'section_' . $this->get_id(),
[
'label' => $this->get_title(),
'tab' => $this->get_id(),
]
);
$this->add_control(
'global_image_lightbox',
[
'label' => esc_html__( 'Image Lightbox', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'description' => esc_html__( 'Open all image links in a lightbox popup window. The lightbox will automatically work on any link that leads to an image file.', 'elementor' ),
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_enable_counter',
[
'label' => esc_html__( 'Counter', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_enable_fullscreen',
[
'label' => esc_html__( 'Fullscreen', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_enable_zoom',
[
'label' => esc_html__( 'Zoom', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_enable_share',
[
'label' => esc_html__( 'Share', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_title_src',
[
'label' => esc_html__( 'Title', 'elementor' ),
'type' => Controls_Manager::SELECT,
'options' => [
'' => esc_html__( 'None', 'elementor' ),
'title' => esc_html__( 'Title', 'elementor' ),
'caption' => esc_html__( 'Caption', 'elementor' ),
'alt' => esc_html__( 'Alt', 'elementor' ),
'description' => esc_html__( 'Description', 'elementor' ),
],
'default' => 'title',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_description_src',
[
'label' => esc_html__( 'Description', 'elementor' ),
'type' => Controls_Manager::SELECT,
'options' => [
'' => esc_html__( 'None', 'elementor' ),
'title' => esc_html__( 'Title', 'elementor' ),
'caption' => esc_html__( 'Caption', 'elementor' ),
'alt' => esc_html__( 'Alt', 'elementor' ),
'description' => esc_html__( 'Description', 'elementor' ),
],
'default' => 'description',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_color',
[
'label' => esc_html__( 'Background Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'.elementor-lightbox' => 'background-color: {{VALUE}}',
],
]
);
$this->add_control(
'lightbox_ui_color',
[
'label' => esc_html__( 'UI Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'.elementor-lightbox' => '--lightbox-ui-color: {{VALUE}}',
],
]
);
$this->add_control(
'lightbox_ui_color_hover',
[
'label' => esc_html__( 'UI Hover Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'.elementor-lightbox' => '--lightbox-ui-color-hover: {{VALUE}}',
],
]
);
$this->add_control(
'lightbox_text_color',
[
'label' => esc_html__( 'Text Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'.elementor-lightbox' => '--lightbox-text-color: {{VALUE}}',
],
]
);
$this->add_control(
'lightbox_icons_size',
[
'label' => esc_html__( 'Toolbar Icons Size', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'vw', 'custom' ],
'selectors' => [
'.elementor-lightbox' => '--lightbox-header-icons-size: {{SIZE}}{{UNIT}}',
],
'separator' => 'before',
]
);
$this->add_control(
'lightbox_slider_icons_size',
[
'label' => esc_html__( 'Navigation Icons Size', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'vw', 'custom' ],
'selectors' => [
'.elementor-lightbox' => '--lightbox-navigation-icons-size: {{SIZE}}{{UNIT}}',
],
'separator' => 'before',
]
);
$this->end_controls_section();
}
}
kits/documents/kit.php 0000666 00000012562 15220772103 0011030 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\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_name
*/
public function register_tab( $id, $class_name ) {
$this->tabs[ $id ] = new $class_name( $this );
}
/**
* @inheritDoc
*/
protected function get_initial_config() {
$config = parent::get_initial_config();
foreach ( $this->tabs as $id => $tab ) {
$config['tabs'][ $id ] = [
'id' => $id,
'title' => $tab->get_title(),
'icon' => $tab->get_icon(),
'group' => $tab->get_group(),
'helpUrl' => $tab->get_help_url(),
'additionalContent' => $tab->get_additional_tab_content(),
];
}
return $config;
}
/**
* @since 3.1.0
* @access protected
*/
protected function register_controls() {
$this->register_document_controls();
foreach ( $this->tabs as $tab ) {
$tab->register_controls();
}
}
protected function get_post_statuses() {
return [
'draft' => sprintf( '%s (%s)', esc_html__( 'Disabled', 'elementor' ), esc_html__( 'Draft', 'elementor' ) ),
'publish' => esc_html__( 'Published', 'elementor' ),
];
}
public function add_repeater_row( $control_id, $item ) {
$meta_key = PageManager::META_KEY;
$document_settings = $this->get_meta( $meta_key );
if ( ! $document_settings ) {
$document_settings = [];
}
if ( ! isset( $document_settings[ $control_id ] ) ) {
$document_settings[ $control_id ] = [];
}
$document_settings[ $control_id ][] = $item;
$page_settings_manager = SettingsManager::get_settings_managers( 'page' );
$page_settings_manager->save_settings( $document_settings, $this->get_id() );
/** @var Kit $autosave */
$autosave = $this->get_autosave();
if ( $autosave ) {
$autosave->add_repeater_row( $control_id, $item );
}
// Remove Post CSS.
$post_css = Post_CSS::create( $this->post->ID );
$post_css->delete();
// Refresh Cache.
Plugin::$instance->documents->get( $this->post->ID, false );
$post_css = Post_CSS::create( $this->post->ID );
$post_css->enqueue();
}
/**
* Register default tabs (menu pages) for site settings.
*/
private function register_tabs() {
$tabs = [
'global-colors' => Tabs\Global_Colors::class,
'global-typography' => Tabs\Global_Typography::class,
'theme-style-typography' => Tabs\Theme_Style_Typography::class,
'theme-style-buttons' => Tabs\Theme_Style_Buttons::class,
'theme-style-images' => Tabs\Theme_Style_Images::class,
'theme-style-form-fields' => Tabs\Theme_Style_Form_Fields::class,
'settings-site-identity' => Tabs\Settings_Site_Identity::class,
'settings-background' => Tabs\Settings_Background::class,
'settings-layout' => Tabs\Settings_Layout::class,
'settings-lightbox' => Tabs\Settings_Lightbox::class,
'settings-page-transitions' => Tabs\Settings_Page_Transitions::class,
'settings-custom-css' => Tabs\Settings_Custom_CSS::class,
];
foreach ( $tabs as $id => $class ) {
$this->register_tab( $id, $class );
}
do_action( 'elementor/kit/register_tabs', $this );
}
}
modules-manager.php 0000666 00000006650 15220772103 0010347 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',
'wp-rest',
'safe-mode',
'ai',
'notifications',
'usage',
'dev-tools',
'landing-pages',
'compatibility-tag',
'generator-tag',
'elements-color-picker',
'elementor-counter',
'shapes',
'favorites',
'admin-top-bar',
'element-manager',
'pro-free-trial-popup',
'nested-elements',
// Depends on Nested Elements module
'nested-tabs',
'nested-accordion',
'container-converter',
'web-cli',
'promotions',
'pro-install',
'notes',
'performance-lab',
'lazyload',
'image-loading-optimization',
'kit-elements-defaults',
'announcements',
'editor-app-bar',
'site-navigation',
'styleguide',
'element-cache',
'apps',
'home',
'link-in-bio',
'floating-buttons',
'content-sanitizer',
'atomic-widgets',
'global-classes',
'variables',
'wc-product-editor',
'checklist',
'cloud-library',
'cloud-kit-library',
'atomic-opt-in',
'components',
];
}
/**
* 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';
}
}
isolation/elementor-adapter-interface.php 0000666 00000000760 15220772103 0014632 0 ustar 00 <?php
namespace Elementor\Core\Isolation;
interface Elementor_Adapter_Interface {
public function get_kit_settings();
public function get_main_post();
public function is_active_kit_default(): bool;
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;
public function get_template_type( $template_id ): string;
public function get_tier(): string;
}
isolation/wordpress-adapter-interface.php 0000666 00000001734 15220772103 0014672 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 get_user_preferences( $preference_key );
public function set_user_preferences( $preference_key, $value );
public function is_new_installation();
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 );
public function get_posts( $args );
public function get_post_types( $args = [], $output = 'names', $operator = 'and' );
}
isolation/wordpress-adapter.php 0000666 00000004766 15220772103 0012744 0 ustar 00 <?php
namespace Elementor\Core\Isolation;
use Elementor\Core\Settings\Manager;
use Elementor\Core\Upgrade\Manager as Upgrade_Manager;
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 get_user_preferences( $preference_key ) {
return Manager::get_settings_managers( 'editorPreferences' )
->get_model()
->get_settings( $preference_key );
}
public function set_user_preferences( $preference_key, $value ) {
Manager::get_settings_managers( 'editorPreferences' )
->get_model()
->set_settings( $preference_key, $value );
}
public function is_new_installation() {
return Upgrade_Manager::is_new_installation();
}
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 );
}
public function get_posts( $args ): array {
return get_posts( $args );
}
public function get_post_types( $args = [], $output = 'names', $operator = 'and' ): array {
return get_post_types( $args, $output, $operator );
}
}
base/traits/shared-widget-controls-trait.php 0000666 00000016014 15220772103 0015213 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 ); ?>"
aria-hidden="true"
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 0000666 00000025474 15220772103 0016220 0 ustar 00 <?php
namespace Elementor\Core\Base\BackgroundProcess;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Link 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, [ $this, 'handle_cron_healthcheck' ] );
add_filter( 'cron_schedules', [ $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() . wp_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' ] = [
'interval' => MINUTE_IN_SECONDS * $interval,
'display' => sprintf(
/* translators: %d: Interval in minutes. */
esc_html__( 'Every %d minutes', 'elementor' ),
$interval,
),
];
return $schedules;
}
/**
* Handle cron healthcheck
*
* Restart the background process if not already running
* and data exists in the queue.
*/
public function handle_cron_healthcheck() {
if ( $this->is_process_running() ) {
// Background process already running.
exit;
}
if ( $this->is_queue_empty() ) {
// No data to process.
$this->clear_scheduled_event();
exit;
}
$this->handle();
exit;
}
/**
* Schedule event
*/
protected function schedule_event() {
if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
}
}
/**
* Clear scheduled event
*/
protected function clear_scheduled_event() {
$timestamp = wp_next_scheduled( $this->cron_hook_identifier );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
}
}
/**
* Cancel Process
*
* Stop processing queue items, clear cronjob and delete batch.
*/
public function cancel_process() {
if ( ! $this->is_queue_empty() ) {
$batch = $this->get_batch();
$this->delete( $batch->key );
wp_clear_scheduled_hook( $this->cron_hook_identifier );
}
}
/**
* Task
*
* Override this method to perform any actions required on each
* queue item. Return the modified item for further processing
* in the next pass through. Or, return false to remove the
* item from the queue.
*
* @param mixed $item Queue item to iterate over.
*
* @return mixed
*/
abstract protected function task( $item );
}
base/background-process/wp-async-request.php 0000666 00000005553 15220772103 0015224 0 ustar 00 <?php
namespace Elementor\Core\Base\BackgroundProcess;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Link 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: [])
*
* @var array
* @access protected
*/
protected $data = [];
/**
* Initiate new async request
*/
public function __construct() {
$this->identifier = $this->prefix . '_' . $this->action;
add_action( 'wp_ajax_' . $this->identifier, [ $this, 'maybe_handle' ] );
add_action( 'wp_ajax_nopriv_' . $this->identifier, [ $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 [
'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 [
'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/base-object.php 0000666 00000013071 15220772103 0010352 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;
}
}
editor/data/globals/endpoints/typography.php 0000666 00000003137 15220772103 0015317 0 ustar 00 <?php
namespace Elementor\Core\Editor\Data\Globals\Endpoints;
use Elementor\Plugin;
class Typography extends Base {
public function get_name() {
return 'typography';
}
public function get_format() {
return 'globals/typography/{id}';
}
protected function get_kit_items() {
$result = [];
$kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend();
// Use raw settings that doesn't have default values.
$kit_raw_settings = $kit->get_data( 'settings' );
if ( isset( $kit_raw_settings['system_typography'] ) ) {
$system_items = $kit_raw_settings['system_typography'];
} else {
// Get default items, but without empty defaults.
$control = $kit->get_controls( 'system_typography' );
$system_items = $control['default'];
}
$custom_items = $kit->get_settings( 'custom_typography' );
if ( ! $custom_items ) {
$custom_items = [];
}
$items = array_merge( $system_items, $custom_items );
foreach ( $items as $index => &$item ) {
foreach ( $item as $setting => $value ) {
$new_setting = str_replace( 'styles_', '', $setting, $count );
if ( $count ) {
$item[ $new_setting ] = $value;
unset( $item[ $setting ] );
}
}
$id = $item['_id'];
$result[ $id ] = [
'title' => $item['title'] ?? '',
'id' => $id,
];
unset( $item['_id'], $item['title'] );
$result[ $id ]['value'] = $item;
}
return $result;
}
protected function convert_db_format( $item ) {
$db_format = [
'_id' => $item['id'],
'title' => $item['title'] ?? '',
];
$db_format = array_merge( $item['value'], $db_format );
return $db_format;
}
}
editor/loader/v2/editor-v2-loader.php 0000666 00000012753 15220772103 0013432 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;
use Elementor\Modules\AtomicWidgets\Image\Placeholder_Image;
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-ui',
'editor-v1-adapters',
self::ENV_PACKAGE,
'http-client',
'icons',
'locations',
'menus',
'query',
'schema',
'store',
'session',
'twing',
'ui',
'utils',
'wp-media',
'editor-current-user',
];
const EXTENSIONS = [
'editor-documents',
'editor-notifications',
'editor-panels',
'editor-elements-panel',
];
/**
* 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', [
'@elementor/http-client' => [
'base_url' => rest_url(),
'headers' => [
'X-WP-Nonce' => wp_create_nonce( 'wp_rest' ),
],
],
'@elementor/editor-controls' => [
'background_placeholder_image' => Placeholder_Image::get_background_placeholder_image(),
],
] );
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', self::EXTENSIONS );
}
private function get_styles(): array {
$styles = apply_filters( 'elementor/editor/v2/styles', [] );
return Collection::make( $styles )
->unique()
->all();
}
}
editor/loader/v1/editor-v1-loader.php 0000666 00000004073 15220772103 0013424 0 ustar 00 <?php
namespace Elementor\Core\Editor\Loader\V1;
use Elementor\Core\Editor\Loader\Common\Editor_Common_Scripts_Settings;
use Elementor\Core\Editor\Loader\Editor_Base_Loader;
use Elementor\Plugin;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Editor_V1_Loader extends Editor_Base_Loader {
/**
* @return void
*/
public function init() {
// Loading UI and Icons v2 scrips for the use of new features that should live in V1.
$packages_to_register = [ 'ui', 'icons', 'query' ];
foreach ( $packages_to_register as $package ) {
$this->assets_config_provider->load( $package );
}
}
/**
* @return void
*/
public function register_scripts() {
parent::register_scripts();
$assets_url = $this->config->get( 'assets_url' );
$min_suffix = $this->config->get( 'min_suffix' );
foreach ( $this->assets_config_provider->all() as $package => $config ) {
wp_register_script(
$config['handle'],
"{$assets_url}js/packages/{$package}/{$package}{$min_suffix}.js",
$config['deps'],
ELEMENTOR_VERSION,
true
);
}
wp_register_script(
'elementor-editor-loader-v1',
"{$assets_url}js/editor-loader-v1{$min_suffix}.js",
[ 'elementor-editor' ],
ELEMENTOR_VERSION,
true
);
}
/**
* @return void
*/
public function enqueue_scripts() {
parent::enqueue_scripts();
// Must be last.
wp_enqueue_script( 'elementor-editor-loader-v1' );
Utils::print_js_config(
'elementor-editor',
'ElementorConfig',
Editor_Common_Scripts_Settings::get()
);
}
/**
* @return void
*/
public function print_root_template() {
// Exposing the path for the view part to render the body of the editor template.
$body_file_path = __DIR__ . '/templates/editor-body-v1-view.php';
include ELEMENTOR_PATH . 'includes/editor-templates/editor-wrapper.php';
}
/**
* @return void
*/
public function register_additional_templates() {
parent::register_additional_templates();
Plugin::$instance->common->add_template( ELEMENTOR_PATH . 'includes/editor-templates/responsive-bar.php' );
}
}
editor/loader/v1/js/editor-loader-v1.js 0000666 00000000032 15220772103 0013654 0 ustar 00 window.elementor.start();
editor/loader/editor-base-loader.php 0000666 00000013213 15220772103 0013456 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" );
}
}
}
breakpoints/manager.php 0000666 00000036633 15220772103 0011226 0 ustar 00 <?php
namespace Elementor\Core\Breakpoints;
use Elementor\Core\Base\Module;
use Elementor\Core\Kits\Documents\Tabs\Settings_Layout;
use Elementor\Core\Responsive\Files\Frontend;
use Elementor\Modules\DevTools\Deprecation;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Manager extends Module {
const BREAKPOINT_SETTING_PREFIX = 'viewport_';
const BREAKPOINT_KEY_MOBILE = 'mobile';
const BREAKPOINT_KEY_MOBILE_EXTRA = 'mobile_extra';
const BREAKPOINT_KEY_TABLET = 'tablet';
const BREAKPOINT_KEY_TABLET_EXTRA = 'tablet_extra';
const BREAKPOINT_KEY_LAPTOP = 'laptop';
const BREAKPOINT_KEY_DESKTOP = 'desktop';
const BREAKPOINT_KEY_WIDESCREEN = 'widescreen';
/**
* Breakpoints
*
* An array containing instances of the all of the system's available breakpoints.
*
* @since 3.2.0
* @access private
*
* @var Breakpoint[]
*/
private $breakpoints;
/**
* Active Breakpoints
*
* An array containing instances of the enabled breakpoints.
*
* @since 3.2.0
* @access private
*
* @var Breakpoint[]
*/
private $active_breakpoints;
/**
* Responsive Control Duplication Mode.
*
* Determines the current responsive control generation mode.
* Options are:
* -- 'on': Responsive controls are duplicated in `add_responsive_control()`.
* -- 'off': Responsive controls are NOT duplicated in `add_responsive_control()`.
* -- 'dynamic': Responsive controls are only duplicated if their config contains `'dynamic' => 'active' => true`.
*
* When generating Post CSS, the mode is set to 'on'. When generating Dynamic CSS, the mode is set to 'dynamic'.
*
* default value is 'off'.
*
* @since 3.4.0
* @access private
*
* @var string
*/
private $responsive_control_duplication_mode = 'off';
private $icons_map;
/**
* Has Custom Breakpoints
*
* A flag that holds a cached value that indicates if there are active custom-breakpoints.
*
* @since 3.5.0
* @access private
*
* @var boolean
*/
private $has_custom_breakpoints;
public function get_name() {
return 'breakpoints';
}
/**
* Get Breakpoints
*
* Retrieve the array containing instances of all breakpoints existing in the system, or a single breakpoint if a
* name is passed.
*
* @since 3.2.0
*
* @param $breakpoint_name
* @return Breakpoint[]|Breakpoint
*/
public function get_breakpoints( $breakpoint_name = null ) {
if ( ! $this->breakpoints ) {
$this->init_breakpoints();
}
return self::get_items( $this->breakpoints, $breakpoint_name );
}
/**
* Get Active Breakpoints
*
* Retrieve the array of --enabled-- breakpoints, or a single breakpoint if a name is passed.
*
* @since 3.2.0
*
* @param string|null $breakpoint_name
* @return Breakpoint[]|Breakpoint
*/
public function get_active_breakpoints( $breakpoint_name = null ) {
if ( ! $this->active_breakpoints ) {
$this->init_active_breakpoints();
}
return self::get_items( $this->active_breakpoints, $breakpoint_name );
}
/**
* Get Active Devices List
*
* Retrieve an array containing the keys of all active devices, including 'desktop'.
*
* @since 3.2.0
*
* @param array $args
* @return array
*/
public function get_active_devices_list( $args = [] ) {
$default_args = [
'add_desktop' => true,
'reverse' => false,
'desktop_first' => false,
];
$args = array_merge( $default_args, $args );
$active_devices = array_keys( Plugin::$instance->breakpoints->get_active_breakpoints() );
if ( $args['add_desktop'] ) {
// Insert the 'desktop' device in the correct position.
if ( ! $args['desktop_first'] && in_array( 'widescreen', $active_devices, true ) ) {
$widescreen_index = array_search( 'widescreen', $active_devices, true );
array_splice( $active_devices, $widescreen_index, 0, [ 'desktop' ] );
} else {
$active_devices[] = 'desktop';
}
}
if ( $args['reverse'] ) {
$active_devices = array_reverse( $active_devices );
}
return $active_devices;
}
/** Has Custom Breakpoints
*
* Checks whether there are currently custom breakpoints saved in the database.
* Returns true if there are breakpoint values saved in the active kit.
* If the user has activated any additional custom breakpoints (mobile extra, tablet extra, laptop, widescreen) -
* that is considered as having custom breakpoints.
*
* @since 3.2.0
*
* @return boolean
*/
public function has_custom_breakpoints() {
if ( isset( $this->has_custom_breakpoints ) ) {
return $this->has_custom_breakpoints;
}
$breakpoints = $this->get_active_breakpoints();
$additional_breakpoints = [
self::BREAKPOINT_KEY_MOBILE_EXTRA,
self::BREAKPOINT_KEY_TABLET_EXTRA,
self::BREAKPOINT_KEY_LAPTOP,
self::BREAKPOINT_KEY_WIDESCREEN,
];
foreach ( $breakpoints as $breakpoint_name => $breakpoint ) {
if ( in_array( $breakpoint_name, $additional_breakpoints, true ) ) {
$this->has_custom_breakpoints = true;
return true;
}
/** @var Breakpoint $breakpoint */
if ( $breakpoint->is_custom() ) {
$this->has_custom_breakpoints = true;
return true;
}
}
$this->has_custom_breakpoints = false;
return false;
}
/**
* Get Device Min Breakpoint
*
* For a given device, return the minimum possible breakpoint. Except for the cases of mobile and widescreen
* devices, A device's min breakpoint is determined by the previous device's max breakpoint + 1px.
*
* @since 3.2.0
*
* @param string $device_name
* @return int the min breakpoint of the passed device
*/
public function get_device_min_breakpoint( $device_name ) {
if ( 'desktop' === $device_name ) {
return $this->get_desktop_min_point();
}
$active_breakpoints = $this->get_active_breakpoints();
$current_device_breakpoint = $active_breakpoints[ $device_name ];
// Since this method is called multiple times, usage of class variables is to memory and processing time.
// Get only the keys for active breakpoints.
$breakpoint_keys = array_keys( $active_breakpoints );
if ( $breakpoint_keys[0] === $device_name ) {
// For the lowest breakpoint, the min point is always 320.
$min_breakpoint = 320;
} elseif ( 'min' === $current_device_breakpoint->get_direction() ) {
// 'min-width' breakpoints only have a minimum point. The breakpoint value itself the device min point.
$min_breakpoint = $current_device_breakpoint->get_value();
} else {
// This block handles all other devices.
$device_name_index = array_search( $device_name, $breakpoint_keys, true );
$previous_index = $device_name_index - 1;
$previous_breakpoint_key = $breakpoint_keys[ $previous_index ];
/** @var Breakpoint $previous_breakpoint */
$previous_breakpoint = $active_breakpoints[ $previous_breakpoint_key ];
$min_breakpoint = $previous_breakpoint->get_value() + 1;
}
return $min_breakpoint;
}
/**
* Get Desktop Min Breakpoint
*
* Returns the minimum possible breakpoint for the default (desktop) device.
*
* @since 3.2.0
*
* @return int the min breakpoint of the passed device
*/
public function get_desktop_min_point() {
$active_breakpoints = $this->get_active_breakpoints();
$desktop_previous_device = $this->get_desktop_previous_device_key();
return $active_breakpoints[ $desktop_previous_device ]->get_value() + 1;
}
public function refresh() {
unset( $this->has_custom_breakpoints );
$this->init_breakpoints();
$this->init_active_breakpoints();
}
/**
* Get Responsive Icons Classes Map
*
* If a $device parameter is passed, this method retrieves the device's icon class list (the ones attached to the `<i>`
* element). If no parameter is passed, it returns an array of devices containing each device's icon class list.
*
* This method was created because 'mobile_extra' and 'tablet_extra' breakpoint icons need to be tilted by 90
* degrees, and this tilt is achieved in CSS via the class `eicon-tilted`.
*
* @since 3.4.0
*
* @return array|string
*/
public function get_responsive_icons_classes_map( $device = null ) {
if ( ! $this->icons_map ) {
$this->icons_map = [
'mobile' => 'eicon-device-mobile',
'mobile_extra' => 'eicon-device-mobile eicon-tilted',
'tablet' => 'eicon-device-tablet',
'tablet_extra' => 'eicon-device-tablet eicon-tilted',
'laptop' => 'eicon-device-laptop',
'desktop' => 'eicon-device-desktop',
'widescreen' => 'eicon-device-wide',
];
}
return self::get_items( $this->icons_map, $device );
}
/**
* Get Default Config
*
* Retrieve the default breakpoints config array. The 'selector' property is used for CSS generation (the
* Stylesheet::add_device() method).
*
* @return array
*/
public static function get_default_config() {
return [
self::BREAKPOINT_KEY_MOBILE => [
'label' => esc_html__( 'Mobile Portrait', 'elementor' ),
'default_value' => 767,
'direction' => 'max',
],
self::BREAKPOINT_KEY_MOBILE_EXTRA => [
'label' => esc_html__( 'Mobile Landscape', 'elementor' ),
'default_value' => 880,
'direction' => 'max',
],
self::BREAKPOINT_KEY_TABLET => [
'label' => esc_html__( 'Tablet Portrait', 'elementor' ),
'default_value' => 1024,
'direction' => 'max',
],
self::BREAKPOINT_KEY_TABLET_EXTRA => [
'label' => esc_html__( 'Tablet Landscape', 'elementor' ),
'default_value' => 1200,
'direction' => 'max',
],
self::BREAKPOINT_KEY_LAPTOP => [
'label' => esc_html__( 'Laptop', 'elementor' ),
'default_value' => 1366,
'direction' => 'max',
],
self::BREAKPOINT_KEY_WIDESCREEN => [
'label' => esc_html__( 'Widescreen', 'elementor' ),
'default_value' => 2400,
'direction' => 'min',
],
];
}
/**
* Get Breakpoints Config
*
* Iterates over an array of all of the system's breakpoints (both active and inactive), queries each breakpoint's
* class instance, and generates an array containing data on each breakpoint: its label, current value, direction
* ('min'/'max') and whether it is enabled or not.
*
* @return array
*/
public function get_breakpoints_config() {
$breakpoints = $this->get_breakpoints();
$config = [];
foreach ( $breakpoints as $breakpoint_name => $breakpoint ) {
$config[ $breakpoint_name ] = [
'label' => $breakpoint->get_label(),
'value' => $breakpoint->get_value(),
'default_value' => $breakpoint->get_default_value(),
'direction' => $breakpoint->get_direction(),
'is_enabled' => $breakpoint->is_enabled(),
];
}
return $config;
}
/**
* Get Responsive Control Duplication Mode
*
* Retrieve the value of the $responsive_control_duplication_mode private class variable.
* See the variable's PHPDoc for details.
*
* @since 3.4.0
* @access public
*/
public function get_responsive_control_duplication_mode() {
return $this->responsive_control_duplication_mode;
}
/**
* Set Responsive Control Duplication Mode
*
* Sets the value of the $responsive_control_duplication_mode private class variable.
* See the variable's PHPDoc for details.
*
* @since 3.4.0
*
* @access public
* @param string $mode
*/
public function set_responsive_control_duplication_mode( $mode ) {
$this->responsive_control_duplication_mode = $mode;
}
/**
* Get Stylesheet Templates Path
*
* @since 3.2.0
* @access public
* @static
*/
public static function get_stylesheet_templates_path() {
return ELEMENTOR_ASSETS_PATH . 'css/templates/';
}
/**
* Compile Stylesheet Templates
*
* @since 3.2.0
* @access public
* @static
*/
public static function compile_stylesheet_templates() {
foreach ( self::get_stylesheet_templates() as $file_name => $template_path ) {
$file = new Frontend( $file_name, $template_path );
$file->update();
}
}
/**
* Init Breakpoints
*
* Creates the breakpoints array, containing instances of each breakpoint. Returns an array of ALL breakpoints,
* both enabled and disabled.
*
* @since 3.2.0
*/
private function init_breakpoints() {
$breakpoints = [];
$setting_prefix = self::BREAKPOINT_SETTING_PREFIX;
$active_breakpoint_keys = [
$setting_prefix . self::BREAKPOINT_KEY_MOBILE,
$setting_prefix . self::BREAKPOINT_KEY_TABLET,
];
if ( Plugin::$instance->experiments->is_feature_active( 'additional_custom_breakpoints' ) ) {
$kit_active_id = Plugin::$instance->kits_manager->get_active_id();
// Get the breakpoint settings saved in the kit directly from the DB to avoid initializing the kit too early.
$raw_kit_settings = get_post_meta( $kit_active_id, '_elementor_page_settings', true );
// If there is an existing kit with an active breakpoints value saved, use it.
if ( isset( $raw_kit_settings[ Settings_Layout::ACTIVE_BREAKPOINTS_CONTROL_ID ] ) ) {
$active_breakpoint_keys = $raw_kit_settings[ Settings_Layout::ACTIVE_BREAKPOINTS_CONTROL_ID ];
}
}
$default_config = self::get_default_config();
foreach ( $default_config as $breakpoint_name => $breakpoint_config ) {
$args = [ 'name' => $breakpoint_name ] + $breakpoint_config;
// Make sure the two default breakpoints (mobile, tablet) are always enabled.
if ( self::BREAKPOINT_KEY_MOBILE === $breakpoint_name || self::BREAKPOINT_KEY_TABLET === $breakpoint_name ) {
// Make sure the default Mobile and Tablet breakpoints are always enabled.
$args['is_enabled'] = true;
} else {
// If the breakpoint is in the active breakpoints array, make sure it's instantiated as enabled.
$args['is_enabled'] = in_array( $setting_prefix . $breakpoint_name, $active_breakpoint_keys, true );
}
$breakpoints[ $breakpoint_name ] = new Breakpoint( $args );
}
$this->breakpoints = $breakpoints;
}
/**
* Init Active Breakpoints
*
* Create/Refresh the array of --enabled-- breakpoints.
*
* @since 3.2.0
*/
private function init_active_breakpoints() {
$this->active_breakpoints = array_filter( $this->get_breakpoints(), function( $breakpoint ) {
/** @var Breakpoint $breakpoint */
return $breakpoint->is_enabled();
} );
}
private function get_desktop_previous_device_key() {
$config_array_keys = array_keys( $this->get_active_breakpoints() );
$num_of_devices = count( $config_array_keys );
// If the widescreen breakpoint is active, the device that's previous to desktop is the last one before
// widescreen.
if ( self::BREAKPOINT_KEY_WIDESCREEN === $config_array_keys[ $num_of_devices - 1 ] ) {
$desktop_previous_device = $config_array_keys[ $num_of_devices - 2 ];
} else {
// If the widescreen breakpoint isn't active, we just take the last device returned by the config.
$desktop_previous_device = $config_array_keys[ $num_of_devices - 1 ];
}
return $desktop_previous_device;
}
/**
* Get Stylesheet Templates
*
* @since 3.2.0
* @access private
* @static
*/
private static function get_stylesheet_templates() {
$templates_paths = glob( self::get_stylesheet_templates_path() . '*.css' );
$templates = [];
foreach ( $templates_paths as $template_path ) {
$file_name = 'custom-' . basename( $template_path );
$templates[ $file_name ] = $template_path;
}
$deprecated_hook = 'elementor/core/responsive/get_stylesheet_templates';
$replacement_hook = 'elementor/core/breakpoints/get_stylesheet_template';
/**
* @type Deprecation $deprecation_module
*/
$deprecation_module = Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation;
// TODO: REMOVE THIS DEPRECATED HOOK IN ELEMENTOR v3.10.0/v4.0.0
$templates = $deprecation_module->apply_deprecated_filter( $deprecated_hook, [ $templates ], '3.2.0', $replacement_hook );
return apply_filters( $replacement_hook, $templates );
}
}
breakpoints/breakpoint.php 0000666 00000005525 15220772103 0011746 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'];
}
}
dynamic-tags/data-tag.php 0000666 00000001363 15220772103 0011325 0 ustar 00 <?php
namespace Elementor\Core\DynamicTags;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor base data tag.
*
* An abstract class to register new Elementor data tags.
*
* @since 2.0.0
* @abstract
*/
abstract class Data_Tag extends Base_Tag {
/**
* @since 2.0.0
* @access protected
* @abstract
*
* @param array $options
*/
abstract protected function get_value( array $options = [] );
/**
* @since 2.0.0
* @access public
*/
final public function get_content_type() {
return 'plain';
}
/**
* @since 2.0.0
* @access public
*
* @param array $options
*
* @return mixed
*/
public function get_content( array $options = [] ) {
return $this->get_value( $options );
}
}
dynamic-tags/tag.php 0000666 00000004340 15220772103 0010414 0 ustar 00 <?php
namespace Elementor\Core\DynamicTags;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor tag.
*
* An abstract class to register new Elementor tag.
*
* @since 2.0.0
* @abstract
*/
abstract class Tag extends Base_Tag {
const WRAPPED_TAG = false;
/**
* @since 2.0.0
* @access public
*
* @param array $options
*
* @return string
*/
public function get_content( array $options = [] ) {
$settings = $this->get_settings();
ob_start();
$this->render();
$value = ob_get_clean();
if ( ! Utils::is_empty( $value ) ) {
// TODO: fix spaces in `before`/`after` if WRAPPED_TAG ( conflicted with .elementor-tag { display: inline-flex; } );
if ( ! Utils::is_empty( $settings, 'before' ) ) {
$value = wp_kses_post( $settings['before'] ) . $value;
}
if ( ! Utils::is_empty( $settings, 'after' ) ) {
$value .= wp_kses_post( $settings['after'] );
}
if ( static::WRAPPED_TAG ) :
$value = '<span id="elementor-tag-' . esc_attr( $this->get_id() ) . '" class="elementor-tag">' . $value . '</span>';
endif;
} elseif ( ! Utils::is_empty( $settings, 'fallback' ) ) {
$value = wp_kses_post_deep( $settings['fallback'] );
}
return $value;
}
/**
* @since 2.0.0
* @access public
*/
final public function get_content_type() {
return 'ui';
}
/**
* @since 2.0.9
* @access public
*/
public function get_editor_config() {
$config = parent::get_editor_config();
$config['wrapped_tag'] = $this::WRAPPED_TAG;
return $config;
}
/**
* @since 2.0.0
* @access protected
*/
protected function register_advanced_section() {
$this->start_controls_section(
'advanced',
[
'label' => esc_html__( 'Advanced', 'elementor' ),
]
);
$this->add_control(
'before',
[
'label' => esc_html__( 'Before', 'elementor' ),
'ai' => [
'active' => false,
],
]
);
$this->add_control(
'after',
[
'label' => esc_html__( 'After', 'elementor' ),
'ai' => [
'active' => false,
],
]
);
$this->add_control(
'fallback',
[
'label' => esc_html__( 'Fallback', 'elementor' ),
'ai' => [
'active' => false,
],
]
);
$this->end_controls_section();
}
}
dynamic-tags/dynamic-css.php 0000666 00000005733 15220772103 0012062 0 ustar 00 <?php
namespace Elementor\Core\DynamicTags;
use Elementor\Controls_Stack;
use Elementor\Core\Files\CSS\Post as Post_CSS;
use Elementor\Core\Files\CSS\Post_Local_Cache;
use Elementor\Core\Files\CSS\Post_Preview;
use Elementor\Element_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Dynamic_CSS extends Post_Local_Cache {
private $post_dynamic_elements_ids;
private $post_id_for_data;
protected function get_post_id_for_data() {
if ( empty( $this->post_dynamic_elements_ids ) ) {
return null;
}
return $this->post_id_for_data;
}
protected function is_global_parsing_supported() {
return false;
}
protected function render_styles( Element_Base $element ) {
$id = $element->get_id();
if ( in_array( $id, $this->post_dynamic_elements_ids ) ) {
parent::render_styles( $element );
}
foreach ( $element->get_children() as $child_element ) {
$this->render_styles( $child_element );
}
}
/**
* Dynamic_CSS constructor.
*
* @since 2.0.13
* @access public
*
* @param int $post_id Post ID.
* @param Post_CSS $post_css_file
*/
public function __construct( $post_id, Post_CSS $post_css_file ) {
if ( $post_css_file instanceof Post_Preview ) {
$this->post_id_for_data = $post_css_file->get_post_id_for_data();
} else {
$this->post_id_for_data = $post_id;
}
$this->post_dynamic_elements_ids = $post_css_file->get_meta( 'dynamic_elements_ids' );
parent::__construct( $post_id );
}
/**
* @since 2.0.13
* @access public
*/
public function get_name() {
return 'dynamic';
}
/**
* Get Responsive Control Duplication Mode
*
* @since 3.4.0
*
* @return string
*/
protected function get_responsive_control_duplication_mode() {
return 'dynamic';
}
/**
* @since 2.0.13
* @access protected
*/
protected function use_external_file() {
return false;
}
/**
* @since 2.0.13
* @access protected
*/
protected function get_file_handle_id() {
return 'elementor-post-dynamic-' . $this->get_post_id_for_data();
}
/**
* @since 2.0.13
* @access public
*/
public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, ?array $all_controls = null ) {
$dynamic_settings = $controls_stack->get_settings( '__dynamic__' );
if ( ! empty( $dynamic_settings ) ) {
$controls = array_intersect_key( $controls, $dynamic_settings );
$all_controls = $controls_stack->get_controls();
$parsed_dynamic_settings = $controls_stack->parse_dynamic_settings( $values, $controls );
foreach ( $controls as $control ) {
if ( ! empty( $control['style_fields'] ) ) {
$this->add_repeater_control_style_rules( $controls_stack, $control, $values[ $control['name'] ], $placeholders, $replacements );
}
if ( empty( $control['selectors'] ) ) {
continue;
}
$this->add_control_style_rules( $control, $parsed_dynamic_settings, $all_controls, $placeholders, $replacements );
}
}
}
}
dynamic-tags/base-tag.php 0000666 00000007656 15220772103 0011341 0 ustar 00 <?php
namespace Elementor\Core\DynamicTags;
use Elementor\Controls_Stack;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor base tag.
*
* An abstract class to register new Elementor tags.
*
* @since 2.0.0
* @abstract
*/
abstract class Base_Tag extends Controls_Stack {
/**
* @since 2.0.0
* @access public
* @static
*/
final public static function get_type() {
return 'tag';
}
/**
* @since 2.0.0
* @access public
* @abstract
*/
abstract public function get_categories();
/**
* @since 2.0.0
* @access public
* @abstract
*/
abstract public function get_group();
/**
* @since 2.0.0
* @access public
* @abstract
*/
abstract public function get_title();
/**
* @since 2.0.0
* @access public
* @abstract
*
* @param array $options
*/
abstract public function get_content( array $options = [] );
/**
* @since 2.0.0
* @access public
* @abstract
*/
abstract public function get_content_type();
/**
* @since 2.0.0
* @access public
*/
public function get_panel_template_setting_key() {
return '';
}
/**
* @since 2.0.0
* @access public
*/
public function is_settings_required() {
return false;
}
/**
* @since 2.0.9
* @access public
*/
public function get_editor_config() {
ob_start();
$this->print_panel_template();
$panel_template = ob_get_clean();
return [
'name' => $this->get_name(),
'title' => $this->get_title(),
'panel_template' => $panel_template,
'categories' => $this->get_categories(),
'group' => $this->get_group(),
'controls' => $this->get_controls(),
'content_type' => $this->get_content_type(),
'settings_required' => $this->is_settings_required(),
'editable' => $this->is_editable(),
];
}
/**
* @since 2.0.0
* @access public
*/
public function print_panel_template() {
$panel_template_setting_key = $this->get_panel_template_setting_key();
if ( ! $panel_template_setting_key ) {
return;
}
?><#
var key = <?php echo esc_html( $panel_template_setting_key ); ?>;
if ( key ) {
var settingsKey = "<?php echo esc_html( $panel_template_setting_key ); ?>";
/*
* If the tag has controls,
* and key is an existing control (and not an old one),
* and the control has options (select/select2),
* and the key is an existing option (and not in a group or an old one).
*/
if ( controls && controls[settingsKey] ) {
var controlSettings = controls[settingsKey];
if ( controlSettings.options && controlSettings.options[ key ] ) {
key = controlSettings.options[ key ];
} else if ( controlSettings.groups ) {
var label = _.filter( _.pluck( _.pluck( controls.key.groups, 'options' ), key ) );
if ( label[0] ) {
key = label[0];
}
}
}
print( '(' + _.escape( key ) + ')' );
}
#>
<?php
}
/**
* @since 2.0.0
* @access public
*/
final public function get_unique_name() {
return 'tag-' . $this->get_name();
}
/**
* @since 2.0.0
* @access protected
*/
protected function register_advanced_section() {}
/**
* @since 2.0.0
* @access protected
*/
final protected function init_controls() {
Plugin::$instance->controls_manager->open_stack( $this );
$this->start_controls_section( 'settings', [
'label' => esc_html__( 'Settings', 'elementor' ),
] );
if ( $this->has_own_method( '_register_controls' ) ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_register_controls', '3.1.0', __CLASS__ . '::register_controls()' );
$this->_register_controls();
} else {
$this->register_controls();
}
$this->end_controls_section();
// If in fact no controls were registered, empty the stack
if ( 1 === count( Plugin::$instance->controls_manager->get_stacks( $this->get_unique_name() )['controls'] ) ) {
Plugin::$instance->controls_manager->open_stack( $this );
}
$this->register_advanced_section();
}
}
dynamic-tags/manager.php 0000666 00000026115 15220772103 0011257 0 ustar 00 <?php
namespace Elementor\Core\DynamicTags;
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
use Elementor\Core\Files\CSS\Post;
use Elementor\Core\Files\CSS\Post_Preview;
use Elementor\Plugin;
use Elementor\User;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Manager {
const TAG_LABEL = 'elementor-tag';
const MODE_RENDER = 'render';
const MODE_REMOVE = 'remove';
const DYNAMIC_SETTING_KEY = '__dynamic__';
private $tags_groups = [];
private $tags_info = [];
private $parsing_mode = self::MODE_RENDER;
/**
* Dynamic tags manager constructor.
*
* Initializing Elementor dynamic tags manager.
*
* @since 2.0.0
* @access public
*/
public function __construct() {
$this->add_actions();
}
/**
* Parse dynamic tags text.
*
* Receives the dynamic tag text, and returns a single value or multiple values
* from the tag callback function.
*
* @since 2.0.0
* @access public
*
* @param string $text Dynamic tag text.
* @param array $settings The dynamic tag settings.
* @param callable $parse_callback The functions that renders the dynamic tag.
*
* @return string|string[]|mixed A single string or an array of strings with
* the return values from each tag callback
* function.
*/
public function parse_tags_text( $text, array $settings, callable $parse_callback ) {
if ( ! empty( $settings['returnType'] ) && 'object' === $settings['returnType'] ) {
$value = $this->parse_tag_text( $text, $settings, $parse_callback );
} else {
$value = preg_replace_callback( '/\[' . self::TAG_LABEL . '.+?(?=\])\]/', function( $tag_text_match ) use ( $settings, $parse_callback ) {
return $this->parse_tag_text( $tag_text_match[0], $settings, $parse_callback );
}, $text );
}
return $value;
}
/**
* Parse dynamic tag text.
*
* Receives the dynamic tag text, and returns the value from the callback
* function.
*
* @since 2.0.0
* @access public
*
* @param string $tag_text Dynamic tag text.
* @param array $settings The dynamic tag settings.
* @param callable $parse_callback The functions that renders the dynamic tag.
*
* @return string|array|mixed If the tag was not found an empty string or an
* empty array will be returned, otherwise the
* return value from the tag callback function.
*/
public function parse_tag_text( $tag_text, array $settings, callable $parse_callback ) {
$tag_data = $this->tag_text_to_tag_data( $tag_text );
if ( ! $tag_data ) {
if ( ! empty( $settings['returnType'] ) && 'object' === $settings['returnType'] ) {
return [];
}
return '';
}
return call_user_func_array( $parse_callback, array_values( $tag_data ) );
}
/**
* @since 2.0.0
* @access public
*
* @param string $tag_text
*
* @return array|null
*/
public function tag_text_to_tag_data( $tag_text ) {
preg_match( '/id="(.*?(?="))"/', $tag_text, $tag_id_match );
preg_match( '/name="(.*?(?="))"/', $tag_text, $tag_name_match );
preg_match( '/settings="(.*?(?="]))/', $tag_text, $tag_settings_match );
if ( ! $tag_id_match || ! $tag_name_match || ! $tag_settings_match ) {
return null;
}
return [
'id' => $tag_id_match[1],
'name' => $tag_name_match[1],
'settings' => json_decode( urldecode( $tag_settings_match[1] ), true ),
];
}
/**
* Dynamic tag to text.
*
* Retrieve the shortcode that represents the dynamic tag.
*
* @since 2.0.0
* @access public
*
* @param Base_Tag $tag An instance of the dynamic tag.
*
* @return string The shortcode that represents the dynamic tag.
*/
public function tag_to_text( Base_Tag $tag ) {
return sprintf( '[%1$s id="%2$s" name="%3$s" settings="%4$s"]', self::TAG_LABEL, $tag->get_id(), $tag->get_name(), urlencode( wp_json_encode( $tag->get_settings(), JSON_FORCE_OBJECT ) ) );
}
/**
* @since 2.0.0
* @access public
* @param string $tag_id
* @param string $tag_name
* @param array $settings
*
* @return string
*/
public function tag_data_to_tag_text( $tag_id, $tag_name, array $settings = [] ) {
$tag = $this->create_tag( $tag_id, $tag_name, $settings );
if ( ! $tag ) {
return '';
}
return $this->tag_to_text( $tag );
}
/**
* @since 2.0.0
* @access public
* @param string $tag_id
* @param string $tag_name
* @param array $settings
*
* @return Tag|null
*/
public function create_tag( $tag_id, $tag_name, array $settings = [] ) {
$tag_info = $this->get_tag_info( $tag_name );
if ( ! $tag_info ) {
return null;
}
$tag_class = $tag_info['class'];
return new $tag_class( [
'settings' => $settings,
'id' => $tag_id,
] );
}
/**
* @since 2.0.0
* @access public
*
* @param $tag_id
* @param $tag_name
* @param array $settings
*
* @return null|string
*/
public function get_tag_data_content( $tag_id, $tag_name, array $settings = [] ) {
if ( self::MODE_REMOVE === $this->parsing_mode ) {
return null;
}
$tag = $this->create_tag( $tag_id, $tag_name, $settings );
if ( ! $tag ) {
return null;
}
return $tag->get_content();
}
/**
* @since 2.0.0
* @access public
*
* @param $tag_name
*
* @return mixed|null
*/
public function get_tag_info( $tag_name ) {
$tags = $this->get_tags();
if ( empty( $tags[ $tag_name ] ) ) {
return null;
}
return $tags[ $tag_name ];
}
/**
* @since 2.0.9
* @access public
*/
public function get_tags() {
if ( ! did_action( 'elementor/dynamic_tags/register_tags' ) ) {
/**
* Register dynamic tags.
*
* Fires when Elementor registers dynamic tags.
*
* @since 2.0.9
* @deprecated 3.5.0 Use `elementor/dynamic_tags/register` hook instead.
*
* @param Manager $this Dynamic tags manager.
*/
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->do_deprecated_action(
'elementor/dynamic_tags/register_tags',
[ $this ],
'3.5.0',
'elementor/dynamic_tags/register'
);
}
if ( ! did_action( 'elementor/dynamic_tags/register' ) ) {
/**
* Register dynamic tags.
*
* Fires when Elementor registers dynamic tags.
*
* @since 3.5.0
*
* @param Manager $this Dynamic tags manager.
*/
do_action( 'elementor/dynamic_tags/register', $this );
}
return $this->tags_info;
}
/**
* @since 2.0.0
* @access public
* @deprecated 3.5.0 Use `register()` method instead.
*
* @param string $class
*/
public function register_tag( $class ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function(
__METHOD__,
'3.5.0',
'register()'
);
/** @var Base_Tag $tag */
$instance = new $class();
$this->register( $instance );
}
/**
* Register a new Dynamic Tag.
*
* @param Base_Tag $dynamic_tag_instance
*
* @return void
* @since 3.5.0
* @access public
*/
public function register( Base_Tag $dynamic_tag_instance ) {
$this->tags_info[ $dynamic_tag_instance->get_name() ] = [
'class' => get_class( $dynamic_tag_instance ),
'instance' => $dynamic_tag_instance,
];
}
/**
* @since 2.0.9
* @access public
* @deprecated 3.5.0 Use `unregister()` method instead.
*
* @param string $tag_name
*/
public function unregister_tag( $tag_name ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function(
__METHOD__,
'3.5.0',
'unregister()'
);
$this->unregister( $tag_name );
}
/**
* Unregister a dynamic tag.
*
* @since 3.5.0
* @access public
*
* @param string $tag_name Dynamic Tag name to unregister.
*
* @return void
*/
public function unregister( $tag_name ) {
unset( $this->tags_info[ $tag_name ] );
}
/**
* @since 2.0.0
* @access public
*
* @param $group_name
* @param array $group_settings
*/
public function register_group( $group_name, array $group_settings ) {
$default_group_settings = [
'title' => '',
];
$group_settings = array_merge( $default_group_settings, $group_settings );
$this->tags_groups[ $group_name ] = $group_settings;
}
/**
* @since 2.0.0
* @access public
*/
public function print_templates() {
foreach ( $this->get_tags() as $tag_name => $tag_info ) {
$tag = $tag_info['instance'];
if ( ! $tag instanceof Tag ) {
continue;
}
$tag->print_template();
}
}
/**
* @since 2.0.0
* @access public
*/
public function get_tags_config() {
$config = [];
foreach ( $this->get_tags() as $tag_name => $tag_info ) {
/** @var Tag $tag */
$tag = $tag_info['instance'];
$config[ $tag_name ] = $tag->get_editor_config();
}
return $config;
}
/**
* @since 2.0.0
* @access public
*/
public function get_config() {
return [
'tags' => $this->get_tags_config(),
'groups' => $this->tags_groups,
];
}
/**
* @since 2.0.0
* @access public
*
* @throws \Exception If post ID is missing.
* @throws \Exception If current user don't have permissions to edit the post.
*/
public function ajax_render_tags( $data ) {
if ( empty( $data['post_id'] ) ) {
throw new \Exception( 'Missing post id.' );
}
if ( ! User::is_current_user_can_edit( $data['post_id'] ) ) {
throw new \Exception( 'Access denied.' );
}
Plugin::$instance->db->switch_to_post( $data['post_id'] );
/**
* Before dynamic tags rendered.
*
* Fires before Elementor renders the dynamic tags.
*
* @since 2.0.0
*/
do_action( 'elementor/dynamic_tags/before_render' );
$tags_data = [];
foreach ( $data['tags'] as $tag_key ) {
$tag_key_parts = explode( '-', $tag_key );
$tag_name = base64_decode( $tag_key_parts[0] );
$tag_settings = json_decode( urldecode( base64_decode( $tag_key_parts[1] ) ), true );
$tag = $this->create_tag( null, $tag_name, $tag_settings );
$tags_data[ $tag_key ] = $tag->get_content();
}
/**
* After dynamic tags rendered.
*
* Fires after Elementor renders the dynamic tags.
*
* @since 2.0.0
*/
do_action( 'elementor/dynamic_tags/after_render' );
return $tags_data;
}
/**
* @since 2.0.0
* @access public
*
* @param $mode
*/
public function set_parsing_mode( $mode ) {
$this->parsing_mode = $mode;
}
/**
* @since 2.0.0
* @access public
*/
public function get_parsing_mode() {
return $this->parsing_mode;
}
/**
* @since 2.1.0
* @access public
* @param Post $css_file
*/
public function after_enqueue_post_css( $css_file ) {
$post_id = $css_file->get_post_id();
$should_enqueue = apply_filters( 'elementor/css-file/dynamic/should_enqueue', true, $post_id );
if ( $should_enqueue ) {
$css_file = Dynamic_CSS::create( $post_id, $css_file );
$css_file->enqueue();
}
}
/**
* @since 2.3.0
* @access public
*/
public function register_ajax_actions( Ajax $ajax ) {
$ajax->register_ajax_action( 'render_tags', [ $this, 'ajax_render_tags' ] );
}
/**
* @since 2.0.0
* @access private
*/
private function add_actions() {
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
add_action( 'elementor/css-file/post/enqueue', [ $this, 'after_enqueue_post_css' ] );
}
}
behaviors/interfaces/lock-behavior.php 0000666 00000001175 15220772103 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();
}
page-assets/data-managers/base.php 0000666 00000016042 15220772104 0013116 0 ustar 00 <?php
namespace Elementor\Core\Page_Assets\Data_Managers;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor Assets Data.
*
* @since 3.3.0
*/
abstract class Base {
const ASSETS_DATA_KEY = '_elementor_assets_data';
/**
* @var array
*/
protected $assets_data;
/**
* @var string
*/
protected $content_type;
/**
* @var string
*/
protected $assets_category;
/**
* @var array
*/
private $assets_config;
/**
* @var array
*/
private $files_data;
/**
* Get Asset Content.
*
* Responsible for extracting the asset data from a certain file.
* Will be triggered automatically when the asset data does not exist, or when the asset version was changed.
*
* @since 3.3.0
* @access public
*
* @return string
*/
abstract protected function get_asset_content();
/**
* Get Asset Key.
*
* The asset data will be saved in the DB under this key.
*
* @since 3.3.0
* @access protected
*
* @return string
*/
protected function get_key() {
return $this->assets_config['key'];
}
/**
* Get Relative Version.
*
* The asset data will be re-evaluated according the version number.
*
* @since 3.3.0
* @access protected
*
* @return string
*/
protected function get_version() {
return $this->assets_config['version'];
}
/**
* Get Asset Path.
*
* The asset data will be extracted from the file path.
*
* @since 3.3.0
* @access protected
*
* @return string
*/
protected function get_file_path() {
return $this->assets_config['file_path'];
}
/**
* Get Config Data.
*
* Holds a unique data relevant for the specific assets category type.
*
* @since 3.3.0
* @access protected
*
* @return string|array
*/
protected function get_config_data( $key = '' ) {
if ( isset( $this->assets_config['data'] ) ) {
if ( $key ) {
if ( isset( $this->assets_config['data'][ $key ] ) ) {
return $this->assets_config['data'][ $key ];
}
return '';
}
return $this->assets_config['data'];
}
return [];
}
/**
* Set Asset Data.
*
* Responsible for setting the current asset data.
*
* @since 3.3.0
* @access protected
*
* @return void
*/
protected function set_asset_data( $asset_key ) {
if ( ! isset( $this->assets_data[ $asset_key ] ) ) {
$this->assets_data[ $asset_key ] = [];
}
$this->assets_data[ $asset_key ]['content'] = $this->get_asset_content();
$this->assets_data[ $asset_key ]['version'] = $this->get_version();
$this->save_asset_data( $asset_key );
}
/**
* Save Asset Data.
*
* Responsible for saving the asset data in the DB.
*
* @since 3.3.0
* @access protected
*
* @param string $asset_key
*
* @return void
*/
protected function save_asset_data( $asset_key ) {
$assets_data = $this->get_saved_assets_data();
$content_type = $this->content_type;
$assets_category = $this->assets_category;
$assets_data[ $content_type ][ $assets_category ][ $asset_key ] = $this->assets_data[ $asset_key ];
update_option( self::ASSETS_DATA_KEY, $assets_data );
}
/**
* Is Asset Version Changed.
*
* Responsible for comparing the saved asset data version to the current relative version.
*
* @since 3.3.0
* @access protected
*
* @param string $version
*
* @return boolean
*/
protected function is_asset_version_changed( $version ) {
return $this->get_version() !== $version;
}
/**
* Get File Data.
*
* Getting a file content or size.
*
* @since 3.3.0
* @access protected
*
* @param string $data_type (exists|content|size).
* @param string $file_key - In case that the same file data is needed for multiple assets (like a JSON file), the file data key should be the same for all shared assets to make sure that the file is being read only once.
*
* @return string|number
*/
protected function get_file_data( $data_type, $file_key = '' ) {
$asset_key = $file_key ? $file_key : $this->get_key();
if ( isset( $this->files_data[ $asset_key ][ $data_type ] ) ) {
return $this->files_data[ $asset_key ][ $data_type ];
}
if ( ! isset( $this->files_data[ $asset_key ] ) ) {
$this->files_data[ $asset_key ] = [];
}
$asset_path = $this->get_file_path();
if ( 'exists' === $data_type ) {
$data = file_exists( $asset_path );
} elseif ( 'content' === $data_type ) {
$data = Utils::file_get_contents( $asset_path );
if ( ! $data ) {
$data = '';
}
} elseif ( 'size' === $data_type ) {
$data = file_exists( $asset_path ) ? filesize( $asset_path ) : 0;
}
$this->files_data[ $asset_key ][ $data_type ] = $data;
return $data;
}
/**
* Get Saved Assets Data.
*
* Getting the assets data from the DB.
*
* @since 3.3.0
* @access protected
*
* @return array
*/
protected function get_saved_assets_data() {
$assets_data = get_option( self::ASSETS_DATA_KEY, [] );
$content_type = $this->content_type;
$assets_category = $this->assets_category;
if ( ! isset( $assets_data[ $content_type ] ) ) {
$assets_data[ $content_type ] = [];
}
if ( ! isset( $assets_data[ $content_type ][ $assets_category ] ) ) {
$assets_data[ $content_type ][ $assets_category ] = [];
}
return $assets_data;
}
/**
* Get Config.
*
* Getting the assets data config.
*
* @since 3.5.0
* @access protected
*
* @return array
*/
protected function get_config( $data ) {
return [];
}
/**
* Init Asset Data.
*
* Initialize the asset data and handles the asset content updates when needed.
*
* @since 3.3.0
* @access public
*
* @param array $config {
* @type string 'key'
* @type string 'version'
* @type string 'file_path'
* @type array 'data'
* }
*
* @return void
*/
public function init_asset_data( $config ) {
$this->assets_config = $config;
$asset_key = $config['key'];
$asset_data = isset( $this->assets_data[ $asset_key ] ) ? $this->assets_data[ $asset_key ] : [];
if ( ! $asset_data || $this->is_asset_version_changed( $asset_data['version'] ) ) {
$this->set_asset_data( $asset_key );
}
}
/**
* Get Asset Data From Config.
*
* Getting the asset data content from config.
*
* @since 3.3.0
* @access public
*
* @param array $config {
* @type string 'key'
* @type string 'version'
* @type string 'file_path'
* @type array 'data'
* }
*
* @return mixed
*/
public function get_asset_data_from_config( array $config ) {
$this->init_asset_data( $config );
$asset_key = $config['key'];
return $this->assets_data[ $asset_key ]['content'];
}
/**
* Get Asset Data.
*
* Getting the asset data content.
*
* @since 3.5.0
* @access public
*
* @param array $data
*
* @return mixed
*/
public function get_asset_data( array $data ) {
$config = $this->get_config( $data );
return $this->get_asset_data_from_config( $config );
}
public function __construct() {
$assets_data = $this->get_saved_assets_data();
$content_type = $this->content_type;
$assets_category = $this->assets_category;
$this->assets_data = $assets_data[ $content_type ][ $assets_category ];
}
}
settings/base/css-manager.php 0000666 00000004545 15220772104 0012243 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 0000666 00000000565 15220772104 0011727 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/editor-preferences/manager.php 0000666 00000002646 15220772104 0014330 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/general/manager.php 0000666 00000005024 15220772104 0012151 0 ustar 00 <?php
namespace Elementor\Core\Settings\General;
use Elementor\Core\Files\CSS\Base;
use Elementor\Core\Settings\Base\CSS_Manager;
use Elementor\Core\Settings\Base\Model as BaseModel;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* This class is deprecated, use Plugin::$instance->kits_manager->get_active_kit_for_frontend() instead.
* it changed to support call like this: Manager::get_settings_managers( 'general' )->get_model()->get_settings( 'elementor_default_generic_fonts' )
*
* @deprecated 3.0.0 Use `Plugin::$instance->kits_manager->get_active_kit_for_frontend()` instead.
*/
class Manager extends CSS_Manager {
/**
* Meta key for the general settings.
*
* @deprecated 3.0.0
*/
const META_KEY = '_elementor_general_settings';
/**
* General settings manager constructor.
*
* Initializing Elementor general settings manager.
*
* @since 1.6.0
* @deprecated 3.0.0
* @access public
*/
public function __construct() {
parent::__construct();
_deprecated_file( __FILE__, '3.0.0', 'Plugin::$instance->kits_manager->get_active_kit_for_frontend()' );
$name = $this->get_css_file_name();
remove_action( "elementor/css-file/{$name}/parse", [ $this, 'add_settings_css_rules' ] );
}
/**
* Get manager name.
*
* Retrieve general settings manager name.
*
* @since 1.6.0
* @deprecated 3.0.0
* @access public
*
* @return string Manager name.
*/
public function get_name() {
return 'general';
}
/**
* Get model for config.
*
* Retrieve the model for settings configuration.
*
* @since 1.6.0
* @deprecated 3.0.0
* @access public
*
* @return BaseModel The model object.
*/
public function get_model_for_config() {
return $this->get_model();
}
/**
* @deprecated 3.0.0
*/
protected function get_saved_settings( $id ) {
return [];
}
/**
* Get CSS file name.
*
* Retrieve CSS file name for the general settings manager.
*
* @since 1.6.0
* @deprecated 3.0.0
* @access protected
*
* @return string CSS file name.
*/
protected function get_css_file_name() {
return 'global';
}
/**
* @deprecated 3.0.0
*/
protected function save_settings_to_db( array $settings, $id ) {
throw new \Exception( __CLASS__ . ' is deprecated. Use Plugin::$instance->kits_manager->get_active_kit_for_frontend() instead.' );
}
/**
* @deprecated 3.0.0
*/
protected function get_model_for_css_file( Base $css_file ) {
return false;
}
/**
* @deprecated 3.0.0
*/
protected function get_css_file_for_update( $id ) {
return false;
}
}
admin/menu/base.php 0000666 00000007041 15220772104 0010231 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/feedback.php 0000666 00000013304 15220772104 0010076 0 ustar 00 <?php
namespace Elementor\Core\Admin;
use Elementor\Api;
use Elementor\Core\Base\Module;
use Elementor\Plugin;
use Elementor\Tracker;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Feedback extends Module {
/**
* @since 2.2.0
* @access public
*/
public function __construct() {
add_action( 'current_screen', function () {
if ( ! $this->is_plugins_screen() ) {
return;
}
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_feedback_dialog_scripts' ] );
} );
// Ajax.
add_action( 'wp_ajax_elementor_deactivate_feedback', [ $this, 'ajax_elementor_deactivate_feedback' ] );
}
/**
* Get module name.
*
* Retrieve the module name.
*
* @since 1.7.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'feedback';
}
/**
* Enqueue feedback dialog scripts.
*
* Registers the feedback dialog scripts and enqueues them.
*
* @since 1.0.0
* @access public
*/
public function enqueue_feedback_dialog_scripts() {
add_action( 'admin_footer', [ $this, 'print_deactivate_feedback_dialog' ] );
$suffix = Utils::is_script_debug() ? '' : '.min';
wp_register_script(
'elementor-admin-feedback',
ELEMENTOR_ASSETS_URL . 'js/admin-feedback' . $suffix . '.js',
[
'elementor-common',
'wp-i18n',
],
ELEMENTOR_VERSION,
true
);
wp_enqueue_script( 'elementor-admin-feedback' );
wp_set_script_translations( 'elementor-admin-feedback', 'elementor' );
}
/**
* Print deactivate feedback dialog.
*
* Display a dialog box to ask the user why he deactivated Elementor.
*
* Fired by `admin_footer` filter.
*
* @since 1.0.0
* @access public
*/
public function print_deactivate_feedback_dialog() {
$deactivate_reasons = [
'no_longer_needed' => [
'title' => esc_html__( 'I no longer need the plugin', 'elementor' ),
'input_placeholder' => '',
],
'found_a_better_plugin' => [
'title' => esc_html__( 'I found a better plugin', 'elementor' ),
'input_placeholder' => esc_html__( 'Please share which plugin', 'elementor' ),
],
'couldnt_get_the_plugin_to_work' => [
'title' => esc_html__( 'I couldn\'t get the plugin to work', 'elementor' ),
'input_placeholder' => '',
],
'temporary_deactivation' => [
'title' => esc_html__( 'It\'s a temporary deactivation', 'elementor' ),
'input_placeholder' => '',
],
'elementor_pro' => [
'title' => esc_html__( 'I have Elementor Pro', 'elementor' ),
'input_placeholder' => '',
'alert' => esc_html__( 'Wait! Don\'t deactivate Elementor. You have to activate both Elementor and Elementor Pro in order for the plugin to work.', 'elementor' ),
],
'other' => [
'title' => esc_html__( 'Other', 'elementor' ),
'input_placeholder' => esc_html__( 'Please share the reason', 'elementor' ),
],
];
?>
<div id="elementor-deactivate-feedback-dialog-wrapper">
<div id="elementor-deactivate-feedback-dialog-header">
<i class="eicon-elementor-square" aria-hidden="true"></i>
<span id="elementor-deactivate-feedback-dialog-header-title"><?php echo esc_html__( 'Quick Feedback', 'elementor' ); ?></span>
</div>
<form id="elementor-deactivate-feedback-dialog-form" method="post">
<?php
wp_nonce_field( '_elementor_deactivate_feedback_nonce' );
?>
<input type="hidden" name="action" value="elementor_deactivate_feedback" />
<div id="elementor-deactivate-feedback-dialog-form-caption"><?php echo esc_html__( 'If you have a moment, please share why you are deactivating Elementor:', 'elementor' ); ?></div>
<div id="elementor-deactivate-feedback-dialog-form-body">
<?php foreach ( $deactivate_reasons as $reason_key => $reason ) : ?>
<div class="elementor-deactivate-feedback-dialog-input-wrapper">
<input id="elementor-deactivate-feedback-<?php echo esc_attr( $reason_key ); ?>" class="elementor-deactivate-feedback-dialog-input" type="radio" name="reason_key" value="<?php echo esc_attr( $reason_key ); ?>" />
<label for="elementor-deactivate-feedback-<?php echo esc_attr( $reason_key ); ?>" class="elementor-deactivate-feedback-dialog-label"><?php echo esc_html( $reason['title'] ); ?></label>
<?php if ( ! empty( $reason['input_placeholder'] ) ) : ?>
<input class="elementor-feedback-text" type="text" name="reason_<?php echo esc_attr( $reason_key ); ?>" placeholder="<?php echo esc_attr( $reason['input_placeholder'] ); ?>" />
<?php endif; ?>
<?php if ( ! empty( $reason['alert'] ) ) : ?>
<div class="elementor-feedback-text"><?php echo esc_html( $reason['alert'] ); ?></div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</form>
</div>
<?php
}
/**
* Ajax elementor deactivate feedback.
*
* Send the user feedback when Elementor is deactivated.
*
* Fired by `wp_ajax_elementor_deactivate_feedback` action.
*
* @since 1.0.0
* @access public
*/
public function ajax_elementor_deactivate_feedback() {
$wpnonce = Utils::get_super_global_value( $_POST, '_wpnonce' ); // phpcs:ignore -- Nonce verification is made in `wp_verify_nonce()`.
if ( ! wp_verify_nonce( $wpnonce, '_elementor_deactivate_feedback_nonce' ) ) {
wp_send_json_error();
}
if ( ! current_user_can( 'activate_plugins' ) ) {
wp_send_json_error( 'Permission denied' );
}
$reason_key = Utils::get_super_global_value( $_POST, 'reason_key' ) ?? '';
$reason_text = Utils::get_super_global_value( $_POST, "reason_{$reason_key}" ) ?? '';
Api::send_feedback( $reason_key, $reason_text );
wp_send_json_success();
}
/**
* @since 2.3.0
* @access private
*/
private function is_plugins_screen() {
return in_array( get_current_screen()->id, [ 'plugins', 'plugins-network' ] );
}
}
admin/ui/components/button.php 0000666 00000004575 15220772104 0012501 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, true ) ) {
return $default_options[ $option ];
}
return $default_options;
}
public function __construct( array $options ) {
$this->options = $this->merge_properties( $this->get_default_options(), $options );
}
}
wp-api.php 0000666 00000002512 15220772104 0006456 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 ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
if ( ! $this->plugins ) {
$this->plugins = new Collection( get_plugins() );
}
return $this->plugins;
}
/**
* @return Collection
*/
public function get_active_plugins() {
return $this->get_plugins()
->only( get_option( 'active_plugins' ) );
}
/**
* @return object|array
*/
public function plugins_api( $action, $args ) {
return plugins_api( $action, $args );
}
/**
* @return bool
*/
public function is_plugin_active( $plugin ) {
return is_plugin_active( $plugin );
}
/**
* @return bool|int|null|true
*/
public function activate_plugin( $plugin ) {
return activate_plugin( $plugin );
}
public function wp_attachment_is_image( $post = null ) {
return wp_attachment_is_image( $post );
}
}
common/modules/finder/module.php 0000666 00000005064 15220772104 0012762 0 ustar 00 <?php
namespace Elementor\Core\Common\Modules\Finder;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Finder Module
*
* Responsible for initializing Elementor Finder functionality
*/
class Module extends BaseModule {
/**
* Categories manager.
*
* @access private
*
* @var Categories_Manager
*/
private $categories_manager;
/**
* Module constructor.
*
* @since 2.3.0
* @access public
*/
public function __construct() {
$this->categories_manager = new Categories_Manager();
$this->add_template();
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
}
/**
* Get name.
*
* @since 2.3.0
* @access public
*
* @return string
*/
public function get_name() {
return 'finder';
}
/**
* Add template.
*
* @since 2.3.0
* @access public
*/
public function add_template() {
Plugin::$instance->common->add_template( __DIR__ . '/template.php' );
}
/**
* Register ajax actions.
*
* @since 2.3.0
* @access public
*
* @param Ajax $ajax
*/
public function register_ajax_actions( Ajax $ajax ) {
$ajax->register_ajax_action( 'finder_get_category_items', [ $this, 'ajax_get_category_items' ] );
}
/**
* Ajax get category items.
*
* @since 2.3.0
* @access public
*
* @param array $data
*
* @return array
*/
public function ajax_get_category_items( array $data ) {
if ( ! current_user_can( 'manage_options' ) ) {
throw new \Exception( 'Access denied.' );
}
$category = $this->categories_manager->get_categories( $data['category'] );
return $category->get_category_items( $data );
}
/**
* Get init settings.
*
* @since 2.3.0
* @access protected
*
* @return array
*/
protected function get_init_settings() {
$categories = $this->categories_manager->get_categories();
$categories_data = [];
foreach ( $categories as $category_name => $category ) {
$categories_data[ $category_name ] = array_merge( $category->get_settings(), [ 'name' => $category_name ] );
}
/**
* Finder categories.
*
* Filters the list of finder categories. This hook is used to manage Finder
* categories - to add new categories, remove and edit existing categories.
*
* @since 2.3.0
*
* @param array $categories_data A list of finder categories.
*/
$categories_data = apply_filters( 'elementor/finder/categories', $categories_data );
return [
'data' => $categories_data,
];
}
}
common/modules/finder/template.php 0000666 00000003632 15220772104 0013307 0 ustar 00 <?php
namespace Elementor\Modules\Finder;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
?>
<script type="text/template" id="tmpl-elementor-finder">
<div id="elementor-finder__search">
<i class="eicon-search" aria-hidden="true"></i>
<input id="elementor-finder__search__input" placeholder="<?php echo esc_attr__( 'Type to find anything in Elementor', 'elementor' ); ?>" autocomplete="off">
</div>
<div id="elementor-finder__content"></div>
</script>
<script type="text/template" id="tmpl-elementor-finder-results-container">
<div id="elementor-finder__no-results"><?php echo esc_html__( 'No Results Found', 'elementor' ); ?></div>
<div id="elementor-finder__results"></div>
</script>
<script type="text/template" id="tmpl-elementor-finder__results__category">
<div class="elementor-finder__results__category__title">{{{ title }}}</div>
<div class="elementor-finder__results__category__items"></div>
</script>
<script type="text/template" id="tmpl-elementor-finder__results__item">
<a href="{{ url }}" class="elementor-finder__results__item__link">
<div class="elementor-finder__results__item__icon">
<i class="eicon-{{{ icon }}}" aria-hidden="true"></i>
</div>
<div class="elementor-finder__results__item__title">{{{ title }}}</div>
<# if ( description ) { #>
<div class="elementor-finder__results__item__description">- {{{ description }}}</div>
<# } #>
<# if ( lock ) { #>
<div class="elementor-finder__results__item__badge"><i class="{{{ lock.badge.icon }}}"></i>{{ lock.badge.text }}</div>
<# } #>
</a>
<# if ( actions.length ) { #>
<div class="elementor-finder__results__item__actions">
<# jQuery.each( actions, function() { #>
<a class="elementor-finder__results__item__action elementor-finder__results__item__action--{{ this.name }}" href="{{ this.url }}" target="_blank">
<i class="eicon-{{{ this.icon }}}"></i>
</a>
<# } ); #>
</div>
<# } #>
</script>
common/modules/finder/base-category.php 0000666 00000003127 15220772104 0014220 0 ustar 00 <?php
namespace Elementor\Core\Common\Modules\Finder;
use Elementor\Core\Base\Base_Object;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Base Category
*
* Base class for Elementor Finder categories.
*/
abstract class Base_Category extends Base_Object {
/**
* Get title.
*
* @since 2.3.0
* @abstract
* @access public
*
* @return string
*/
abstract public function get_title();
/**
* Get a unique category ID.
*
* TODO: Make abstract.
*
* @since 3.5.0
* @deprecated 3.5.0
* @access public
*
* @return string
*/
public function get_id() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function(
get_class( $this ) . '::' . __FUNCTION__,
'3.5.0',
'This method will be replaced with an abstract method.'
);
return '';
}
/**
* Get category items.
*
* @since 2.3.0
* @abstract
* @access public
*
* @param array $options
*
* @return array
*/
abstract public function get_category_items( array $options = [] );
/**
* Is dynamic.
*
* Determine if the category is dynamic.
*
* @since 2.3.0
* @access public
*
* @return bool
*/
public function is_dynamic() {
return false;
}
/**
* Get init settings.
*
* @since 2.3.0
* @access protected
*
* @return array
*/
protected function get_init_settings() {
$settings = [
'title' => $this->get_title(),
'dynamic' => $this->is_dynamic(),
];
if ( ! $settings['dynamic'] ) {
$settings['items'] = $this->get_category_items();
}
return $settings;
}
}
common/modules/finder/categories/general.php 0000666 00000004504 15220772104 0015235 0 ustar 00 <?php
namespace Elementor\Core\Common\Modules\Finder\Categories;
use Elementor\Core\Common\Modules\Finder\Base_Category;
use Elementor\Core\RoleManager\Role_Manager;
use Elementor\Plugin;
use Elementor\TemplateLibrary\Source_Local;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* General Category
*
* Provides general items related to Elementor Admin.
*/
class General extends Base_Category {
/**
* Get title.
*
* @since 2.3.0
* @access public
*
* @return string
*/
public function get_title() {
return esc_html__( 'General', 'elementor' );
}
public function get_id() {
return 'general';
}
/**
* Get category items.
*
* @since 2.3.0
* @access public
*
* @param array $options
*
* @return array
*/
public function get_category_items( array $options = [] ) {
return [
'saved-templates' => [
'title' => esc_html__( 'Saved Templates', 'elementor' ),
'icon' => 'library-save',
'url' => Source_Local::get_admin_url(),
'keywords' => [ 'template', 'section', 'page', 'library' ],
],
'system-info' => [
'title' => esc_html__( 'System Info', 'elementor' ),
'icon' => 'info-circle-o',
'url' => admin_url( 'admin.php?page=elementor-system-info' ),
'keywords' => [ 'system', 'info', 'environment', 'elementor' ],
],
'role-manager' => [
'title' => esc_html__( 'Role Manager', 'elementor' ),
'icon' => 'person',
'url' => Role_Manager::get_url(),
'keywords' => [ 'role', 'manager', 'user', 'elementor' ],
],
'knowledge-base' => [
'title' => esc_html__( 'Knowledge Base', 'elementor' ),
'url' => admin_url( 'admin.php?page=go_knowledge_base_site' ),
'keywords' => [ 'help', 'knowledge', 'docs', 'elementor' ],
],
'theme-builder' => [
'title' => esc_html__( 'Theme Builder', 'elementor' ),
'icon' => 'library-save',
'url' => Plugin::$instance->app->get_settings( 'menu_url' ),
'keywords' => [ 'template', 'header', 'footer', 'single', 'archive', 'search', '404', 'library' ],
],
'kit-library' => [
'title' => esc_html__( 'Website Templates', 'elementor' ),
'icon' => 'kit-parts',
'url' => Plugin::$instance->app->get_base_url() . '#/kit-library',
'keywords' => [ 'Website Templates', 'kit library', 'kit', 'library', 'site parts', 'parts', 'assets', 'templates' ],
],
];
}
}
common/modules/finder/categories/site.php 0000666 00000004047 15220772104 0014566 0 ustar 00 <?php
namespace Elementor\Core\Common\Modules\Finder\Categories;
use Elementor\Core\Common\Modules\Finder\Base_Category;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Site Category
*
* Provides general site items.
*/
class Site extends Base_Category {
/**
* Get title.
*
* @since 2.3.0
* @access public
*
* @return string
*/
public function get_title() {
return esc_html__( 'Site', 'elementor' );
}
public function get_id() {
return 'site';
}
/**
* Get category items.
*
* @since 2.3.0
* @access public
*
* @param array $options
*
* @return array
*/
public function get_category_items( array $options = [] ) {
return [
'homepage' => [
'title' => esc_html__( 'Homepage', 'elementor' ),
'url' => home_url(),
'icon' => 'home-heart',
'keywords' => [ 'home', 'page' ],
],
'wordpress-dashboard' => [
'title' => esc_html__( 'Dashboard', 'elementor' ),
'icon' => 'dashboard',
'url' => admin_url(),
'keywords' => [ 'dashboard', 'wordpress' ],
],
'wordpress-menus' => [
'title' => esc_html__( 'Menus', 'elementor' ),
'icon' => 'wordpress',
'url' => admin_url( 'nav-menus.php' ),
'keywords' => [ 'menu', 'wordpress' ],
],
'wordpress-themes' => [
'title' => esc_html__( 'Themes', 'elementor' ),
'icon' => 'wordpress',
'url' => admin_url( 'themes.php' ),
'keywords' => [ 'themes', 'wordpress' ],
],
'wordpress-customizer' => [
'title' => esc_html__( 'Customizer', 'elementor' ),
'icon' => 'wordpress',
'url' => admin_url( 'customize.php' ),
'keywords' => [ 'customizer', 'wordpress' ],
],
'wordpress-plugins' => [
'title' => esc_html__( 'Plugins', 'elementor' ),
'icon' => 'wordpress',
'url' => admin_url( 'plugins.php' ),
'keywords' => [ 'plugins', 'wordpress' ],
],
'wordpress-users' => [
'title' => esc_html__( 'Users', 'elementor' ),
'icon' => 'wordpress',
'url' => admin_url( 'users.php' ),
'keywords' => [ 'users', 'profile', 'wordpress' ],
],
];
}
}
common/modules/finder/categories/edit.php 0000666 00000005271 15220772104 0014547 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 0000666 00000004055 15220772104 0014761 0 ustar 00 <?php
namespace Elementor\Core\Common\Modules\Finder\Categories;
use Elementor\Core\Common\Modules\Finder\Base_Category;
use Elementor\Tools as ElementorTools;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Tools Category
*
* Provides items related to Elementor's tools.
*/
class Tools extends Base_Category {
/**
* Get title.
*
* @since 2.3.0
* @access public
*
* @return string
*/
public function get_title() {
return esc_html__( 'Tools', 'elementor' );
}
public function get_id() {
return 'tools';
}
/**
* Get category items.
*
* @since 2.3.0
* @access public
*
* @param array $options
*
* @return array
*/
public function get_category_items( array $options = [] ) {
$tools_url = ElementorTools::get_url();
$items = [
'tools' => [
'title' => esc_html__( 'Tools', 'elementor' ),
'icon' => 'tools',
'url' => $tools_url,
'keywords' => [ 'tools', 'regenerate css', 'safe mode', 'debug bar', 'sync library', 'elementor' ],
],
'replace-url' => [
'title' => esc_html__( 'Replace URL', 'elementor' ),
'icon' => 'tools',
'url' => $tools_url . '#tab-replace_url',
'keywords' => [ 'tools', 'replace url', 'domain', 'elementor' ],
],
'maintenance-mode' => [
'title' => esc_html__( 'Maintenance Mode', 'elementor' ),
'icon' => 'tools',
'url' => $tools_url . '#tab-maintenance_mode',
'keywords' => [ 'tools', 'maintenance', 'coming soon', 'elementor' ],
],
'import-export' => [
'title' => esc_html__( 'Import Export', 'elementor' ),
'icon' => 'import-export',
'url' => $tools_url . '#tab-import-export-kit',
'keywords' => [ 'tools', 'import export', 'import', 'export', 'kit' ],
],
];
if ( ElementorTools::can_user_rollback_versions() ) {
$items['version-control'] = [
'title' => esc_html__( 'Version Control', 'elementor' ),
'icon' => 'time-line',
'url' => $tools_url . '#tab-versions',
'keywords' => [ 'tools', 'version', 'control', 'rollback', 'beta', 'elementor' ],
];
}
return $items;
}
}
common/modules/finder/categories/settings.php 0000666 00000004534 15220772104 0015463 0 ustar 00 <?php
namespace Elementor\Core\Common\Modules\Finder\Categories;
use Elementor\Core\Common\Modules\Finder\Base_Category;
use Elementor\Modules\ElementManager\Module as ElementManagerModule;
use Elementor\Settings as ElementorSettings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Settings Category
*
* Provides items related to Elementor's settings.
*/
class Settings extends Base_Category {
/**
* Get title.
*
* @since 2.3.0
* @access public
*
* @return string
*/
public function get_title() {
return esc_html__( 'Settings', 'elementor' );
}
public function get_id() {
return 'settings';
}
/**
* Get category items.
*
* @since 2.3.0
* @access public
*
* @param array $options
*
* @return array
*/
public function get_category_items( array $options = [] ) {
return [
'general-settings' => [
'title' => esc_html__( 'General Settings', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'general' ),
'keywords' => [ 'general', 'settings', 'elementor' ],
],
'integrations' => [
'title' => esc_html__( 'Integrations', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'integrations' ),
'keywords' => [ 'integrations', 'settings', 'elementor' ],
],
'advanced' => [
'title' => esc_html__( 'Advanced', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'advanced' ),
'keywords' => [ 'advanced', 'settings', 'elementor' ],
],
'performance' => [
'title' => esc_html__( 'Performance', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'performance' ),
'keywords' => [ 'performance', 'settings', 'elementor' ],
],
'experiments' => [
'title' => esc_html__( 'Experiments', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'experiments' ),
'keywords' => [ 'settings', 'elementor', 'experiments' ],
],
'features' => [
'title' => esc_html__( 'Features', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'experiments' ),
'keywords' => [ 'settings', 'elementor', 'features' ],
],
'element-manager' => [
'title' => esc_html__( 'Element Manager', 'elementor' ),
'url' => admin_url( 'admin.php?page=' . ElementManagerModule::PAGE_ID ),
'keywords' => [ 'settings', 'elements', 'widgets', 'manager' ],
],
];
}
}
common/modules/finder/categories-manager.php 0000666 00000007273 15220772104 0015236 0 ustar 00 <?php
namespace Elementor\Core\Common\Modules\Finder;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Categories_Manager {
/**
* @access private
*
* @var Base_Category[]
*/
private $categories;
/**
* @var array
*/
private $categories_list = [
'edit',
'general',
'create',
'site',
'settings',
'tools',
];
/**
* Add category.
*
* @since 2.3.0
* @deprecated 3.5.0 Use `register()` method instead.
* @access public
*
* @param string $category_name
* @param Base_Category $category
*
* @deprecated 3.5.0 Use `register()` method instead.
*/
public function add_category( $category_name, Base_Category $category ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function(
__METHOD__,
'3.5.0',
'register()'
);
$this->register( $category, $category_name );
}
/**
* Register finder category.
*
* @since 3.5.0
* @access public
*
* @param Base_Category $finder_category_instance An Instance of a category.
* @param string $finder_category_name A Category name. Deprecated parameter.
*
* @return void
*/
public function register( Base_Category $finder_category_instance, $finder_category_name = null ) {
// TODO: For BC. Remove in the future.
if ( $finder_category_name ) {
Plugin::instance()->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_argument(
'$finder_category_name', '3.5.0'
);
} else {
$finder_category_name = $finder_category_instance->get_id();
}
$this->categories[ $finder_category_name ] = $finder_category_instance;
}
/**
* Unregister a finder category.
*
* @param string $finder_category_name - Category to unregister.
*
* @return void
* @since 3.6.0
* @access public
*/
public function unregister( $finder_category_name ) {
unset( $this->categories[ $finder_category_name ] );
}
/**
* Get categories.
*
* Retrieve the registered categories, or a specific category if the category name
* is provided as a parameter.
*
* @since 2.3.0
* @access public
*
* @param string $category Category name.
*
* @return Base_Category|Base_Category[]|null
*/
public function get_categories( $category = '' ) {
if ( ! $this->categories ) {
$this->init_categories();
}
if ( $category ) {
if ( isset( $this->categories[ $category ] ) ) {
return $this->categories[ $category ];
}
return null;
}
return $this->categories;
}
/**
* Init categories.
*
* Used to initialize the native finder categories.
*
* @since 2.3.0
* @access private
*/
private function init_categories() {
foreach ( $this->categories_list as $category_name ) {
$class_name = __NAMESPACE__ . '\Categories\\' . $category_name;
$this->register( new $class_name() );
}
/**
* Elementor Finder categories init.
*
* Fires after Elementor Finder initialize it's native categories.
*
* This hook should be used to add your own Finder categories.
*
* @since 2.3.0
* @deprecated 3.5.0 Use `elementor/finder/register` hook instead.
*
* @param Categories_Manager $this.
*/
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->do_deprecated_action(
'elementor/finder/categories/init',
[ $this ],
'3.5.0',
'elementor/finder/register'
);
/**
* Elementor Finder categories registration.
*
* Fires after Elementor Finder initialize it's native categories.
*
* This hook should be used to register your own Finder categories.
*
* @since 3.5.0
*
* @param Categories_Manager $this Finder Categories manager.
*/
do_action( 'elementor/finder/register', $this );
}
}
common/modules/ajax/module.php 0000666 00000015615 15220772104 0012441 0 ustar 00 <?php
namespace Elementor\Core\Common\Modules\Ajax;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Utils\Exceptions;
use Elementor\Plugin;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor ajax manager.
*
* Elementor ajax manager handler class is responsible for handling Elementor
* ajax requests, ajax responses and registering actions applied on them.
*
* @since 2.0.0
*/
class Module extends BaseModule {
const NONCE_KEY = 'elementor_ajax';
/**
* Ajax actions.
*
* Holds all the register ajax action.
*
* @since 2.0.0
* @access private
*
* @var array
*/
private $ajax_actions = [];
/**
* Ajax requests.
*
* Holds all the register ajax requests.
*
* @since 2.0.0
* @access private
*
* @var array
*/
private $requests = [];
/**
* Ajax response data.
*
* Holds all the response data for all the ajax requests.
*
* @since 2.0.0
* @access private
*
* @var array
*/
private $response_data = [];
/**
* Current ajax action ID.
*
* Holds all the ID for the current ajax action.
*
* @since 2.0.0
* @access private
*
* @var string|null
*/
private $current_action_id = null;
/**
* Ajax manager constructor.
*
* Initializing Elementor ajax manager.
*
* @since 2.0.0
* @access public
*/
public function __construct() {
add_action( 'wp_ajax_elementor_ajax', [ $this, 'handle_ajax_request' ] );
}
/**
* Get module name.
*
* Retrieve the module name.
*
* @since 1.7.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'ajax';
}
/**
* Register ajax action.
*
* Add new actions for a specific ajax request and the callback function to
* be handle the response.
*
* @since 2.0.0
* @access public
*
* @param string $tag Ajax request name/tag.
* @param callable $callback The callback function.
*/
public function register_ajax_action( $tag, $callback ) {
if ( ! did_action( 'elementor/ajax/register_actions' ) ) {
_doing_it_wrong( __METHOD__, esc_html( sprintf( 'Use `%s` hook to register ajax action.', 'elementor/ajax/register_actions' ) ), '2.0.0' );
}
$this->ajax_actions[ $tag ] = compact( 'tag', 'callback' );
}
/**
* Handle ajax request.
*
* Verify ajax nonce, and run all the registered actions for this request.
*
* Fired by `wp_ajax_elementor_ajax` action.
*
* @since 2.0.0
* @access public
*/
public function handle_ajax_request() {
if ( ! $this->verify_request_nonce() ) {
$this->add_response_data( false, esc_html__( 'Token Expired.', 'elementor' ) )
->send_error( Exceptions::UNAUTHORIZED );
}
$editor_post_id = 0;
if ( ! empty( $_REQUEST['editor_post_id'] ) ) {
$editor_post_id = absint( $_REQUEST['editor_post_id'] );
Plugin::$instance->db->switch_to_post( $editor_post_id );
}
/**
* Register ajax actions.
*
* Fires when an ajax request is received and verified.
*
* Used to register new ajax action handles.
*
* @since 2.0.0
*
* @param self $this An instance of ajax manager.
*/
do_action( 'elementor/ajax/register_actions', $this );
if ( ! empty( $_REQUEST['actions'] ) ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, each action should sanitize its own data.
$this->requests = json_decode( wp_unslash( $_REQUEST['actions'] ), true );
}
foreach ( $this->requests as $id => $action_data ) {
$this->current_action_id = $id;
if ( ! isset( $this->ajax_actions[ $action_data['action'] ] ) ) {
$this->add_response_data( false, esc_html__( 'Action not found.', 'elementor' ), Exceptions::BAD_REQUEST );
continue;
}
if ( $editor_post_id ) {
$action_data['data']['editor_post_id'] = $editor_post_id;
}
try {
$data = $action_data['data'] ?? [];
$results = call_user_func( $this->ajax_actions[ $action_data['action'] ]['callback'], $data, $this );
if ( false === $results ) {
$this->add_response_data( false );
} else {
$this->add_response_data( true, $results );
}
} catch ( \Exception $e ) {
$this->add_response_data( false, $e->getMessage(), $e->getCode() );
}
}
$this->current_action_id = null;
$this->send_success();
}
/**
* Get current action data.
*
* Retrieve the data for the current ajax request.
*
* @since 2.0.1
* @access public
*
* @return bool|mixed Ajax request data if action exist, False otherwise.
*/
public function get_current_action_data() {
if ( ! $this->current_action_id ) {
return false;
}
return $this->requests[ $this->current_action_id ];
}
/**
* Create nonce.
*
* Creates a cryptographic token to
* give the user an access to Elementor ajax actions.
*
* @since 2.3.0
* @access public
*
* @return string The nonce token.
*/
public function create_nonce() {
return wp_create_nonce( self::NONCE_KEY );
}
/**
* Verify request nonce.
*
* Whether the request nonce verified or not.
*
* @since 2.3.0
* @access public
*
* @return bool True if request nonce verified, False otherwise.
*/
public function verify_request_nonce() {
return wp_verify_nonce( Utils::get_super_global_value( $_REQUEST, '_nonce' ), self::NONCE_KEY );
}
protected function get_init_settings() {
return [
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => $this->create_nonce(),
];
}
/**
* Ajax success response.
*
* Send a JSON response data back to the ajax request, indicating success.
*
* @since 2.0.0
* @access protected
*/
private function send_success() {
$response = [
'success' => true,
'data' => [
'responses' => $this->response_data,
],
];
$json = wp_json_encode( $response );
while ( ob_get_status() ) {
ob_end_clean();
}
header( 'Content-Type: application/json; charset=UTF-8' );
echo $json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
wp_die( '', '', [ 'response' => null ] );
}
/**
* Ajax failure response.
*
* Send a JSON response data back to the ajax request, indicating failure.
*
* @since 2.0.0
* @access protected
*
* @param null $code
*/
private function send_error( $code = null ) {
wp_send_json_error( [
'responses' => $this->response_data,
], $code );
}
/**
* Add response data.
*
* Add new response data to the array of all the ajax requests.
*
* @since 2.0.0
* @access protected
*
* @param bool $success True if the requests returned successfully, False
* otherwise.
* @param mixed $data Optional. Response data. Default is null.
*
* @param int $code Optional. Response code. Default is 200.
*
* @return Module An instance of ajax manager.
*/
private function add_response_data( $success, $data = null, $code = 200 ) {
$this->response_data[ $this->current_action_id ] = [
'success' => $success,
'code' => $code,
'data' => $data,
];
return $this;
}
}
common/modules/event-tracker/module.php 0000666 00000001642 15220772104 0014263 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 0000666 00000011410 15220772104 0013355 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'] = wp_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/connect/apps/common-app.php 0000666 00000001615 15220772104 0014666 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/base-user-app.php 0000666 00000001061 15220772104 0015257 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/library.php 0000666 00000011036 15220772104 0014262 0 ustar 00 <?php
namespace Elementor\Core\Common\Modules\Connect\Apps;
use Elementor\Api;
use Elementor\User;
use Elementor\Plugin;
use Elementor\Core\Common\Modules\Connect\Module as ConnectModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Library extends Common_App {
public function get_title() {
return esc_html__( 'Library', 'elementor' );
}
/**
* @since 2.3.0
* @access protected
*/
protected function get_slug() {
return 'library';
}
public function get_template_content( $id ) {
if ( ! $this->is_connected() ) {
return new \WP_Error( '401', esc_html__( 'Connecting to the Library failed. Please try reloading the page and try again', 'elementor' ) );
}
$body_args = [
'id' => $id,
// Which API version is used.
'api_version' => ELEMENTOR_VERSION,
// Which language to return.
'site_lang' => get_bloginfo( 'language' ),
];
/**
* API: Template body args.
*
* Filters the body arguments send with the GET request when fetching the content.
*
* @since 1.0.0
*
* @param array $body_args Body arguments.
*/
$body_args = apply_filters( 'elementor/api/get_templates/body_args', $body_args );
$template_content = $this->request( 'get_template_content', $body_args, true );
if ( is_wp_error( $template_content ) && 401 === $template_content->get_error_code() ) {
// Normalize 401 message
return new \WP_Error( 401, esc_html__( 'Connecting to the Library failed. Please try reloading the page and try again', 'elementor' ) );
}
return $template_content;
}
public function localize_settings( $settings ) {
$is_connected = $this->is_connected();
/** @var ConnectModule $connect */
$connect = Plugin::$instance->common->get_component( 'connect' );
$user_id = $this->get_user_id();
return array_replace_recursive( $settings, [
'library_connect' => [
'is_connected' => $is_connected,
'user_id' => $user_id,
'subscription_plans' => $connect->get_subscription_plans( 'template-library' ),
// TODO: Remove `base_access_level`.
'base_access_level' => ConnectModule::ACCESS_LEVEL_CORE,
'base_access_tier' => ConnectModule::ACCESS_TIER_FREE,
'current_access_level' => ConnectModule::ACCESS_LEVEL_CORE,
'current_access_tier' => ConnectModule::ACCESS_TIER_FREE,
'plan_type' => ConnectModule::ACCESS_TIER_FREE,
],
] );
}
public function library_connect_popup_seen() {
User::set_introduction_viewed( [
'introductionKey' => 'library_connect',
] );
}
/**
* @param \Elementor\Core\Common\Modules\Ajax\Module $ajax_manager
*/
public function register_ajax_actions( $ajax_manager ) {
$ajax_manager->register_ajax_action( 'library_connect_popup_seen', [ $this, 'library_connect_popup_seen' ] );
}
private function get_user_id() {
$token = $this->get( 'access_token' );
if ( ! is_string( $token ) ) {
return null;
}
$parts = explode( '.', $token );
if ( count( $parts ) !== 3 ) {
return null;
}
try {
$payload_encoded = $parts[1];
$payload_encoded = str_pad( $payload_encoded, strlen( $payload_encoded ) + ( 4 - strlen( $payload_encoded ) % 4 ) % 4, '=' );
$payload_json = base64_decode( strtr( $payload_encoded, '-_', '+/' ), true );
$payload = json_decode( $payload_json, true );
if ( ! isset( $payload['sub'] ) ) {
return null;
}
return $payload['sub'];
} catch ( Exception $e ) {
error_log( 'JWT Decoding Error: ' . $e->getMessage() );
return null;
}
}
/**
* After Connect
*
* After Connecting to the library, re-fetch the library data to get it up to date.
*
* @since 3.7.0
*/
protected function after_connect() {
Api::get_library_data( true );
}
protected function get_app_info() {
return [
'user_common_data' => [
'label' => 'User Common Data',
'value' => get_user_option( $this->get_option_name(), get_current_user_id() ),
],
'connect_site_key' => [
'label' => 'Site Key',
'value' => get_option( self::OPTION_CONNECT_SITE_KEY ),
],
];
}
protected function get_popup_success_event_data() {
return [
'access_level' => ConnectModule::ACCESS_LEVEL_CORE,
'access_tier' => ConnectModule::ACCESS_TIER_FREE,
'plan_type' => ConnectModule::ACCESS_TIER_FREE,
'tracking_opted_in' => $this->get( 'data_share_opted_in' ) ?? false,
];
}
protected function init() {
add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
add_filter( 'elementor/common/localize_settings', [ $this, 'localize_settings' ] );
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
}
}
common/modules/connect/rest/rest-api.php 0000666 00000010230 15220772104 0014347 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/connect-menu-item.php 0000666 00000002237 15220772104 0015205 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/events-manager/module.php 0000666 00000004412 15220772104 0014423 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 );
}
}
app/modules/kit-library/module.php 0000666 00000000722 15220772104 0013230 0 ustar 00 <?php
namespace Elementor\Core\App\Modules\KitLibrary;
use Elementor\Core\Base\Module as BaseModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* This App class exists for backwards compatibility with 3rd parties.
*
* @deprecated 3.8.0
*/
class Module extends BaseModule {
/**
* @deprecated 3.8.0
*/
const VERSION = '1.0.0';
/**
* @deprecated 3.8.0
*/
public function get_name() {
return 'kit-library-bc';
}
}
app/modules/onboarding/module.php 0000666 00000000721 15220772104 0013120 0 ustar 00 <?php
namespace Elementor\Core\App\Modules\Onboarding;
use Elementor\Core\Base\Module as BaseModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* This App class exists for backwards compatibility with 3rd parties.
*
* @deprecated 3.8.0
*/
class Module extends BaseModule {
/**
* @deprecated 3.8.0
*/
const VERSION = '1.0.0';
/**
* @deprecated 3.8.0
*/
public function get_name() {
return 'onboarding-bc';
}
}
app/modules/import-export/module.php 0000666 00000001131 15220772104 0013623 0 ustar 00 <?php
namespace Elementor\App\Modules\ImportExport;
use Elementor\App\Modules\ImportExport\Module as Import_Export_Module;
use Elementor\Core\Base\Module as BaseModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* This App class exists for backwards compatibility with 3rd parties.
*
* @deprecated 3.8.0
*/
class Module extends BaseModule {
/**
* @deprecated 3.8.0
*/
const VERSION = '1.0.0';
/**
* @var mixed
* @deprecated 3.8.0
*/
public $import;
/**
* @deprecated 3.8.0
*/
public function get_name() {
return 'import-export-bc';
}
}
logger/items/log-item-interface.php 0000666 00000002205 15220772104 0013333 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();
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 0000666 00000000413 15220772104 0010446 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';
}
}
utils/svg/svg-sanitizer.php 0000666 00000041224 15220772104 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 = [
'accent-height',
'accumulate',
'additivive',
'alignment-baseline',
'aria-hidden',
'aria-controls',
'aria-describedby',
'aria-description',
'aria-expanded',
'aria-haspopup',
'aria-label',
'aria-labelledby',
'aria-roledescription',
'ascent',
'attributename',
'attributetype',
'azimuth',
'basefrequency',
'baseline-shift',
'begin',
'bias',
'by',
'class',
'clip',
'clip-path',
'clip-rule',
'clippathunits',
'color',
'color-interpolation',
'color-interpolation-filters',
'color-profile',
'color-rendering',
'cx',
'cy',
'd',
'dx',
'dy',
'diffuseconstant',
'direction',
'display',
'divisor',
'dominant-baseline',
'dur',
'edgemode',
'elevation',
'end',
'fill',
'fill-opacity',
'fill-rule',
'filter',
'filterres',
'filterunits',
'flood-color',
'flood-opacity',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'fx',
'fy',
'g1',
'g2',
'glyph-name',
'glyphref',
'gradienttransform',
'gradientunits',
'height',
'href',
'id',
'image-rendering',
'in',
'in2',
'k',
'k1',
'k2',
'k3',
'k4',
'kerning',
'keypoints',
'keysplines',
'keytimes',
'lang',
'lengthadjust',
'letter-spacing',
'kernelmatrix',
'kernelunitlength',
'lighting-color',
'local',
'marker-end',
'marker-mid',
'marker-start',
'markerheight',
'markerunits',
'markerwidth',
'mask',
'maskcontentunits',
'maskunits',
'max',
'media',
'method',
'mode',
'min',
'name',
'numoctaves',
'offset',
'opacity',
'operator',
'order',
'orient',
'orientation',
'origin',
'overflow',
'paint-order',
'path',
'pathlength',
'patterncontentunits',
'patterntransform',
'patternunits',
'points',
'preservealpha',
'preserveaspectratio',
'primitiveunits',
'r',
'rx',
'ry',
'radius',
'refx',
'refy',
'repeatcount',
'repeatdur',
'requiredfeatures',
'restart',
'result',
'role',
'rotate',
'scale',
'seed',
'shape-rendering',
'spacing',
'specularconstant',
'specularexponent',
'spreadmethod',
'startoffset',
'stddeviation',
'stitchtiles',
'stop-color',
'stop-opacity',
'stroke',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'style',
'surfacescale',
'systemlanguage',
'tabindex',
'targetx',
'targety',
'transform',
'transform-origin',
'text-anchor',
'text-decoration',
'text-rendering',
'textlength',
'type',
'u1',
'u2',
'underline-position',
'underline-thickness',
'unicode',
'unicode-bidi',
'values',
'vector-effect',
'vert-adv-y',
'vert-origin-x',
'vert-origin-y',
'viewbox',
'visibility',
'width',
'word-spacing',
'wrap',
'writing-mode',
'x',
'x1',
'x2',
'xchannelselector',
'xlink:href',
'xlink:title',
'xmlns',
'xmlns:se',
'xmlns:xlink',
'xml:lang',
'xml:space',
'y',
'y1',
'y2',
'ychannelselector',
'z',
'zoomandpan',
];
/**
* 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',
'animate',
'animateMotion',
'animateTransform',
'circle',
'clippath',
'defs',
'desc',
'ellipse',
'feBlend',
'feColorMatrix',
'feComponentTransfer',
'feComposite',
'feConvolveMatrix',
'feDiffuseLighting',
'feDisplacementMap',
'feDistantLight',
'feDropShadow',
'feFlood',
'feFuncA',
'feFuncB',
'feFuncG',
'feFuncR',
'feGaussianBlur',
'feImage',
'feMerge',
'feMergeNode',
'feMorphology',
'feOffset',
'fePointLight',
'feSpecularLighting',
'feSpotLight',
'feTile',
'feTurbulence',
'filter',
'foreignobject',
'g',
'image',
'line',
'lineargradient',
'marker',
'mask',
'metadata',
'mpath',
'path',
'pattern',
'polygon',
'polyline',
'radialgradient',
'rect',
'set',
'stop',
'style',
'svg',
'switch',
'symbol',
'text',
'textpath',
'title',
'tspan',
'use',
'view',
];
/**
* 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/api/parse-errors.php 0000666 00000001777 15220772104 0011632 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/parse-result.php 0000666 00000001144 15220772104 0011620 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 0000666 00000001217 15220772104 0011750 0 ustar 00 <?php
namespace Elementor\Core\Utils\Api;
class Error_Builder {
private string $message;
private int $status;
private string $code;
private function __construct( $code, $status = 500 ) {
$this->code = $code;
$this->status = $status;
}
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 build() {
return new \WP_Error( $this->code, $this->message, [ 'status' => $this->status ] );
}
}
utils/import-export/url.php 0000666 00000003241 15220772104 0012054 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 0000666 00000131041 15220772104 0013210 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;
}
}
if ( ! function_exists( 'wp_import_cleanup' ) ) {
require_once ABSPATH . 'wp-admin/includes/import.php';
}
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
*
* @var string
*/
private $id;
/**
* Information to import from WXR file.
*/
// phpcs:ignore Squiz.Commenting.VariableComment.MissingVar
private $version;
private $authors = [];
private $posts = [];
private $terms = [];
private $base_url = '';
private $page_on_front;
private $base_blog_url = '';
/**
* Mappings from old information to new.
*/
// phpcs:ignore Squiz.Commenting.VariableComment.MissingVar
private $processed_taxonomies;
private $processed_terms = [];
private $processed_posts = [];
private $processed_authors = [];
private $author_mapping = [];
private $processed_menu_items = [];
private $post_orphans = [];
private $menu_item_orphans = [];
private $mapped_terms_slug = [];
private $fetch_attachments = false;
private $url_remap = [];
private $featured_images = [];
/**
* @var array[] [meta_key => meta_value] Meta value that should be set for every imported post.
*/
private $posts_meta = [];
/**
* @var array[] [meta_key => meta_value] Meta value that should be set for every imported term.
*/
private $terms_meta = [];
/**
* Parses filename from a Content-Disposition header value.
*
* As per RFC6266:
*
* content-disposition = "Content-Disposition" ":"
* disposition-type *( ";" disposition-parm )
*
* disposition-type = "inline" | "attachment" | disp-ext-type
* ; case-insensitive
* disp-ext-type = token
*
* disposition-parm = filename-parm | disp-ext-parm
*
* filename-parm = "filename" "=" value
* | "filename*" "=" ext-value
*
* disp-ext-parm = token "=" value
* | ext-token "=" ext-value
* ext-token = <the characters in token, followed by "*">
*
* @param string[] $disposition_header List of Content-Disposition header values.
*
* @return string|null Filename if available, or null if not found.
* @link http://tools.ietf.org/html/rfc2388
* @link http://tools.ietf.org/html/rfc6266
*
* @see WP_REST_Attachments_Controller::get_filename_from_disposition()
*/
protected static function get_filename_from_disposition( $disposition_header ) {
// Get the filename.
$filename = null;
foreach ( $disposition_header as $value ) {
$value = trim( $value );
if ( strpos( $value, ';' ) === false ) {
continue;
}
list( $type, $attr_parts ) = explode( ';', $value, 2 );
$attr_parts = explode( ';', $attr_parts );
$attributes = [];
foreach ( $attr_parts as $part ) {
if ( strpos( $part, '=' ) === false ) {
continue;
}
list( $key, $value ) = explode( '=', $part, 2 );
$attributes[ trim( $key ) ] = trim( $value );
}
if ( empty( $attributes['filename'] ) ) {
continue;
}
$filename = trim( $attributes['filename'] );
// Unquote quoted filename, but after trimming.
if ( substr( $filename, 0, 1 ) === '"' && substr( $filename, -1, 1 ) === '"' ) {
$filename = substr( $filename, 1, -1 );
}
}
return $filename;
}
/**
* Retrieves file extension by mime type.
*
* @param string $mime_type Mime type to search extension for.
*
* @return string|null File extension if available, or null if not found.
*/
protected static function get_file_extension_by_mime_type( $mime_type ) {
static $map = null;
if ( is_array( $map ) ) {
return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null;
}
$mime_types = wp_get_mime_types();
$map = array_flip( $mime_types );
// Some types have multiple extensions, use only the first one.
foreach ( $map as $type => $extensions ) {
$map[ $type ] = strtok( $extensions, '|' );
}
return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null;
}
/**
* The main controller for the actual import stage.
*
* @param string $file Path to the WXR file for importing
*/
private function import( $file ) {
add_filter( 'import_post_meta_key', function ( $key ) {
return $this->is_valid_meta_key( $key );
} );
add_filter( 'http_request_timeout', function () {
return self::DEFAULT_BUMP_REQUEST_TIMEOUT;
} );
if ( ! $this->import_start( $file ) ) {
return;
}
$this->set_author_mapping();
wp_suspend_cache_invalidation( true );
$imported_summary = [
'terms' => $this->process_terms(),
'posts' => $this->process_posts(),
];
wp_suspend_cache_invalidation( false );
// Update incorrect/missing information in the DB.
$this->backfill_parents();
$this->backfill_attachment_urls();
$this->remap_featured_images();
$this->import_end();
$is_some_succeed = false;
foreach ( $imported_summary as $item ) {
if ( $item > 0 ) {
$is_some_succeed = true;
break;
}
}
if ( $is_some_succeed ) {
$this->output['status'] = 'success';
$this->output['summary'] = $imported_summary;
}
}
/**
* Parses the WXR file and prepares us for the task of processing parsed data.
*
* @param string $file Path to the WXR file for importing
*/
private function import_start( $file ) {
if ( ! is_file( $file ) ) {
$this->output['errors'] = [ esc_html__( 'The file does not exist, please try again.', 'elementor' ) ];
return false;
}
$import_data = $this->parse( $file );
if ( is_wp_error( $import_data ) ) {
$this->output['errors'] = [ $import_data->get_error_message() ];
return false;
}
$this->version = $import_data['version'];
$this->set_authors_from_import( $import_data );
$this->posts = $this->filter_import_posts( $import_data['posts'] );
$this->terms = $import_data['terms'];
$this->base_url = esc_url( $import_data['base_url'] );
$this->base_blog_url = esc_url( $import_data['base_blog_url'] );
$this->page_on_front = $import_data['page_on_front'];
wp_defer_term_counting( true );
wp_defer_comment_counting( true );
do_action( 'import_start' );
return true;
}
private function filter_import_posts( array $posts ): array {
if ( isset( $this->args['include'] ) ) {
$filtered_posts = [];
foreach ( $posts as $post ) {
if ( in_array( $post['post_id'], $this->args['include'], true ) ) {
$filtered_posts[] = $post;
}
}
return $filtered_posts;
}
return $posts;
}
/**
* Performs post-import cleanup of files and the cache
*/
private function import_end() {
wp_import_cleanup( $this->id );
wp_cache_flush();
foreach ( get_taxonomies() as $tax ) {
delete_option( "{$tax}_children" );
_get_term_hierarchy( $tax );
}
wp_defer_term_counting( false );
wp_defer_comment_counting( false );
do_action( 'import_end' );
}
/**
* Retrieve authors from parsed WXR data and set it to `$this->>authors`.
*
* Uses the provided author information from WXR 1.1 files
* or extracts info from each post for WXR 1.0 files
*
* @param array $import_data Data returned by a WXR parser
*/
private function set_authors_from_import( $import_data ) {
if ( ! empty( $import_data['authors'] ) ) {
$this->authors = $import_data['authors'];
// No author information, grab it from the posts.
} else {
foreach ( $import_data['posts'] as $post ) {
$login = sanitize_user( $post['post_author'], true );
if ( empty( $login ) ) {
$this->output['errors'][] = sprintf(
/* translators: %s: Post author. */
esc_html__( 'Failed to import author %s. Their posts will be attributed to the current user.', 'elementor' ),
$post['post_author']
);
continue;
}
if ( ! isset( $this->authors[ $login ] ) ) {
$this->authors[ $login ] = [
'author_login' => $login,
'author_display_name' => $post['post_author'],
];
}
}
}
}
/**
* Map old author logins to local user IDs based on decisions made
* in import options form. Can map to an existing user, create a new user
* or falls back to the current user in case of error with either of the previous
*/
private function set_author_mapping() {
if ( ! isset( $this->args['imported_authors'] ) ) {
return;
}
$create_users = apply_filters( 'import_allow_create_users', self::DEFAULT_ALLOW_CREATE_USERS );
foreach ( (array) $this->args['imported_authors'] as $i => $old_login ) {
// Multisite adds strtolower to sanitize_user. Need to sanitize here to stop breakage in process_posts.
$sanitized_old_login = sanitize_user( $old_login, true );
$old_id = isset( $this->authors[ $old_login ]['author_id'] ) ? (int) $this->authors[ $old_login ]['author_id'] : false;
if ( ! empty( $this->args['user_map'][ $i ] ) ) {
$user = get_userdata( (int) $this->args['user_map'][ $i ] );
if ( isset( $user->ID ) ) {
if ( $old_id ) {
$this->processed_authors[ $old_id ] = $user->ID;
}
$this->author_mapping[ $sanitized_old_login ] = $user->ID;
}
} elseif ( $create_users ) {
$user_id = 0;
if ( ! empty( $this->args['user_new'][ $i ] ) ) {
$user_id = wp_create_user( $this->args['user_new'][ $i ], wp_generate_password() );
} elseif ( '1.0' !== $this->version ) {
$user_data = [
'user_login' => $old_login,
'user_pass' => wp_generate_password(),
'user_email' => isset( $this->authors[ $old_login ]['author_email'] ) ? $this->authors[ $old_login ]['author_email'] : '',
'display_name' => $this->authors[ $old_login ]['author_display_name'],
'first_name' => isset( $this->authors[ $old_login ]['author_first_name'] ) ? $this->authors[ $old_login ]['author_first_name'] : '',
'last_name' => isset( $this->authors[ $old_login ]['author_last_name'] ) ? $this->authors[ $old_login ]['author_last_name'] : '',
];
$user_id = wp_insert_user( $user_data );
}
if ( ! is_wp_error( $user_id ) ) {
if ( $old_id ) {
$this->processed_authors[ $old_id ] = $user_id;
}
$this->author_mapping[ $sanitized_old_login ] = $user_id;
} else {
$error = sprintf(
/* translators: %s: Author display name. */
esc_html__( 'Failed to create new user for %s. Their posts will be attributed to the current user.', 'elementor' ),
$this->authors[ $old_login ]['author_display_name']
);
if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) {
$error .= PHP_EOL . $user_id->get_error_message();
}
$this->output['errors'][] = $error;
}
}
// Failsafe: if the user_id was invalid, default to the current user.
if ( ! isset( $this->author_mapping[ $sanitized_old_login ] ) ) {
if ( $old_id ) {
$this->processed_authors[ $old_id ] = (int) get_current_user_id();
}
$this->author_mapping[ $sanitized_old_login ] = (int) get_current_user_id();
}
}
}
/**
* Create new terms based on import information
*
* Doesn't create a term its slug already exists
*
* @return array|array[] the ids of succeed/failed imported terms.
*/
private function process_terms() {
$result = [
'succeed' => [],
'failed' => [],
];
$this->terms = apply_filters( 'wp_import_terms', $this->terms );
if ( empty( $this->terms ) ) {
return $result;
}
foreach ( $this->terms as $term ) {
// if the term already exists in the correct taxonomy leave it alone
$term_id = term_exists( $term['slug'], $term['term_taxonomy'] );
if ( $term_id ) {
if ( is_array( $term_id ) ) {
$term_id = $term_id['term_id'];
}
if ( isset( $term['term_id'] ) ) {
if ( 'nav_menu' === $term['term_taxonomy'] ) {
// BC - support old kits that the menu terms are part of the 'nav_menu_item' post type
// and not part of the taxonomies.
if ( ! empty( $this->processed_taxonomies[ $term['term_taxonomy'] ] ) ) {
foreach ( $this->processed_taxonomies[ $term['term_taxonomy'] ] as $processed_term ) {
$old_slug = $processed_term['old_slug'];
$new_slug = $processed_term['new_slug'];
$this->mapped_terms_slug[ $old_slug ] = $new_slug;
$result['succeed'][ $old_slug ] = $new_slug;
}
continue;
} else {
$term = $this->handle_duplicated_nav_menu_term( $term );
}
} else {
$this->processed_terms[ (int) $term['term_id'] ] = (int) $term_id;
$result['succeed'][ (int) $term['term_id'] ] = (int) $term_id;
continue;
}
}
}
if ( empty( $term['term_parent'] ) ) {
$parent = 0;
} else {
$parent = term_exists( $term['term_parent'], $term['term_taxonomy'] );
if ( is_array( $parent ) ) {
$parent = $parent['term_id'];
}
}
$description = isset( $term['term_description'] ) ? $term['term_description'] : '';
$args = [
'slug' => $term['slug'],
'description' => wp_slash( $description ),
'parent' => (int) $parent,
];
$id = wp_insert_term( wp_slash( $term['term_name'] ), $term['term_taxonomy'], $args );
if ( ! is_wp_error( $id ) ) {
if ( isset( $term['term_id'] ) ) {
$this->processed_terms[ (int) $term['term_id'] ] = $id['term_id'];
$result['succeed'][ (int) $term['term_id'] ] = $id['term_id'];
$this->update_term_meta( $id['term_id'] );
}
} else {
/* translators: 1: Term taxonomy, 2: Term name. */
$error = sprintf( esc_html__( 'Failed to import %1$s %2$s', 'elementor' ), $term['term_taxonomy'], $term['term_name'] );
if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) {
$error .= PHP_EOL . $id->get_error_message();
}
$result['failed'][] = $id;
$this->output['errors'][] = $error;
continue;
}
$this->process_termmeta( $term, $id['term_id'] );
}
unset( $this->terms );
return $result;
}
/**
* Add metadata to imported term.
*
* @param array $term Term data from WXR import.
* @param int $term_id ID of the newly created term.
*/
private function process_termmeta( $term, $term_id ) {
if ( ! function_exists( 'add_term_meta' ) ) {
return;
}
if ( ! isset( $term['termmeta'] ) ) {
$term['termmeta'] = [];
}
/**
* Filters the metadata attached to an imported term.
*
* @param array $termmeta Array of term meta.
* @param int $term_id ID of the newly created term.
* @param array $term Term data from the WXR import.
*/
$term['termmeta'] = apply_filters( 'wp_import_term_meta', $term['termmeta'], $term_id, $term );
if ( empty( $term['termmeta'] ) ) {
return;
}
foreach ( $term['termmeta'] as $meta ) {
/**
* Filters the meta key for an imported piece of term meta.
*
* @param string $meta_key Meta key.
* @param int $term_id ID of the newly created term.
* @param array $term Term data from the WXR import.
*/
$key = apply_filters( 'import_term_meta_key', $meta['key'], $term_id, $term );
if ( ! $key ) {
continue;
}
// Export gets meta straight from the DB so could have a serialized string
$value = maybe_unserialize( $meta['value'] );
add_term_meta( $term_id, wp_slash( $key ), wp_slash_strings_only( $value ) );
/**
* Fires after term meta is imported.
*
* @param int $term_id ID of the newly created term.
* @param string $key Meta key.
* @param mixed $value Meta value.
*/
do_action( 'import_term_meta', $term_id, $key, $value );
}
}
/**
* Create new posts based on import information
*
* Posts marked as having a parent which doesn't exist will become top level items.
* Doesn't create a new post if: the post type doesn't exist, the given post ID
* is already noted as imported or a post with the same title and date already exists.
* Note that new/updated terms, comments and meta are imported for the last of the above.
*
* @return array the ids of succeed/failed imported posts.
*/
private function process_posts() {
$result = [
'succeed' => [],
'failed' => [],
];
$this->posts = apply_filters( 'wp_import_posts', $this->posts );
foreach ( $this->posts as $post ) {
$post = apply_filters( 'wp_import_post_data_raw', $post );
if ( ! post_type_exists( $post['post_type'] ) ) {
/* translators: 1: Post title, 2: Post type. */
$this->output['errors'][] = sprintf( esc_html__( 'Failed to import %1$s: Invalid post type %2$s', 'elementor' ), $post['post_title'], $post['post_type'] );
do_action( 'wp_import_post_exists', $post );
continue;
}
if ( isset( $this->processed_posts[ $post['post_id'] ] ) && ! empty( $post['post_id'] ) ) {
continue;
}
if ( 'auto-draft' === $post['status'] ) {
continue;
}
if ( 'nav_menu_item' === $post['post_type'] ) {
$result['succeed'] += $this->process_menu_item( $post );
continue;
}
$post_type_object = get_post_type_object( $post['post_type'] );
$post_parent = (int) $post['post_parent'];
if ( $post_parent ) {
// if we already know the parent, map it to the new local ID.
if ( isset( $this->processed_posts[ $post_parent ] ) ) {
$post_parent = $this->processed_posts[ $post_parent ];
// otherwise record the parent for later.
} else {
$this->post_orphans[ (int) $post['post_id'] ] = $post_parent;
$post_parent = 0;
}
}
// Map the post author.
$author = sanitize_user( $post['post_author'], true );
if ( isset( $this->author_mapping[ $author ] ) ) {
$author = $this->author_mapping[ $author ];
} else {
$author = (int) get_current_user_id();
}
$postdata = [
'post_author' => $author,
'post_content' => $post['post_content'],
'post_excerpt' => $post['post_excerpt'],
'post_title' => $post['post_title'],
'post_status' => $post['status'],
'post_name' => $post['post_name'],
'comment_status' => $post['comment_status'],
'ping_status' => $post['ping_status'],
'guid' => $post['guid'],
'post_parent' => $post_parent,
'menu_order' => $post['menu_order'],
'post_type' => $post['post_type'],
'post_password' => $post['post_password'],
];
$original_post_id = $post['post_id'];
$postdata = apply_filters( 'wp_import_post_data_processed', $postdata, $post );
$postdata = wp_slash( $postdata );
if ( 'attachment' === $postdata['post_type'] ) {
$remote_url = ! empty( $post['attachment_url'] ) ? $post['attachment_url'] : $post['guid'];
// try to use _wp_attached file for upload folder placement to ensure the same location as the export site
// e.g. location is 2003/05/image.jpg but the attachment post_date is 2010/09, see media_handle_upload()
$postdata['upload_date'] = $post['post_date'];
if ( isset( $post['postmeta'] ) ) {
foreach ( $post['postmeta'] as $meta ) {
if ( '_wp_attached_file' === $meta['key'] ) {
if ( preg_match( '%^[0-9]{4}/[0-9]{2}%', $meta['value'], $matches ) ) {
$postdata['upload_date'] = $matches[0];
}
break;
}
}
}
$post_id = $this->process_attachment( $postdata, $remote_url );
$comment_post_id = $post_id;
} else {
$post_id = wp_insert_post( $postdata, true );
$this->update_post_meta( $post_id );
$comment_post_id = $post_id;
do_action( 'wp_import_insert_post', $post_id, $original_post_id, $postdata, $post );
}
if ( is_wp_error( $post_id ) ) {
/* translators: 1: Post type singular label, 2: Post title. */
$error = sprintf( __( 'Failed to import %1$s %2$s', 'elementor' ), $post_type_object->labels->singular_name, $post['post_title'] );
if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) {
$error .= PHP_EOL . $post_id->get_error_message();
}
$result['failed'][] = $original_post_id;
$this->output['errors'][] = $error;
continue;
}
$result['succeed'][ $original_post_id ] = $post_id;
if ( 1 === $post['is_sticky'] ) {
stick_post( $post_id );
}
if ( $this->page_on_front === $original_post_id ) {
update_option( 'page_on_front', $post_id );
}
// Map pre-import ID to local ID.
$this->processed_posts[ (int) $post['post_id'] ] = (int) $post_id;
if ( ! isset( $post['terms'] ) ) {
$post['terms'] = [];
}
$post['terms'] = apply_filters( 'wp_import_post_terms', $post['terms'], $post_id, $post );
// add categories, tags and other terms
if ( ! empty( $post['terms'] ) ) {
$terms_to_set = [];
foreach ( $post['terms'] as $term ) {
// back compat with WXR 1.0 map 'tag' to 'post_tag'
$taxonomy = ( 'tag' === $term['domain'] ) ? 'post_tag' : $term['domain'];
$term_exists = term_exists( $term['slug'], $taxonomy );
$term_id = is_array( $term_exists ) ? $term_exists['term_id'] : $term_exists;
if ( ! $term_id ) {
$t = wp_insert_term( $term['name'], $taxonomy, [ 'slug' => $term['slug'] ] );
if ( ! is_wp_error( $t ) ) {
$term_id = $t['term_id'];
$this->update_term_meta( $term_id );
do_action( 'wp_import_insert_term', $t, $term, $post_id, $post );
} else {
/* translators: 1: Taxonomy name, 2: Term name. */
$error = sprintf( esc_html__( 'Failed to import %1$s %2$s', 'elementor' ), $taxonomy, $term['name'] );
if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) {
$error .= PHP_EOL . $t->get_error_message();
}
$this->output['errors'][] = $error;
do_action( 'wp_import_insert_term_failed', $t, $term, $post_id, $post );
continue;
}
}
$terms_to_set[ $taxonomy ][] = (int) $term_id;
}
foreach ( $terms_to_set as $tax => $ids ) {
$tt_ids = wp_set_post_terms( $post_id, $ids, $tax );
do_action( 'wp_import_set_post_terms', $tt_ids, $ids, $tax, $post_id, $post );
}
unset( $post['terms'], $terms_to_set );
}
if ( ! isset( $post['comments'] ) ) {
$post['comments'] = [];
}
$post['comments'] = apply_filters( 'wp_import_post_comments', $post['comments'], $post_id, $post );
// Add/update comments.
if ( ! empty( $post['comments'] ) ) {
$num_comments = 0;
$inserted_comments = [];
foreach ( $post['comments'] as $comment ) {
$comment_id = $comment['comment_id'];
$newcomments[ $comment_id ]['comment_post_ID'] = $comment_post_id;
$newcomments[ $comment_id ]['comment_author'] = $comment['comment_author'];
$newcomments[ $comment_id ]['comment_author_email'] = $comment['comment_author_email'];
$newcomments[ $comment_id ]['comment_author_IP'] = $comment['comment_author_IP'];
$newcomments[ $comment_id ]['comment_author_url'] = $comment['comment_author_url'];
$newcomments[ $comment_id ]['comment_date'] = $comment['comment_date'];
$newcomments[ $comment_id ]['comment_date_gmt'] = $comment['comment_date_gmt'];
$newcomments[ $comment_id ]['comment_content'] = $comment['comment_content'];
$newcomments[ $comment_id ]['comment_approved'] = $comment['comment_approved'];
$newcomments[ $comment_id ]['comment_type'] = $comment['comment_type'];
$newcomments[ $comment_id ]['comment_parent'] = $comment['comment_parent'];
$newcomments[ $comment_id ]['commentmeta'] = isset( $comment['commentmeta'] ) ? $comment['commentmeta'] : [];
if ( isset( $this->processed_authors[ $comment['comment_user_id'] ] ) ) {
$newcomments[ $comment_id ]['user_id'] = $this->processed_authors[ $comment['comment_user_id'] ];
}
}
ksort( $newcomments );
foreach ( $newcomments as $key => $comment ) {
if ( isset( $inserted_comments[ $comment['comment_parent'] ] ) ) {
$comment['comment_parent'] = $inserted_comments[ $comment['comment_parent'] ];
}
$comment_data = wp_slash( $comment );
unset( $comment_data['commentmeta'] ); // Handled separately, wp_insert_comment() also expects `comment_meta`.
$comment_data = wp_filter_comment( $comment_data );
$inserted_comments[ $key ] = wp_insert_comment( $comment_data );
do_action( 'wp_import_insert_comment', $inserted_comments[ $key ], $comment, $comment_post_id, $post );
foreach ( $comment['commentmeta'] as $meta ) {
$value = maybe_unserialize( $meta['value'] );
add_comment_meta( $inserted_comments[ $key ], wp_slash( $meta['key'] ), wp_slash_strings_only( $value ) );
}
$num_comments++;
}
unset( $newcomments, $inserted_comments, $post['comments'] );
}
if ( ! isset( $post['postmeta'] ) ) {
$post['postmeta'] = [];
}
$post['postmeta'] = apply_filters( 'wp_import_post_meta', $post['postmeta'], $post_id, $post );
// Add/update post meta.
if ( ! empty( $post['postmeta'] ) ) {
foreach ( $post['postmeta'] as $meta ) {
$key = apply_filters( 'import_post_meta_key', $meta['key'], $post_id, $post );
$value = false;
if ( '_edit_last' === $key ) {
if ( isset( $this->processed_authors[ (int) $meta['value'] ] ) ) {
$value = $this->processed_authors[ (int) $meta['value'] ];
} else {
$key = false;
}
}
if ( $key ) {
// Export gets meta straight from the DB so could have a serialized string.
if ( ! $value ) {
$value = maybe_unserialize( $meta['value'] );
}
add_post_meta( $post_id, wp_slash( $key ), wp_slash_strings_only( $value ) );
do_action( 'import_post_meta', $post_id, $key, $value );
// If the post has a featured image, take note of this in case of remap.
if ( '_thumbnail_id' === $key ) {
$this->featured_images[ $post_id ] = (int) $value;
}
}
}
}
}
unset( $this->posts );
return $result;
}
/**
* Attempt to create a new menu item from import data
*
* Fails for draft, orphaned menu items and those without an associated nav_menu
* or an invalid nav_menu term. If the post type or term object which the menu item
* represents doesn't exist then the menu item will not be imported (waits until the
* end of the import to retry again before discarding).
*
* @param array $item Menu item details from WXR file
*/
private function process_menu_item( $item ) {
$result = [];
// Skip draft, orphaned menu items.
if ( 'draft' === $item['status'] ) {
return;
}
$menu_slug = false;
if ( isset( $item['terms'] ) ) {
// Loop through terms, assume first nav_menu term is correct menu.
foreach ( $item['terms'] as $term ) {
if ( 'nav_menu' === $term['domain'] ) {
$menu_slug = $term['slug'];
break;
}
}
}
// No nav_menu term associated with this menu item.
if ( ! $menu_slug ) {
$this->output['errors'][] = esc_html__( 'Menu item skipped due to missing menu slug', 'elementor' );
return $result;
}
// If menu was already exists, refer the items to the duplicated menu created.
if ( array_key_exists( $menu_slug, $this->mapped_terms_slug ) ) {
$menu_slug = $this->mapped_terms_slug[ $menu_slug ];
}
$menu_id = term_exists( $menu_slug, 'nav_menu' );
if ( ! $menu_id ) {
/* translators: %s: Menu slug. */
$this->output['errors'][] = sprintf( esc_html__( 'Menu item skipped due to invalid menu slug: %s', 'elementor' ), $menu_slug );
return $result;
} else {
$menu_id = is_array( $menu_id ) ? $menu_id['term_id'] : $menu_id;
}
$post_meta_key_value = [];
foreach ( $item['postmeta'] as $meta ) {
$post_meta_key_value[ $meta['key'] ] = $meta['value'];
}
$_menu_item_type = $post_meta_key_value['_menu_item_type'];
$_menu_item_url = $post_meta_key_value['_menu_item_url'];
// Skip menu items 'taxonomy' type, when the taxonomy is not exits.
if ( 'taxonomy' === $_menu_item_type && ! taxonomy_exists( $post_meta_key_value['_menu_item_object'] ) ) {
return $result;
}
// Skip menu items 'post_type' type, when the post type is not exits.
if ( 'post_type' === $_menu_item_type && ! post_type_exists( $post_meta_key_value['_menu_item_object'] ) ) {
return $result;
}
$_menu_item_object_id = $post_meta_key_value['_menu_item_object_id'];
if ( 'taxonomy' === $_menu_item_type && isset( $this->processed_terms[ (int) $_menu_item_object_id ] ) ) {
$_menu_item_object_id = $this->processed_terms[ (int) $_menu_item_object_id ];
} elseif ( 'post_type' === $_menu_item_type && isset( $this->processed_posts[ (int) $_menu_item_object_id ] ) ) {
$_menu_item_object_id = $this->processed_posts[ (int) $_menu_item_object_id ];
} elseif ( 'custom' === $_menu_item_type ) {
$_menu_item_url = Url::migrate( $_menu_item_url, $this->base_blog_url );
} else {
return $result;
}
$_menu_item_menu_item_parent = $post_meta_key_value['_menu_item_menu_item_parent'];
if ( isset( $this->processed_menu_items[ (int) $_menu_item_menu_item_parent ] ) ) {
$_menu_item_menu_item_parent = $this->processed_menu_items[ (int) $_menu_item_menu_item_parent ];
} elseif ( $_menu_item_menu_item_parent ) {
$this->menu_item_orphans[ (int) $item['post_id'] ] = (int) $_menu_item_menu_item_parent;
$_menu_item_menu_item_parent = 0;
}
// wp_update_nav_menu_item expects CSS classes as a space separated string
$_menu_item_classes = maybe_unserialize( $post_meta_key_value['_menu_item_classes'] );
if ( is_array( $_menu_item_classes ) ) {
$_menu_item_classes = implode( ' ', $_menu_item_classes );
}
$args = [
'menu-item-object-id' => $_menu_item_object_id,
'menu-item-object' => $post_meta_key_value['_menu_item_object'],
'menu-item-parent-id' => $_menu_item_menu_item_parent,
'menu-item-position' => (int) $item['menu_order'],
'menu-item-type' => $_menu_item_type,
'menu-item-title' => $item['post_title'],
'menu-item-url' => $_menu_item_url,
'menu-item-description' => $item['post_content'],
'menu-item-attr-title' => $item['post_excerpt'],
'menu-item-target' => $post_meta_key_value['_menu_item_target'],
'menu-item-classes' => $_menu_item_classes,
'menu-item-xfn' => $post_meta_key_value['_menu_item_xfn'],
'menu-item-status' => $item['status'],
];
$id = wp_update_nav_menu_item( $menu_id, 0, $args );
if ( $id && ! is_wp_error( $id ) ) {
$this->processed_menu_items[ (int) $item['post_id'] ] = (int) $id;
$result[ $item['post_id'] ] = $id;
$this->update_post_meta( $id );
}
return $result;
}
/**
* If fetching attachments is enabled then attempt to create a new attachment
*
* @param array $post Attachment post details from WXR
* @param string $url URL to fetch attachment from
*
* @return int|WP_Error Post ID on success, WP_Error otherwise
*/
private function process_attachment( $post, $url ) {
if ( ! $this->fetch_attachments ) {
return new WP_Error( 'attachment_processing_error', esc_html__( 'Fetching attachments is not enabled', 'elementor' ) );
}
// if the URL is absolute, but does not contain address, then upload it assuming base_site_url.
if ( preg_match( '|^/[\w\W]+$|', $url ) ) {
$url = rtrim( $this->base_url, '/' ) . $url;
}
$upload = $this->fetch_remote_file( $url, $post );
if ( is_wp_error( $upload ) ) {
return $upload;
}
$info = wp_check_filetype( $upload['file'] );
if ( $info ) {
$post['post_mime_type'] = $info['type'];
} else {
return new WP_Error( 'attachment_processing_error', esc_html__( 'Invalid file type', 'elementor' ) );
}
$post['guid'] = $upload['url'];
// As per wp-admin/includes/upload.php.
$post_id = wp_insert_attachment( $post, $upload['file'] );
$this->update_post_meta( $post_id );
wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) );
// Remap resized image URLs, works by stripping the extension and remapping the URL stub.
if ( preg_match( '!^image/!', $info['type'] ) ) {
$parts = pathinfo( $url );
$name = basename( $parts['basename'], ".{$parts['extension']}" ); // PATHINFO_FILENAME in PHP 5.2
$parts_new = pathinfo( $upload['url'] );
$name_new = basename( $parts_new['basename'], ".{$parts_new['extension']}" );
$this->url_remap[ $parts['dirname'] . '/' . $name ] = $parts_new['dirname'] . '/' . $name_new;
}
return $post_id;
}
/**
* Attempt to download a remote file attachment
*
* @param string $url URL of item to fetch
* @param array $post Attachment details
*
* @return array|WP_Error Local file location details on success, WP_Error otherwise
*/
private function fetch_remote_file( $url, $post ) {
// Extract the file name from the URL.
$file_name = basename( wp_parse_url( $url, PHP_URL_PATH ) );
if ( ! $file_name ) {
$file_name = md5( $url );
}
$tmp_file_name = wp_tempnam( $file_name );
if ( ! $tmp_file_name ) {
return new WP_Error( 'import_no_file', esc_html__( 'Could not create temporary file.', 'elementor' ) );
}
// Fetch the remote URL and write it to the placeholder file.
$remote_response = wp_safe_remote_get( $url, [
'timeout' => 300,
'stream' => true,
'filename' => $tmp_file_name,
'headers' => [
'Accept-Encoding' => 'identity',
],
] );
if ( is_wp_error( $remote_response ) ) {
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', sprintf( /* translators: 1: WordPress error message, 2: WordPress error code. */ esc_html__( 'Request failed due to an error: %1$s (%2$s)', 'elementor' ), esc_html( $remote_response->get_error_message() ), esc_html( $remote_response->get_error_code() ) ) );
}
$remote_response_code = (int) wp_remote_retrieve_response_code( $remote_response );
// Make sure the fetch was successful.
if ( 200 !== $remote_response_code ) {
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', sprintf( /* translators: 1: HTTP error message, 2: HTTP error code. */ esc_html__( 'Remote server returned the following unexpected result: %1$s (%2$s)', 'elementor' ), get_status_header_desc( $remote_response_code ), esc_html( $remote_response_code ) ) );
}
$headers = wp_remote_retrieve_headers( $remote_response );
// Request failed.
if ( ! $headers ) {
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', esc_html__( 'Remote server did not respond', 'elementor' ) );
}
$filesize = (int) filesize( $tmp_file_name );
if ( 0 === $filesize ) {
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', esc_html__( 'Zero size file downloaded', 'elementor' ) );
}
if ( ! isset( $headers['content-encoding'] ) && isset( $headers['content-length'] ) && $filesize !== (int) $headers['content-length'] ) {
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', esc_html__( 'Downloaded file has incorrect size', 'elementor' ) );
}
$max_size = (int) apply_filters( 'import_attachment_size_limit', self::DEFAULT_IMPORT_ATTACHMENT_SIZE_LIMIT );
if ( ! empty( $max_size ) && $filesize > $max_size ) {
@unlink( $tmp_file_name );
/* translators: %s: Max file size. */
return new WP_Error( 'import_file_error', sprintf( esc_html__( 'Remote file is too large, limit is %s', 'elementor' ), size_format( $max_size ) ) );
}
// Override file name with Content-Disposition header value.
if ( ! empty( $headers['content-disposition'] ) ) {
$file_name_from_disposition = self::get_filename_from_disposition( (array) $headers['content-disposition'] );
if ( $file_name_from_disposition ) {
$file_name = $file_name_from_disposition;
}
}
// Set file extension if missing.
$file_ext = pathinfo( $file_name, PATHINFO_EXTENSION );
if ( ! $file_ext && ! empty( $headers['content-type'] ) ) {
$extension = self::get_file_extension_by_mime_type( $headers['content-type'] );
if ( $extension ) {
$file_name = "{$file_name}.{$extension}";
}
}
// Handle the upload like _wp_handle_upload() does.
$wp_filetype = wp_check_filetype_and_ext( $tmp_file_name, $file_name );
$ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
$type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
$proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect.
if ( $proper_filename ) {
$file_name = $proper_filename;
}
if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
return new WP_Error( 'import_file_error', esc_html__( 'Sorry, this file type is not permitted for security reasons.', 'elementor' ) );
}
$uploads = wp_upload_dir( $post['upload_date'] );
if ( ! ( $uploads && false === $uploads['error'] ) ) {
return new WP_Error( 'upload_dir_error', $uploads['error'] );
}
// Move the file to the uploads dir.
$file_name = wp_unique_filename( $uploads['path'], $file_name );
$new_file = $uploads['path'] . "/$file_name";
$move_new_file = copy( $tmp_file_name, $new_file );
if ( ! $move_new_file ) {
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', esc_html__( 'The uploaded file could not be moved', 'elementor' ) );
}
// Set correct file permissions.
$stat = stat( dirname( $new_file ) );
$perms = $stat['mode'] & 0000666;
chmod( $new_file, $perms );
$upload = [
'file' => $new_file,
'url' => $uploads['url'] . "/$file_name",
'type' => $wp_filetype['type'],
'error' => false,
];
// Keep track of the old and new urls so we can substitute them later.
$this->url_remap[ $url ] = $upload['url'];
$this->url_remap[ $post['guid'] ] = $upload['url']; // r13735, really needed?
// Keep track of the destination if the remote url is redirected somewhere else.
if ( isset( $headers['x-final-location'] ) && $headers['x-final-location'] !== $url ) {
$this->url_remap[ $headers['x-final-location'] ] = $upload['url'];
}
return $upload;
}
/**
* Attempt to associate posts and menu items with previously missing parents
*
* An imported post's parent may not have been imported when it was first created
* so try again. Similarly for child menu items and menu items which were missing
* the object (e.g. post) they represent in the menu
*/
private function backfill_parents() {
global $wpdb;
// Find parents for post orphans.
foreach ( $this->post_orphans as $child_id => $parent_id ) {
$local_child_id = false;
$local_parent_id = false;
if ( isset( $this->processed_posts[ $child_id ] ) ) {
$local_child_id = $this->processed_posts[ $child_id ];
}
if ( isset( $this->processed_posts[ $parent_id ] ) ) {
$local_parent_id = $this->processed_posts[ $parent_id ];
}
if ( $local_child_id && $local_parent_id ) {
$wpdb->update( $wpdb->posts, [ 'post_parent' => $local_parent_id ], [ 'ID' => $local_child_id ], '%d', '%d' );
clean_post_cache( $local_child_id );
}
}
// Find parents for menu item orphans.
foreach ( $this->menu_item_orphans as $child_id => $parent_id ) {
$local_child_id = 0;
$local_parent_id = 0;
if ( isset( $this->processed_menu_items[ $child_id ] ) ) {
$local_child_id = $this->processed_menu_items[ $child_id ];
}
if ( isset( $this->processed_menu_items[ $parent_id ] ) ) {
$local_parent_id = $this->processed_menu_items[ $parent_id ];
}
if ( $local_child_id && $local_parent_id ) {
update_post_meta( $local_child_id, '_menu_item_menu_item_parent', (int) $local_parent_id );
}
}
}
/**
* Use stored mapping information to update old attachment URLs
*/
private function backfill_attachment_urls() {
global $wpdb;
// Make sure we do the longest urls first, in case one is a substring of another.
uksort( $this->url_remap, function ( $a, $b ) {
// Return the difference in length between two strings.
return strlen( $b ) - strlen( $a );
} );
foreach ( $this->url_remap as $from_url => $to_url ) {
// Remap urls in post_content.
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url ) );
// Remap enclosure urls.
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url ) );
}
}
/**
* Update _thumbnail_id meta to new, imported attachment IDs
*/
private function remap_featured_images() {
// Cycle through posts that have a featured image.
foreach ( $this->featured_images as $post_id => $value ) {
if ( isset( $this->processed_posts[ $value ] ) ) {
$new_id = $this->processed_posts[ $value ];
// Only update if there's a difference.
if ( $new_id !== $value ) {
update_post_meta( $post_id, '_thumbnail_id', $new_id );
}
}
}
}
/**
* Parse a WXR file
*
* @param string $file Path to WXR file for parsing
*
* @return array Information gathered from the WXR file
*/
private function parse( $file ) {
$parser = new WXR_Parser();
return $parser->parse( $file );
}
/**
* Decide if the given meta key maps to information we will want to import
*
* @param string $key The meta key to check
*
* @return string|bool The key if we do want to import, false if not
*/
private function is_valid_meta_key( $key ) {
// Skip attachment metadata since we'll regenerate it from scratch.
// Skip _edit_lock as not relevant for import
if ( in_array( $key, [ '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock' ] ) ) {
return false;
}
return $key;
}
/**
* @param $term
* @return mixed
*/
private function handle_duplicated_nav_menu_term( $term ) {
$duplicate_slug = $term['slug'] . '-duplicate';
$duplicate_name = $term['term_name'] . ' duplicate';
while ( term_exists( $duplicate_slug, 'nav_menu' ) ) {
$duplicate_slug .= '-duplicate';
$duplicate_name .= ' duplicate';
}
$this->mapped_terms_slug[ $term['slug'] ] = $duplicate_slug;
$term['slug'] = $duplicate_slug;
$term['term_name'] = $duplicate_name;
return $term;
}
/**
* Add all term_meta to specified term.
*
* @param $term_id
* @return void
*/
private function update_term_meta( $term_id ) {
foreach ( $this->terms_meta as $meta_key => $meta_value ) {
update_term_meta( $term_id, $meta_key, $meta_value );
}
}
/**
* Add all post_meta to specified term.
*
* @param $post_id
* @return void
*/
private function update_post_meta( $post_id ) {
foreach ( $this->posts_meta as $meta_key => $meta_value ) {
update_post_meta( $post_id, $meta_key, $meta_value );
}
}
public function run() {
$this->import( $this->requested_file_path );
return $this->output;
}
/**
* @param $file
* @param $args
*/
public function __construct( $file, $args = [] ) {
if ( ! function_exists( 'wp_tempnam' ) || ! function_exists( 'wp_upload_dir' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
if ( ! function_exists( 'wp_insert_term' ) ) {
require_once ABSPATH . 'wp-admin/includes/taxonomy.php';
}
if ( ! function_exists( 'wp_insert_attachment' ) || ! function_exists( 'wp_update_attachment_metadata' ) || ! function_exists( 'wp_generate_attachment_metadata' ) ) {
require_once ABSPATH . 'wp-admin/includes/image.php';
}
if ( ! function_exists( 'wp_update_nav_menu_item' ) ) {
require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
}
if ( ! function_exists( 'wp_create_user' ) || ! function_exists( 'wp_insert_user' ) ) {
require_once ABSPATH . 'wp-admin/includes/user.php';
}
if ( ! function_exists( 'wp_import_cleanup' ) ) {
require_once ABSPATH . 'wp-admin/includes/import.php';
}
if ( ! function_exists( 'stick_post' ) ) {
require_once ABSPATH . 'wp-admin/includes/post.php';
}
$this->requested_file_path = $file;
$this->args = $args;
if ( ! empty( $this->args['fetch_attachments'] ) ) {
$this->fetch_attachments = true;
}
if ( isset( $this->args['posts'] ) && is_array( $this->args['posts'] ) ) {
$this->processed_posts = $this->args['posts'];
}
if ( isset( $this->args['terms'] ) && is_array( $this->args['terms'] ) ) {
$this->processed_terms = $this->args['terms'];
}
if ( isset( $this->args['taxonomies'] ) && is_array( $this->args['taxonomies'] ) ) {
$this->processed_taxonomies = $this->args['taxonomies'];
}
if ( ! empty( $this->args['posts_meta'] ) ) {
$this->posts_meta = $this->args['posts_meta'];
}
if ( ! empty( $this->args['terms_meta'] ) ) {
$this->terms_meta = $this->args['terms_meta'];
}
}
}
utils/import-export/parsers/wxr-parser-xml.php 0000666 00000016704 15220772104 0015651 0 ustar 00 <?php
namespace Elementor\Core\Utils\ImportExport\Parsers;
use Elementor\Utils;
use WP_Error;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* WordPress eXtended RSS file parser implementations,
* Originally made by WordPress part of WordPress/Importer.
* https://plugins.trac.wordpress.org/browser/wordpress-importer/trunk/parsers/class-wxr-parser-xml.php
*
* What was done:
* Reformat of the code.
* Added PHPDOC.
* Changed text domain.
* Added clear() method.
* Added undeclared class properties.
* Changed methods visibility.
*/
/**
* WXR Parser that makes use of the XML Parser PHP extension.
*/
class WXR_Parser_XML {
private static $wp_tags = [
'wp:post_id',
'wp:post_date',
'wp:post_date_gmt',
'wp:comment_status',
'wp:ping_status',
'wp:attachment_url',
'wp:status',
'wp:post_name',
'wp:post_parent',
'wp:menu_order',
'wp:post_type',
'wp:post_password',
'wp:is_sticky',
'wp:term_id',
'wp:category_nicename',
'wp:category_parent',
'wp:cat_name',
'wp:category_description',
'wp:tag_slug',
'wp:tag_name',
'wp:tag_description',
'wp:term_taxonomy',
'wp:term_parent',
'wp:term_name',
'wp:term_description',
'wp:author_id',
'wp:author_login',
'wp:author_email',
'wp:author_display_name',
'wp:author_first_name',
'wp:author_last_name',
];
private static $wp_sub_tags = [
'wp:comment_id',
'wp:comment_author',
'wp:comment_author_email',
'wp:comment_author_url',
'wp:comment_author_IP',
'wp:comment_date',
'wp:comment_date_gmt',
'wp:comment_content',
'wp:comment_approved',
'wp:comment_type',
'wp:comment_parent',
'wp:comment_user_id',
];
/**
* @var string
*/
private $wxr_version;
/**
* @var string
*/
private $cdata;
/**
* @var array
*/
private $data;
/**
* @var array
*/
private $sub_data;
/**
* @var boolean
*/
private $in_post;
/**
* @var boolean
*/
private $in_tag;
/**
* @var boolean
*/
private $in_sub_tag;
/**
* @var array
*/
private $authors;
/**
* @var array
*/
private $posts;
/**
* @var array
*/
private $term;
/**
* @var array
*/
private $category;
/**
* @var array
*/
private $tag;
/**
* @var string
*/
private $base_url;
/**
* @var string
*/
private $base_blog_url;
/**
* @param string $file
*
* @return array|WP_Error
*/
public function parse( $file ) {
$this->clear();
$xml = xml_parser_create( 'UTF-8' );
xml_parser_set_option( $xml, XML_OPTION_SKIP_WHITE, 1 );
xml_parser_set_option( $xml, XML_OPTION_CASE_FOLDING, 0 );
xml_set_object( $xml, $this );
xml_set_character_data_handler( $xml, function ( $parser, $cdata ) {
$this->cdata( $cdata );
} );
$tag_open_callback = function ( $parse, $tag, $attr ) {
$this->tag_open( $tag, $attr );
};
$tag_close_callback = function ( $parser, $tag ) {
$this->tag_close( $tag );
};
xml_set_element_handler( $xml, $tag_open_callback, $tag_close_callback );
if ( ! xml_parse( $xml, Utils::file_get_contents( $file ), true ) ) {
$current_line = xml_get_current_line_number( $xml );
$current_column = xml_get_current_column_number( $xml );
$error_code = xml_get_error_code( $xml );
$error_string = xml_error_string( $error_code );
return new WP_Error( 'XML_parse_error', 'There was an error when reading this WXR file', [
$current_line,
$current_column,
$error_string,
] );
}
xml_parser_free( $xml );
if ( ! preg_match( '/^\d+\.\d+$/', $this->wxr_version ) ) {
return new WP_Error( 'WXR_parse_error', esc_html__( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'elementor' ) );
}
return [
'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;
} elseif ( $this->in_tag ) {
$this->data[ $this->in_tag ] = $this->cdata;
$this->in_tag = false;
}
}
$this->cdata = '';
}
private function clear() {
$this->wxr_version = '';
$this->cdata = '';
$this->data = [];
$this->sub_data = [];
$this->in_post = false;
$this->in_tag = false;
$this->in_sub_tag = false;
$this->authors = [];
$this->posts = [];
$this->term = [];
$this->category = [];
$this->tag = [];
}
}
utils/import-export/parsers/wxr-parser.php 0000666 00000002557 15220772104 0015054 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/wp-exporter.php 0000666 00000064116 15220772104 0013556 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.
'include' => [], // Array of post IDs to include in the export.
];
/**
* @var array
*/
private $args;
/**
* @var \wpdb
*/
private $wpdb;
private $terms;
private $exported_posts = [];
/**
* Run export, by requested args.
* Returns XML with exported data.
*
* @return array
*/
public function run() {
if ( 'all' !== $this->args['content'] && post_type_exists( $this->args['content'] ) ) {
$ptype = get_post_type_object( $this->args['content'] );
if ( ! $ptype->can_export ) {
$this->args['content'] = 'post';
}
$where = $this->wpdb->prepare( "{$this->wpdb->posts}.post_type = %s", $this->args['content'] );// phpcs:ignore
} else {
$post_types = get_post_types( [ 'can_export' => true ] );
$esses = array_fill( 0, count( $post_types ), '%s' );
$where = $this->wpdb->prepare( "{$this->wpdb->posts}.post_type IN (" . implode( ',', $esses ) . ')', $post_types );// phpcs:ignore
}
if ( $this->args['status'] && ( 'post' === $this->args['content'] || 'page' === $this->args['content'] || 'nav_menu_item' === $this->args['content'] ) ) {
$where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_status = %s", $this->args['status'] );// phpcs:ignore
} else {
$where .= " AND {$this->wpdb->posts}.post_status != 'auto-draft'";
}
$join = '';
if ( $this->args['category'] && 'post' === $this->args['content'] ) {
$term = term_exists( $this->args['category'], 'category' );
if ( $term ) {
$join = "INNER JOIN {$this->wpdb->term_relationships} ON ({$this->wpdb->posts}.ID = {$this->wpdb->term_relationships}.object_id)";
$where .= $this->wpdb->prepare( " AND {$this->wpdb->term_relationships}.term_taxonomy_id = %d", $term['term_taxonomy_id'] );// phpcs:ignore
}
}
if ( in_array( $this->args['content'], [ 'post', 'page', 'attachment' ], true ) ) {
if ( $this->args['author'] ) {
$where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_author = %d", $this->args['author'] );// phpcs:ignore
}
if ( $this->args['start_date'] ) {
$where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_date >= %s", gmdate( 'Y-m-d', strtotime( $this->args['start_date'] ) ) );// phpcs:ignore
}
if ( $this->args['end_date'] ) {
$where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_date < %s", gmdate( 'Y-m-d', strtotime( '+1 month', strtotime( $this->args['end_date'] ) ) ) );// phpcs:ignore
}
}
$limit = '';
if ( -1 !== (int) $this->args['limit'] ) {
$limit = 'LIMIT ' . (int) $this->args['limit'] . ' OFFSET ' . (int) $this->args['offset'];
}
if ( ! empty( $this->args['include'] ) ) {
$include_ids = array_map( 'absint', $this->args['include'] );
$include_placeholders = implode( ',', array_fill( 0, count( $include_ids ), '%d' ) );
$where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.ID IN ($include_placeholders)", $include_ids ); // phpcs:ignore
}
if ( ! empty( $this->args['meta_query'] ) ) {
if ( $join ) {
$join .= ' ';
}
if ( $where ) {
$where .= ' ';
}
$meta_query = new \WP_Meta_Query( $this->args['meta_query'] );
global $wpdb;
$query_clauses = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
$join .= $query_clauses['join'];
$where .= $query_clauses['where'];
}
// Grab a snapshot of post IDs, just in case it changes during the export.
$post_ids = $this->wpdb->get_col( "SELECT ID FROM {$this->wpdb->posts} $join WHERE $where $limit" );// phpcs:ignore
$thumbnail_ids = [];
if ( ! empty( $this->args['include_post_featured_image_as_attachment'] ) ) {
foreach ( $post_ids as $post_id ) {
$thumbnail_id = get_post_meta( $post_id, '_thumbnail_id', true );
if ( $thumbnail_id && ! in_array( $thumbnail_id, $post_ids, true ) ) {
$thumbnail_ids [] = $thumbnail_id;
}
}
}
return [
'ids' => $post_ids,
'xml' => $this->get_xml_export( array_merge( $post_ids, $thumbnail_ids ) ),
'posts' => $this->exported_posts,
];
}
/**
* 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;
$is_valid_utf8 = wp_check_invalid_utf8( $str, true ) === $str;
if ( ! $is_valid_utf8 ) {
$str = utf8_encode( $str );
}
$str = '<![CDATA[' . str_replace( ']]>', ']]]]><![CDATA[>', $str ) . ']]>';
return $str;
}
/**
* Return the URL of the site.
*
* @return string Site URL.
*/
private function wxr_site_url() {
if ( is_multisite() ) {
// Multisite: the base URL.
return network_home_url();
} else {
// WordPress (single site): the blog URL.
return get_bloginfo_rss( 'url' );
}
}
/**
* Return a cat_name XML tag from a given category object.
*
* @param \WP_Term $category Category Object.
*
* @return string
*/
private function wxr_cat_name( $category ) {
if ( empty( $category->name ) ) {
return '';
}
return $this->indent( 3 ) . '<wp:cat_name>' . $this->wxr_cdata( $category->name ) . '</wp:cat_name>' . PHP_EOL;
}
/**
* Return a category_description XML tag from a given category object.
*
* @param \WP_Term $category Category Object.
*
* @return string
*/
private function wxr_category_description( $category ) {
if ( empty( $category->description ) ) {
return '';
}
return $this->indent( 3 ) . '<wp:category_description>' . $this->wxr_cdata( $category->description ) . "</wp:category_description>\n";
}
/**
* Return a tag_name XML tag from a given tag object.
*
* @param \WP_Term $tag Tag Object.
*
* @return string
*/
private function wxr_tag_name( $tag ) {
if ( empty( $tag->name ) ) {
return '';
}
return $this->indent( 3 ) . '<wp:tag_name>' . $this->wxr_cdata( $tag->name ) . '</wp:tag_name>' . PHP_EOL;
}
/**
* Return a tag_description XML tag from a given tag object.
*
* @param \WP_Term $tag Tag Object.
*
* @return string
*/
private function wxr_tag_description( $tag ) {
if ( empty( $tag->description ) ) {
return '';
}
return $this->indent( 3 ) . '<wp:tag_description>' . $this->wxr_cdata( $tag->description ) . '</wp:tag_description>' . PHP_EOL;
}
/**
* Return a term_name XML tag from a given term object.
*
* @param \WP_Term $term Term Object.
*
* @return string
*/
private function wxr_term_name( $term ) {
if ( empty( $term->name ) ) {
return '';
}
return $this->indent( 3 ) . '<wp:term_name>' . $this->wxr_cdata( $term->name ) . '</wp:term_name>' . PHP_EOL;
}
/**
* Return a term_description XML tag from a given term object.
*
* @param \WP_Term $term Term Object.
*
* @return string
*/
private function wxr_term_description( $term ) {
if ( empty( $term->description ) ) {
return '';
}
return $this->indent( 3 ) . '<wp:term_description>' . $this->wxr_cdata( $term->description ) . '</wp:term_description>' . PHP_EOL;
}
/**
* Return term meta XML tags for a given term object.
*
* @param \WP_Term $term Term object.
*
* @return string
*/
private function wxr_term_meta( $term ) {
$result = '';
$termmeta = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->termmeta} WHERE term_id = %d", $term->term_id ) );// phpcs:ignore
foreach ( $termmeta as $meta ) {
/**
* Filters whether to selectively skip term meta used for WXR exports.
*
* Returning a truthy value from the filter will skip the current meta
* object from being exported.
*
* @since 4.6.0
*
* @param bool $skip Whether to skip the current piece of term meta. Default false.
* @param string $meta_key Current meta key.
* @param object $meta Current meta object.
*/
if ( ! apply_filters( 'wxr_export_skip_termmeta', false, $meta->meta_key, $meta ) ) {
$result .= sprintf( $this->indent( 3 ) . "<wp:termmeta>\n\t\t\t<wp:meta_key>%s</wp:meta_key>\n\t\t\t<wp:meta_value>%s</wp:meta_value>\n\t\t</wp:termmeta>\n", $this->wxr_cdata( $meta->meta_key ), $this->wxr_cdata( $meta->meta_value ) );
}
}
return $result;
}
/**
* Return list of authors with posts.
*
* @param int[] $post_ids Optional. Array of post IDs to filter the query by.
*
* @return string
*/
private function wxr_authors_list( array $post_ids = null ) {
$result = '';
if ( ! empty( $post_ids ) ) {
$post_ids = array_map( 'absint', $post_ids );
$and = 'AND ID IN ( ' . implode( ', ', $post_ids ) . ')';
} else {
$and = '';
}
$authors = [];
$results = $this->wpdb->get_results( "SELECT DISTINCT post_author FROM {$this->wpdb->posts} WHERE post_status != 'auto-draft' $and" );// phpcs:ignore
foreach ( (array) $results as $r ) {
$authors[] = get_userdata( $r->post_author );
}
$authors = array_filter( $authors );
foreach ( $authors as $author ) {
$result .= $this->indent( 2 ) . '<wp:author>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:author_id>' . (int) $author->ID . '</wp:author_id>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:author_login>' . $this->wxr_cdata( $author->user_login ) . '</wp:author_login>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:author_email>' . $this->wxr_cdata( $author->user_email ) . '</wp:author_email>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:author_display_name>' . $this->wxr_cdata( $author->display_name ) . '</wp:author_display_name>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:author_first_name>' . $this->wxr_cdata( $author->first_name ) . '</wp:author_first_name>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:author_last_name>' . $this->wxr_cdata( $author->last_name ) . '</wp:author_last_name>' . PHP_EOL;
$result .= $this->indent( 2 ) . '</wp:author>' . PHP_EOL;
}
return $result;
}
/**
* Return list of categories.
*
* @param array $cats
*
* @return string
*/
private function wxr_categories_list( array $cats ) {
$result = '';
foreach ( $cats as $c ) {
$result .= $this->indent( 2 ) . '<wp:category>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:term_id>' . (int) $c->term_id . '</wp:term_id>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:category_nicename>' . $this->wxr_cdata( $c->slug ) . '</wp:category_nicename>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:category_parent>' . $this->wxr_cdata( $c->parent ? $cats[ $c->parent ]->slug : '' ) . '</wp:category_parent>' . PHP_EOL;
$result .= $this->wxr_cat_name( $c ) .
$this->wxr_category_description( $c ) .
$this->wxr_term_meta( $c );
$result .= $this->indent( 2 ) . '</wp:category>' . PHP_EOL;
}
return $result;
}
/**
* Return list of tags.
*
* @param array $tags
*
* @return string
*/
private function wxr_tags_list( array $tags ) {
$result = '';
foreach ( $tags as $t ) {
$result .= $this->indent( 2 ) . '<wp:tag>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:term_id>' . (int) $t->term_id . '</wp:term_id>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:tag_slug>' . $this->wxr_cdata( $t->slug ) . '</wp:tag_slug>' . PHP_EOL;
$result .= $this->wxr_tag_name( $t ) .
$this->wxr_tag_description( $t ) .
$this->wxr_term_meta( $t );
$result .= $this->indent( 2 ) . '</wp:tag>' . PHP_EOL;
}
return $result;
}
/**
* Return list of terms.
*
* @param array $terms
*
* @return string
*/
private function wxr_terms_list( array $terms ) {
$result = '';
foreach ( $terms as $t ) {
$result .= $this->indent( 2 ) . '<wp:term>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:term_id>' . $this->wxr_cdata( $t->term_id ) . '</wp:term_id>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:term_taxonomy>' . $this->wxr_cdata( $t->taxonomy ) . '</wp:term_taxonomy>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:term_slug>' . $this->wxr_cdata( $t->slug ) . '</wp:term_slug>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:term_parent>' . $this->wxr_cdata( $t->parent ? $terms[ $t->parent ]->slug : '' ) . '</wp:term_parent>' . PHP_EOL;
$result .= $this->wxr_term_name( $t ) .
$this->wxr_term_description( $t ) .
$this->wxr_term_meta( $t );
$result .= $this->indent( 2 ) . '</wp:term>' . PHP_EOL;
}
return $result;
}
/**
* Return list of posts, by requested `$post_ids`.
*
* @param array $post_ids
*
* @return string
*/
private function wxr_posts_list( array $post_ids ) {
$result = '';
if ( $post_ids ) {
global $wp_query;
// Fake being in the loop.
$wp_query->in_the_loop = true;
// Fetch 20 posts at a time rather than loading the entire table into memory.
while ( $next_posts = array_splice( $post_ids, 0, 20 ) ) {
$where = 'WHERE ID IN (' . implode( ',', $next_posts ) . ')';
$posts = $this->wpdb->get_results( "SELECT * FROM {$this->wpdb->posts} $where" );// phpcs:ignore
// Begin Loop.
foreach ( $posts as $post ) {
setup_postdata( $post );
$this->exported_posts[ $post->ID ] = [
'id' => $post->ID,
'title' => $post->post_title,
];
$title = apply_filters( 'the_title_rss', $post->post_title );
/**
* Filters the post content used for WXR exports.
*
* @since 2.5.0
*
* @param string $post_content Content of the current post.
*/
$content = $this->wxr_cdata( apply_filters( 'the_content_export', $post->post_content ) );
/**
* Filters the post excerpt used for WXR exports.
*
* @since 2.6.0
*
* @param string $post_excerpt Excerpt for the current post.
*/
$excerpt = $this->wxr_cdata( apply_filters( 'the_excerpt_export', $post->post_excerpt ) );
$result .= $this->indent( 2 ) . '<item>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<title>' . $title . '</title>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<link>' . esc_url( get_permalink() ) . '</link>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<pubDate>' . mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ) . '</pubDate>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<dc:creator>' . $this->wxr_cdata( get_the_author_meta( 'login' ) ) . '</dc:creator>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<guid isPermaLink="false">' . $this->wxr_cdata( get_the_author_meta( 'login' ) ) . '</guid>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<description></description>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<content:encoded>' . $content . '</content:encoded>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<excerpt:encoded>' . $excerpt . '</excerpt:encoded>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:post_id>' . (int) $post->ID . '</wp:post_id>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:post_date>' . $this->wxr_cdata( $post->post_date ) . '</wp:post_date>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:post_date_gmt>' . $this->wxr_cdata( $post->post_date_gmt ) . '</wp:post_date_gmt>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:comment_status>' . $this->wxr_cdata( $post->comment_status ) . '</wp:comment_status>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:ping_status>' . $this->wxr_cdata( $post->ping_status ) . '</wp:ping_status>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:post_name>' . $this->wxr_cdata( $post->post_name ) . '</wp:post_name>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:status>' . $this->wxr_cdata( $post->post_status ) . '</wp:status>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:post_parent>' . $this->wxr_cdata( $post->post_parent ) . '</wp:post_parent>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:menu_order>' . (int) $post->menu_order . '</wp:menu_order>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:post_type>' . $this->wxr_cdata( $post->post_type ) . '</wp:post_type>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:post_password>' . $this->wxr_cdata( $post->post_password ) . '</wp:post_password>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:is_sticky>' . ( is_sticky( $post->ID ) ? 1 : 0 ) . '</wp:is_sticky>' . PHP_EOL;
if ( 'attachment' === $post->post_type ) {
$result .= $this->indent( 3 ) . '<wp:attachment_url>' . $this->wxr_cdata( wp_get_attachment_url( $post->ID ) ) . '</wp:attachment_url>' . PHP_EOL;
}
$result .= $this->wxr_post_taxonomy( $post );
$postmeta = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->postmeta} WHERE post_id = %d", $post->ID ) );// phpcs:ignore
foreach ( $postmeta as $meta ) {
/**
* Filters whether to selectively skip post meta used for WXR exports.
*
* Returning a truthy value from the filter will skip the current meta
* object from being exported.
*
* @since 3.3.0
*
* @param bool $skip Whether to skip the current post meta. Default false.
* @param string $meta_key Current meta key.
* @param object $meta Current meta object.
*/
if ( apply_filters( 'wxr_export_skip_postmeta', false, $meta->meta_key, $meta ) ) {
continue;
}
$result .= $this->indent( 3 ) . '<wp:postmeta>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:meta_key>' . $this->wxr_cdata( $meta->meta_key ) . '</wp:meta_key>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:meta_value>' . $this->wxr_cdata( $meta->meta_value ) . '</wp:meta_value>' . PHP_EOL;
$result .= $this->indent( 3 ) . '</wp:postmeta>' . PHP_EOL;
}
$_comments = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->comments} WHERE comment_post_ID = %d AND comment_approved <> 'spam'", $post->ID ) );// phpcs:ignore
$comments = array_map( 'get_comment', $_comments );
foreach ( $comments as $c ) {
$result .= $this->indent( 3 ) . '<wp:comment>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_id>' . (int) $c->comment_ID . '</wp:comment_id>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_author>' . $this->wxr_cdata( $c->comment_author ) . '</wp:comment_author>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_author_email>' . $this->wxr_cdata( $c->comment_author_email ) . '</wp:comment_author_email>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_author_url>' . $this->wxr_cdata( $c->comment_author_url ) . '</wp:comment_author_url>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_author_IP>' . $this->wxr_cdata( $c->comment_author_IP ) . '</wp:comment_author_IP>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_date>' . $this->wxr_cdata( $c->comment_date ) . '</wp:comment_date>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_date_gmt>' . $this->wxr_cdata( $c->comment_date_gmt ) . '</wp:comment_date_gmt>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_content>' . $this->wxr_cdata( $c->comment_content ) . '</wp:comment_content>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_approved>' . $this->wxr_cdata( $c->comment_approved ) . '</wp:comment_approved>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_type>' . $this->wxr_cdata( $c->comment_type ) . '</wp:comment_type>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_parent>' . $this->wxr_cdata( $c->comment_parent ) . '</wp:comment_parent>' . PHP_EOL;
$result .= $this->indent( 4 ) . '<wp:comment_user_id>' . (int) $c->user_id . '</wp:comment_user_id>' . PHP_EOL;
$c_meta = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->commentmeta} WHERE comment_id = %d", $c->comment_ID ) );// phpcs:ignore
foreach ( $c_meta as $meta ) {
/**
* Filters whether to selectively skip comment meta used for WXR exports.
*
* Returning a truthy value from the filter will skip the current meta
* object from being exported.
*
* @since 4.0.0
*
* @param bool $skip Whether to skip the current comment meta. Default false.
* @param string $meta_key Current meta key.
* @param object $meta Current meta object.
*/
if ( apply_filters( 'wxr_export_skip_commentmeta', false, $meta->meta_key, $meta ) ) {
continue;
}
$result .= $this->indent( 4 ) . '<wp:commentmeta>' . PHP_EOL;
$result .= $this->indent( 5 ) . '<wp:meta_key>' . $this->wxr_cdata( $meta->meta_key ) . '</wp:meta_key>' . PHP_EOL;
$result .= $this->indent( 5 ) . '<wp:meta_value>' . $this->wxr_cdata( $meta->meta_key ) . '</wp:meta_value>' . PHP_EOL;
$result .= $this->indent( 4 ) . '</wp:commentmeta>' . PHP_EOL;
}
$result .= $this->indent( 3 ) . '</wp:comment>' . PHP_EOL;
}
$result .= $this->indent( 2 ) . '</item>' . PHP_EOL;
}
}
}
return $result;
}
/**
* Return all navigation menu terms
*
* @return string
*/
private function wxr_nav_menu_terms() {
$nav_menus = wp_get_nav_menus();
if ( empty( $nav_menus ) || ! is_array( $nav_menus ) ) {
return '';
}
$result = '';
foreach ( $nav_menus as $menu ) {
$this->terms[ $menu->term_id ] = $menu;
$result .= $this->indent( 2 ) . '<wp:term>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:term_id>' . (int) $menu->term_id . '</wp:term_id>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:term_taxonomy>nav_menu</wp:term_taxonomy>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:term_slug>' . $this->wxr_cdata( $menu->slug ) . '</wp:term_slug>' . PHP_EOL;
$result .= $this->indent( 3 ) . '<wp:term_name>' . $this->wxr_cdata( $menu->name ) . '</wp:term_name>' . PHP_EOL;
$result .= $this->indent( 2 ) . '</wp:term>' . PHP_EOL;
}
return $result;
}
/**
* Return list of taxonomy terms, in XML tag format, associated with a post
*
* @param \WP_Post $post
*
* @return string
*/
private function wxr_post_taxonomy( $post ) {
$result = '';
$taxonomies = get_object_taxonomies( $post->post_type );
if ( empty( $taxonomies ) ) {
return $result;
}
$terms = wp_get_object_terms( $post->ID, $taxonomies );
foreach ( (array) $terms as $term ) {
$result .= $this->indent( 3 ) . "<category domain=\"{$term->taxonomy}\" nicename=\"{$term->slug}\">" . $this->wxr_cdata( $term->name ) . '</category>' . PHP_EOL;
}
return $result;
}
/**
* Get's the XML export.
*
* @param $post_ids
*
* @return string
*/
private function get_xml_export( array $post_ids ) {
$charset = get_bloginfo( 'charset' );
$generator = get_the_generator( 'export' );
$wxr_version = self::WXR_VERSION;
$wxr_site_url = $this->wxr_site_url();
$rss_info_name = get_bloginfo_rss( 'name' );
$rss_info_url = get_bloginfo_rss( 'url' );
$rss_info_description = get_bloginfo_rss( 'description' );
$rss_info_language = get_bloginfo_rss( 'language' );
$pub_date = gmdate( 'D, d M Y H:i:s +0000' );
$show_page_on_front = 'page' === get_option( 'show_on_front' );
$page_on_front_xml = '';
if ( $show_page_on_front ) {
$page_on_front_id = (int) get_option( 'page_on_front' );
if ( in_array( $page_on_front_id, $post_ids ) ) {
$page_on_front_xml = "<wp:page_on_front>$page_on_front_id</wp:page_on_front>";
}
}
$dynamic = $this->wxr_authors_list( $post_ids );
ob_start();
/** This action is documented in wp-includes/feed-rss2.php */
do_action( 'rss2_head' );
$rss2_head = ob_get_clean();
$dynamic .= $rss2_head;
if ( 'all' === $this->args['content'] || 'nav_menu_item' === $this->args['content'] ) {
$dynamic .= $this->wxr_nav_menu_terms();
}
$dynamic .= $this->wxr_posts_list( $post_ids );
$result = <<<EOT
<?xml version="1.0" encoding="$charset" ?>
<!-- This is a WordPress eXtended RSS file generated by WordPress as an export of your site. -->
<!-- It contains information about your site's posts, pages, comments, categories, and other content. -->
<!-- You may use this file to transfer that content from one site to another. -->
<!-- This file is not intended to serve as a complete backup of your site. -->
<!-- To import this information into a WordPress site follow these steps: -->
<!-- 1. Log in to that site as an administrator. -->
<!-- 2. Go to Tools: Import in the WordPress admin panel. -->
<!-- 3. Install the "WordPress" importer from the list. -->
<!-- 4. Activate & Run Importer. -->
<!-- 5. Upload this file using the form provided on that page. -->
<!-- 6. You will first be asked to map the authors in this export file to users -->
<!-- on the site. For each author, you may choose to map to an -->
<!-- existing user on the site or to create a new user. -->
<!-- 7. WordPress will then import each of the posts, pages, comments, categories, etc. -->
<!-- contained in this file into your site. -->
$generator
<rss version="2.0"
xmlns:excerpt="http://wordpress.org/export/$wxr_version/excerpt/"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:wp="http://wordpress.org/export/$wxr_version/"
>
<channel>
<title>$rss_info_name</title>
<link>$rss_info_url</link>
<description>$rss_info_description</description>
<pubDate>$pub_date</pubDate>
<language>$rss_info_language</language>
<wp:wxr_version>$wxr_version</wp:wxr_version>
<wp:base_site_url>$wxr_site_url</wp:base_site_url>
<wp:base_blog_url>$rss_info_url</wp:base_blog_url>
$page_on_front_xml
$dynamic
</channel>
</rss>
EOT;
return $result;
}
public function __construct( array $args = [] ) {
global $wpdb;
$this->args = wp_parse_args( $args, self::$default_args );
$this->wpdb = $wpdb;
}
}
utils/promotions/filtered-promotions-manager.php 0000666 00000004143 15220772104 0016251 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 ) ) );
}
}
files/assets/svg/svg-handler.php 0000666 00000015612 15220772104 0012703 0 ustar 00 <?php
namespace Elementor\Core\Files\Assets\Svg;
use Elementor\Core\Files\Assets\Files_Upload_Handler;
use Elementor\Core\Files\File_Types\Svg;
use Elementor\Core\Files\Uploads_Manager;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* SVG Handler
*
* @deprecated 3.5.0 Use `Elementor\Core\Files\File_Types\Svg` instead, accessed by calling: `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );`
*/
class Svg_Handler extends Files_Upload_Handler {
/**
* Inline svg attachment meta key
*
* @deprecated 3.5.0
*/
const META_KEY = '_elementor_inline_svg';
/**
* @deprecated 3.5.0
*/
const SCRIPT_REGEX = '/(?:\w+script|data):/xi';
/**
* Attachment ID.
*
* Holds the current attachment ID.
*
* @deprecated 3.5.0
*
* @var int
*/
private $attachment_id;
/**
* @deprecated 3.5.0
*/
public static function get_name() {
return 'svg-handler';
}
/**
* Get meta
*
* @deprecated 3.5.0
*
* @return mixed
*/
protected function get_meta() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' );
return get_post_meta( $this->attachment_id, self::META_KEY, true );
}
/**
* Update meta
*
* @deprecated 3.5.0
*
* @param $meta
*/
protected function update_meta( $meta ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' );
update_post_meta( $this->attachment_id, self::META_KEY, $meta );
}
/**
* Delete meta
*
* @deprecated 3.5.0
*/
protected function delete_meta() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' );
delete_post_meta( $this->attachment_id, self::META_KEY );
}
/**
* Get mime type
*
* @deprecated 3.5.0
*/
public function get_mime_type() {
return 'image/svg+xml';
}
/**
* Get file type
*
* @deprecated 3.5.0
*/
public function get_file_type() {
return 'svg';
}
/**
* Delete meta cache
*
* @deprecated 3.5.0 Use `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' )->delete_meta_cache()` instead.
*/
public function delete_meta_cache() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Plugin::$instance->uploads_manager->get_file_type_handlers( \'svg\' )->delete_meta_cache()' );
/** @var Svg $svg_handler */
$svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );
$svg_handler->delete_meta_cache();
}
/**
* Get inline svg
*
* @deprecated 3.5.0 Use `Elementor\Core\Files\File_Types\Svg::get_inline_svg()` instead.
*
* @param $attachment_id
*
* @return bool|mixed|string
*/
public static function get_inline_svg( $attachment_id ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Elementor\Core\Files\File_Types\Svg::get_inline_svg()' );
return Svg::get_inline_svg( $attachment_id );
}
/**
* Sanitize svg
*
* @deprecated 3.5.0 Use `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' )->delete_meta_cache()->sanitize_svg()` instead.
*
* @param $filename
*
* @return bool
*/
public function sanitize_svg( $filename ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Plugin::$instance->uploads_manager->get_file_type_handlers( \'svg\' )->delete_meta_cache()->sanitize_svg()' );
/** @var Svg $svg_handler */
$svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );
return $svg_handler->sanitize_svg( $filename );
}
/**
* Sanitizer
*
* @deprecated 3.5.0 Use `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' )->sanitizer()` instead.
*
* @param $content
*
* @return bool|string
*/
public function sanitizer( $content ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Plugin::$instance->uploads_manager->get_file_type_handlers( \'svg\' )->sanitizer()' );
/** @var Svg $svg_handler */
$svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );
return $svg_handler->sanitizer( $content );
}
/**
* Prepare attachment for js
*
* @deprecated 3.5.0 Use `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' )->wp_prepare_attachment_for_js()` instead.
*
* @param $attachment_data
* @param $attachment
* @param $meta
*
* @return mixed
*/
public function wp_prepare_attachment_for_js( $attachment_data, $attachment, $meta ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Plugin::$instance->uploads_manager->get_file_type_handlers( \'svg\' )->wp_prepare_attachment_for_js()' );
/** @var Svg $svg_handler */
$svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );
return $svg_handler->wp_prepare_attachment_for_js( $attachment_data, $attachment, $meta );
}
/**
* Set attachment id
*
* @deprecated 3.5.0
*
* @param $attachment_id
*
* @return int
*/
public function set_attachment_id( $attachment_id ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' );
$this->attachment_id = $attachment_id;
return $this->attachment_id;
}
/**
* Get attachment id
*
* @deprecated 3.5.0
*
* @return int
*/
public function get_attachment_id() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0' );
return $this->attachment_id;
}
/**
* Set svg meta data
*
* @deprecated 3.5.0 Use `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' )->set_svg_meta_data()` instead.
*
* @return mixed
*/
public function set_svg_meta_data( $data, $id ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Plugin::$instance->uploads_manager->get_file_type_handlers( \'svg\' )->set_svg_meta_data()' );
/** @var Svg $svg_handler */
$svg_handler = Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );
return $svg_handler->set_svg_meta_data( $data, $id );
}
/**
* Handle upload prefilter
*
* @deprecated 3.5.0 Use `Elementor\Plugin::$instance->uploads_manager->handle_elementor_wp_media_upload()` instead.
*
* @param $file
*
* @return mixed
*/
public function handle_upload_prefilter( $file ) {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.5.0', 'Elementor\Plugin::$instance->uploads_manager->handle_elementor_wp_media_upload()' );
return Plugin::$instance->uploads_manager->handle_elementor_wp_media_upload( $file );
}
}
files/assets/json/json-handler.php 0000666 00000001302 15220772104 0013216 0 ustar 00 <?php
namespace Elementor\Core\Files\Assets\Json;
use Elementor\Core\Files\Assets\Files_Upload_Handler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Json Handler
*
* @deprecated 3.5.0 Use `Elementor\Core\Files\File_Types\Svg` instead, accessed by calling `Plugin::$instance->uploads_manager->get_file_type_handlers( 'svg' );`
*/
class Json_Handler extends Files_Upload_Handler {
/**
* @deprecated 3.5.0
*/
public static function get_name() {
return 'json-handler';
}
/**
* @deprecated 3.5.0
*/
public function get_mime_type() {
return 'application/json';
}
/**
* @deprecated 3.5.0
*/
public function get_file_type() {
return 'json';
}
}
files/file-types/svg.php 0000666 00000013230 15220772104 0011242 0 ustar 00 <?php
namespace Elementor\Core\Files\File_Types;
use Elementor\Core\Utils\Exceptions;
use Elementor\Core\Utils\Svg\Svg_Sanitizer;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Svg extends Base {
/**
* Inline svg attachment meta key
*/
const META_KEY = '_elementor_inline_svg';
const SCRIPT_REGEX = '/(?:\w+script|data):/xi';
/**
* Get File Extension
*
* Returns the file type's file extension
*
* @since 3.5.0
* @access public
*
* @return string - file extension
*/
public function get_file_extension() {
return 'svg';
}
/**
* Get Mime Type
*
* Returns the file type's mime type
*
* @since 3.5.0
* @access public
*
* @return string mime type
*/
public function get_mime_type() {
return 'image/svg+xml';
}
/**
* Sanitize SVG
*
* @since 3.5.0
* @access public
*
* @param $filename
* @return bool
*/
public function sanitize_svg( $filename ) {
return ( new SVG_Sanitizer() )->sanitize_file( $filename );
}
/**
* Validate File
*
* @since 3.3.0
* @access public
*
* @param $file
* @return bool|\WP_Error
*/
public function validate_file( $file ) {
if ( ! $this->sanitize_svg( $file['tmp_name'] ) ) {
return new \WP_Error( Exceptions::FORBIDDEN, esc_html__( 'This file is not allowed for security reasons.', 'elementor' ) );
}
return true;
}
/**
* Sanitizer
*
* @since 3.5.0
* @access public
*
* @param $content
* @return bool|string
*/
public function sanitizer( $content ) {
return ( new SVG_Sanitizer() )->sanitize( $content );
}
/**
* WP Prepare Attachment For J
*
* Runs on the `wp_prepare_attachment_for_js` filter.
*
* @since 3.5.0
* @access public
*
* @param $attachment_data
* @param $attachment
* @param $meta
*
* @return mixed
*/
public function wp_prepare_attachment_for_js( $attachment_data, $attachment, $meta ) {
if ( 'image' !== $attachment_data['type'] || 'svg+xml' !== $attachment_data['subtype'] || ! class_exists( 'SimpleXMLElement' ) ) {
return $attachment_data;
}
$svg = self::get_inline_svg( $attachment->ID );
if ( ! $svg ) {
return $attachment_data;
}
try {
$svg = new \SimpleXMLElement( $svg );
} catch ( \Exception $e ) {
return $attachment_data;
}
$src = $attachment_data['url'];
$width = (int) $svg['width'];
$height = (int) $svg['height'];
// Media Gallery
$attachment_data['image'] = compact( 'src', 'width', 'height' );
$attachment_data['thumb'] = compact( 'src', 'width', 'height' );
// Single Details of Image
$attachment_data['sizes']['full'] = [
'height' => $height,
'width' => $width,
'url' => $src,
'orientation' => $height > $width ? 'portrait' : 'landscape',
];
return $attachment_data;
}
/**
* Set Svg Meta Data
*
* Adds dimensions metadata to uploaded SVG files, since WordPress doesn't do it.
*
* @since 3.5.0
* @access public
*
* @return mixed
*/
public function set_svg_meta_data( $data, $id ) {
$attachment = get_post( $id ); // Filter makes sure that the post is an attachment.
$mime_type = $attachment->post_mime_type;
// If the attachment is an svg
if ( 'image/svg+xml' === $mime_type ) {
// If the svg metadata are empty or the width is empty or the height is empty.
// then get the attributes from xml.
if ( empty( $data ) || empty( $data['width'] ) || empty( $data['height'] ) ) {
$attachment = wp_get_attachment_url( $id );
$xml = simplexml_load_file( $attachment );
if ( ! empty( $xml ) ) {
$attr = $xml->attributes();
$view_box = explode( ' ', $attr->viewBox );// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$data['width'] = isset( $attr->width ) && preg_match( '/\d+/', $attr->width, $value ) ? (int) $value[0] : ( 4 === count( $view_box ) ? (int) $view_box[2] : null );
$data['height'] = isset( $attr->height ) && preg_match( '/\d+/', $attr->height, $value ) ? (int) $value[0] : ( 4 === count( $view_box ) ? (int) $view_box[3] : null );
}
}
}
return $data;
}
/**
* Delete Meta Cache
*
* Deletes the Inline SVG post meta entry.
*
* @since 3.5.0
* @access public
*/
public function delete_meta_cache() {
delete_post_meta_by_key( self::META_KEY );
}
/**
* File Sanitizer Can Run
*
* Checks if the classes required for the file sanitizer are in memory.
*
* @since 3.5.0
* @access public
* @static
*
* @return bool
*/
public static function file_sanitizer_can_run() {
return class_exists( 'DOMDocument' ) && class_exists( 'SimpleXMLElement' );
}
/**
* Get Inline SVG
*
* @since 3.5.0
* @access public
* @static
*
* @param $attachment_id
* @return bool|mixed|string
*/
public static function get_inline_svg( $attachment_id ) {
$svg = get_post_meta( $attachment_id, self::META_KEY, true );
if ( ! empty( $svg ) ) {
$valid_svg = ( new SVG_Sanitizer() )->sanitize( $svg );
return ( false === $valid_svg ) ? '' : $valid_svg;
}
$attachment_file = get_attached_file( $attachment_id );
if ( ! file_exists( $attachment_file ) ) {
return '';
}
$svg = Utils::file_get_contents( $attachment_file );
$valid_svg = ( new SVG_Sanitizer() )->sanitize( $svg );
if ( false === $valid_svg ) {
return '';
}
if ( ! empty( $valid_svg ) ) {
update_post_meta( $attachment_id, self::META_KEY, $valid_svg );
}
return $valid_svg;
}
public function __construct() {
add_filter( 'wp_update_attachment_metadata', [ $this, 'set_svg_meta_data' ], 10, 2 );
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ], 10, 3 );
add_action( 'elementor/core/files/clear_cache', [ $this, 'delete_meta_cache' ] );
}
}
files/file-types/base.php 0000666 00000002503 15220772104 0011356 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/zip.php 0000666 00000011142 15220772104 0011245 0 ustar 00 <?php
namespace Elementor\Core\Files\File_Types;
use Elementor\Core\Base\Document;
use Elementor\Core\Utils\Exceptions;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor File Types Base.
*
* The File Types Base class provides base methods used by all file type handler classes.
* These methods are used in file upl
*
* @since 3.3.0
*/
class Zip extends Base {
/**
* Get File Extension
*
* Returns the file type's file extension
*
* @since 3.3.0
*
* @return string - file extension
*/
public function get_file_extension() {
return 'zip';
}
/**
* Get Mime Type
*
* Returns the file type's mime type
*
* @since 3.5.0
*
* @return string mime type
*/
public function get_mime_type() {
return 'application/zip';
}
/**
* Get File Property Name
*
* Get the property name to look for in the $_FILES superglobal
*
* @since 3.3.0
*
* @return string
*/
public function get_file_property_name() {
return 'zip_upload';
}
/**
* Extract
*
* Performs the extraction of the zip files to a temporary directory.
* Returns an error if for some reason the ZipArchive utility isn't available.
* Otherwise, Returns an array containing the temporary extraction directory, and the list of extracted files.
*
* @since 3.3.0
*
* @param string $file_path
* @param array|null $allowed_file_types
* @return array|\WP_Error
*/
public function extract( $file_path, $allowed_file_types ) {
if ( ! class_exists( '\ZipArchive' ) ) {
return new \WP_Error( 'zip_error', 'PHP Zip extension not loaded' );
}
$zip = new \ZipArchive();
/**
* Zip Extraction Directory
*
* Filters the extraction directory for uploaded zip files.
*
* @since 3.6.0
*
* @param string $extraction_directory A temporary upload directory generated by Elementor's Uploads Manager
*/
$extraction_directory = apply_filters( 'elementor/files/zip/extraction-directory', '' );
if ( ! $extraction_directory ) {
$extraction_directory = Plugin::$instance->uploads_manager->create_unique_dir();
}
$zip->open( $file_path );
// if an array of allowed file types is provided, get the filtered file list to extract.
$allowed_files = $allowed_file_types ? $this->get_allowed_files( $zip, $allowed_file_types ) : null;
$zip->extractTo( $extraction_directory, $allowed_files );
$zip->close();
return [
'extraction_directory' => $extraction_directory,
'files' => $this->find_temp_files( $extraction_directory ),
];
}
/**
* Get Allowed Files
*
* Accepts a zipArchive instance and an array of allowed file types. Iterates over the zip archive's files and
* checks if their extensions are in the list of allowed file types. Returns an array containing all valid files.
*
* @since 3.3.0
*
* @param \ZipArchive $zip
* @param array $allowed_file_types
* @return array
*/
private function get_allowed_files( $zip, $allowed_file_types ) {
$allowed_files = [];
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
for ( $i = 0; $i < $zip->numFiles; $i++ ) {
$filename = $zip->getNameIndex( $i );
$extension = pathinfo( $filename, PATHINFO_EXTENSION );
// Skip files with transversal paths.
if ( strpos( $filename, '..' ) !== false ) {
continue;
}
if ( in_array( $extension, $allowed_file_types, true ) ) {
$allowed_files[] = $filename;
}
}
return $allowed_files;
}
/**
* Find temporary files.
*
* Recursively finds a list of temporary files from the extracted zip file.
*
* Example return data:
*
* [
* 0 => '/www/wp-content/uploads/elementor/tmp/5eb3a7a411d44/templates/block-2-col-marble-title.json',
* 1 => '/www/wp-content/uploads/elementor/tmp/5eb3a7a411d44/templates/block-2-col-text-and-photo.json',
* ]
*
* @since 2.9.8
* @access private
*
* @param string $temp_path - The temporary file path to scan for template files
*
* @return array An array of temporary files on the filesystem
*/
private function find_temp_files( $temp_path ) {
$file_names = [];
$possible_file_names = array_diff( scandir( $temp_path ), [ '.', '..' ] );
// Find nested files in the unzipped path. This happens for example when the user imports a Website Kit.
foreach ( $possible_file_names as $possible_file_name ) {
$full_possible_file_name = $temp_path . $possible_file_name;
if ( is_dir( $full_possible_file_name ) ) {
$file_names = array_merge( $file_names, $this->find_temp_files( $full_possible_file_name . '/' ) );
} else {
$file_names[] = $full_possible_file_name;
}
}
return $file_names;
}
}
files/file-types/json.php 0000666 00000001040 15220772104 0011410 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/fonts/google-font.php 0000666 00000017072 15220772104 0011743 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 );
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
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/css/post-preview.php 0000666 00000002375 15220772104 0011626 0 ustar 00 <?php
namespace Elementor\Core\Files\CSS;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor post preview CSS file.
*
* Elementor CSS file handler class is responsible for generating the post
* preview CSS file.
*
* @since 1.9.0
*/
class Post_Preview extends Post_Local_Cache {
/**
* Preview ID.
*
* Holds the ID of the current post being previewed.
*
* @var int
*/
private $post_id_for_data;
/**
* Post preview CSS file constructor.
*
* Initializing the CSS file of the post preview. Set the post ID and the
* parent ID and initiate the stylesheet.
*
* @since 1.9.0
* @access public
*
* @param int $post_id Post ID.
*/
public function __construct( $post_id ) {
$this->post_id_for_data = $post_id;
$parent_id = wp_get_post_parent_id( $post_id );
parent::__construct( $parent_id );
}
protected function get_post_id_for_data() {
return $this->post_id_for_data;
}
/**
* Get file handle ID.
*
* Retrieve the handle ID for the previewed post CSS file.
*
* @since 1.9.0
* @access protected
*
* @return string CSS file handle ID.
*/
protected function get_file_handle_id() {
return 'elementor-preview-' . $this->get_post_id_for_data();
}
}
files/css/base.php 0000666 00000067420 15220772104 0010076 0 ustar 00 <?php
namespace Elementor\Core\Files\CSS;
use Elementor\Base_Data_Control;
use Elementor\Control_Repeater;
use Elementor\Controls_Manager;
use Elementor\Controls_Stack;
use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
use Elementor\Core\Files\Base as Base_File;
use Elementor\Core\DynamicTags\Manager;
use Elementor\Core\DynamicTags\Tag;
use Elementor\Core\Frontend\Performance;
use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
use Elementor\Plugin;
use Elementor\Stylesheet;
use Elementor\Icons_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor CSS file.
*
* Elementor CSS file handler class is responsible for generating CSS files.
*
* @since 1.2.0
* @abstract
*/
abstract class Base extends Base_File {
/**
* Elementor CSS file generated status.
*
* The parsing result after generating CSS file.
*/
const CSS_STATUS_FILE = 'file';
/**
* Elementor inline CSS status.
*
* The parsing result after generating inline CSS.
*/
const CSS_STATUS_INLINE = 'inline';
/**
* Elementor CSS empty status.
*
* The parsing result when an empty CSS returned.
*/
const CSS_STATUS_EMPTY = 'empty';
/**
* Fonts.
*
* Holds the list of fonts.
*
* @access private
*
* @var array
*/
private $fonts = [];
private $icons_fonts = [];
private $dynamic_elements_ids = [];
private $preserved_dynamic_style_values = [];
/**
* Stylesheet object.
*
* Holds the CSS file stylesheet instance.
*
* @access protected
*
* @var Stylesheet
*/
protected $stylesheet_obj;
/**
* Printed.
*
* Holds the list of printed files.
*
* @access protected
*
* @var array
*/
private static $printed = [];
/**
* Get CSS file name.
*
* Retrieve the CSS file name.
*
* @since 1.6.0
* @access public
* @abstract
*/
abstract public function get_name();
protected function is_global_parsing_supported() {
return false;
}
/**
* Use external file.
*
* Whether to use external CSS file of not. When there are new schemes or settings
* updates.
*
* @since 1.9.0
* @access protected
*
* @return bool True if the CSS requires an update, False otherwise.
*/
protected function use_external_file() {
return 'internal' !== get_option( 'elementor_css_print_method' );
}
/**
* Update the CSS file.
*
* Delete old CSS, parse the CSS, save the new file and update the database.
*
* This method also sets the CSS status to be used later on in the render posses.
*
* @since 1.2.0
* @access public
*/
public function update() {
$this->update_file();
$meta = $this->get_meta();
$meta['time'] = time();
$content = $this->get_content();
if ( empty( $content ) ) {
$meta['status'] = self::CSS_STATUS_EMPTY;
$meta['css'] = '';
} else {
$use_external_file = $this->use_external_file();
if ( $use_external_file ) {
$meta['status'] = self::CSS_STATUS_FILE;
} else {
$meta['status'] = self::CSS_STATUS_INLINE;
$meta['css'] = $content;
}
}
$meta['dynamic_elements_ids'] = $this->dynamic_elements_ids;
$this->update_meta( $meta );
}
/**
* @since 2.1.0
* @access public
*/
public function write() {
if ( $this->use_external_file() ) {
parent::write();
}
}
/**
* @since 3.0.0
* @access public
*/
public function delete() {
if ( $this->use_external_file() ) {
parent::delete();
} else {
$this->delete_meta();
}
}
/**
* Get Responsive Control Duplication Mode
*
* @since 3.4.0
*
* @return string
*/
protected function get_responsive_control_duplication_mode() {
return 'on';
}
/**
* Enqueue CSS.
*
* Either enqueue the CSS file in Elementor or add inline style.
*
* This method is also responsible for loading the fonts.
*
* @since 1.2.0
* @access public
*/
public function enqueue() {
$handle_id = $this->get_file_handle_id();
if ( isset( self::$printed[ $handle_id ] ) ) {
return;
}
self::$printed[ $handle_id ] = true;
$meta = $this->get_meta();
if ( self::CSS_STATUS_EMPTY === $meta['status'] ) {
return;
}
/**
* Enqueue CSS file.
*
* Fires before enqueuing a CSS file.
*
* @param Base $this The current CSS file.
*/
do_action( 'elementor/css-file/before_enqueue', $this );
// First time after clear cache and etc.
if ( '' === $meta['status'] || $this->is_update_required() ) {
$this->update();
$meta = $this->get_meta();
}
if ( self::CSS_STATUS_INLINE === $meta['status'] ) {
$dep = $this->get_inline_dependency();
// If the dependency has already been printed ( like a template in footer )
if ( wp_styles()->query( $dep, 'done' ) ) {
printf( '<style id="%1$s">%2$s</style>', $this->get_file_handle_id(), $meta['css'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
wp_add_inline_style( $dep, $meta['css'] );
}
} elseif ( self::CSS_STATUS_FILE === $meta['status'] ) { // Re-check if it's not empty after CSS update.
wp_enqueue_style( $this->get_file_handle_id(), $this->get_url(), $this->get_enqueue_dependencies(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
}
// Handle fonts.
if ( ! empty( $meta['fonts'] ) ) {
foreach ( $meta['fonts'] as $font ) {
Plugin::$instance->frontend->enqueue_font( $font );
}
}
if ( ! empty( $meta['icons'] ) ) {
$icons_types = Icons_Manager::get_icon_manager_tabs();
foreach ( $meta['icons'] as $icon_font ) {
if ( ! isset( $icons_types[ $icon_font ] ) ) {
continue;
}
Plugin::$instance->frontend->enqueue_font( $icon_font );
}
}
$name = $this->get_name();
/**
* Enqueue CSS file.
*
* Fires when CSS file is enqueued on Elementor.
*
* The dynamic portion of the hook name, `$name`, refers to the CSS file name.
*
* @since 2.0.0
*
* @param Base $this The current CSS file.
*/
do_action( "elementor/css-file/{$name}/enqueue", $this );
/**
* Enqueue CSS file.
*
* Fires after enqueuing a CSS file.
*
* @param Base $this The current CSS file.
*/
do_action( 'elementor/css-file/after_enqueue', $this );
}
/**
* Print CSS.
*
* Output the final CSS inside the `<style>` tags and all the frontend fonts in
* use.
*
* @since 1.9.4
* @access public
*/
public function print_css() {
echo '<style>' . $this->get_content() . '</style>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
Plugin::$instance->frontend->print_fonts_links();
}
/**
* Add control rules.
*
* Parse the CSS for all the elements inside any given control.
*
* This method recursively renders the CSS for all the selectors in the control.
*
* @since 1.2.0
* @access public
*
* @param array $control The controls.
* @param array $controls_stack The controls stack.
* @param callable $value_callback Callback function for the value.
* @param array $placeholders Placeholders.
* @param array $replacements Replacements.
* @param array $values Global Values.
*/
public function add_control_rules( array $control, array $controls_stack, callable $value_callback, array $placeholders, array $replacements, array $values = [] ) {
if ( empty( $control['selectors'] ) ) {
return;
}
$control_global_key = $control['name'];
if ( ! empty( $control['groupType'] ) ) {
$control_global_key = $control['groupPrefix'] . $control['groupType'];
}
$global_values = [];
$global_key = '';
if ( ! empty( $values['__globals__'] ) ) {
$global_values = $values['__globals__'];
}
if ( ! empty( $global_values[ $control_global_key ] ) ) {
$global_key = $global_values[ $control_global_key ];
}
if ( ! $global_key ) {
$value = call_user_func( $value_callback, $control );
if ( null === $value ) {
return;
}
}
$stylesheet = $this->get_stylesheet();
$control = apply_filters( 'elementor/files/css/selectors', $control, $value ?? [], $this );
foreach ( $control['selectors'] as $selector => $css_property ) {
$output_css_property = '';
if ( $global_key ) {
$selector_global_value = $this->get_selector_global_value( $control, $global_key );
if ( $selector_global_value ) {
$output_css_property = preg_replace( '/(:)[^;]+(;?)/', '$1' . $selector_global_value . '$2', $css_property );
}
} else {
try {
if ( $this->unit_has_custom_selector( $control, $value ) ) {
$css_property = $control['unit_selectors_dictionary'][ $value['unit'] ];
}
$output_css_property = preg_replace_callback( '/{{(?:([^.}]+)\.)?([^}| ]*)(?: *\|\| *(?:([^.}]+)\.)?([^}| ]*) *)*}}/', function( $matches ) use ( $control, $value_callback, $controls_stack, $value, $css_property ) {
$external_control_missing = $matches[1] && ! isset( $controls_stack[ $matches[1] ] );
$parsed_value = '';
$value = apply_filters( 'elementor/files/css/property', $value, $css_property, $matches, $control );
if ( ! $external_control_missing ) {
$parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[2], $matches[1] );
}
if ( '' === $parsed_value ) {
if ( isset( $matches[4] ) ) {
$parsed_value = $matches[4];
$is_string_value = preg_match( '/^([\'"])(.*)\1$/', $parsed_value, $string_matches );
if ( $is_string_value ) {
$parsed_value = $string_matches[2];
} elseif ( ! is_numeric( $parsed_value ) ) {
if ( $matches[3] && ! isset( $controls_stack[ $matches[3] ] ) ) {
return '';
}
$parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[4], $matches[3] );
}
}
if ( '' === $parsed_value ) {
if ( $external_control_missing ) {
return '';
}
throw new \Exception();
}
}
if ( '__EMPTY__' === $parsed_value ) {
$parsed_value = '';
}
return $parsed_value;
}, $css_property );
} catch ( \Exception $e ) {
return;
}
}
if ( ! $output_css_property ) {
continue;
}
$device_pattern = '/^(?:\([^\)]+\)){1,2}/';
preg_match( $device_pattern, $selector, $device_rules );
$query = [];
if ( $device_rules ) {
$selector = preg_replace( $device_pattern, '', $selector );
preg_match_all( '/\(([^)]+)\)/', $device_rules[0], $pure_device_rules );
$pure_device_rules = $pure_device_rules[1];
foreach ( $pure_device_rules as $device_rule ) {
if ( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $device_rule ) {
continue;
}
$device = preg_replace( '/\+$/', '', $device_rule );
$endpoint = $device === $device_rule ? 'max' : 'min';
$query[ $endpoint ] = $device;
}
}
$parsed_selector = str_replace( $placeholders, $replacements, $selector );
if ( ! $query && ! empty( $control['responsive'] ) ) {
$query = array_intersect_key( $control['responsive'], array_flip( [ 'min', 'max' ] ) );
if ( ! empty( $query['max'] ) && Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $query['max'] ) {
unset( $query['max'] );
}
}
$stylesheet->add_rules( $parsed_selector, $output_css_property, $query );
}
}
protected function unit_has_custom_selector( $control, $value ) {
return isset( $control['unit_selectors_dictionary'] ) && isset( $control['unit_selectors_dictionary'][ $value['unit'] ] );
}
/**
* @param array $control
* @param mixed $value
* @param array $controls_stack
* @param callable $value_callback
* @param string $placeholder
* @param string $parser_control_name
*
* @return string
*/
public function parse_property_placeholder( array $control, $value, array $controls_stack, $value_callback, $placeholder, $parser_control_name = null ) {
if ( $parser_control_name ) {
// If both the processed control and the control name found in the placeholder are responsive
if ( ! empty( $control['responsive'] ) && ! empty( $controls_stack[ $parser_control_name ]['responsive'] ) ) {
$device_suffix = Controls_Manager::get_responsive_control_device_suffix( $control );
$control = $controls_stack[ $parser_control_name . $device_suffix ] ?? $controls_stack[ $parser_control_name ];
} else {
$control = $controls_stack[ $parser_control_name ];
}
$value = call_user_func( $value_callback, $control );
}
// If the control value is empty, check for global default. `0` (integer, string) are falsy but are valid values.
if ( empty( $value ) && '0' !== $value && 0 !== $value ) {
$value = $this->get_control_global_default_value( $control );
}
if ( Controls_Manager::FONT === $control['type'] ) {
$this->add_font( $value );
}
/** @var Base_Data_Control $control_obj */
$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
return (string) $control_obj->get_style_value( $placeholder, $value, $control );
}
/**
* Get the fonts.
*
* Retrieve the list of fonts.
*
* @since 1.9.0
* @access public
*
* @return array Fonts.
*/
public function get_fonts() {
return $this->fonts;
}
/**
* Get stylesheet.
*
* Retrieve the CSS file stylesheet instance.
*
* @since 1.2.0
* @access public
*
* @return Stylesheet The stylesheet object.
*/
public function get_stylesheet() {
if ( ! $this->stylesheet_obj ) {
$this->init_stylesheet();
}
return $this->stylesheet_obj;
}
/**
* Add controls stack style rules.
*
* Parse the CSS for all the elements inside any given controls stack.
*
* This method recursively renders the CSS for all the child elements in the stack.
*
* @since 1.6.0
* @access public
*
* @param Controls_Stack $controls_stack The controls stack.
* @param array $controls Controls array.
* @param array $values Values array.
* @param array $placeholders Placeholders.
* @param array $replacements Replacements.
* @param array $all_controls All controls.
*/
public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, ?array $all_controls = null ) {
if ( ! $all_controls ) {
$all_controls = $controls_stack->get_controls();
}
$parsed_dynamic_settings = $controls_stack->parse_dynamic_settings( $values, $controls );
foreach ( $controls as $control ) {
if ( ! empty( $control['style_fields'] ) ) {
$this->add_repeater_control_style_rules( $controls_stack, $control, $values[ $control['name'] ], $placeholders, $replacements );
}
if ( ! empty( $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
$this->add_dynamic_control_style_rules( $control, $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] );
}
if ( Controls_Manager::ICONS === $control['type'] ) {
$this->icons_fonts[] = $values[ $control['name'] ]['library'];
}
if ( ! empty( $parsed_dynamic_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
// Dynamic CSS should not be added to the CSS files.
// Instead it's handled by \Elementor\Core\DynamicTags\Dynamic_CSS
// and printed in a style tag.
$should_preserve_value = isset( $control['control_type'] ) && 'content' === $control['control_type'];
if ( $should_preserve_value ) {
$this->preserved_dynamic_style_values[ $control['name'] ] = $parsed_dynamic_settings[ $control['name'] ];
}
unset( $parsed_dynamic_settings[ $control['name'] ] );
$this->dynamic_elements_ids[] = $controls_stack->get_id();
continue;
}
if ( empty( $control['selectors'] ) ) {
continue;
}
$this->add_control_style_rules( $control, $parsed_dynamic_settings, $all_controls, $placeholders, $replacements );
}
}
/**
* Get file handle ID.
*
* Retrieve the file handle ID.
*
* @since 1.2.0
* @access protected
* @abstract
*
* @return string CSS file handle ID.
*/
abstract protected function get_file_handle_id();
/**
* Render CSS.
*
* Parse the CSS.
*
* @since 1.2.0
* @access protected
* @abstract
*/
abstract protected function render_css();
protected function get_default_meta() {
return array_merge( parent::get_default_meta(), [
'fonts' => array_unique( $this->fonts ),
'icons' => array_unique( $this->icons_fonts ),
'dynamic_elements_ids' => [],
'status' => '',
] );
}
/**
* Get enqueue dependencies.
*
* Retrieve the name of the stylesheet used by `wp_enqueue_style()`.
*
* @since 1.2.0
* @access protected
*
* @return array Name of the stylesheet.
*/
protected function get_enqueue_dependencies() {
return [];
}
/**
* Get inline dependency.
*
* Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
*
* @since 1.2.0
* @access protected
*
* @return string Name of the stylesheet.
*/
protected function get_inline_dependency() {
return '';
}
/**
* Is update required.
*
* Whether the CSS requires an update. When there are new schemes or settings
* updates.
*
* @since 1.2.0
* @access protected
*
* @return bool True if the CSS requires an update, False otherwise.
*/
protected function is_update_required() {
return false;
}
/**
* Parse CSS.
*
* Parsing the CSS file.
*
* @since 1.2.0
* @access protected
*/
protected function parse_content() {
Performance::set_use_style_controls( true );
$initial_responsive_controls_duplication_mode = Plugin::$instance->breakpoints->get_responsive_control_duplication_mode();
Plugin::$instance->breakpoints->set_responsive_control_duplication_mode( $this->get_responsive_control_duplication_mode() );
$this->render_css();
$name = $this->get_name();
/**
* Parse CSS file.
*
* Fires when CSS file is parsed on Elementor.
*
* The dynamic portion of the hook name, `$name`, refers to the CSS file name.
*
* @since 2.0.0
*
* @param Base $this The current CSS file.
*/
do_action( "elementor/css-file/{$name}/parse", $this );
Plugin::$instance->breakpoints->set_responsive_control_duplication_mode( $initial_responsive_controls_duplication_mode );
Performance::set_use_style_controls( false );
return $this->get_stylesheet()->__toString();
}
/**
* Add control style rules.
*
* Register new style rules for the control.
*
* @since 1.6.0
* @access private
*
* @param array $control The control.
* @param array $values Values array.
* @param array $controls The controls stack.
* @param array $placeholders Placeholders.
* @param array $replacements Replacements.
*/
protected function add_control_style_rules( array $control, array $values, array $controls, array $placeholders, array $replacements ) {
$this->add_control_rules(
$control, $controls, function( $control ) use ( $values ) {
return $this->get_style_control_value( $control, $values );
}, $placeholders, $replacements, $values
);
}
/**
* Get Control Global Default Value
*
* If the control has a global default value, and the corresponding global default setting is enabled, this method
* fetches and returns the global default value. Otherwise, it returns null.
*
* @since 3.7.0
* @access private
*
* @param $control
* @return string|null
*/
private function get_control_global_default_value( $control ) {
if ( empty( $control['global']['default'] ) ) {
return null;
}
// If the control value is empty, and the control has a global default set, fetch the global value and use it.
$global_enabled = false;
if ( 'color' === $control['type'] ) {
$global_enabled = Plugin::$instance->kits_manager->is_custom_colors_enabled();
} elseif ( isset( $control['groupType'] ) && 'typography' === $control['groupType'] ) {
$global_enabled = Plugin::$instance->kits_manager->is_custom_typography_enabled();
}
$value = null;
// Only apply the global default if Global Colors are enabled.
if ( $global_enabled ) {
$value = $this->get_selector_global_value( $control, $control['global']['default'] );
}
return $value;
}
/**
* Get style control value.
*
* Retrieve the value of the style control for any give control and values.
*
* It will retrieve the control name and return the style value.
*
* @since 1.6.0
* @access private
*
* @param array $control The control.
* @param array $values Values array.
*
* @return mixed Style control value.
*/
private function get_style_control_value( array $control, array $values ) {
if ( ! empty( $values['__globals__'][ $control['name'] ] ) ) {
// When the control itself has no global value, but it refers to another control global value
return $this->get_selector_global_value( $control, $values['__globals__'][ $control['name'] ] );
}
$value = isset( $values[ $control['name'] ] )
? $values[ $control['name'] ]
: $this->preserved_dynamic_style_values[ $control['name'] ] ?? null;
if ( isset( $control['selectors_dictionary'][ $value ] ) ) {
$value = $control['selectors_dictionary'][ $value ];
}
if ( ! is_numeric( $value ) && ! is_float( $value ) && empty( $value ) ) {
return null;
}
return $value;
}
/**
* Init stylesheet.
*
* Initialize CSS file stylesheet by creating a new `Stylesheet` object and register new
* breakpoints for the stylesheet.
*
* @since 1.2.0
* @access private
*/
private function init_stylesheet() {
$this->stylesheet_obj = new Stylesheet();
$active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints();
foreach ( $active_breakpoints as $breakpoint_name => $breakpoint ) {
$this->stylesheet_obj->add_device( $breakpoint_name, $breakpoint->get_value() );
}
}
/**
* Add repeater control style rules.
*
* Register new style rules for the repeater control.
*
* @since 2.0.0
* @access private
*
* @param Controls_Stack $controls_stack The control stack.
* @param array $repeater_control The repeater control.
* @param array $repeater_values Repeater values array.
* @param array $placeholders Placeholders.
* @param array $replacements Replacements.
*/
protected function add_repeater_control_style_rules( Controls_Stack $controls_stack, array $repeater_control, array $repeater_values, array $placeholders, array $replacements ) {
$placeholders = array_merge( $placeholders, [ '{{CURRENT_ITEM}}' ] );
foreach ( $repeater_control['style_fields'] as $index => $item ) {
$this->add_controls_stack_style_rules(
$controls_stack,
$item,
$repeater_values[ $index ],
$placeholders,
array_merge( $replacements, [ '.elementor-repeater-item-' . $repeater_values[ $index ]['_id'] ] ),
$repeater_control['fields']
);
}
}
/**
* Add dynamic control style rules.
*
* Register new style rules for the dynamic control.
*
* @since 2.0.0
* @access private
*
* @param array $control The control.
* @param string $value The value.
*/
protected function add_dynamic_control_style_rules( array $control, $value ) {
Plugin::$instance->dynamic_tags->parse_tags_text( $value, $control, function( $id, $name, $settings ) {
$tag = Plugin::$instance->dynamic_tags->create_tag( $id, $name, $settings );
if ( ! $tag instanceof Tag ) {
return;
}
$this->add_controls_stack_style_rules( $tag, $this->get_style_controls( $tag ), $tag->get_active_settings(), [ '{{WRAPPER}}' ], [ '#elementor-tag-' . $id ] );
} );
}
private function get_selector_global_value( $control, $global_key ) {
$data = Plugin::$instance->data_manager_v2->run( $global_key );
if ( empty( $data['value'] ) ) {
return null;
}
$global_args = explode( '?id=', $global_key );
$id = $global_args[1];
if ( ! empty( $control['groupType'] ) ) {
$strings_to_replace = [ $control['groupPrefix'] ];
$active_breakpoint_keys = array_keys( Plugin::$instance->breakpoints->get_active_breakpoints() );
foreach ( $active_breakpoint_keys as $breakpoint ) {
$strings_to_replace[] = '_' . $breakpoint;
}
$property_name = str_replace( $strings_to_replace, '', $control['name'] );
// TODO: This check won't retrieve the proper answer for array values (multiple controls).
if ( empty( $data['value'][ Global_Typography::TYPOGRAPHY_GROUP_PREFIX . $property_name ] ) ) {
return null;
}
$property_name = str_replace( '_', '-', $property_name );
$value = "var( --e-global-$control[groupType]-$id-$property_name )";
if ( $control['groupPrefix'] . 'font_family' === $control['name'] ) {
$default_generic_fonts = Plugin::$instance->kits_manager->get_current_settings( 'default_generic_fonts' );
if ( $default_generic_fonts ) {
$value .= ", $default_generic_fonts";
}
}
} else {
$value = "var( --e-global-$control[type]-$id )";
}
return $value;
}
final protected function get_active_controls( Controls_Stack $controls_stack, ?array $controls = null, ?array $settings = null ) {
if ( ! $controls ) {
$controls = $controls_stack->get_controls();
}
if ( ! $settings ) {
$settings = $controls_stack->get_controls_settings();
}
if ( $this->is_global_parsing_supported() ) {
$settings = $this->parse_global_settings( $settings, $controls );
}
$active_controls = array_reduce(
array_keys( $controls ), function( $active_controls, $control_key ) use ( $controls_stack, $controls, $settings ) {
$control = $controls[ $control_key ];
if ( $controls_stack->is_control_visible( $control, $settings, $controls ) ) {
$active_controls[ $control_key ] = $control;
}
return $active_controls;
}, []
);
return $active_controls;
}
final public function get_style_controls( Controls_Stack $controls_stack, ?array $controls = null, ?array $settings = null ) {
$controls = $this->get_active_controls( $controls_stack, $controls, $settings );
$style_controls = [];
foreach ( $controls as $control_name => $control ) {
$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
if ( ! $control_obj instanceof Base_Data_Control ) {
continue;
}
$control = array_merge( $control_obj->get_settings(), $control );
if ( $control_obj instanceof Control_Repeater ) {
$style_fields = [];
foreach ( $controls_stack->get_settings( $control_name ) as $item ) {
$style_fields[] = $this->get_style_controls( $controls_stack, $control['fields'], $item );
}
$control['style_fields'] = $style_fields;
}
if ( ! empty( $control['selectors'] ) || ! empty( $control['dynamic'] ) || $this->is_global_control( $controls_stack, $control_name, $controls ) || ! empty( $control['style_fields'] ) ) {
$style_controls[ $control_name ] = $control;
}
}
return $style_controls;
}
private function parse_global_settings( array $settings, array $controls ) {
foreach ( $controls as $control ) {
$control_name = $control['name'];
$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
if ( ! $control_obj instanceof Base_Data_Control ) {
continue;
}
if ( $control_obj instanceof Control_Repeater ) {
foreach ( $settings[ $control_name ] as & $field ) {
$field = $this->parse_global_settings( $field, $control['fields'] );
}
continue;
}
if ( empty( $control['global']['active'] ) ) {
continue;
}
if ( empty( $settings['__globals__'][ $control_name ] ) ) {
continue;
}
$settings[ $control_name ] = 'global';
}
return $settings;
}
private function is_global_control( Controls_Stack $controls_stack, $control_name, $controls ) {
$control = $controls[ $control_name ];
$control_global_key = $control_name;
if ( ! empty( $control['groupType'] ) ) {
$control_global_key = $control['groupPrefix'] . $control['groupType'];
}
if ( empty( $controls[ $control_global_key ]['global']['active'] ) ) {
return false;
}
$globals = $controls_stack->get_settings( '__globals__' );
return ! empty( $globals[ $control_global_key ] );
}
public function add_font( $font ) {
if ( ! in_array( $font, $this->fonts, true ) ) {
$this->fonts[] = $font;
}
}
}
files/css/post-local-cache.php 0000666 00000001367 15220772104 0012300 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 0000666 00000021663 15220772104 0010150 0 ustar 00 <?php
namespace Elementor\Core\Files\CSS;
use Elementor\Controls_Stack;
use Elementor\Core\DynamicTags\Dynamic_CSS;
use Elementor\Core\Kits\Manager;
use Elementor\Element_Base;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor post CSS file.
*
* Elementor CSS file handler class is responsible for generating the single
* post CSS file.
*
* @since 1.2.0
*/
class Post extends Base {
/**
* Elementor post CSS file prefix.
*/
const FILE_PREFIX = 'post-';
const META_KEY = '_elementor_css';
/**
* Post ID.
*
* Holds the current post ID.
*
* @var int
*/
private $post_id;
protected function is_global_parsing_supported() {
return true;
}
/**
* Post CSS file constructor.
*
* Initializing the CSS file of the post. Set the post ID and initiate the stylesheet.
*
* @since 1.2.0
* @access public
*
* @param int $post_id Post ID.
*/
public function __construct( $post_id ) {
$this->post_id = $post_id;
parent::__construct( static::FILE_PREFIX . $post_id . '.css' );
}
/**
* Get CSS file name.
*
* Retrieve the CSS file name.
*
* @since 1.6.0
* @access public
*
* @return string CSS file name.
*/
public function get_name() {
return 'post';
}
/**
* Get post ID.
*
* Retrieve the ID of current post.
*
* @since 1.2.0
* @access public
*
* @return int Post ID.
*/
public function get_post_id() {
return $this->post_id;
}
/**
* Get unique element selector.
*
* Retrieve the unique selector for any given element.
*
* @since 1.2.0
* @access public
*
* @param Element_Base $element The element.
*
* @return string Unique element selector.
*/
public function get_element_unique_selector( Element_Base $element ) {
return '.elementor-' . $this->post_id . ' .elementor-element' . $element->get_unique_selector();
}
/**
* Load meta data.
*
* Retrieve the post CSS file meta data.
*
* @since 1.2.0
* @access protected
*
* @return array Post CSS file meta data.
*/
protected function load_meta() {
return get_post_meta( $this->post_id, static::META_KEY, true );
}
/**
* Update meta data.
*
* Update the global CSS file meta data.
*
* @since 1.2.0
* @access protected
*
* @param array $meta New meta data.
*/
protected function update_meta( $meta ) {
update_post_meta( $this->post_id, static::META_KEY, $meta );
}
/**
* Delete meta.
*
* Delete the file meta data.
*
* @since 2.1.0
* @access protected
*/
protected function delete_meta() {
delete_post_meta( $this->post_id, static::META_KEY );
}
/**
* Get post data.
*
* Retrieve raw post data from the database.
*
* @since 1.9.0
* @access protected
*
* @return array Post data.
*/
protected function get_data() {
$document = Plugin::$instance->documents->get( $this->post_id );
return $document ? $document->get_elements_data() : [];
}
/**
* Render CSS.
*
* Parse the CSS for all the elements.
*
* @since 1.2.0
* @access protected
*/
protected function render_css() {
$data = $this->get_data();
if ( ! empty( $data ) ) {
foreach ( $data as $element_data ) {
$element = Plugin::$instance->elements_manager->create_element_instance( $element_data );
if ( ! $element ) {
continue;
}
$this->render_styles( $element );
}
}
}
/**
* Enqueue CSS.
*
* Enqueue the post CSS file in Elementor.
*
* This method ensures that the post was actually built with elementor before
* enqueueing the post CSS file.
*
* @since 1.2.2
* @access public
*/
public function enqueue() {
$document = Plugin::$instance->documents->get( $this->post_id );
if ( ! $document || ! $document->is_built_with_elementor() ) {
return;
}
parent::enqueue();
}
/**
* Add controls-stack style rules.
*
* Parse the CSS for all the elements inside any given controls stack.
*
* This method recursively renders the CSS for all the child elements in the stack.
*
* @since 1.6.0
* @access public
*
* @param Controls_Stack $controls_stack The controls stack.
* @param array $controls Controls array.
* @param array $values Values array.
* @param array $placeholders Placeholders.
* @param array $replacements Replacements.
* @param array $all_controls All controls.
*/
public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, ?array $all_controls = null ) {
parent::add_controls_stack_style_rules( $controls_stack, $controls, $values, $placeholders, $replacements, $all_controls );
if ( $controls_stack instanceof Element_Base ) {
foreach ( $controls_stack->get_children() as $child_element ) {
$this->render_styles( $child_element );
}
}
}
/**
* Get enqueue dependencies.
*
* Retrieve the name of the stylesheet used by `wp_enqueue_style()`.
*
* @since 1.2.0
* @access protected
*
* @return array Name of the stylesheet.
*/
protected function get_enqueue_dependencies() {
return [ 'elementor-frontend' ];
}
/**
* Get inline dependency.
*
* Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
*
* @since 1.2.0
* @access protected
*
* @return string Name of the stylesheet.
*/
protected function get_inline_dependency() {
return 'elementor-frontend';
}
/**
* Get file handle ID.
*
* Retrieve the handle ID for the post CSS file.
*
* @since 1.2.0
* @access protected
*
* @return string CSS file handle ID.
*/
protected function get_file_handle_id() {
return 'elementor-post-' . $this->post_id;
}
/**
* Render styles.
*
* Parse the CSS for any given element.
*
* @since 1.2.0
* @access protected
*
* @param Element_Base $element The element.
*/
protected function render_styles( Element_Base $element ) {
/**
* Before element parse CSS.
*
* Fires before the CSS of the element is parsed.
*
* @since 1.2.0
*
* @param Post $this The post CSS file.
* @param Element_Base $element The element.
*/
do_action( 'elementor/element/before_parse_css', $this, $element );
$this->render_element_global_styles( $element );
$this->render_element_styles( $element );
/**
* After element parse CSS.
*
* Fires after the CSS of the element is parsed.
*
* @since 1.2.0
*
* @param Post $this The post CSS file.
* @param Element_Base $element The element.
*/
do_action( 'elementor/element/parse_css', $this, $element );
}
private function render_element_styles( Element_Base $element ) {
$this->add_controls_stack_style_rules(
$element,
$this->get_style_controls( $element, null, $element->get_parsed_dynamic_settings() ),
$element->get_settings(),
[ '{{ID}}', '{{WRAPPER}}' ],
[ $element->get_id(), $this->get_element_unique_selector( $element ) ]
);
}
private function render_element_global_styles( Element_Base $element ) {
if ( $this instanceof Dynamic_CSS ) {
return;
}
/** @var Manager $module */
$kits_manager = Plugin::$instance->kits_manager;
$custom_colors_enabled = $kits_manager->is_custom_colors_enabled();
$custom_typography_enabled = $kits_manager->is_custom_typography_enabled();
$controls = $element->get_controls();
$global_controls = [];
$global_values['__globals__'] = [];
foreach ( $controls as $control ) {
$this->build_global_controls_and_values(
$control,
$controls,
$global_controls,
$global_values,
$custom_colors_enabled,
$custom_typography_enabled
);
}
foreach ( $global_controls as $control ) {
$this->add_control_rules(
$control,
$controls,
function( $control ) {},
[ '{{WRAPPER}}' ],
[ '.elementor-widget-' . $element->get_name() ],
$global_values
);
}
}
private function build_global_controls_and_values( $control, $controls, &$global_controls, &$global_values, $custom_colors_enabled, $custom_typography_enabled ) {
$is_color_control = 'color' === $control['type'];
$is_typography_control = isset( $control['groupType'] ) && 'typography' === $control['groupType'];
// If it is a color/typography control and default colors/typography are disabled,
// don't add the default CSS.
if ( ( $is_color_control && ! $custom_colors_enabled ) || ( $is_typography_control && ! $custom_typography_enabled ) ) {
return;
}
$global_control = $control;
// Handle group controls that don't have a default global property.
if ( ! empty( $control['groupType'] ) ) {
$global_control = $controls[ $control['groupPrefix'] . $control['groupType'] ];
}
// If the control has a default global defined, add it to the globals array
// that is used in add_control_rules.
if ( ! empty( $control['global']['default'] ) ) {
$global_values['__globals__'][ $control['name'] ] = $global_control['global']['default'];
}
if ( ! empty( $global_control['global']['default'] ) ) {
$global_controls[] = $control;
}
}
}
frontend/performance.php 0000666 00000001161 15220772104 0011400 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 $is_use ): void {
static::$use_style_controls = $is_use;
}
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;
}
}
frontend/render-modes/render-mode-base.php 0000666 00000004240 15220772104 0014575 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.
*
* @throws \Exception If the `get_name` method is not implemented.
*/
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 If the `get_name` method is not implemented.
*/
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 0000666 00000000632 15220772104 0015154 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;
}
}