dvadf
File manager - Edit - /home/centroca/public_html/modules.tar
Back
history/revisions-manager.php 0000644 00000024011 15252521347 0012413 0 ustar 00 <?php namespace Elementor\Modules\History; use Elementor\Core\Base\Document; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Files\CSS\Post as Post_CSS; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor history revisions manager. * * Elementor history revisions manager handler class is responsible for * registering and managing Elementor revisions manager. * * @since 1.7.0 */ class Revisions_Manager { /** * Maximum number of revisions to display. */ const MAX_REVISIONS_TO_DISPLAY = 50; /** * Authors list. * * Holds all the authors. * * @access private * * @var array */ private static $authors = []; /** * History revisions manager constructor. * * Initializing Elementor history revisions manager. * * @since 1.7.0 * @access public */ public function __construct() { self::register_actions(); } /** * @since 1.7.0 * @access public * @static */ public static function handle_revision() { add_filter( 'wp_save_post_revision_check_for_changes', '__return_false' ); } /** * @since 2.0.0 * @access public * @static * * @param $post_content * @param $post_id * * @return string */ public static function avoid_delete_auto_save( $post_content, $post_id ) { // Add a temporary string in order the $post will not be equal to the $autosave // in edit-form-advanced.php:210 $document = Plugin::$instance->documents->get( $post_id ); if ( $document && $document->is_built_with_elementor() ) { $post_content .= '<!-- Created with Elementor -->'; } return $post_content; } /** * @since 2.0.0 * @access public * @static */ public static function remove_temp_post_content() { global $post; $document = Plugin::$instance->documents->get( $post->ID ); if ( ! $document || ! $document->is_built_with_elementor() ) { return; } $post->post_content = str_replace( '<!-- Created with Elementor -->', '', $post->post_content ); } /** * @since 1.7.0 * @access public * @static * * @param int $post_id * @param array $query_args * @param bool $parse_result * * @return array */ public static function get_revisions( $post_id = 0, $query_args = [], $parse_result = true ) { $post = get_post( $post_id ); if ( ! $post || empty( $post->ID ) ) { return []; } $revisions = []; $default_query_args = [ 'posts_per_page' => self::MAX_REVISIONS_TO_DISPLAY, 'meta_key' => '_elementor_data', ]; $query_args = array_merge( $default_query_args, $query_args ); $posts = wp_get_post_revisions( $post->ID, $query_args ); if ( ! wp_revisions_enabled( $post ) ) { $autosave = Utils::get_post_autosave( $post->ID ); if ( $autosave ) { if ( $parse_result ) { array_unshift( $posts, $autosave ); } else { array_unshift( $posts, $autosave->ID ); } } } if ( $parse_result ) { array_unshift( $posts, $post ); } else { array_unshift( $posts, $post->ID ); return $posts; } $current_time = current_time( 'timestamp' ); /** @var \WP_Post $revision */ foreach ( $posts as $revision ) { $date = date_i18n( _x( 'M j @ H:i', 'revision date format', 'elementor' ), strtotime( $revision->post_modified ) ); $human_time = human_time_diff( strtotime( $revision->post_modified ), $current_time ); if ( $revision->ID === $post->ID ) { $type = 'current'; $type_label = esc_html__( 'Current Version', 'elementor' ); } elseif ( false !== strpos( $revision->post_name, 'autosave' ) ) { $type = 'autosave'; $type_label = esc_html__( 'Autosave', 'elementor' ); } else { $type = 'revision'; $type_label = esc_html__( 'Revision', 'elementor' ); } if ( ! isset( self::$authors[ $revision->post_author ] ) ) { self::$authors[ $revision->post_author ] = [ 'avatar' => get_avatar( $revision->post_author, 22 ), 'display_name' => get_the_author_meta( 'display_name', $revision->post_author ), ]; } $revisions[] = [ 'id' => $revision->ID, 'author' => self::$authors[ $revision->post_author ]['display_name'], 'timestamp' => strtotime( $revision->post_modified ), 'date' => sprintf( /* translators: 1: Human readable time difference, 2: Date. */ esc_html__( '%1$s ago (%2$s)', 'elementor' ), '<time>' . $human_time . '</time>', '<time>' . $date . '</time>' ), 'type' => $type, 'typeLabel' => $type_label, 'gravatar' => self::$authors[ $revision->post_author ]['avatar'], ]; } return $revisions; } /** * @since 1.9.2 * @access public * @static */ public static function update_autosave( $autosave_data ) { self::save_revision( $autosave_data['ID'] ); } /** * @since 1.7.0 * @access public * @static */ public static function save_revision( $revision_id ) { $parent_id = wp_is_post_revision( $revision_id ); if ( $parent_id ) { Plugin::$instance->db->safe_copy_elementor_meta( $parent_id, $revision_id ); } } /** * @since 1.7.0 * @access public * @static */ public static function restore_revision( $parent_id, $revision_id ) { $parent = Plugin::$instance->documents->get( $parent_id ); $revision = Plugin::$instance->documents->get( $revision_id ); if ( ! $parent || ! $revision ) { return; } $is_built_with_elementor = $revision->is_built_with_elementor(); $parent->set_is_built_with_elementor( $is_built_with_elementor ); if ( ! $is_built_with_elementor ) { return; } Plugin::$instance->db->copy_elementor_meta( $revision_id, $parent_id ); $post_css = Post_CSS::create( $parent_id ); $post_css->update(); } /** * @since 2.3.0 * @access public * @static * * @param $data * * @return array * @throws \Exception If the revision ID is not set. */ public static function ajax_get_revision_data( array $data ) { if ( ! isset( $data['id'] ) ) { throw new \Exception( 'You must set the revision ID.' ); } $revision = Plugin::$instance->documents->get_with_permissions( $data['id'] ); return [ 'settings' => $revision->get_settings(), 'elements' => $revision->get_elements_data(), ]; } /** * @since 1.7.0 * @access public * @static */ public static function add_revision_support_for_all_post_types() { $post_types = get_post_types_by_support( 'elementor' ); foreach ( $post_types as $post_type ) { add_post_type_support( $post_type, 'revisions' ); } } /** * @since 2.0.0 * @access public * @static * @param array $return_data * @param Document $document * * @return array */ public static function on_ajax_save_builder_data( $return_data, $document ) { $post_id = $document->get_main_id(); $latest_revisions = self::get_revisions( $post_id, [ 'posts_per_page' => 1, ] ); $all_revision_ids = self::get_revisions( $post_id, [ 'fields' => 'ids', ], false ); // Send revisions data only if has revisions. if ( ! empty( $latest_revisions ) ) { $current_revision_id = self::current_revision_id( $post_id ); $return_data = array_replace_recursive( $return_data, [ 'config' => [ 'document' => [ 'revisions' => [ 'current_id' => $current_revision_id, ], ], ], 'latest_revisions' => $latest_revisions, 'revisions_ids' => $all_revision_ids, ] ); } return $return_data; } /** * @since 1.7.0 * @access public * @static */ public static function db_before_save( $status, $has_changes ) { if ( $has_changes ) { self::handle_revision(); } } public static function document_config( $settings, $post_id ) { $settings['revisions'] = [ 'enabled' => ( $post_id && wp_revisions_enabled( get_post( $post_id ) ) ), 'current_id' => self::current_revision_id( $post_id ), ]; return $settings; } /** * Localize settings. * * Add new localized settings for the revisions manager. * * Fired by `elementor/editor/editor_settings` filter. * * @since 1.7.0 * @deprecated 3.1.0 * @access public * @static */ public static function editor_settings() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0' ); return []; } /** * @throws \Exception If the user doesn't have permissions or not found. */ public static function ajax_get_revisions( $data ) { Plugin::$instance->documents->check_permissions( $data['editor_post_id'] ); return self::get_revisions(); } /** * @since 2.3.0 * @access public * @static */ public static function register_ajax_actions( Ajax $ajax ) { $ajax->register_ajax_action( 'get_revisions', [ __CLASS__, 'ajax_get_revisions' ] ); $ajax->register_ajax_action( 'get_revision_data', [ __CLASS__, 'ajax_get_revision_data' ] ); } /** * @since 1.7.0 * @access private * @static */ private static function register_actions() { add_action( 'wp_restore_post_revision', [ __CLASS__, 'restore_revision' ], 10, 2 ); add_action( 'init', [ __CLASS__, 'add_revision_support_for_all_post_types' ], 9999 ); add_filter( 'elementor/document/config', [ __CLASS__, 'document_config' ], 10, 2 ); add_action( 'elementor/db/before_save', [ __CLASS__, 'db_before_save' ], 10, 2 ); add_action( '_wp_put_post_revision', [ __CLASS__, 'save_revision' ] ); add_action( 'wp_creating_autosave', [ __CLASS__, 'update_autosave' ] ); add_action( 'elementor/ajax/register_actions', [ __CLASS__, 'register_ajax_actions' ] ); // Hack to avoid delete the auto-save revision in WP editor. add_filter( 'edit_post_content', [ __CLASS__, 'avoid_delete_auto_save' ], 10, 2 ); add_action( 'edit_form_after_title', [ __CLASS__, 'remove_temp_post_content' ] ); if ( wp_doing_ajax() ) { add_filter( 'elementor/documents/ajax_save/return_data', [ __CLASS__, 'on_ajax_save_builder_data' ], 10, 2 ); } } /** * @since 1.9.0 * @access private * @static */ private static function current_revision_id( $post_id ) { $current_revision_id = $post_id; $autosave = Utils::get_post_autosave( $post_id ); if ( is_object( $autosave ) ) { $current_revision_id = $autosave->ID; } return $current_revision_id; } } history/module.php 0000644 00000002121 15252521347 0010245 0 ustar 00 <?php namespace Elementor\Modules\History; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor history module. * * Elementor history module handler class is responsible for registering and * managing Elementor history modules. * * @since 1.7.0 */ class Module extends BaseModule { /** * Get module name. * * Retrieve the history module name. * * @since 1.7.0 * @access public * * @return string Module name. */ public function get_name() { return 'history'; } /** * @since 2.3.0 * @access public */ public function add_templates() { Plugin::$instance->common->add_template( __DIR__ . '/views/history-panel-template.php' ); Plugin::$instance->common->add_template( __DIR__ . '/views/revisions-panel-template.php' ); } /** * History module constructor. * * Initializing Elementor history module. * * @since 1.7.0 * @access public */ public function __construct() { add_action( 'elementor/editor/init', [ $this, 'add_templates' ] ); } } history/views/history-panel-template.php 0000644 00000003664 15252521347 0014541 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <script type="text/template" id="tmpl-elementor-panel-history-page"> <div id="elementor-panel-elements-navigation" class="elementor-panel-navigation"> <button class="elementor-component-tab elementor-panel-navigation-tab" data-tab="actions"><?php echo esc_html__( 'Actions', 'elementor' ); ?></button> <button class="elementor-component-tab elementor-panel-navigation-tab" data-tab="revisions"><?php echo esc_html__( 'Revisions', 'elementor' ); ?></button> </div> <div id="elementor-panel-history-content"></div> </script> <script type="text/template" id="tmpl-elementor-panel-history-tab"> <div id="elementor-history-list"></div> <div class="elementor-history-revisions-message"><?php echo esc_html__( 'Switch to Revisions tab for older versions', 'elementor' ); ?></div> </script> <script type="text/template" id="tmpl-elementor-panel-history-no-items"> <img class="elementor-nerd-box-icon" src="<?php // PHPCS - Safe Elementor SVG echo ELEMENTOR_ASSETS_URL . 'images/information.svg'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" loading="lazy" alt="<?php echo esc_attr__( 'Elementor', 'elementor' ); ?>" /> <div class="elementor-nerd-box-title"><?php echo esc_html__( 'No History Yet', 'elementor' ); ?></div> <div class="elementor-nerd-box-message"><?php echo esc_html__( 'Once you start working, you\'ll be able to redo / undo any action you make in the editor.', 'elementor' ); ?></div> </script> <script type="text/template" id="tmpl-elementor-panel-history-item"> <div class="elementor-history-item__details"> <span class="elementor-history-item__title">{{{ title }}}</span> <span class="elementor-history-item__subtitle">{{{ subTitle }}}</span> <span class="elementor-history-item__action">{{{ action }}}</span> </div> <div class="elementor-history-item__icon"> <span class="eicon" aria-hidden="true"></span> </div> </script> history/views/revisions-panel-template.php 0000644 00000006745 15252521347 0015064 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <script type="text/template" id="tmpl-elementor-panel-revisions"> <div class="elementor-panel-box"> <div class="elementor-panel-revisions-buttons"> <button class="elementor-button e-btn-txt e-revision-discard" disabled> <?php echo esc_html__( 'Discard', 'elementor' ); ?> </button> <button class="elementor-button e-revision-save" disabled> <?php echo esc_html__( 'Apply', 'elementor' ); ?> </button> </div> </div> <div class="elementor-panel-box"> <div id="elementor-revisions-list" class="elementor-panel-box-content"></div> </div> </script> <script type="text/template" id="tmpl-elementor-panel-revisions-no-revisions"> <# var no_revisions_1 = '<?php echo esc_html__( 'Revision history lets you save your previous versions of your work, and restore them any time.', 'elementor' ); ?>', no_revisions_2 = '<?php echo esc_html__( 'Start designing your page and you will be able to see the entire revision history here.', 'elementor' ); ?>', revisions_disabled_1 = '<?php echo esc_html__( 'It looks like the post revision feature is unavailable in your website.', 'elementor' ); ?>', revisions_disabled_2 = '<?php printf( /* translators: %1$s Link open tag, %2$s: Link close tag. */ esc_html__( 'Learn more about %1$sWordPress revisions%2$s', 'elementor' ), '<a target="_blank" href="https://go.elementor.com/wordpress-revisions/">', '</a>' ); ?>'; #> <img class="elementor-nerd-box-icon" src="<?php // PHPCS - Safe Elementor SVG echo ELEMENTOR_ASSETS_URL . 'images/information.svg' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" loading="lazy" alt="<?php echo esc_attr__( 'Elementor', 'elementor' ); ?>" /> <div class="elementor-nerd-box-title"><?php echo esc_html__( 'No Revisions Saved Yet', 'elementor' ); ?></div> <div class="elementor-nerd-box-message">{{{ elementor.config.document.revisions.enabled ? no_revisions_1 : revisions_disabled_1 }}}</div> <div class="elementor-nerd-box-message">{{{ elementor.config.document.revisions.enabled ? no_revisions_2 : revisions_disabled_2 }}}</div> </script> <script type="text/template" id="tmpl-elementor-panel-revisions-loading"> <i class="eicon-loading eicon-animation-spin" aria-hidden="true"></i> </script> <script type="text/template" id="tmpl-elementor-panel-revisions-revision-item"> <button class="elementor-revision-item__wrapper {{ type }}"> <div class="elementor-revision-item__gravatar">{{{ gravatar }}}</div> <div class="elementor-revision-item__details"> <div class="elementor-revision-date" title="{{{ new Date( timestamp * 1000 ) }}}">{{{ date }}}</div> <div class="elementor-revision-meta"> <span>{{{ typeLabel }}}</span> <?php echo esc_html__( 'By', 'elementor' ); ?> {{{ author }}} <span>(#{{{ id }}})</span> </div> </div> <div class="elementor-revision-item__tools"> <i class="elementor-revision-item__tools-spinner eicon-loading eicon-animation-spin" aria-hidden="true"></i> <# if ( 'current' === type ) { #> <i class="elementor-revision-item__tools-current eicon-check" aria-hidden="true"></i> <span class="elementor-screen-only"><?php echo esc_html__( 'Published', 'elementor' ); ?></span> <# } #> <!-- <# if ( 'revision' === type ) { #>--> <!-- <i class="eicon-undo" aria-hidden="true"></i>--> <!-- <span class="elementor-screen-only">--><?php // echo esc_html__( 'Restore', 'elementor' ); ?><!--</span>--> <!-- <# } #>--> </div> </button> </script> design-system-sync/classes/classes-provider.php 0000644 00000010304 15252521347 0015750 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync\Classes; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; use Elementor\Modules\DesignSystemSync\Module; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; if ( ! defined( 'ABSPATH' ) ) { exit; } class Classes_Provider { private static $cached_classes = null; public static function get_all_classes(): array { if ( null !== self::$cached_classes ) { return self::$cached_classes; } $classes_data = Global_Classes_Repository::make() ->all() ->get(); self::$cached_classes = $classes_data['items'] ?? []; return self::$cached_classes; } public static function get_synced_classes(): array { $synced_ids = Global_Classes_Sync_Map::make()->get_synced_ids(); if ( empty( $synced_ids ) ) { return []; } return Global_Classes_Repository::make()->get_by_ids( $synced_ids ); } public static function clear_cache() { self::$cached_classes = null; } public static function get_default_breakpoint_props( array $variants ): array { $all = self::get_all_normal_state_variant_props( $variants ); return $all[ Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP ] ?? []; } public static function get_all_normal_state_variant_props( array $variants ): array { $result = []; foreach ( $variants as $variant ) { if ( ! isset( $variant['meta'] ) ) { continue; } $meta = $variant['meta']; if ( ! array_key_exists( 'breakpoint', $meta ) || ! array_key_exists( 'state', $meta ) ) { continue; } $state = $meta['state']; if ( ! in_array( $state, [ null, 'normal' ], true ) ) { continue; } $breakpoint = $meta['breakpoint']; $breakpoint_key = ( null === $breakpoint ) ? Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP : $breakpoint; $result[ $breakpoint_key ] = $variant['props'] ?? []; } return $result; } public static function has_typography_props( array $props ): bool { foreach ( Sync_Typography_Props::get_css_props() as $key ) { if ( isset( $props[ $key ] ) ) { return true; } } return false; } public static function get_typography_classes(): array { $synced_classes = self::get_synced_classes(); if ( empty( $synced_classes ) ) { return []; } $typography_classes = []; foreach ( $synced_classes as $id => $class ) { $variants = $class['variants'] ?? []; $default_props = self::get_default_breakpoint_props( $variants ); if ( empty( $default_props ) ) { continue; } if ( ! self::has_typography_props( $default_props ) ) { continue; } $typography_classes[] = [ 'id' => $id, 'label' => $class['label'] ?? '', 'props' => $default_props, 'variants_props' => self::get_all_normal_state_variant_props( $variants ), ]; } return $typography_classes; } public static function get_synced_typography_css_entries(): array { $synced_classes = self::get_synced_classes(); $grouped_entries = []; $schema = Style_Schema::get(); $props_resolver = Render_Props_Resolver::for_styles(); foreach ( $synced_classes as $id => $class ) { $label = sanitize_text_field( $class['label'] ?? '' ); if ( empty( $label ) ) { continue; } $all_variant_props = self::get_all_normal_state_variant_props( $class['variants'] ?? [] ); if ( empty( $all_variant_props ) ) { continue; } $desktop_props = $all_variant_props[ Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP ] ?? []; if ( ! self::has_typography_props( $desktop_props ) ) { continue; } $v3_id = Module::get_v3_sync_id( $label ); foreach ( $all_variant_props as $device => $props ) { if ( empty( $props ) ) { continue; } $resolved_props = $props_resolver->resolve( $schema, $props ); if ( ! isset( $grouped_entries[ $device ] ) ) { $grouped_entries[ $device ] = []; } foreach ( Sync_Typography_Props::get_css_props() as $prop_name ) { if ( empty( $resolved_props[ $prop_name ] ) ) { continue; } $grouped_entries[ $device ][] = "--e-global-typography-{$v3_id}-{$prop_name}:{$resolved_props[ $prop_name ]};"; } } } return $grouped_entries; } } design-system-sync/classes/stylesheet-manager.php 0000644 00000003606 15252521347 0016273 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync\Classes; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Core\Files\Base as Base_File; use Elementor\Plugin; use Elementor\Stylesheet; if ( ! defined( 'ABSPATH' ) ) { exit; } class Stylesheet_Manager extends Base_File { const FILE_NAME = 'design-system-sync.css'; const DEFAULT_FILES_DIR = 'design-system-sync/'; const META_KEY = '_elementor_design_system_sync_css_meta'; public function __construct() { parent::__construct( self::FILE_NAME ); } public function generate(): ?array { $this->update(); if ( ! file_exists( $this->get_path() ) ) { return null; } return [ 'url' => $this->get_url(), 'version' => $this->get_meta( 'time' ), ]; } public function enqueue(): void { if ( ! file_exists( $this->get_path() ) ) { $this->generate(); } if ( ! file_exists( $this->get_path() ) ) { return; } wp_enqueue_style( 'elementor-design-system-sync', $this->get_url(), [], $this->get_meta( 'time' ) ); } protected function parse_content(): string { $stylesheet = new Stylesheet(); $breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints(); foreach ( $breakpoints as $breakpoint_name => $breakpoint ) { $stylesheet->add_device( $breakpoint_name, $breakpoint->get_value() ); } $color_entries = Variables_Provider::get_synced_color_css_entries(); if ( ! empty( $color_entries ) ) { $stylesheet->add_raw_css( ':root { ' . implode( ' ', $color_entries ) . ' }' ); } $typography_entries = Classes_Provider::get_synced_typography_css_entries(); foreach ( $typography_entries as $device => $entries ) { $css = ':root { ' . implode( ' ', $entries ) . ' }'; $device_key = ( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $device ) ? '' : $device; $stylesheet->add_raw_css( $css, $device_key ); } return (string) $stylesheet; } } design-system-sync/classes/global-typography-extension.php 0000644 00000007243 15252521347 0020151 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync\Classes; use Elementor\Controls_Manager; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Core\Kits\Documents\Tabs\Global_Typography; use Elementor\Modules\DesignSystemSync\Controls\V4_Typography_List; use Elementor\Modules\DesignSystemSync\Module; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Global_Typography_Extension { public function register_hooks() { add_action( 'elementor/kit/global-typography/register_controls', [ $this, 'add_v4_classes_section' ] ); add_filter( 'elementor/globals/typography/items', [ $this, 'add_v4_classes_to_typography_selector' ] ); } public function add_v4_classes_section( Global_Typography $tab ) { if ( ! Plugin::$instance->editor->is_edit_mode() ) { return; } $v4_typography_classes = Classes_Provider::get_typography_classes(); if ( empty( $v4_typography_classes ) ) { return; } $tab->add_control( 'heading_v4_typography_classes', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Atomic Classes', 'elementor' ), 'separator' => 'before', ] ); $tab->add_control( 'heading_v4_typography_classes_description', [ 'type' => Controls_Manager::RAW_HTML, 'raw' => esc_html__( 'V4 classes can only be edited when applied on an atom.', 'elementor' ), 'content_classes' => 'elementor-descriptor', ] ); $items = []; foreach ( $v4_typography_classes as $class ) { $label = sanitize_text_field( $class['label'] ?? '' ); if ( empty( $label ) ) { continue; } $items[] = [ 'title' => $label ]; } $tab->add_control( 'v4_typography_classes_display', [ 'type' => V4_Typography_List::TYPE, 'items' => $items, ] ); } public function add_v4_classes_to_typography_selector( array $items ): array { $v4_classes = Classes_Provider::get_typography_classes(); if ( empty( $v4_classes ) ) { return $items; } $v4_items = []; foreach ( $v4_classes as $class ) { $label = sanitize_text_field( $class['label'] ?? '' ); if ( empty( $label ) ) { continue; } $id = Module::get_v3_sync_id( $label ); $props = $class['props'] ?? []; if ( empty( $props ) ) { continue; } $value = $this->convert_v4_props_to_v3_format( $props ); $variants_props = $class['variants_props'] ?? []; foreach ( $variants_props as $breakpoint => $bp_props ) { if ( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $breakpoint ) { continue; } if ( empty( $bp_props ) ) { continue; } $bp_values = $this->convert_v4_props_to_v3_format( $bp_props ); foreach ( $bp_values as $key => $val ) { if ( $this->is_responsive_prop( $key ) ) { $value[ $key . '_' . $breakpoint ] = $val; } } } $v4_items[ $id ] = [ 'id' => $id, 'title' => $label, 'value' => $value, 'group' => 'v4', ]; } return array_merge( $v4_items, $items ); } private function is_responsive_prop( string $v3_key ): bool { return in_array( $v3_key, Sync_Typography_Props::RESPONSIVE_V3_PROPS, true ); } private function convert_v4_props_to_v3_format( array $v4_props ): array { $v3_format = []; foreach ( Sync_Typography_Props::PROP_MAP as $v4_prop => $v3_prop ) { if ( ! isset( $v4_props[ $v4_prop ] ) || empty( $v4_props[ $v4_prop ] ) ) { continue; } $v3_format[ $v3_prop ] = $this->extract_v4_prop_value( $v4_props[ $v4_prop ] ); } if ( ! empty( $v3_format ) ) { $v3_format['typography_typography'] = 'custom'; } return $v3_format; } private function extract_v4_prop_value( $prop ) { if ( ! empty( $prop['value'] ) ) { return $prop['value']; } return $prop; } } design-system-sync/classes/sync-typography-props.php 0000644 00000001467 15252521347 0017016 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; } class Sync_Typography_Props { const PROP_MAP = [ 'font-family' => 'typography_font_family', 'font-size' => 'typography_font_size', 'font-weight' => 'typography_font_weight', 'font-style' => 'typography_font_style', 'text-decoration' => 'typography_text_decoration', 'line-height' => 'typography_line_height', 'letter-spacing' => 'typography_letter_spacing', 'word-spacing' => 'typography_word_spacing', 'text-transform' => 'typography_text_transform', ]; const RESPONSIVE_V3_PROPS = [ 'typography_font_size', 'typography_line_height', 'typography_letter_spacing', 'typography_word_spacing', ]; public static function get_css_props(): array { return array_keys( self::PROP_MAP ); } } design-system-sync/classes/global-classes-sync-map.php 0000644 00000003320 15252521347 0017103 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync\Classes; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\GlobalClasses\Concerns\Has_Kit_Dependency; if ( ! defined( 'ABSPATH' ) ) { exit; } class Global_Classes_Sync_Map { use Has_Kit_Dependency; const META_KEY = '_elementor_global_classes_sync_to_v3'; private ?array $cache = null; private function __construct() { } public static function make( ?Kit $kit = null ): self { $instance = new self(); if ( ! $kit ) { return $instance; } return $instance->set_kit( $kit ); } public function get_synced_ids(): array { return array_keys( $this->read_stored() ); } public function is_synced( string $id ): bool { return isset( $this->read_stored()[ $id ] ); } public function set_map( array $id_to_true ): bool { $kit = $this->get_kit(); if ( ! $kit ) { return false; } $result = $kit->update_meta( self::META_KEY, $id_to_true ); $this->cache = $id_to_true; return false !== $result; } public function apply_changes( array $touched_items, array $deleted_ids ): bool { $current = $this->read_stored(); foreach ( $deleted_ids as $id ) { unset( $current[ $id ] ); } foreach ( $touched_items as $id => $item ) { if ( ! empty( $item['sync_to_v3'] ) && (bool) $item['sync_to_v3'] ) { $current[ $id ] = true; } else { unset( $current[ $id ] ); } } return $this->set_map( $current ); } private function read_stored(): array { if ( null !== $this->cache ) { return $this->cache; } $kit = $this->get_kit(); if ( ! $kit ) { $this->cache = []; return []; } $raw = $kit->get_meta( self::META_KEY ); $this->cache = is_array( $raw ) ? $raw : []; return $this->cache; } } design-system-sync/classes/controller.php 0000644 00000002373 15252521347 0014655 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync\Classes; use Exception; use WP_REST_Server; use WP_REST_Response; if ( ! defined( 'ABSPATH' ) ) { exit; } class Controller { const API_NAMESPACE = 'elementor/v1'; const API_BASE = 'design-system-sync'; const HTTP_CREATED = 201; const HTTP_NO_CONTENT = 204; const HTTP_INTERNAL_SERVER_ERROR = 500; public function register_hooks() { add_action( 'rest_api_init', [ $this, 'register_routes' ] ); } public function register_routes() { register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/stylesheet', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'generate' ], 'permission_callback' => [ $this, 'has_permission' ], ] ); } public function generate(): WP_REST_Response { try { $stylesheet = new Stylesheet_Manager(); $result = $stylesheet->generate(); if ( null === $result ) { return new WP_REST_Response( null, self::HTTP_NO_CONTENT ); } return new WP_REST_Response( $result, self::HTTP_CREATED ); } catch ( Exception $e ) { return new WP_REST_Response( [ 'message' => $e->getMessage() ], self::HTTP_INTERNAL_SERVER_ERROR ); } } public function has_permission(): bool { return current_user_can( 'edit_posts' ); } } design-system-sync/classes/variables-provider.php 0000644 00000003636 15252521347 0016275 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync\Classes; use Elementor\Modules\DesignSystemSync\Module; use Elementor\Modules\Variables\Services\Batch_Operations\Batch_Processor; use Elementor\Modules\Variables\Services\Variables_Service; use Elementor\Modules\Variables\Storage\Variables_Repository; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Variables_Provider { private static $cached_variables = null; public static function get_all_variables(): array { if ( null !== self::$cached_variables ) { return self::$cached_variables; } $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return []; } $repository = new Variables_Repository( $kit ); $service = new Variables_Service( $repository, new Batch_Processor() ); self::$cached_variables = $service->get_variables_list(); return self::$cached_variables; } public static function get_synced_color_variables(): array { $all_variables = self::get_all_variables(); $color_variables = []; foreach ( $all_variables as $id => $variable ) { if ( isset( $variable['deleted'] ) && $variable['deleted'] ) { continue; } if ( empty( $variable['type'] ) || 'global-color-variable' !== $variable['type'] ) { continue; } if ( empty( $variable['sync_to_v3'] ) ) { continue; } $color_variables[ $id ] = $variable; } return $color_variables; } public static function clear_cache() { self::$cached_variables = null; } public static function get_synced_color_css_entries(): array { $synced_variables = self::get_synced_color_variables(); $css_entries = []; foreach ( $synced_variables as $id => $variable ) { $label = sanitize_text_field( $variable['label'] ?? '' ); if ( empty( $label ) ) { continue; } $v3_id = Module::get_v3_sync_id( $label ); $css_entries[] = "--e-global-color-{$v3_id}:var(--{$label});"; } return $css_entries; } } design-system-sync/classes/global-colors-extension.php 0000644 00000005045 15252521347 0017242 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync\Classes; use Elementor\Controls_Manager; use Elementor\Core\Kits\Documents\Tabs\Global_Colors; use Elementor\Modules\DesignSystemSync\Controls\V4_Color_Variable_List; use Elementor\Modules\DesignSystemSync\Module; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Global_Colors_Extension { public function register_hooks() { add_action( 'elementor/kit/global-colors/register_controls', [ $this, 'add_v4_variables_section' ] ); add_filter( 'elementor/globals/colors/items', [ $this, 'add_v4_variables_section_to_color_selector' ] ); } public function add_v4_variables_section( Global_Colors $tab ) { if ( ! Plugin::$instance->editor->is_edit_mode() ) { return; } $v4_colors = $this->get_v4_color_variables(); if ( empty( $v4_colors ) ) { return; } $items = []; foreach ( $v4_colors as $variable ) { $items[] = [ '_id' => $variable['id'], 'title' => $variable['label'], 'color' => strtoupper( $variable['value'] ), ]; } $tab->add_control( 'heading_v4_variables', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Atomic Variables', 'elementor' ), 'separator' => 'before', ] ); $tab->add_control( 'v4_color_variables_display', [ 'type' => V4_Color_Variable_List::TYPE, 'items' => $items, ] ); } public function add_v4_variables_section_to_color_selector( array $items ): array { $v4_colors = $this->get_v4_color_variables(); if ( empty( $v4_colors ) ) { return $items; } foreach ( $v4_colors as $color ) { $label = sanitize_text_field( $color['label'] ?? '' ); if ( empty( $label ) ) { continue; } $id = Module::get_v3_sync_id( $label ); $items[ $id ] = [ 'id' => $id, 'title' => $label, 'value' => strtoupper( $color['value'] ), 'group' => 'v4', ]; } return $items; } private function get_v4_color_variables(): array { $synced_variables = Variables_Provider::get_synced_color_variables(); if ( empty( $synced_variables ) ) { return []; } $color_variables = []; foreach ( $synced_variables as $id => $variable ) { $value = $variable['value'] ?? ''; if ( is_array( $value ) && isset( $value['value'] ) ) { $value = $value['value']; } $color_variables[] = [ 'id' => $id, 'label' => $variable['label'] ?? '', 'value' => $value, 'order' => $variable['order'] ?? 0, ]; } usort( $color_variables, function( $a, $b ) { return $a['order'] <=> $b['order']; } ); return $color_variables; } } design-system-sync/module.php 0000644 00000004214 15252521347 0012316 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync; use Elementor\Core\Base\Module as BaseModule; use Elementor\Modules\DesignSystemSync\Classes\Stylesheet_Manager; use Elementor\Modules\DesignSystemSync\Classes\Controller; if ( ! defined( 'ABSPATH' ) ) { exit; } class Module extends BaseModule { const MODULE_NAME = 'design-system-sync'; public static function get_v3_sync_id( string $label ): string { return 'v4-' . strtolower( $label ); } public function get_name() { return self::MODULE_NAME; } public function __construct() { parent::__construct(); $this->register_hooks(); } private function register_hooks() { ( new Classes\Global_Colors_Extension() )->register_hooks(); ( new Classes\Global_Typography_Extension() )->register_hooks(); ( new Controller() )->register_hooks(); add_action( 'elementor/controls/register', [ $this, 'register_controls' ] ); add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_editor_scripts' ] ); add_action( 'elementor/editor/after_enqueue_styles', [ $this, 'enqueue_editor_styles' ] ); add_action( 'elementor/global_classes/update', [ $this, 'clear_classes_cache' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_sync_stylesheet' ] ); } public function register_controls( $controls_manager ) { require_once __DIR__ . '/controls/v4-color-variable-list.php'; require_once __DIR__ . '/controls/v4-typography-list.php'; $controls_manager->register( new Controls\V4_Color_Variable_List() ); $controls_manager->register( new Controls\V4_Typography_List() ); } public function enqueue_editor_scripts() { wp_enqueue_script( 'elementor-design-system-sync-editor', $this->get_js_assets_url( 'design-system-sync' ), [], ELEMENTOR_VERSION, true ); } public function enqueue_editor_styles() { wp_enqueue_style( 'elementor-design-system-sync-editor', $this->get_css_assets_url( 'modules/design-system-sync/design-system-sync' ), [], ELEMENTOR_VERSION ); } public function clear_classes_cache() { Classes\Classes_Provider::clear_cache(); } public function enqueue_sync_stylesheet() { ( new Stylesheet_Manager() )->enqueue(); } } design-system-sync/controls/v4-color-variable-list.php 0000644 00000003345 15252521347 0017101 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync\Controls; use Elementor\Base_UI_Control; if ( ! defined( 'ABSPATH' ) ) { exit; } class V4_Color_Variable_List extends Base_UI_Control { const TYPE = 'v4_color_variable_list'; public function get_type() { return self::TYPE; } public function content_template() { ?> <label> <span class="elementor-control-title">{{{ data.label }}}</span> </label> <div class="elementor-repeater-fields-wrapper" role="list"> <# _.each( data.items, function( item ) { var title = item[ data.title_field ] || ''; var color = item[ data.color_field ] || ''; #> <div class="elementor-repeater-fields" role="listitem"> <div class="elementor-repeater-row-controls e-v4-color-variable-list__row"> <span class="elementor-control-title e-v4-color-variable-list__title">{{{ title }}}</span> <span class="e-v4-color-variable-list__edit-btn-wrapper"> <button class="e-v4-color-variable-list__edit-btn" onclick="window.dispatchEvent( new CustomEvent( 'elementor/open-variables-manager' ) )"> <i class="eicon-pencil" aria-hidden="true"></i> </button> <span class="e-v4-color-variable-list__tooltip"><?php echo esc_html__( 'Edit in Variables manager', 'elementor' ); ?></span> </span> <span class="e-v4-color-variable-list__color-value">{{ color }}</span> <span class="e-v4-color-variable-list__color-swatch" style="background-color:{{ color }}; box-shadow: inset 0 0 0 7px var(--e-a-bg-default), 0 0 0 1px var(--e-a-border-color);"></span> </div> </div> <# } ); #> </div> <?php } protected function get_default_settings() { return [ 'items' => [], 'title_field' => 'title', 'color_field' => 'color', ]; } } design-system-sync/controls/v4-typography-list.php 0000644 00000002122 15252521347 0016376 0 ustar 00 <?php namespace Elementor\Modules\DesignSystemSync\Controls; use Elementor\Base_UI_Control; if ( ! defined( 'ABSPATH' ) ) { exit; } class V4_Typography_List extends Base_UI_Control { const TYPE = 'v4_typography_list'; public function get_type() { return self::TYPE; } public function content_template() { ?> <label> <span class="elementor-control-title">{{{ data.label }}}</span> </label> <div class="elementor-repeater-fields-wrapper" role="list"> <# _.each( data.items, function( item ) { var title = item[ data.title_field ] || ''; #> <div class="elementor-repeater-fields" role="listitem"> <div class="elementor-repeater-row-controls e-v4-typography-list__row"> <span class="elementor-control-title e-v4-typography-list__title">{{{ title }}}</span> <button class="e-v4-typography-list__edit-btn" disabled> <i class="eicon-edit" aria-hidden="true"></i> </button> </div> </div> <# } ); #> </div> <?php } protected function get_default_settings() { return [ 'items' => [], 'title_field' => 'title', ]; } } gutenberg/module.php 0000644 00000014062 15252521347 0010535 0 ustar 00 <?php namespace Elementor\Modules\Gutenberg; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Plugin; use Elementor\User; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { protected $is_gutenberg_editor_active = false; /** * @since 2.1.0 * @access public */ public function get_name() { return 'gutenberg'; } /** * @since 2.1.0 * @access public * @static */ public static function is_active() { return function_exists( 'register_block_type' ); } /** * @since 2.1.0 * @access public */ public function register_elementor_rest_field() { register_rest_field( get_post_types( '', 'names' ), 'gutenberg_elementor_mode', [ 'update_callback' => function( $request_value, $obj ) { if ( ! User::is_current_user_can_edit( $obj->ID ) ) { return false; } $document = Plugin::$instance->documents->get( $obj->ID ); if ( ! $document ) { return false; } $document->set_is_built_with_elementor( false ); return true; }, ] ); } /** * @since 2.1.0 * @access public */ public function enqueue_assets() { $document = Plugin::$instance->documents->get( get_the_ID() ); if ( ! $document || ! $document->is_editable_by_current_user() ) { return; } $this->is_gutenberg_editor_active = true; $suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_script( 'elementor-gutenberg', ELEMENTOR_ASSETS_URL . 'js/gutenberg' . $suffix . '.js', [ 'jquery' ], ELEMENTOR_VERSION, true ); $elementor_settings = [ 'isElementorMode' => $document->is_built_with_elementor(), 'editLink' => $document->get_edit_url(), ]; Utils::print_js_config( 'elementor-gutenberg', 'ElementorGutenbergSettings', $elementor_settings ); } /** * @since 2.1.0 * @access public */ public function print_admin_js_template() { if ( ! $this->is_gutenberg_editor_active ) { return; } ?> <script id="elementor-gutenberg-button-switch-mode" type="text/html"> <div id="elementor-switch-mode"> <button id="elementor-switch-mode-button" type="button" class="button button-primary button-large"> <span class="elementor-switch-mode-on"><?php echo esc_html__( '← Back to WordPress Editor', 'elementor' ); ?></span> <span class="elementor-switch-mode-off"> <i class="eicon-elementor-square" aria-hidden="true"></i> <?php echo esc_html__( 'Edit with Elementor', 'elementor' ); ?> </span> </button> </div> </script> <script id="elementor-gutenberg-panel" type="text/html"> <div id="elementor-editor"> <div id="elementor-go-to-edit-page-link"> <button id="elementor-editor-button" class="button button-primary button-hero"> <i class="eicon-elementor-square" aria-hidden="true"></i> <?php echo esc_html__( 'Edit with Elementor', 'elementor' ); ?> </button> <div class="elementor-loader-wrapper"> <div class="elementor-loader"> <div class="elementor-loader-boxes"> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> <div class="elementor-loader-box"></div> </div> </div> <div class="elementor-loading-title"><?php echo esc_html__( 'Loading', 'elementor' ); ?></div> </div> </div> </div> </script> <script id="elementor-gutenberg-button-tmpl" type="text/html"> <div id="elementor-edit-button-gutenberg"> <button id="elementor-edit-mode-button" type="button" class="button button-primary button-large"> <span class="elementor-edit-mode-gutenberg"> <i class="eicon-elementor-square" aria-hidden="true"></i> <?php echo esc_html__( 'Edit with Elementor', 'elementor' ); ?> </span> </button> </div> </script> <?php } /** * @since 2.1.0 * @access public */ public function __construct() { add_action( 'rest_api_init', [ $this, 'register_elementor_rest_field' ] ); add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_assets' ] ); add_action( 'admin_footer', [ $this, 'print_admin_js_template' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'dequeue_assets' ], 999 ); } public function dequeue_assets() { if ( ! static::is_optimized_gutenberg_loading_enabled() ) { return; } if ( ! static::should_dequeue_gutenberg_assets() ) { return; } wp_dequeue_style( 'wp-block-library' ); wp_dequeue_style( 'wp-block-library-theme' ); wp_dequeue_style( 'wc-block-style' ); wp_dequeue_style( 'wc-blocks-style' ); } /** * Check whether the "Optimized Gutenberg Loading" settings is enabled. * * The 'elementor_optimized_gutenberg_loading' option can be enabled/disabled from the Elementor settings. * For BC, when the option has not been saved in the database, the default '1' value is returned. * * @since 3.21.0 * @access private */ private static function is_optimized_gutenberg_loading_enabled(): bool { return (bool) get_option( 'elementor_optimized_gutenberg_loading', '1' ); } private static function should_dequeue_gutenberg_assets(): bool { $post = get_post(); if ( empty( $post->ID ) ) { return false; } if ( ! static::is_built_with_elementor( $post ) ) { return false; } if ( static::is_gutenberg_in_post( $post ) ) { return false; } return true; } private static function is_built_with_elementor( $post ): bool { $document = Plugin::$instance->documents->get( $post->ID ); if ( ! $document || ! $document->is_built_with_elementor() ) { return false; } return true; } private static function is_gutenberg_in_post( $post ): bool { if ( has_blocks( $post ) ) { return true; } if ( static::current_theme_is_fse_theme() ) { return true; } return false; } private static function current_theme_is_fse_theme(): bool { if ( function_exists( 'wp_is_block_theme' ) ) { return (bool) wp_is_block_theme(); } if ( function_exists( 'gutenberg_is_fse_theme' ) ) { return (bool) gutenberg_is_fse_theme(); } return false; } } kit-elements-defaults/usage.php 0000644 00000001365 15252521347 0012602 0 ustar 00 <?php namespace Elementor\Modules\KitElementsDefaults; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Usage { public function register() { add_filter( 'elementor/tracker/send_tracking_data_params', function ( array $params ) { $params['usages']['kit']['defaults'] = $this->get_usage_data(); return $params; } ); } private function get_usage_data() { $elements_defaults = $this->get_elements_defaults() ?? []; return [ 'count' => count( $elements_defaults ), 'elements' => array_keys( $elements_defaults ), ]; } private function get_elements_defaults() { $kit = Plugin::$instance->kits_manager->get_active_kit(); return $kit->get_json_meta( Module::META_KEY ); } } kit-elements-defaults/utils/settings-sanitizer.php 0000644 00000013065 15252521347 0016504 0 ustar 00 <?php namespace Elementor\Modules\KitElementsDefaults\Utils; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Element_Base; use Elementor\Elements_Manager; use Elementor\Core\Base\Document; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Settings_Sanitizer { const SPECIAL_SETTINGS = [ '__dynamic__', '__globals__', ]; /** * @var Elements_Manager */ private $elements_manager; /** * @var array */ private $widget_types; /** * @var Element_Base | null */ private $pending_element = null; /** * @var array | null */ private $pending_settings = null; /** * @param Elements_Manager $elements_manager * @param array $widget_types */ public function __construct( Elements_Manager $elements_manager, array $widget_types = [] ) { $this->elements_manager = $elements_manager; $this->widget_types = $widget_types; } /** * @param $type * * @return $this */ public function for( $type ) { $this->pending_element = $this->create_element( $type ); return $this; } /** * @param $settings * * @return $this */ public function using( $settings ) { $this->pending_settings = $settings; return $this; } /** * @return $this */ public function reset() { $this->pending_element = null; $this->pending_settings = null; return $this; } /** * @return bool */ public function is_prepared() { return $this->pending_element && is_array( $this->pending_settings ); } /** * @return $this */ public function remove_invalid_settings() { if ( ! $this->is_prepared() ) { return $this; } $valid_settings_keys = $this->get_valid_settings_keys( $this->pending_element->get_controls() ); $this->pending_settings = $this->filter_invalid_settings( $this->pending_settings, array_merge( $valid_settings_keys, self::SPECIAL_SETTINGS ) ); foreach ( self::SPECIAL_SETTINGS as $special_setting ) { if ( ! isset( $this->pending_settings[ $special_setting ] ) ) { continue; } $this->pending_settings[ $special_setting ] = $this->filter_invalid_settings( $this->pending_settings[ $special_setting ], $valid_settings_keys ); } return $this; } public function kses_deep() { if ( ! $this->is_prepared() ) { return $this; } $this->pending_settings = map_deep( $this->pending_settings, function( $value ) { if ( ! is_string( $value ) ) { return $value; } return wp_kses_post( $value ); } ); return $this; } /** * @param Document $document * * @return $this */ public function prepare_for_export( Document $document ) { return $this->run_import_export_sanitize_process( $document, 'on_export' ); } /** * @param Document $document * * @return $this */ public function prepare_for_import( Document $document ) { return $this->run_import_export_sanitize_process( $document, 'on_import' ); } /** * @return array */ public function get() { if ( ! $this->is_prepared() ) { return []; } $settings = $this->pending_settings; $this->reset(); return $settings; } /** * @param string $type * * @return Element_Base|null */ private function create_element( $type ) { $is_widget = in_array( $type, $this->widget_types, true ); $is_inner_section = 'inner-section' === $type; if ( $is_inner_section ) { return $this->elements_manager->create_element_instance( [ 'elType' => 'section', 'isInner' => true, 'id' => '0', ] ); } if ( $is_widget ) { return $this->elements_manager->create_element_instance( [ 'elType' => 'widget', 'widgetType' => $type, 'id' => '0', ] ); } return $this->elements_manager->create_element_instance( [ 'elType' => $type, 'id' => '0', ] ); } /** * @param Document $document * @param $process_type * * @return $this */ private function run_import_export_sanitize_process( Document $document, $process_type ) { if ( ! $this->is_prepared() ) { return $this; } $result = $document->process_element_import_export( $this->pending_element, $process_type, [ 'settings' => $this->pending_settings ] ); if ( empty( $result['settings'] ) ) { return $this; } $this->pending_settings = $result['settings']; return $this; } /** * Get all the available settings of a specific element, including responsive settings. * * @param array $controls * * @return array */ private function get_valid_settings_keys( $controls ) { if ( ! $controls ) { return []; } $control_keys = array_keys( $controls ); $optional_responsive_keys = [ Breakpoints_Manager::BREAKPOINT_KEY_MOBILE, Breakpoints_Manager::BREAKPOINT_KEY_MOBILE_EXTRA, Breakpoints_Manager::BREAKPOINT_KEY_TABLET, Breakpoints_Manager::BREAKPOINT_KEY_TABLET_EXTRA, Breakpoints_Manager::BREAKPOINT_KEY_LAPTOP, Breakpoints_Manager::BREAKPOINT_KEY_WIDESCREEN, ]; $settings = []; foreach ( $control_keys as $control_key ) { // Add the responsive settings. foreach ( $optional_responsive_keys as $responsive_key ) { $settings[] = "{$control_key}_{$responsive_key}"; } // Add the setting itself (not responsive). $settings[] = $control_key; } return $settings; } /** * Remove invalid settings. * * @param $settings * @param $valid_settings_keys * * @return array */ private function filter_invalid_settings( $settings, $valid_settings_keys ) { return array_filter( $settings, function ( $setting_key ) use ( $valid_settings_keys ) { return in_array( $setting_key, $valid_settings_keys, true ); }, ARRAY_FILTER_USE_KEY ); } } kit-elements-defaults/module.php 0000644 00000002663 15252521347 0012765 0 ustar 00 <?php namespace Elementor\Modules\KitElementsDefaults; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Core\Base\Module as BaseModule; use Elementor\Modules\KitElementsDefaults\Data\Controller; use Elementor\Plugin; use Elementor\Modules\KitElementsDefaults\ImportExport\Import_Export; use Elementor\Modules\KitElementsDefaults\ImportExportCustomization\Import_Export_Customization; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const META_KEY = '_elementor_elements_default_values'; public function get_name() { return 'kit-elements-defaults'; } private function enqueue_scripts() { wp_enqueue_script( 'elementor-kit-elements-defaults-editor', $this->get_js_assets_url( 'kit-elements-defaults-editor' ), [ 'elementor-common', 'elementor-editor-modules', 'elementor-editor-document', 'wp-i18n', ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-kit-elements-defaults-editor', 'elementor' ); } public function __construct() { parent::__construct(); add_action( 'elementor/editor/before_enqueue_scripts', function () { $this->enqueue_scripts(); } ); Plugin::$instance->data_manager_v2->register_controller( new Controller() ); ( new Usage() )->register(); if ( is_admin() ) { ( new Import_Export() )->register(); ( new Import_Export_Customization() )->register(); } } } kit-elements-defaults/import-export/runners/import.php 0000644 00000004145 15252521347 0017334 0 ustar 00 <?php namespace Elementor\Modules\KitElementsDefaults\ImportExport\Runners; use Elementor\Modules\KitElementsDefaults\ImportExport\Import_Export; use Elementor\Plugin; use Elementor\Core\Utils\Collection; use Elementor\Modules\KitElementsDefaults\Module; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer; use Elementor\App\Modules\ImportExport\Runners\Import\Import_Runner_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Import extends Import_Runner_Base { public static function get_name(): string { return 'elements-default-values'; } public function should_import( array $data ) { // Together with site-settings. return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) && ! empty( $data['site_settings']['settings'] ) && ! empty( $data['extracted_directory_path'] ) ); } public function import( array $data, array $imported_data ) { $kit = Plugin::$instance->kits_manager->get_active_kit(); $file_name = Import_Export::FILE_NAME; $default_values = ImportExportUtils::read_json_file( "{$data['extracted_directory_path']}/{$file_name}.json" ); if ( ! $kit || ! $default_values ) { return []; } $element_types = array_keys( Plugin::$instance->elements_manager->get_element_types() ); $widget_types = array_keys( Plugin::$instance->widgets_manager->get_widget_types() ); $types = array_merge( $element_types, $widget_types ); $sanitizer = new Settings_Sanitizer( Plugin::$instance->elements_manager, $widget_types ); $default_values = ( new Collection( $default_values ) ) ->filter( function ( $settings, $type ) use ( $types ) { return in_array( $type, $types, true ); } ) ->map( function ( $settings, $type ) use ( $sanitizer, $kit ) { return $sanitizer ->for( $type ) ->using( $settings ) ->remove_invalid_settings() ->kses_deep() ->prepare_for_import( $kit ) ->get(); } ) ->all(); $kit->update_json_meta( Module::META_KEY, $default_values ); return $default_values; } } kit-elements-defaults/import-export/runners/export.php 0000644 00000003251 15252521347 0017340 0 ustar 00 <?php namespace Elementor\Modules\KitElementsDefaults\ImportExport\Runners; use Elementor\Modules\KitElementsDefaults\ImportExport\Import_Export; use Elementor\Plugin; use Elementor\Core\Utils\Collection; use Elementor\Modules\KitElementsDefaults\Module; use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer; use Elementor\App\Modules\ImportExport\Runners\Export\Export_Runner_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Export extends Export_Runner_Base { public static function get_name(): string { return 'elements-default-values'; } public function should_export( array $data ) { // Together with site-settings. return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) ); } public function export( array $data ) { $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return [ 'manifest' => [], 'files' => [], ]; } $default_values = $kit->get_json_meta( Module::META_KEY ); if ( ! $default_values ) { return [ 'manifest' => [], 'files' => [], ]; } $sanitizer = new Settings_Sanitizer( Plugin::$instance->elements_manager, array_keys( Plugin::$instance->widgets_manager->get_widget_types() ) ); $default_values = ( new Collection( $default_values ) ) ->map( function ( $settings, $type ) use ( $sanitizer, $kit ) { return $sanitizer ->for( $type ) ->using( $settings ) ->remove_invalid_settings() ->kses_deep() ->prepare_for_export( $kit ) ->get(); } ) ->all(); return [ 'files' => [ 'path' => Import_Export::FILE_NAME, 'data' => $default_values, ], ]; } } kit-elements-defaults/import-export/import-export.php 0000644 00000001553 15252521347 0017157 0 ustar 00 <?php namespace Elementor\Modules\KitElementsDefaults\ImportExport; use Elementor\App\Modules\ImportExport\Processes\Export; use Elementor\App\Modules\ImportExport\Processes\Import; use Elementor\Modules\KitElementsDefaults\ImportExport\Runners\Export as Export_Runner; use Elementor\Modules\KitElementsDefaults\ImportExport\Runners\Import as Import_Runner; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Import_Export { const FILE_NAME = 'kit-elements-defaults'; public function register() { // Revert kit is working by default, using the site-settings runner. add_action( 'elementor/import-export/export-kit', function ( Export $export ) { $export->register( new Export_Runner() ); } ); add_action( 'elementor/import-export/import-kit', function ( Import $import ) { $import->register( new Import_Runner() ); } ); } } kit-elements-defaults/data/controller.php 0000644 00000007756 15252521347 0014604 0 ustar 00 <?php namespace Elementor\Modules\KitElementsDefaults\Data; use Elementor\Core\Frontend\Performance; use Elementor\Modules\KitElementsDefaults\Module; use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer; use Elementor\Plugin; use Elementor\Data\V2\Base\Exceptions\Error_404; use Elementor\Data\V2\Base\Controller as Base_Controller; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Base_Controller { public function get_name() { return 'kit-elements-defaults'; } public function register_endpoints() { $this->index_endpoint->register_item_route(\WP_REST_Server::EDITABLE, [ 'id_arg_name' => 'type', 'id_arg_type_regex' => '[\w\-\_]+', 'type' => [ 'type' => 'string', 'description' => 'The type of the element.', 'required' => true, 'validate_callback' => function( $type ) { return $this->validate_type( $type ); }, ], 'settings' => [ 'description' => 'All the default values for the requested type', 'required' => true, 'type' => 'object', 'validate_callback' => function( $settings ) { return is_array( $settings ); }, 'sanitize_callback' => function( $settings, \WP_REST_Request $request ) { Performance::set_use_style_controls( true ); $sanitizer = new Settings_Sanitizer( Plugin::$instance->elements_manager, array_keys( Plugin::$instance->widgets_manager->get_widget_types() ) ); $sanitized_data = $sanitizer ->for( $request->get_param( 'type' ) ) ->using( $settings ) ->remove_invalid_settings() ->kses_deep() ->get(); Performance::set_use_style_controls( false ); return $sanitized_data; }, ], ] ); $this->index_endpoint->register_item_route(\WP_REST_Server::DELETABLE, [ 'id_arg_name' => 'type', 'id_arg_type_regex' => '[\w\-\_]+', 'type' => [ 'type' => 'string', 'description' => 'The type of the element.', 'required' => true, 'validate_callback' => function( $type ) { return $this->validate_type( $type ); }, ], ] ); } public function get_collection_params() { return []; } public function get_items( $request ) { $this->validate_kit(); $kit = Plugin::$instance->kits_manager->get_active_kit(); return (object) $kit->get_json_meta( Module::META_KEY ); } public function update_item( $request ) { $this->validate_kit(); $kit = Plugin::$instance->kits_manager->get_active_kit(); $data = $kit->get_json_meta( Module::META_KEY ); $data[ $request->get_param( 'type' ) ] = $request->get_param( 'settings' ); $kit->update_json_meta( Module::META_KEY, $data ); return (object) []; } public function delete_item( $request ) { $this->validate_kit(); $kit = Plugin::$instance->kits_manager->get_active_kit(); $data = $kit->get_json_meta( Module::META_KEY ); unset( $data[ $request->get_param( 'type' ) ] ); $kit->update_json_meta( Module::META_KEY, $data ); return (object) []; } private function validate_kit() { $kit = Plugin::$instance->kits_manager->get_active_kit(); $is_valid_kit = $kit && $kit->get_main_id(); if ( ! $is_valid_kit ) { throw new Error_404( 'Kit doesn\'t exist.' ); } } private function validate_type( $param ) { $element_types = array_keys( Plugin::$instance->elements_manager->get_element_types() ); $widget_types = array_keys( Plugin::$instance->widgets_manager->get_widget_types() ); return in_array( $param, array_merge( $element_types, $widget_types ), true ); } public function get_items_permissions_check( $request ) { return current_user_can( 'edit_posts' ); } /** * TODO: Should be removed once the infra will support it. */ public function get_item_permissions_check( $request ) { return $this->get_items_permissions_check( $request ); } public function update_item_permissions_check( $request ) { return current_user_can( 'manage_options' ); } public function delete_item_permissions_check( $request ) { return current_user_can( 'manage_options' ); } } kit-elements-defaults/import-export-customization/runners/import.php 0000644 00000004265 15252521347 0022245 0 ustar 00 <?php namespace Elementor\Modules\KitElementsDefaults\ImportExportCustomization\Runners; use Elementor\Modules\KitElementsDefaults\ImportExportCustomization\Import_Export_Customization; use Elementor\Plugin; use Elementor\Core\Utils\Collection; use Elementor\Modules\KitElementsDefaults\Module; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Import_Runner_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Import extends Import_Runner_Base { public static function get_name(): string { return 'elements-default-values'; } public function should_import( array $data ) { // Together with site-settings. return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) && ! empty( $data['site_settings']['settings'] ) && ! empty( $data['extracted_directory_path'] ) ); } public function import( array $data, array $imported_data ) { $kit = Plugin::$instance->kits_manager->get_active_kit(); $file_name = Import_Export_Customization::FILE_NAME; $default_values = ImportExportUtils::read_json_file( "{$data['extracted_directory_path']}/{$file_name}.json" ); if ( ! $kit || ! $default_values ) { return []; } $element_types = array_keys( Plugin::$instance->elements_manager->get_element_types() ); $widget_types = array_keys( Plugin::$instance->widgets_manager->get_widget_types() ); $types = array_merge( $element_types, $widget_types ); $sanitizer = new Settings_Sanitizer( Plugin::$instance->elements_manager, $widget_types ); $default_values = ( new Collection( $default_values ) ) ->filter( function ( $settings, $type ) use ( $types ) { return in_array( $type, $types, true ); } ) ->map( function ( $settings, $type ) use ( $sanitizer, $kit ) { return $sanitizer ->for( $type ) ->using( $settings ) ->remove_invalid_settings() ->kses_deep() ->prepare_for_import( $kit ) ->get(); } ) ->all(); $kit->update_json_meta( Module::META_KEY, $default_values ); return $default_values; } } kit-elements-defaults/import-export-customization/runners/export.php 0000644 00000003401 15252521347 0022243 0 ustar 00 <?php namespace Elementor\Modules\KitElementsDefaults\ImportExportCustomization\Runners; use Elementor\Modules\KitElementsDefaults\ImportExportCustomization\Import_Export_Customization; use Elementor\Plugin; use Elementor\Core\Utils\Collection; use Elementor\Modules\KitElementsDefaults\Module; use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer; use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Export_Runner_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Export extends Export_Runner_Base { public static function get_name(): string { return 'elements-default-values'; } public function should_export( array $data ) { // Together with site-settings. return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) ); } public function export( array $data ) { $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return [ 'manifest' => [], 'files' => [], ]; } $default_values = $kit->get_json_meta( Module::META_KEY ); if ( ! $default_values ) { return [ 'manifest' => [], 'files' => [], ]; } $sanitizer = new Settings_Sanitizer( Plugin::$instance->elements_manager, array_keys( Plugin::$instance->widgets_manager->get_widget_types() ) ); $default_values = ( new Collection( $default_values ) ) ->map( function ( $settings, $type ) use ( $sanitizer, $kit ) { return $sanitizer ->for( $type ) ->using( $settings ) ->remove_invalid_settings() ->kses_deep() ->prepare_for_export( $kit ) ->get(); } ) ->all(); return [ 'files' => [ 'path' => Import_Export_Customization::FILE_NAME, 'data' => $default_values, ], 'manifest' => [], ]; } } kit-elements-defaults/import-export-customization/import-export-customization.php 0000644 00000001726 15252521347 0024775 0 ustar 00 <?php namespace Elementor\Modules\KitElementsDefaults\ImportExportCustomization; use Elementor\App\Modules\ImportExportCustomization\Processes\Export; use Elementor\App\Modules\ImportExportCustomization\Processes\Import; use Elementor\Modules\KitElementsDefaults\ImportExportCustomization\Runners\Export as Export_Runner; use Elementor\Modules\KitElementsDefaults\ImportExportCustomization\Runners\Import as Import_Runner; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Import_Export_Customization { const FILE_NAME = 'kit-elements-defaults'; public function register() { // Revert kit is working by default, using the site-settings runner. add_action( 'elementor/import-export-customization/export-kit', function ( Export $export ) { $export->register( new Export_Runner() ); } ); add_action( 'elementor/import-export-customization/import-kit', function ( Import $import ) { $import->register( new Import_Runner() ); } ); } } markdown-render/module.php 0000644 00000013535 15252521347 0011656 0 ustar 00 <?php namespace Elementor\Modules\MarkdownRender; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Plugin; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; } class Module extends BaseModule { const EXPERIMENT_NAME = 'markdown_rendering'; const CACHE_META_KEY = '_elementor_markdown_cache'; public function get_name() { return 'markdown-render'; } public static function get_experimental_data() { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Generate website markdown', 'elementor' ), 'description' => sprintf( '%1$s <a href="https://go.elementor.com/wp-dash-generate-website-markdown-article/" target="_blank">%2$s</a>', esc_html__( 'Serve your pages as Markdown files so AI agents can ingest and understand your content more efficiently.', 'elementor' ), esc_html__( 'Learn more', 'elementor' ) ), 'default' => Experiments_Manager::STATE_INACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_ALPHA, ]; } public function __construct() { parent::__construct(); add_action( 'template_redirect', [ $this, 'maybe_serve_markdown' ], 1 ); add_action( 'elementor/core/files/clear_cache', [ $this, 'clear_all_markdown_cache' ] ); add_action( 'save_post', [ $this, 'clear_post_markdown_cache' ] ); add_action( 'activated_plugin', [ $this, 'clear_all_markdown_cache' ] ); add_action( 'deactivated_plugin', [ $this, 'clear_all_markdown_cache' ] ); add_action( 'switch_theme', [ $this, 'clear_all_markdown_cache' ] ); if ( is_admin() ) { add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 100 ); } } public function maybe_serve_markdown() { if ( ! $this->is_markdown_request() ) { return; } if ( ! is_singular() ) { return; } $post_id = get_the_ID(); $post = get_post( $post_id ); if ( ! $post ) { return; } $is_preview = $this->is_valid_preview_request( $post_id ); if ( ! $is_preview && 'publish' !== $post->post_status ) { return; } if ( post_password_required( $post ) ) { return; } $document = $is_preview ? Plugin::$instance->documents->get_doc_for_frontend( $post_id ) : Plugin::$instance->documents->get( $post_id ); if ( ! $document || ! $document->is_built_with_elementor() ) { return; } if ( $is_preview ) { $markdown = ( new Markdown_Renderer() )->render( $document ); } else { $markdown = $this->get_cached_markdown( $post_id ); if ( false === $markdown ) { $markdown = ( new Markdown_Renderer() )->render( $document ); $this->set_cached_markdown( $post_id, $markdown ); } } nocache_headers(); status_header( 200 ); header( 'Content-Type: text/markdown; charset=utf-8' ); header( 'X-Content-Type-Options: nosniff' ); echo $markdown; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } private function is_valid_preview_request( int $post_id ): bool { if ( ! is_preview() ) { return false; } $preview_id = isset( $_GET['preview_id'] ) ? absint( wp_unslash( $_GET['preview_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $preview_nonce = sanitize_text_field( wp_unslash( $_GET['preview_nonce'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! $preview_id || ! wp_verify_nonce( $preview_nonce, 'post_preview_' . $preview_id ) ) { return false; } return current_user_can( 'edit_post', $post_id ); } private function is_markdown_request(): bool { if ( isset( $_GET['format'] ) && 'markdown' === $_GET['format'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return true; } $accept = isset( $_SERVER['HTTP_ACCEPT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT'] ) ) : ''; return false !== strpos( $accept, 'text/markdown' ); } private function get_cached_markdown( int $post_id ) { $cache = get_post_meta( $post_id, self::CACHE_META_KEY, true ); if ( empty( $cache ) || ! is_array( $cache ) ) { return false; } if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) { return false; } return $cache['content'] ?? false; } private function set_cached_markdown( int $post_id, string $markdown ): void { $ttl_hours = (int) get_option( 'elementor_markdown_cache_ttl', 24 ); if ( $ttl_hours <= 0 ) { return; } $cache = [ 'timeout' => time() + ( $ttl_hours * HOUR_IN_SECONDS ), 'content' => $markdown, ]; update_post_meta( $post_id, self::CACHE_META_KEY, $cache ); } public function clear_post_markdown_cache( int $post_id ): void { delete_post_meta( $post_id, self::CACHE_META_KEY ); } public function clear_all_markdown_cache(): void { global $wpdb; $wpdb->delete( $wpdb->postmeta, [ 'meta_key' => self::CACHE_META_KEY ] ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key } public function register_admin_fields( Settings $settings ) { $settings->add_field( Settings::TAB_PERFORMANCE, Settings::TAB_PERFORMANCE, 'markdown_cache_ttl', [ 'label' => esc_html__( 'Markdown Cache', 'elementor' ), 'field_args' => [ 'class' => 'elementor-markdown-cache-ttl', 'type' => 'select', 'std' => '24', 'options' => [ '0' => esc_html__( 'Disable', 'elementor' ), '1' => esc_html__( '1 Hour', 'elementor' ), '6' => esc_html__( '6 Hours', 'elementor' ), '12' => esc_html__( '12 Hours', 'elementor' ), '24' => esc_html__( '1 Day', 'elementor' ), '72' => esc_html__( '3 Days', 'elementor' ), '168' => esc_html__( '1 Week', 'elementor' ), '720' => esc_html__( '1 Month', 'elementor' ), ], 'desc' => esc_html__( 'Specify the duration for which Markdown output is cached. This cache is served to AI crawlers requesting text/markdown content.', 'elementor' ), ], ] ); } } markdown-render/markdown-renderer.php 0000644 00000005526 15252521347 0014020 0 ustar 00 <?php namespace Elementor\Modules\MarkdownRender; use Elementor\Core\Base\Document; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Markdown_Renderer { public function render( Document $document ): string { $frontmatter = $this->build_frontmatter( $document ); $data = $document->get_elements_data(); if ( empty( $data ) ) { return $frontmatter; } $sections = []; foreach ( $data as $element_data ) { $md = $this->render_element( $element_data ); if ( ! empty( trim( $md ) ) ) { $sections[] = $md; } } $body = implode( "\n\n---\n\n", $sections ); $output = $frontmatter . "\n\n" . $body; return apply_filters( 'elementor/markdown/document_output', $output, $document ); } private function build_frontmatter( Document $document ): string { $post_id = $document->get_main_id(); $lines = [ '---' ]; $lines[] = 'title: "' . $this->escape_yaml_string( get_the_title( $post_id ) ) . '"'; $description = $this->get_meta_description( $post_id ); if ( $description ) { $lines[] = 'description: "' . $this->escape_yaml_string( $description ) . '"'; } $thumbnail = get_the_post_thumbnail_url( $post_id, 'full' ); if ( $thumbnail ) { $lines[] = 'featured_image: "' . esc_url( $thumbnail ) . '"'; } $permalink = get_permalink( $post_id ); if ( is_string( $permalink ) && '' !== $permalink ) { $lines[] = 'url: "' . esc_url( $permalink ) . '"'; } $modified_date = get_the_modified_date( 'c', $post_id ); if ( is_string( $modified_date ) && '' !== $modified_date ) { $lines[] = 'date_modified: "' . $this->escape_yaml_string( $modified_date ) . '"'; } $lines[] = '---'; return implode( "\n", $lines ); } private function get_meta_description( int $post_id ): string { $description = get_post_meta( $post_id, '_yoast_wpseo_metadesc', true ); if ( ! empty( $description ) ) { return $description; } $description = get_post_meta( $post_id, '_aioseo_description', true ); if ( ! empty( $description ) ) { return $description; } $excerpt = get_the_excerpt( $post_id ); return ! empty( $excerpt ) ? $excerpt : ''; } private function render_element( array $element_data ): string { $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( ! $element ) { return ''; } $markdown = $element->render_markdown(); return apply_filters( 'elementor/markdown/element_output', $markdown, $element, $element_data ); } private function escape_yaml_string( string $value ): string { $value = html_entity_decode( $value, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); $value = str_replace( "\xE2\x80\x8B", '', $value ); $value = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $value ); return strtr( $value, [ '\\' => '\\\\', '"' => '\\"', "\n" => '\\n', "\r" => '\\r', "\t" => '\\t', ] ); } } markdown-render/html-to-markdown.php 0000644 00000033264 15252521347 0013576 0 ustar 00 <?php namespace Elementor\Modules\MarkdownRender; use WP_HTML_Processor; if ( ! defined( 'ABSPATH' ) ) { exit; } class Html_To_Markdown { const SAFE_URL_SCHEMES = [ 'http', 'https', 'mailto', 'tel', 'ftp', 'ftps' ]; const SAFE_DATA_PREFIXES = [ 'data:image/png', 'data:image/jpeg', 'data:image/jpg', 'data:image/gif', 'data:image/webp', 'data:image/svg+xml' ]; const ESCAPABLE_MARKDOWN_CHARS = [ '\\', '`', '*', '_', '[', ']' ]; const VOID_TAGS = [ 'br', 'hr', 'img', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'param', 'source', 'track', 'wbr' ]; private $output = ''; private $inline_buffer = ''; private $list_stack = []; private $blockquote_depth = 0; private $in_pre = false; private $pre_buffer = ''; private $pre_language = ''; private $in_code = false; private $in_link = false; private $link_href = ''; private $link_buffer = ''; private $in_table = false; private $table_headers = []; private $table_rows = []; private $current_row = []; private $current_cell = ''; private $in_table_head = false; private $row_is_header = false; private $heading_level = 0; public static function convert( string $html ): string { if ( '' === $html ) { return ''; } $instance = new self(); return $instance->run( $html ); } private function run( string $html ): string { $html = $this->normalize_html( $html ); $processor = WP_HTML_Processor::create_full_parser( $html ); if ( null === $processor ) { return $this->fallback( $html ); } try { $this->walk( $processor ); } catch ( \Exception $e ) { return $this->fallback( $html ); } $this->flush_inline(); $output = $this->output; $output = preg_replace( "/[ \t]+\n/", "\n", $output ); $output = preg_replace( "/\n{3,}/", "\n\n", $output ); return trim( $output ); } private function fallback( string $html ): string { $text = wp_strip_all_tags( $html ); $text = html_entity_decode( $text, ENT_QUOTES, 'UTF-8' ); return trim( preg_replace( "/\n{3,}/", "\n\n", $text ) ); } private function normalize_html( string $html ): string { $html = preg_replace( '/\r\n?/', "\n", $html ); $html = preg_replace( '#<script\b[^>]*>.*?</script>#is', '', $html ); $html = preg_replace( '#<style\b[^>]*>.*?</style>#is', '', $html ); $html = str_replace( "\xE2\x80\x8B", '', $html ); return $html; } private function walk( WP_HTML_Processor $p ): void { while ( $p->next_token() ) { $type = $p->get_token_type(); if ( '#text' === $type ) { $this->handle_text( $p->get_modifiable_text() ); continue; } if ( '#tag' !== $type ) { continue; } $tag = strtolower( (string) $p->get_tag() ); $is_closer = $p->is_tag_closer(); $is_void = in_array( $tag, self::VOID_TAGS, true ); if ( $is_closer ) { $this->handle_close( $tag ); continue; } $this->handle_open( $tag, $p ); if ( $is_void ) { $this->handle_close( $tag ); } } } private function handle_text( string $text ): void { if ( '' === $text ) { return; } if ( $this->in_pre ) { $this->pre_buffer .= $text; return; } if ( $this->in_table ) { $this->current_cell .= $this->escape_text( $text, false ); return; } $collapsed = preg_replace( '/[ \t\n]+/', ' ', $text ); $escaped = $this->escape_text( $collapsed, $this->in_code ); if ( $this->in_link ) { $this->link_buffer .= $escaped; return; } $this->inline_buffer .= $escaped; } private function handle_open( string $tag, WP_HTML_Processor $p ): void { switch ( $tag ) { case 'p': case 'div': case 'section': case 'article': case 'header': case 'footer': case 'main': case 'aside': case 'figure': $this->flush_inline(); return; case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': $this->flush_inline(); $this->heading_level = (int) $tag[1]; $this->inline_buffer = str_repeat( '#', $this->heading_level ) . ' '; return; case 'br': if ( $this->in_table ) { $this->current_cell .= ' '; return; } if ( $this->heading_level > 0 ) { $this->inline_buffer .= ' '; return; } $this->inline_buffer .= "\n"; return; case 'hr': $this->flush_inline(); $this->output .= "\n\n---\n\n"; return; case 'strong': case 'b': $this->append_inline( '**' ); return; case 'em': case 'i': $this->append_inline( '*' ); return; case 'del': case 's': case 'strike': $this->append_inline( '~~' ); return; case 'code': if ( $this->in_pre ) { $lang = (string) $p->get_attribute( 'class' ); if ( preg_match( '/language-([A-Za-z0-9_+\-]+)/', $lang, $m ) ) { $this->pre_language = $m[1]; } return; } $this->in_code = true; $this->append_inline( '`CODE_OPEN`' ); return; case 'pre': $this->flush_inline(); $this->in_pre = true; $this->pre_buffer = ''; $this->pre_language = ''; return; case 'a': $href = (string) $p->get_attribute( 'href' ); $this->in_link = true; $this->link_href = $href; $this->link_buffer = ''; return; case 'img': $this->emit_image( $p ); return; case 'ul': case 'ol': if ( ! empty( $this->list_stack ) && '' !== trim( $this->inline_buffer ) ) { $this->emit_list_item(); } else { $this->flush_inline(); } $this->list_stack[] = [ 'type' => $tag, 'index' => 1, ]; return; case 'li': $this->flush_inline(); return; case 'blockquote': $this->flush_inline(); $this->blockquote_depth++; return; case 'table': $this->flush_inline(); $this->in_table = true; $this->table_headers = []; $this->table_rows = []; return; case 'thead': $this->in_table_head = true; return; case 'tr': if ( ! $this->in_table ) { return; } $this->current_row = []; $this->row_is_header = $this->in_table_head; return; case 'th': $this->row_is_header = true; $this->current_cell = ''; return; case 'td': $this->current_cell = ''; return; } } private function handle_close( string $tag ): void { switch ( $tag ) { case 'p': case 'div': case 'section': case 'article': case 'header': case 'footer': case 'main': case 'aside': case 'figure': $this->flush_inline(); return; case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': $this->inline_buffer = preg_replace( '/[ \t]+/', ' ', $this->inline_buffer ); $this->flush_inline(); $this->heading_level = 0; return; case 'strong': case 'b': $this->append_inline( '**' ); return; case 'em': case 'i': $this->append_inline( '*' ); return; case 'del': case 's': case 'strike': $this->append_inline( '~~' ); return; case 'code': if ( $this->in_pre ) { return; } $this->append_inline( '`CODE_CLOSE`' ); $this->in_code = false; return; case 'pre': $this->emit_pre(); $this->in_pre = false; $this->pre_buffer = ''; $this->pre_language = ''; return; case 'a': $this->emit_link(); $this->in_link = false; $this->link_href = ''; $this->link_buffer = ''; return; case 'ul': case 'ol': $this->flush_inline(); array_pop( $this->list_stack ); if ( empty( $this->list_stack ) ) { $this->output .= "\n\n"; } return; case 'li': $this->emit_list_item(); return; case 'blockquote': $this->flush_inline(); $this->blockquote_depth = max( 0, $this->blockquote_depth - 1 ); if ( 0 === $this->blockquote_depth ) { $this->output .= "\n"; } return; case 'table': $this->emit_table(); $this->in_table = false; $this->table_headers = []; $this->table_rows = []; return; case 'thead': $this->in_table_head = false; return; case 'tr': if ( ! $this->in_table ) { return; } if ( $this->row_is_header && empty( $this->table_headers ) ) { $this->table_headers = $this->current_row; } else { $this->table_rows[] = $this->current_row; } $this->current_row = []; $this->row_is_header = false; return; case 'th': case 'td': $this->current_row[] = trim( preg_replace( '/\s+/', ' ', $this->current_cell ) ); $this->current_cell = ''; return; } } private function append_inline( string $marker ): void { if ( $this->in_link ) { $this->link_buffer .= $marker; return; } $this->inline_buffer .= $marker; } private function emit_link(): void { $text = $this->link_buffer; $href = $this->link_href; if ( '' === $href || '#' === $href ) { $this->inline_buffer .= $text; return; } if ( ! $this->is_safe_url( $href ) ) { $this->inline_buffer .= $text; return; } $href = $this->escape_url_for_markdown( $href ); $this->inline_buffer .= '[' . $text . '](' . $href . ')'; } private function emit_image( WP_HTML_Processor $p ): void { $src = (string) $p->get_attribute( 'src' ); $alt = (string) $p->get_attribute( 'alt' ); if ( '' === $src ) { return; } if ( ! $this->is_safe_url( $src ) ) { return; } $alt = $this->escape_text( $alt, false ); $src = $this->escape_url_for_markdown( $src ); $this->inline_buffer .= ''; } private function emit_pre(): void { $content = html_entity_decode( $this->pre_buffer, ENT_QUOTES, 'UTF-8' ); $content = rtrim( $content, "\n" ); $this->output .= "\n\n```" . $this->pre_language . "\n" . $content . "\n```\n\n"; } private function emit_list_item(): void { $content = trim( $this->inline_buffer ); $this->inline_buffer = ''; if ( '' === $content ) { return; } $top = end( $this->list_stack ); if ( false === $top ) { $this->output .= $content . "\n"; return; } $depth = count( $this->list_stack ) - 1; $indent = str_repeat( ' ', $depth ); if ( 'ol' === $top['type'] ) { $marker = $top['index'] . '. '; $this->list_stack[ array_key_last( $this->list_stack ) ]['index']++; } else { $marker = '- '; } $lines = explode( "\n", $content ); $first = array_shift( $lines ); $rendered = $indent . $marker . $first; foreach ( $lines as $line ) { $rendered .= "\n" . $indent . ' ' . $line; } $this->output .= $this->prefix_blockquote( $rendered ) . "\n"; } private function emit_table(): void { if ( empty( $this->table_headers ) && empty( $this->table_rows ) ) { return; } if ( empty( $this->table_headers ) && ! empty( $this->table_rows ) ) { $this->table_headers = array_fill( 0, count( $this->table_rows[0] ), '' ); } $column_count = count( $this->table_headers ); $lines = []; $lines[] = '| ' . implode( ' | ', array_map( [ $this, 'escape_table_cell' ], $this->table_headers ) ) . ' |'; $lines[] = '|' . str_repeat( ' --- |', $column_count ); foreach ( $this->table_rows as $row ) { $row = array_pad( $row, $column_count, '' ); $row = array_slice( $row, 0, $column_count ); $lines[] = '| ' . implode( ' | ', array_map( [ $this, 'escape_table_cell' ], $row ) ) . ' |'; } $this->output .= "\n\n" . implode( "\n", $lines ) . "\n\n"; } private function escape_table_cell( string $value ): string { return str_replace( [ '|', "\n" ], [ '\\|', ' ' ], $value ); } private function flush_inline(): void { if ( '' === $this->inline_buffer ) { return; } $content = $this->finalize_inline( $this->inline_buffer ); $this->inline_buffer = ''; $content = trim( $content ); if ( '' === $content ) { return; } if ( ! empty( $this->list_stack ) ) { $this->inline_buffer = $content; return; } $content = $this->prefix_blockquote( $content ); $this->output .= "\n\n" . $content . "\n\n"; } private function finalize_inline( string $buffer ): string { return preg_replace_callback( '/`CODE_OPEN`(.*?)`CODE_CLOSE`/s', function ( $matches ) { return $this->render_inline_code( $matches[1] ); }, $buffer ); } private function render_inline_code( string $content ): string { $content = preg_replace( '/\s+/', ' ', $content ); preg_match_all( '/`+/', $content, $runs ); $max = 0; foreach ( $runs[0] as $run ) { $max = max( $max, strlen( $run ) ); } $fence = str_repeat( '`', $max + 1 ); $pad = ( '' !== $content && ( '`' === $content[0] || '`' === substr( $content, -1 ) ) ) ? ' ' : ''; return $fence . $pad . $content . $pad . $fence; } private function prefix_blockquote( string $content ): string { if ( 0 === $this->blockquote_depth ) { return $content; } $prefix = str_repeat( '> ', $this->blockquote_depth ); $lines = explode( "\n", $content ); return implode( "\n", array_map( function ( $line ) use ( $prefix ) { return $prefix . $line; }, $lines ) ); } private function escape_text( string $text, bool $in_code ): string { $text = html_entity_decode( $text, ENT_QUOTES, 'UTF-8' ); if ( $in_code ) { return $text; } $replacements = []; foreach ( self::ESCAPABLE_MARKDOWN_CHARS as $char ) { $replacements[ $char ] = '\\' . $char; } return strtr( $text, $replacements ); } private function escape_url_for_markdown( string $url ): string { return strtr( $url, [ ' ' => '%20', '(' => '%28', ')' => '%29', '<' => '%3C', '>' => '%3E', ] ); } private function is_safe_url( string $url ): bool { $url = trim( $url ); if ( '' === $url ) { return false; } if ( '#' === $url[0] || '/' === $url[0] || '?' === $url[0] ) { return true; } $lower = strtolower( $url ); foreach ( self::SAFE_DATA_PREFIXES as $prefix ) { if ( 0 === strpos( $lower, $prefix ) ) { return true; } } if ( ! preg_match( '#^([a-z][a-z0-9+.\-]*):#i', $url, $m ) ) { return true; } return in_array( strtolower( $m[1] ), self::SAFE_URL_SCHEMES, true ); } } library/user-favorites.php 0000644 00000005606 15252521347 0011714 0 ustar 00 <?php namespace Elementor\Modules\Library; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class User_Favorites { const USER_META_KEY = 'elementor_library_favorites'; /** * @var int */ private $user_id; /** * @var array|null */ private $cache; /** * User_Favorites constructor. * * @param $user_id */ public function __construct( $user_id ) { $this->user_id = $user_id; } /** * @param null $vendor * @param null $resource_name * @param false $ignore_cache * * @return array */ public function get( $vendor = null, $resource_name = null, $ignore_cache = false ) { if ( $ignore_cache || empty( $this->cache ) ) { $this->cache = get_user_meta( $this->user_id, self::USER_META_KEY, true ); } if ( ! $this->cache || ! is_array( $this->cache ) ) { return []; } if ( $vendor && $resource_name ) { $key = $this->get_key( $vendor, $resource_name ); return isset( $this->cache[ $key ] ) ? $this->cache[ $key ] : []; } return $this->cache; } /** * @param $vendor * @param $resource_name * @param $id * * @return bool */ public function exists( $vendor, $resource_name, $id ) { return in_array( $id, $this->get( $vendor, $resource_name ), true ); } /** * @param $vendor * @param $resource_name * @param array $value * * @return $this * @throws \Exception If the favorites cannot be saved. */ public function save( $vendor, $resource_name, $value = [] ) { $all_favorites = $this->get(); $all_favorites[ $this->get_key( $vendor, $resource_name ) ] = $value; $result = update_user_meta( $this->user_id, self::USER_META_KEY, $all_favorites ); if ( false === $result ) { throw new \Exception( 'Failed to save user favorites.' ); } $this->cache = $all_favorites; return $this; } /** * @param $vendor * @param $resource_name * @param $id * * @return $this * @throws \Exception If the favorites cannot be added. */ public function add( $vendor, $resource_name, $id ) { $favorites = $this->get( $vendor, $resource_name ); if ( in_array( $id, $favorites, true ) ) { return $this; } $favorites[] = $id; $this->save( $vendor, $resource_name, $favorites ); return $this; } /** * @param $vendor * @param $resource_name * @param $id * * @return $this * @throws \Exception If the favorites cannot be removed. */ public function remove( $vendor, $resource_name, $id ) { $favorites = $this->get( $vendor, $resource_name ); if ( ! in_array( $id, $favorites, true ) ) { return $this; } $favorites = array_filter( $favorites, function ( $item ) use ( $id ) { return $item !== $id; } ); $this->save( $vendor, $resource_name, $favorites ); return $this; } /** * @param $vendor * @param $resource_name * * @return string */ private function get_key( $vendor, $resource_name ) { return "{$vendor}/{$resource_name}"; } } library/module.php 0000644 00000002727 15252521347 0010224 0 ustar 00 <?php namespace Elementor\Modules\Library; use Elementor\Core\Base\Module as BaseModule; use Elementor\Modules\AtomicWidgets\Module as AtomicWidgets_Module; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor library module. * * Elementor library module handler class is responsible for registering and * managing Elementor library modules. * * @since 2.0.0 */ class Module extends BaseModule { /** * Get module name. * * Retrieve the library module name. * * @since 2.0.0 * @access public * * @return string Module name. */ public function get_name() { return 'library'; } /** * Library module constructor. * * Initializing Elementor library module. * * @since 2.0.0 * @access public */ public function __construct() { add_action( 'elementor/documents/register', [ $this, 'register_documents' ] ); } public function register_documents() { Plugin::$instance->documents ->register_document_type( 'not-supported', Documents\Not_Supported::get_class_full_name() ) ->register_document_type( 'page', Documents\Page::get_class_full_name() ) ->register_document_type( 'section', Documents\Section::get_class_full_name() ); $experiments_manager = Plugin::$instance->experiments; if ( $experiments_manager->is_feature_active( 'container' ) ) { Plugin::$instance->documents ->register_document_type( 'container', Documents\Container::get_class_full_name() ); } } } library/traits/library.php 0000644 00000002022 15252521347 0011675 0 ustar 00 <?php namespace Elementor\Modules\Library\Traits; use Elementor\TemplateLibrary\Source_Local; /** * Elementor Library Trait * * This trait is used by all Library Documents and Landing Pages. * * @since 3.1.0 */ trait Library { /** * Print Admin Column Type * * Runs on WordPress' 'manage_{custom post type}_posts_custom_column' hook to modify each row's content. * * @since 3.1.0 * @access public */ public function print_admin_column_type() { $admin_filter_url = admin_url( Source_Local::ADMIN_MENU_SLUG . '&elementor_library_type=' . $this->get_name() ); // PHPCS - Not a user input printf( '<a href="%s">%s</a>', $admin_filter_url, $this->get_title() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Save document type. * * Set new/updated document type. * * @since 3.1.0 * @access public */ public function save_template_type() { parent::save_template_type(); wp_set_object_terms( $this->post->ID, $this->get_name(), Source_Local::TAXONOMY_TYPE_SLUG ); } } library/documents/container.php 0000644 00000002136 15252521347 0012714 0 ustar 00 <?php namespace Elementor\Modules\Library\Documents; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor container library document. * * Elementor container library document handler class is responsible for * handling a document of a container type. * * @since 2.0.0 */ class Container extends Library_Document { public static function get_properties() { $properties = parent::get_properties(); $properties['support_kit'] = true; return $properties; } /** * Get document name. * * Retrieve the document name. * * @since 2.0.0 * @access public * * @return string Document name. */ public function get_name() { return 'container'; } /** * Get document title. * * Retrieve the document title. * * @since 2.0.0 * @access public * @static * * @return string Document title. */ public static function get_title() { return esc_html__( 'Container', 'elementor' ); } /** * Get Type * * Return the container document type. * * @return string */ public static function get_type() { return 'container'; } } library/documents/not-supported.php 0000644 00000002671 15252521347 0013561 0 ustar 00 <?php namespace Elementor\Modules\Library\Documents; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor section library document. * * Elementor section library document handler class is responsible for * handling a document of a section type. */ class Not_Supported extends Library_Document { /** * Get document properties. * * Retrieve the document properties. * * @access public * @static * * @return array Document properties. */ public static function get_properties() { $properties = parent::get_properties(); $properties['admin_tab_group'] = ''; $properties['register_type'] = false; $properties['is_editable'] = false; $properties['show_in_library'] = false; $properties['show_in_finder'] = false; return $properties; } public static function get_type() { return 'not-supported'; } /** * Get document title. * * Retrieve the document title. * * @access public * @static * * @return string Document title. */ public static function get_title() { return esc_html__( 'Not Supported', 'elementor' ); } public function save_template_type() { // Do nothing. } public function print_admin_column_type() { Utils::print_unescaped_internal_string( self::get_title() ); } public function filter_admin_row_actions( $actions ) { unset( $actions['view'] ); return $actions; } } library/documents/section.php 0000644 00000001666 15252521347 0012405 0 ustar 00 <?php namespace Elementor\Modules\Library\Documents; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor section library document. * * Elementor section library document handler class is responsible for * handling a document of a section type. * * @since 2.0.0 */ class Section extends Library_Document { public static function get_properties() { $properties = parent::get_properties(); $properties['support_kit'] = true; $properties['show_in_finder'] = true; return $properties; } public static function get_type() { return 'section'; } /** * Get document title. * * Retrieve the document title. * * @since 2.0.0 * @access public * @static * * @return string Document title. */ public static function get_title() { return esc_html__( 'Section', 'elementor' ); } public static function get_plural_title() { return esc_html__( 'Sections', 'elementor' ); } } library/documents/page.php 0000644 00000003522 15252521347 0011646 0 ustar 00 <?php namespace Elementor\Modules\Library\Documents; use Elementor\Core\DocumentTypes\Post; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor page library document. * * Elementor page library document handler class is responsible for * handling a document of a page type. * * @since 2.0.0 */ class Page extends Library_Document { /** * Get document properties. * * Retrieve the document properties. * * @since 2.0.0 * @access public * @static * * @return array Document properties. */ public static function get_properties() { $properties = parent::get_properties(); $properties['support_wp_page_templates'] = true; $properties['support_kit'] = true; $properties['show_in_finder'] = true; return $properties; } public static function get_type() { return 'page'; } /** * Get document title. * * Retrieve the document title. * * @since 2.0.0 * @access public * @static * * @return string Document title. */ public static function get_title() { return esc_html__( 'Page', 'elementor' ); } public static function get_plural_title() { return esc_html__( 'Pages', 'elementor' ); } public static function get_add_new_title() { return esc_html__( 'Add New Page Template', 'elementor' ); } /** * @since 2.1.3 * @access public */ public function get_css_wrapper_selector() { return 'body.elementor-page-' . $this->get_main_id(); } /** * @since 3.1.0 * @access protected */ protected function register_controls() { parent::register_controls(); Post::register_hide_title_control( $this ); Post::register_style_controls( $this ); } protected function get_remote_library_config() { $config = parent::get_remote_library_config(); $config['type'] = 'page'; $config['default_route'] = 'templates/pages'; return $config; } } library/documents/library-document.php 0000644 00000003507 15252521347 0014215 0 ustar 00 <?php namespace Elementor\Modules\Library\Documents; use Elementor\Core\Base\Document; use Elementor\Modules\Library\Traits\Library; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor library document. * * Elementor library document handler class is responsible for handling * a document of the library type. * * @since 2.0.0 */ abstract class Library_Document extends Document { // Library Document Trait use Library; /** * The taxonomy type slug for the library document. */ const TAXONOMY_TYPE_SLUG = 'elementor_library_type'; /** * The customization group for Kit Export. */ const EXPORT_GROUP = 'site-templates'; /** * Get document properties. * * Retrieve the document properties. * * @since 2.0.0 * @access public * @static * * @return array Document properties. */ public static function get_properties() { $properties = parent::get_properties(); $properties['admin_tab_group'] = 'library'; $properties['show_in_library'] = true; $properties['register_type'] = true; $properties['cpt'] = [ Source_Local::CPT ]; $properties['export_group'] = static::EXPORT_GROUP; return $properties; } /** * Get initial config. * * Retrieve the current element initial configuration. * * Adds more configuration on top of the controls list and the tabs assigned * to the control. This method also adds element name, type, icon and more. * * @since 2.9.0 * @access protected * * @return array The initial config. */ public function get_initial_config() { $config = parent::get_initial_config(); $config['library'] = [ 'save_as_same_type' => true, ]; return $config; } public function get_content( $with_css = false ) { return do_shortcode( parent::get_content( $with_css ) ); } } notes/module.php 0000644 00000002324 15252521347 0007701 0 ustar 00 <?php namespace Elementor\Modules\Notes; use Elementor\Core\Base\Module as BaseModule; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { public function get_name() { return 'notes'; } /** * Enqueue the module scripts. * * @return void */ public function enqueue_scripts() { wp_enqueue_script( 'elementor-notes', $this->get_js_assets_url( 'notes' ), [ 'elementor-editor' ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-notes', 'elementor' ); } /** * Enqueue the module styles. * * @return void */ public function enqueue_styles() { wp_enqueue_style( 'elementor-notes', $this->get_css_assets_url( 'modules/notes/editor' ), [ 'elementor-editor' ], ELEMENTOR_VERSION ); } /** * @return bool */ public static function is_active() { return ! Utils::has_pro(); } /** * Initialize the Notes module. * * @return void */ public function __construct() { parent::__construct(); add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); add_action( 'elementor/editor/after_enqueue_styles', [ $this, 'enqueue_styles' ] ); } } safe-mode/module.php 0000644 00000037420 15252521347 0010416 0 ustar 00 <?php namespace Elementor\Modules\SafeMode; use Elementor\Plugin; use Elementor\Settings; use Elementor\Tools; use Elementor\TemplateLibrary\Source_Local; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends \Elementor\Core\Base\Module { const OPTION_ENABLED = 'elementor_safe_mode'; const OPTION_TOKEN = self::OPTION_ENABLED . '_token'; const MU_PLUGIN_FILE_NAME = 'elementor-safe-mode.php'; const DOCS_HELPED_URL = 'https://go.elementor.com/safe-mode-helped/'; const DOCS_DIDNT_HELP_URL = 'https://go.elementor.com/safe-mode-didnt-helped/'; const DOCS_MU_PLUGINS_URL = 'https://go.elementor.com/safe-mode-mu-plugins/'; const DOCS_TRY_SAFE_MODE_URL = 'https://go.elementor.com/safe-mode/'; const EDITOR_NOTICE_TIMEOUT = 30000; /* ms */ public function get_name() { return 'safe-mode'; } public function register_ajax_actions( Ajax $ajax ) { $ajax->register_ajax_action( 'enable_safe_mode', [ $this, 'ajax_enable_safe_mode' ] ); $ajax->register_ajax_action( 'disable_safe_mode', [ $this, 'disable_safe_mode' ] ); } /** * @param Tools $tools_page */ public function add_admin_button( $tools_page ) { $tools_page->add_fields( Settings::TAB_GENERAL, 'tools', [ 'safe_mode' => [ 'label' => esc_html__( 'Safe Mode', 'elementor' ), 'field_args' => [ 'type' => 'select', 'std' => $this->is_enabled() ? 'global' : '', 'options' => [ '' => esc_html__( 'Disable', 'elementor' ), 'global' => esc_html__( 'Enable', 'elementor' ), ], 'desc' => esc_html__( 'Safe Mode allows you to troubleshoot issues by only loading the editor, without loading the theme or any other plugin.', 'elementor' ), ], ], ] ); } public function on_update_safe_mode( $value ) { if ( 'yes' === $value || 'global' === $value ) { $this->enable_safe_mode(); } else { $this->disable_safe_mode(); } return $value; } /** * @throws \Exception If the safe mode cannot be enabled. */ public function ajax_enable_safe_mode( $data ) { if ( ! current_user_can( 'install_plugins' ) ) { throw new \Exception( 'Access denied.' ); } // It will run `$this->>update_safe_mode`. update_option( 'elementor_safe_mode', 'yes' ); $document = Plugin::$instance->documents->get( $data['editor_post_id'] ); if ( $document ) { return add_query_arg( 'elementor-mode', 'safe', $document->get_edit_url() ); } return false; } public function enable_safe_mode() { if ( ! current_user_can( 'install_plugins' ) ) { return; } WP_Filesystem(); $this->update_allowed_plugins(); if ( ! is_dir( WPMU_PLUGIN_DIR ) ) { wp_mkdir_p( WPMU_PLUGIN_DIR ); add_option( 'elementor_safe_mode_created_mu_dir', true ); } if ( ! is_dir( WPMU_PLUGIN_DIR ) ) { wp_die( esc_html__( 'Cannot enable Safe Mode', 'elementor' ) ); } $results = copy_dir( __DIR__ . '/mu-plugin/', WPMU_PLUGIN_DIR ); if ( is_wp_error( $results ) ) { return; } $token = hash( 'sha256', wp_rand() ); // Only who own this key can use 'elementor-safe-mode'. update_option( self::OPTION_TOKEN, $token ); // Save for later use. setcookie( self::OPTION_TOKEN, $token, time() + HOUR_IN_SECONDS, COOKIEPATH, '', is_ssl(), true ); } public function disable_safe_mode() { if ( ! current_user_can( 'install_plugins' ) ) { return; } $file_path = WP_CONTENT_DIR . '/mu-plugins/elementor-safe-mode.php'; if ( file_exists( $file_path ) ) { unlink( $file_path ); } if ( get_option( 'elementor_safe_mode_created_mu_dir' ) ) { // It will be removed only if it's empty and don't have other mu-plugins. @rmdir( WPMU_PLUGIN_DIR ); } delete_option( 'elementor_safe_mode' ); delete_option( 'elementor_safe_mode_allowed_plugins' ); delete_option( 'theme_mods_elementor-safe' ); delete_option( 'elementor_safe_mode_created_mu_dir' ); delete_option( self::OPTION_TOKEN ); setcookie( self::OPTION_TOKEN, '', 1, '', '', is_ssl(), true ); } public function filter_preview_url( $url ) { return add_query_arg( 'elementor-mode', 'safe', $url ); } public function filter_template() { return ELEMENTOR_PATH . 'modules/page-templates/templates/canvas.php'; } public function print_safe_mode_css() { ?> <style> .elementor-safe-mode-toast { position: absolute; z-index: 10000; /* Over the loading layer */ inset-block-end: 10px; inset-inline-end: 10px; width: 400px; line-height: 30px; display: flex; flex-direction: column; gap: 20px; color: var(--e-a-color-txt); background: var(--e-a-bg-default); padding: 20px 25px 25px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15); border-radius: 5px; font-family: var(--e-a-font-family); } #elementor-try-safe-mode { display: none; } .elementor-safe-mode-toast .elementor-toast-content { font-size: 13px; line-height: 22px; } .elementor-safe-mode-toast .elementor-toast-content a { color: var(--e-a-color-info); } .elementor-safe-mode-toast .elementor-toast-content hr { margin: 15px auto; border: 0 none; border-block-start: var(--e-a-border); } .elementor-safe-mode-toast header { display: flex; align-items: center; gap: 10px; } .elementor-safe-mode-toast header i { font-size: 25px; color: var(--e-a-color-warning); } .elementor-safe-mode-toast header h2 { flex-grow: 1; font-size: 18px; } .elementor-safe-mode-list { display: flex; flex-direction: column; gap: 10px; } .elementor-safe-mode-list-item { margin-inline-start: 15px; list-style: outside; } .elementor-safe-mode-list-item-content { font-style: italic; color: var(--e-a-color-txt); } .elementor-safe-mode-list-item-title { font-weight: 500; } .elementor-safe-mode-mu-plugins { background-color: var(--e-a-bg-hover); color: var(--e-a-color-txt-hover); margin-block-start: 20px; padding: 10px 15px; } </style> <?php } public function print_safe_mode_notice() { $this->print_safe_mode_css() ?> <div class="elementor-safe-mode-toast" id="elementor-safe-mode-message"> <header> <i class="eicon-warning" aria-hidden="true"></i> <h2><?php echo esc_html__( 'Safe Mode ON', 'elementor' ); ?></h2> <a class="elementor-button elementor-safe-mode-button elementor-disable-safe-mode" target="_blank" href="<?php echo esc_url( $this->get_admin_page_url() ); ?>"> <?php echo esc_html__( 'Disable Safe Mode', 'elementor' ); ?> </a> </header> <div class="elementor-toast-content"> <ul class="elementor-safe-mode-list"> <li class="elementor-safe-mode-list-item"> <div class="elementor-safe-mode-list-item-title"><?php echo esc_html__( 'Editor successfully loaded?', 'elementor' ); ?></div> <div class="elementor-safe-mode-list-item-content"> <?php echo esc_html__( 'The issue was probably caused by one of your plugins or theme.', 'elementor' ); echo ' '; printf( /* translators: %1$s Link open tag, %2$s: Link close tag. */ esc_html__( '%1$sClick here%2$s to troubleshoot', 'elementor' ), '<a href="' . self::DOCS_HELPED_URL . '" target="_blank">', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped '</a>' ); ?> </div> </li> <li class="elementor-safe-mode-list-item"> <div class="elementor-safe-mode-list-item-title"><?php echo esc_html__( 'Still experiencing issues?', 'elementor' ); ?></div> <div class="elementor-safe-mode-list-item-content"> <?php printf( /* translators: %1$s Link open tag, %2$s: Link close tag. */ esc_html__( '%1$sClick here%2$s to troubleshoot', 'elementor' ), '<a href="' . self::DOCS_DIDNT_HELP_URL . '" target="_blank">', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped '</a>' ); ?> </div> </li> </ul> <?php $mu_plugins = wp_get_mu_plugins(); if ( 1 < count( $mu_plugins ) ) : ?> <div class="elementor-safe-mode-mu-plugins"> <?php printf( /* translators: %1$s Link open tag, %2$s: Link close tag. */ esc_html__( 'Please note! We couldn\'t deactivate all of your plugins on Safe Mode. Please %1$sread more%2$s about this issue', 'elementor' ), '<a href="' . self::DOCS_MU_PLUGINS_URL . '" target="_blank">', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped '</a>' ); ?> </div> <?php endif; ?> </div> </div> <script> var ElementorSafeMode = function() { var attachEvents = function() { jQuery( '.elementor-disable-safe-mode' ).on( 'click', function( e ) { if ( ! elementorCommon || ! elementorCommon.ajax ) { return; } e.preventDefault(); elementorCommon.ajax.addRequest( 'disable_safe_mode', { success: function() { if ( -1 === location.href.indexOf( 'elementor-mode=safe' ) ) { location.reload(); } else { // Need to remove the URL from browser history. location.replace( location.href.replace( '&elementor-mode=safe', '' ) ); } }, error: function() { alert( 'An error occurred.' ); }, }, true ); } ); }; var init = function() { attachEvents(); }; init(); }; new ElementorSafeMode(); </script> <?php } public function print_try_safe_mode() { if ( ! $this->is_allowed_post_type() ) { return; } $this->print_safe_mode_css(); ?> <div class="elementor-safe-mode-toast" id="elementor-try-safe-mode"> <?php if ( current_user_can( 'install_plugins' ) ) : ?> <header> <i class="eicon-warning" aria-hidden="true"></i> <h2><?php echo esc_html__( 'Can\'t Edit?', 'elementor' ); ?></h2> <a class="elementor-button e-primary elementor-safe-mode-button elementor-enable-safe-mode" target="_blank" href="<?php echo esc_url( $this->get_admin_page_url() ); ?>"> <?php echo esc_html__( 'Enable Safe Mode', 'elementor' ); ?> </a> </header> <div class="elementor-toast-content"> <?php echo esc_html__( 'Having problems loading Elementor? Please enable Safe Mode to troubleshoot.', 'elementor' ); ?> <a href="<?php Utils::print_unescaped_internal_string( self::DOCS_TRY_SAFE_MODE_URL ); ?>" target="_blank"><?php echo esc_html__( 'Learn More', 'elementor' ); ?></a> </div> <?php else : ?> <header> <i class="eicon-warning" aria-hidden="true"></i> <h2><?php echo esc_html__( 'Can\'t Edit?', 'elementor' ); ?></h2> </header> <div class="elementor-toast-content"> <?php echo esc_html__( 'If you are experiencing a loading issue, contact your site administrator to troubleshoot the problem using Safe Mode.', 'elementor' ); ?> <a href="<?php Utils::print_unescaped_internal_string( self::DOCS_TRY_SAFE_MODE_URL ); ?>" target="_blank"><?php echo esc_html__( 'Learn More', 'elementor' ); ?></a> </div> <?php endif; ?> </div> <script> var ElementorTrySafeMode = function() { var attachEvents = function() { jQuery( '.elementor-enable-safe-mode' ).on( 'click', function( e ) { if ( ! elementorCommon || ! elementorCommon.ajax ) { return; } e.preventDefault(); elementorCommon.ajax.addRequest( 'enable_safe_mode', { data: { editor_post_id: '<?php // PHPCS - the method get_post_id is safe. echo Plugin::$instance->editor->get_post_id(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>', }, success: function( url ) { location.assign( url ); }, error: function() { alert( 'An error occurred.' ); }, }, true ); } ); }; var isElementorLoaded = function() { if ( 'undefined' === typeof elementor ) { return false; } if ( ! elementor.loaded ) { return false; } if ( jQuery( '#elementor-loading' ).is( ':visible' ) ) { return false; } return true; }; var handleTrySafeModeNotice = function() { var $notice = jQuery( '#elementor-try-safe-mode' ); if ( isElementorLoaded() ) { $notice.remove(); return; } if ( ! $notice.data( 'visible' ) ) { $notice.attr( 'style', 'display: flex;' ); } // Re-check after 500ms. setTimeout( handleTrySafeModeNotice, 500 ); }; var init = function() { setTimeout( handleTrySafeModeNotice, <?php Utils::print_unescaped_internal_string( self::EDITOR_NOTICE_TIMEOUT ); ?> ); attachEvents(); }; init(); }; new ElementorTrySafeMode(); </script> <?php } public function run_safe_mode() { remove_action( 'elementor/editor/footer', [ $this, 'print_try_safe_mode' ] ); // Avoid notices like for comment.php. add_filter( 'deprecated_file_trigger_error', '__return_false' ); add_filter( 'template_include', [ $this, 'filter_template' ], 999 ); add_filter( 'elementor/document/urls/preview', [ $this, 'filter_preview_url' ] ); add_action( 'elementor/editor/footer', [ $this, 'print_safe_mode_notice' ] ); add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'register_scripts' ], 11 /* After Common Scripts */ ); } public function register_scripts() { wp_add_inline_script( 'elementor-common', 'elementorCommon.ajax.addRequestConstant( "elementor-mode", "safe" );' ); } private function is_enabled() { return get_option( self::OPTION_ENABLED, '' ); } private function get_admin_page_url() { // A fallback URL if the Js doesn't work. return Tools::get_url(); } public function plugin_action_links( $actions ) { $actions['disable'] = '<a href="' . self::get_admin_page_url() . '">' . esc_html__( 'Disable Safe Mode', 'elementor' ) . '</a>'; return $actions; } public function on_deactivated_plugin( $plugin ) { if ( ELEMENTOR_PLUGIN_BASE === $plugin ) { $this->disable_safe_mode(); return; } $allowed_plugins = get_option( 'elementor_safe_mode_allowed_plugins', [] ); $plugin_key = array_search( $plugin, $allowed_plugins, true ); if ( $plugin_key ) { unset( $allowed_plugins[ $plugin_key ] ); update_option( 'elementor_safe_mode_allowed_plugins', $allowed_plugins ); } } public function update_allowed_plugins() { $allowed_plugins = [ 'elementor' => ELEMENTOR_PLUGIN_BASE, ]; if ( defined( 'ELEMENTOR_PRO_PLUGIN_BASE' ) ) { $allowed_plugins['elementor_pro'] = ELEMENTOR_PRO_PLUGIN_BASE; } if ( defined( 'WC_PLUGIN_BASENAME' ) ) { $allowed_plugins['woocommerce'] = WC_PLUGIN_BASENAME; } update_option( 'elementor_safe_mode_allowed_plugins', $allowed_plugins ); } public function __construct() { if ( current_user_can( 'install_plugins' ) ) { add_action( 'elementor/admin/after_create_settings/elementor-tools', [ $this, 'add_admin_button' ] ); } add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); $plugin_file = self::MU_PLUGIN_FILE_NAME; add_filter( "plugin_action_links_{$plugin_file}", [ $this, 'plugin_action_links' ] ); // Use pre_update, in order to catch cases that $value === $old_value and it not updated. add_filter( 'pre_update_option_elementor_safe_mode', [ $this, 'on_update_safe_mode' ], 10, 2 ); add_action( 'elementor/safe_mode/init', [ $this, 'run_safe_mode' ] ); add_action( 'elementor/editor/footer', [ $this, 'print_try_safe_mode' ] ); if ( $this->is_enabled() ) { add_action( 'activated_plugin', [ $this, 'update_allowed_plugins' ] ); add_action( 'deactivated_plugin', [ $this, 'on_deactivated_plugin' ] ); } } private function is_allowed_post_type() { $allowed_post_types = [ 'post', 'page', 'product', Source_Local::CPT, ]; $current_post_type = get_post_type( Plugin::$instance->editor->get_post_id() ); return in_array( $current_post_type, $allowed_post_types ); } } safe-mode/mu-plugin/elementor-safe-mode.php 0000644 00000007466 15252521347 0014705 0 ustar 00 <?php /** * Plugin Name: Elementor Safe Mode * Description: Safe Mode allows you to troubleshoot issues by only loading the editor, without loading the theme or any other plugin. * Plugin URI: https://elementor.com/?utm_source=safe-mode&utm_campaign=plugin-uri&utm_medium=wp-dash * Author: Elementor.com * Version: 1.0.0 * Author URI: https://elementor.com/?utm_source=safe-mode&utm_campaign=author-uri&utm_medium=wp-dash * * Text Domain: elementor * * @package Elementor * @category Safe Mode * * Elementor is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * Elementor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Safe_Mode { const OPTION_ENABLED = 'elementor_safe_mode'; const OPTION_TOKEN = self::OPTION_ENABLED . '_token'; public function is_enabled() { return get_option( self::OPTION_ENABLED ); } public function is_valid_token() { $token = isset( $_COOKIE[ self::OPTION_TOKEN ] ) ? wp_kses_post( wp_unslash( $_COOKIE[ self::OPTION_TOKEN ] ) ) : null; if ( $token && get_option( self::OPTION_TOKEN ) === $token ) { return true; } return false; } public function is_requested() { return ! empty( $_REQUEST['elementor-mode'] ) && 'safe' === $_REQUEST['elementor-mode']; } public function is_editor() { return is_admin() && isset( $_GET['action'] ) && 'elementor' === $_GET['action']; } public function is_editor_preview() { return isset( $_GET['elementor-preview'] ); } public function is_editor_ajax() { // PHPCS - There is already nonce verification in the Ajax Manager return is_admin() && isset( $_POST['action'] ) && 'elementor_ajax' === $_POST['action']; // phpcs:ignore WordPress.Security.NonceVerification.Missing } public function add_hooks() { add_filter( 'pre_option_active_plugins', function () { return get_option( 'elementor_safe_mode_allowed_plugins' ); } ); add_filter( 'pre_option_stylesheet', function () { return 'elementor-safe'; } ); add_filter( 'pre_option_template', function () { return 'elementor-safe'; } ); add_action( 'elementor/init', function () { do_action( 'elementor/safe_mode/init' ); } ); } /** * Plugin row meta. * * Adds row meta links to the plugin list table * * Fired by `plugin_row_meta` filter. * * @access public * * @param array $plugin_meta An array of the plugin's metadata, including * the version, author, author URI, and plugin URI. * @param string $plugin_file Path to the plugin file, relative to the plugins * directory. * * @return array An array of plugin row meta links. */ public function plugin_row_meta( $plugin_meta, $plugin_file, $plugin_data, $status ) { if ( basename( __FILE__ ) === $plugin_file ) { $row_meta = [ 'docs' => '<a href="https://go.elementor.com/safe-mode/" target="_blank">' . esc_html__( 'Learn More', 'elementor' ) . '</a>', ]; $plugin_meta = array_merge( $plugin_meta, $row_meta ); } return $plugin_meta; } public function __construct() { add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 4 ); $enabled_type = $this->is_enabled(); if ( ! $enabled_type || ! $this->is_valid_token() ) { return; } if ( ! $this->is_requested() && 'global' !== $enabled_type ) { return; } if ( ! $this->is_editor() && ! $this->is_editor_preview() && ! $this->is_editor_ajax() ) { return; } $this->add_hooks(); } } new Safe_Mode(); assets-manager/module.php 0000644 00000005650 15252521347 0011470 0 ustar 00 <?php namespace Elementor\Modules\AssetsManager; use Elementor\Plugin; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as Experiments_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; } class Module extends BaseModule { const MODULE_NAME = 'assets-manager'; const EXPERIMENT_NAME = 'e_assets_manager'; private $style_assets; private $script_assets; public function get_name() { return self::MODULE_NAME; } public static function get_experimental_data() { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Dynamic Assets Management', 'elementor' ), 'description' => esc_html__( 'Enable dynamic assets management (JS and CSS lazy-loading).', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_INACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_DEV, ]; } public function is_experiment_active() { return Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ); } public function __construct() { parent::__construct(); if ( ! $this->is_experiment_active() ) { return; } $this->init(); $this->register_hooks(); } private function init() { $this->style_assets = new Assets(); $this->script_assets = new Assets(); return $this; } private function register_hooks() { add_action( 'elementor/editor/after_enqueue_styles', [ $this, 'manage_style_assets' ], PHP_INT_MAX ); add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'manage_script_assets' ], PHP_INT_MAX ); add_action( 'elementor/preview/enqueue_styles', [ $this, 'manage_style_assets' ], PHP_INT_MAX ); add_action( 'elementor/preview/enqueue_scripts', [ $this, 'manage_script_assets' ], PHP_INT_MAX ); add_action( 'elementor/editor/footer', [ $this, 'manage_assets' ], PHP_INT_MAX ); return $this; } public function manage_style_assets() { do_action( 'elementor/assets-manager/register_styles', $this->style_assets ); foreach ( array_keys( $this->style_assets->assets_map() ) as $handle ) { wp_dequeue_style( $handle ); } return $this; } public function manage_script_assets() { do_action( 'elementor/assets-manager/register_scripts', $this->script_assets ); foreach ( array_keys( $this->script_assets->assets_map() ) as $handle ) { wp_dequeue_script( $handle ); } return $this; } public function manage_assets() { wp_enqueue_script( self::MODULE_NAME, $this->get_js_assets_url( self::MODULE_NAME ), [], ELEMENTOR_VERSION, [ 'in_footer' => true, 'strategy' => 'defer', ] ); wp_localize_script( self::MODULE_NAME, 'elementorAssetsManager', [ 'styles' => [ 'map' => $this->style_assets->assets_map(), 'priority_queue' => $this->style_assets->priority_queue(), ], 'scripts' => [ 'map' => $this->script_assets->assets_map(), 'priority_queue' => $this->script_assets->priority_queue(), ], ] ); return $this; } } assets-manager/assets.php 0000644 00000003273 15252521347 0011504 0 ustar 00 <?php namespace Elementor\Modules\AssetsManager; if ( ! defined( 'ABSPATH' ) ) { exit; } class Assets { private $assets; private $assets_map; public function __construct() { $this->assets = []; $this->assets_map = []; } public function append( $handle, $uri, $dependencies = [], $version = '', $options = [] ) { if ( ! array_key_exists( $handle, $this->assets_map ) ) { $this->assets_map[ $handle ] = [ 'uri' => $uri . ( $version ? '?ver=' . $version : '' ), 'options' => $options, ]; $this->assets[ $handle ] = $dependencies; } return $this; } public function assets_map() { return $this->assets_map; } public function priority_queue() { $graph = []; $in_degree = []; foreach ( $this->assets as $handle => $dependencies ) { if ( ! array_key_exists( $handle, $in_degree ) ) { $in_degree[ $handle ] = 0; } foreach ( $dependencies as $dependency ) { if ( ! array_key_exists( $dependency, $graph ) ) { $graph[ $dependency ] = []; } $graph[ $dependency ][] = $handle; $in_degree[ $handle ]++; if ( ! array_key_exists( $dependency, $in_degree ) ) { $in_degree[ $dependency ] = 0; } } } $queue = new \SplQueue(); foreach ( $in_degree as $handle => $count ) { if ( 0 === $count ) { $queue->enqueue( $handle ); } } $priority_queue = []; while ( ! $queue->isEmpty() ) { $current = $queue->dequeue(); $priority_queue[] = $current; if ( ! array_key_exists( $current, $graph ) ) { continue; } foreach ( $graph[ $current ] as $next ) { $in_degree[ $next ]--; if ( 0 === $in_degree[ $next ] ) { $queue->enqueue( $next ); } } } return $priority_queue; } } system-info/reporters/mu-plugins.php 0000644 00000003663 15252521347 0013675 0 ustar 00 <?php namespace Elementor\Modules\System_Info\Reporters; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor must-use plugins report. * * Elementor system report handler class responsible for generating a report for * must-use plugins. * * @since 1.0.0 */ class MU_Plugins extends Base_Plugin { /** * Must-Use plugins. * * Holds the sites must-use plugins list. * * @since 1.0.0 * @access private * * @var array */ private $plugins; /** * Get must-use plugins. * * Retrieve the must-use plugins. * * @since 2.0.0 * @access private * * @return array Must-Use plugins. */ private function get_mu_plugins() { if ( ! $this->plugins ) { $this->plugins = get_mu_plugins(); } return $this->plugins; } /** * Is enabled. * * Whether there are must-use plugins or not. * * @since 1.0.0 * @access public * * @return bool True if the site has must-use plugins, False otherwise. */ public function is_enabled() { return (bool) $this->get_mu_plugins(); } /** * Get must-use plugins reporter title. * * Retrieve must-use plugins reporter title. * * @since 1.0.0 * @access public * * @return string Reporter title. */ public function get_title() { return 'Must-Use Plugins'; } /** * Get must-use plugins report fields. * * Retrieve the required fields for the must-use plugins report. * * @since 1.0.0 * @access public * * @return array Required report fields with field ID and field label. */ public function get_fields() { return [ 'must_use_plugins' => 'Must-Use Plugins', ]; } /** * Get must-use plugins. * * Retrieve the sites must-use plugins. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The must-use plugins list. * } */ public function get_must_use_plugins() { return [ 'value' => $this->get_mu_plugins(), ]; } } system-info/reporters/server.php 0000644 00000027004 15252521347 0013076 0 ustar 00 <?php namespace Elementor\Modules\System_Info\Reporters; use Elementor\Api; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor server environment report. * * Elementor system report handler class responsible for generating a report for * the server environment. * * @since 1.0.0 */ class Server extends Base { const KEY_PATH_WP_CONTENT_DIR = 'wp_content'; const KEY_PATH_UPLOADS_DIR = 'uploads'; const KEY_PATH_ELEMENTOR_UPLOADS_DIR = 'elementor_uploads'; const KEY_PATH_HTACCESS_FILE = '.htaccess'; /** * Get server environment reporter title. * * Retrieve server environment reporter title. * * @since 1.0.0 * @access public * * @return string Reporter title. */ public function get_title() { return 'Server Environment'; } /** * Get server environment report fields. * * Retrieve the required fields for the server environment report. * * @since 1.0.0 * @access public * * @return array Required report fields with field ID and field label. */ public function get_fields() { return [ 'os' => 'Operating System', 'software' => 'Software', 'mysql_version' => 'MySQL version', 'php_version' => 'PHP Version', 'php_memory_limit' => 'PHP Memory Limit', 'php_max_input_vars' => 'PHP Max Input Vars', 'php_max_post_size' => 'PHP Max Post Size', 'gd_installed' => 'GD Installed', 'zip_installed' => 'ZIP Installed', 'write_permissions' => 'Write Permissions', 'elementor_library' => 'Elementor Library', ]; } /** * Get server operating system. * * Retrieve the server operating system. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value Server operating system. * } */ public function get_os() { return [ 'value' => PHP_OS, ]; } /** * Get server software. * * Retrieve the server software. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value Server software. * } */ public function get_software() { return [ 'value' => Utils::get_super_global_value( $_SERVER, 'SERVER_SOFTWARE' ), ]; } /** * Get PHP version. * * Retrieve the PHP version. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value PHP version. * @type string $recommendation Minimum PHP version recommendation. * @type bool $warning Whether to display a warning. * } */ public function get_php_version() { $result = [ 'value' => PHP_VERSION, ]; $recommended_php_version = '7.4'; if ( version_compare( $result['value'], $recommended_php_version, '<' ) ) { $result['recommendation'] = sprintf( /* translators: %s: Recommended PHP version. */ esc_html__( 'We recommend using PHP version %s or higher.', 'elementor' ), $recommended_php_version ); $result['warning'] = true; } return $result; } /** * Get PHP memory limit. * * Retrieve the PHP memory limit. * * @return array { * Report data. * * @type string $value PHP memory limit. * @type string $recommendation Recommendation memory limit. * @type bool $warning Whether to display a warning. True if the limit * is below the recommended 128M, False otherwise. * } */ public function get_php_memory_limit() { $result = [ 'value' => (string) ini_get( 'memory_limit' ), ]; $min_recommended_memory = '128M'; $preferred_memory = '256M'; $memory_limit_bytes = wp_convert_hr_to_bytes( $result['value'] ); $min_recommended_bytes = wp_convert_hr_to_bytes( $min_recommended_memory ); if ( $memory_limit_bytes < $min_recommended_bytes ) { $result['recommendation'] = sprintf( /* translators: 1: Minimum recommended_memory, 2: Preferred memory, 3: WordPress wp-config memory documentation. */ __( 'We recommend setting memory to at least %1$s. (%2$s or higher is preferred) For more information, read about <a href="%3$s">how to increase memory allocated to PHP</a>.', 'elementor' ), $min_recommended_memory, $preferred_memory, 'https://go.elementor.com/wordpress-wp-config-memory/' ); $result['warning'] = true; } return $result; } /** * Get PHP `max_input_vars`. * * Retrieve the value of `max_input_vars` from `php.ini` configuration file. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value PHP `max_input_vars`. * } */ public function get_php_max_input_vars() { return [ 'value' => ini_get( 'max_input_vars' ), ]; } /** * Get PHP `post_max_size`. * * Retrieve the value of `post_max_size` from `php.ini` configuration file. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value PHP `post_max_size`. * } */ public function get_php_max_post_size() { return [ 'value' => ini_get( 'post_max_size' ), ]; } /** * Get GD installed. * * Whether the GD extension is installed. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value Yes if the GD extension is installed, No otherwise. * @type bool $warning Whether to display a warning. True if the GD extension is installed, False otherwise. * } */ public function get_gd_installed() { $gd_installed = extension_loaded( 'gd' ); return [ 'value' => $gd_installed ? 'Yes' : 'No', 'warning' => ! $gd_installed, ]; } /** * Get ZIP installed. * * Whether the ZIP extension is installed. * * @since 2.1.0 * @access public * * @return array { * Report data. * * @type string $value Yes if the ZIP extension is installed, No otherwise. * @type bool $warning Whether to display a warning. True if the ZIP extension is installed, False otherwise. * } */ public function get_zip_installed() { $zip_installed = extension_loaded( 'zip' ); return [ 'value' => $zip_installed ? 'Yes' : 'No', 'warning' => ! $zip_installed, ]; } /** * Get MySQL version. * * Retrieve the MySQL version. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value MySQL version. * } */ public function get_mysql_version() { global $wpdb; $db_server_version = $wpdb->get_results( "SHOW VARIABLES WHERE `Variable_name` IN ( 'version_comment', 'innodb_version' )", OBJECT_K ); $db_server_version_string = $db_server_version['version_comment']->Value . ' v'; // On some hosts, `innodb_version` is empty, in PHP 8.1. if ( isset( $db_server_version['innodb_version'] ) ) { $db_server_version_string .= $db_server_version['innodb_version']->Value; } else { $db_server_version_string .= $wpdb->get_var( 'SELECT VERSION() AS version' ); } return [ 'value' => $db_server_version_string, ]; } /** * Get write permissions. * Check whether the required paths for have writing permissions. * * @since 1.9.0 * @access public * * @return array { * Report data. * * @type string $value Writing permissions status. * @type bool $warning Whether to display a warning. True if some required * folders don't have writing permissions, False otherwise. * } */ public function get_write_permissions(): array { $paths_to_check = [ static::KEY_PATH_HTACCESS_FILE => $this->get_system_path( static::KEY_PATH_HTACCESS_FILE ), static::KEY_PATH_UPLOADS_DIR => $this->get_system_path( static::KEY_PATH_UPLOADS_DIR ), static::KEY_PATH_ELEMENTOR_UPLOADS_DIR => $this->get_system_path( static::KEY_PATH_ELEMENTOR_UPLOADS_DIR ), ]; $paths_permissions = $this->get_paths_permissions( $paths_to_check ); $write_problems = []; if ( ! $paths_permissions[ static::KEY_PATH_UPLOADS_DIR ]['write'] ) { $write_problems[] = 'WordPress uploads directory'; } if ( $paths_permissions[ self::KEY_PATH_ELEMENTOR_UPLOADS_DIR ]['exists'] && ! $paths_permissions[ self::KEY_PATH_ELEMENTOR_UPLOADS_DIR ]['write'] ) { $write_problems[] = 'Elementor uploads directory'; } if ( $paths_permissions[ self::KEY_PATH_HTACCESS_FILE ]['exists'] && ! $paths_permissions[ self::KEY_PATH_HTACCESS_FILE ]['write'] ) { $write_problems[] = '.htaccess file'; } if ( $write_problems ) { $value = 'There are some writing permissions issues with the following directories/files:' . "\n\t\t - "; $value .= implode( "\n\t\t - ", $write_problems ); } else { $value = 'All right'; } return [ 'value' => $value, 'warning' => (bool) $write_problems, ]; } /** * Check for elementor library connectivity. * * Check whether the remote elementor library is reachable. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The status of elementor library connectivity. * @type bool $warning Whether to display a warning. True if elementor * * library is not reachable, False otherwise. * } */ public function get_elementor_library() { $response = wp_remote_get( Api::$api_info_url, [ 'timeout' => 5, 'body' => [ // Which API version is used 'api_version' => ELEMENTOR_VERSION, // Which language to return 'site_lang' => get_bloginfo( 'language' ), ], ] ); if ( is_wp_error( $response ) ) { return [ 'value' => 'Not connected (' . $response->get_error_message() . ')', 'warning' => true, ]; } $http_response_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== (int) $http_response_code ) { $error_msg = 'HTTP Error (' . $http_response_code . ')'; return [ 'value' => 'Not connected (' . $error_msg . ')', 'warning' => true, ]; } $info_data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( empty( $info_data ) ) { return [ 'value' => 'Not connected (Returns invalid JSON)', 'warning' => true, ]; } return [ 'value' => 'Connected', ]; } /** * @param $paths [] Paths to check permissions. * @return array []{exists: bool, read: bool, write: bool, execute: bool} */ public function get_paths_permissions( $paths ): array { $permissions = []; foreach ( $paths as $key_path => $path ) { $permissions[ $key_path ] = $this->get_path_permissions( $path ); } return $permissions; } /** * Get path by path key. * * @param $path_key * @return string */ public function get_system_path( $path_key ): string { switch ( $path_key ) { case static::KEY_PATH_WP_CONTENT_DIR: return WP_CONTENT_DIR; case static::KEY_PATH_HTACCESS_FILE: return file_exists( ABSPATH . '/.htaccess' ) ? ABSPATH . '/.htaccess' : ''; case static::KEY_PATH_UPLOADS_DIR: return wp_upload_dir()['basedir'] ?? ''; case static::KEY_PATH_ELEMENTOR_UPLOADS_DIR: if ( empty( wp_upload_dir()['basedir'] ) ) { return ''; } $elementor_uploads_dir = wp_upload_dir()['basedir'] . '/elementor'; return is_dir( $elementor_uploads_dir ) ? $elementor_uploads_dir : ''; default: return ''; } } /** * Check the permissions of a path. * * @param $path * @return array{exists: bool, read: bool, write: bool, execute: bool} */ public function get_path_permissions( $path ): array { if ( empty( $path ) ) { return [ 'exists' => false, 'read' => false, 'write' => false, 'execute' => false, ]; } return [ 'exists' => true, 'read' => is_readable( $path ), 'write' => is_writeable( $path ), 'execute' => is_executable( $path ), ]; } } system-info/reporters/base-plugin.php 0000644 00000004201 15252521347 0013770 0 ustar 00 <?php namespace Elementor\Modules\System_Info\Reporters; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Plugin extends Base { public static $required_plugins_properties = [ 'Name', 'Version', 'URL', 'Author', ]; public function print_html() { foreach ( $this->get_report( 'html' ) as $field ) { foreach ( $field['value'] as $plugin_info ) : ?> <tr> <td><?php if ( $plugin_info['PluginURI'] ) : $plugin_name = sprintf( '<a href="%s">%s</a>', $plugin_info['PluginURI'], $plugin_info['Name'] ); else : $plugin_name = $plugin_info['Name']; endif; if ( $plugin_info['Version'] ) : $plugin_name .= ' - ' . $plugin_info['Version']; endif; Utils::print_unescaped_internal_string( $plugin_name ); ?></td> <td><?php if ( $plugin_info['Author'] ) : if ( $plugin_info['AuthorURI'] ) : $author = sprintf( '<a href="%s">%s</a>', $plugin_info['AuthorURI'], $plugin_info['Author'] ); else : $author = $plugin_info['Author']; endif; Utils::print_unescaped_internal_string( "By $author" ); endif; ?></td> <td></td> </tr> <?php endforeach; } } public function print_raw( $tabs_count ) { echo PHP_EOL; $required_plugins_properties = array_flip( self::$required_plugins_properties ); unset( $required_plugins_properties['Name'] ); foreach ( $this->get_report( 'raw' ) as $field_name => $field ) : $sub_indent = str_repeat( "\t", $tabs_count ); echo "== {$field['label']} ==" . PHP_EOL; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped foreach ( $field['value'] as $plugin_info ) : $plugin_properties = array_intersect_key( $plugin_info, $required_plugins_properties ); echo esc_html( $sub_indent . $plugin_info['Name'] ); foreach ( $plugin_properties as $property_name => $property ) : echo PHP_EOL . "{$sub_indent}\t{$property_name}: {$property}"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped endforeach; echo PHP_EOL . PHP_EOL; endforeach; endforeach; } } system-info/reporters/theme.php 0000644 00000012020 15252521347 0012662 0 ustar 00 <?php namespace Elementor\Modules\System_Info\Reporters; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor theme report. * * Elementor system report handler class responsible for generating a report for * the theme. * * @since 1.0.0 */ class Theme extends Base { /** * Theme. * * Holds the sites theme object. * * @since 1.0.0 * @access private * * @var \WP_Theme WordPress theme object. */ private $theme = null; /** * Get theme reporter title. * * Retrieve theme reporter title. * * @since 1.0.0 * @access public * * @return string Reporter title. */ public function get_title() { return 'Theme'; } /** * Get theme report fields. * * Retrieve the required fields for the theme report. * * @since 1.0.0 * @access public * * @return array Required report fields with field ID and field label. */ public function get_fields() { $fields = [ 'name' => 'Name', 'version' => 'Version', 'author' => 'Author', 'is_child_theme' => 'Child Theme', ]; if ( $this->get_parent_theme() ) { $parent_fields = [ 'parent_name' => 'Parent Theme Name', 'parent_version' => 'Parent Theme Version', 'parent_author' => 'Parent Theme Author', ]; $fields = array_merge( $fields, $parent_fields ); } return $fields; } /** * Get theme. * * Retrieve the theme. * * @since 1.0.0 * @deprecated 3.1.0 Use `get_theme()` method instead. * @access protected * * @return \WP_Theme WordPress theme object. */ protected function _get_theme() { Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', 'get_theme()' ); return $this->get_theme(); } /** * Get theme. * * Retrieve the theme. * * @since 3.1.0 * @access private * * @return \WP_Theme WordPress theme object. */ private function get_theme() { if ( is_null( $this->theme ) ) { $this->theme = wp_get_theme(); } return $this->theme; } /** * Get parent theme. * * Retrieve the parent theme. * * @since 1.0.0 * @access protected * * @return \WP_Theme|false WordPress theme object, or false if the current theme is not a child theme. */ protected function get_parent_theme() { return $this->get_theme()->parent(); } /** * Get theme name. * * Retrieve the theme name. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The theme name. * } */ public function get_name() { return [ 'value' => $this->get_theme()->get( 'Name' ), ]; } /** * Get theme author. * * Retrieve the theme author. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The theme author. * } */ public function get_author() { return [ 'value' => $this->get_theme()->get( 'Author' ), ]; } /** * Get theme version. * * Retrieve the theme version. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The theme version. * } */ public function get_version() { return [ 'value' => $this->get_theme()->get( 'Version' ), ]; } /** * Is the theme is a child theme. * * Whether the theme is a child theme. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value Yes if the theme is a child theme, No otherwise. * @type string $recommendation Theme source code modification recommendation. * } */ public function get_is_child_theme() { $is_child_theme = is_child_theme(); $result = [ 'value' => $is_child_theme ? 'Yes' : 'No', ]; if ( ! $is_child_theme ) { $result['recommendation'] = sprintf( /* translators: %s: WordPress child themes documentation. */ __( 'If you want to modify the source code of your theme, we recommend using a <a href="%s">child theme</a>.', 'elementor' ), 'https://go.elementor.com/wordpress-child-themes/' ); } return $result; } /** * Get parent theme version. * * Retrieve the parent theme version. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The parent theme version. * } */ public function get_parent_version() { return [ 'value' => $this->get_parent_theme()->get( 'Version' ), ]; } /** * Get parent theme author. * * Retrieve the parent theme author. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The parent theme author. * } */ public function get_parent_author() { return [ 'value' => $this->get_parent_theme()->get( 'Author' ), ]; } /** * Get parent theme name. * * Retrieve the parent theme name. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The parent theme name. * } */ public function get_parent_name() { return [ 'value' => $this->get_parent_theme()->get( 'Name' ), ]; } } system-info/reporters/plugins.php 0000644 00000003743 15252521347 0013255 0 ustar 00 <?php namespace Elementor\Modules\System_Info\Reporters; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor active plugins report. * * Elementor system report handler class responsible for generating a report for * active plugins. * * @since 1.0.0 */ class Plugins extends Base_Plugin { /** * Active plugins. * * Holds the sites active plugins list. * * @since 1.0.0 * @access private * * @var array */ private $plugins; /** * Get active plugins. * * Retrieve the active plugins from the list of all the installed plugins. * * @since 2.0.0 * @access private * * @return array Active plugins. */ private function get_plugins() { if ( ! $this->plugins ) { $this->plugins = Plugin::$instance->wp->get_active_plugins()->all(); } return $this->plugins; } /** * Get active plugins reporter title. * * Retrieve active plugins reporter title. * * @since 1.0.0 * @access public * * @return string Reporter title. */ public function get_title() { return 'Active Plugins'; } /** * Is enabled. * * Whether there are active plugins or not. * * @since 1.0.0 * @access public * * @return bool True if the site has active plugins, False otherwise. */ public function is_enabled() { return (bool) $this->get_plugins(); } /** * Get active plugins report fields. * * Retrieve the required fields for the active plugins report. * * @since 1.0.0 * @access public * * @return array Required report fields with field ID and field label. */ public function get_fields() { return [ 'active_plugins' => 'Active Plugins', ]; } /** * Get active plugins. * * Retrieve the sites active plugins. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The active plugins list. * } */ public function get_active_plugins() { return [ 'value' => $this->get_plugins(), ]; } } system-info/reporters/wordpress.php 0000644 00000012544 15252521347 0013623 0 ustar 00 <?php namespace Elementor\Modules\System_Info\Reporters; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor WordPress environment report. * * Elementor system report handler class responsible for generating a report for * the WordPress environment. * * @since 1.0.0 */ class WordPress extends Base { /** * Get WordPress environment reporter title. * * Retrieve WordPress environment reporter title. * * @since 1.0.0 * @access public * * @return string Reporter title. */ public function get_title() { return 'WordPress Environment'; } /** * Get WordPress environment report fields. * * Retrieve the required fields for the WordPress environment report. * * @since 1.0.0 * @access public * * @return array Required report fields with field ID and field label. */ public function get_fields() { return [ 'version' => 'Version', 'site_url' => 'Site URL', 'home_url' => 'Home URL', 'is_multisite' => 'WP Multisite', 'max_upload_size' => 'Max Upload Size', 'memory_limit' => 'Memory limit', 'max_memory_limit' => 'Max Memory limit', 'permalink_structure' => 'Permalink Structure', 'language' => 'Language', 'timezone' => 'Timezone', 'admin_email' => 'Admin Email', 'debug_mode' => 'Debug Mode', ]; } /** * Get WordPress memory limit. * * Retrieve the WordPress memory limit. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value WordPress memory limit. * } */ public function get_memory_limit() { return [ 'value' => (string) WP_MEMORY_LIMIT, ]; } /** * Get WordPress max memory limit. * * Retrieve the WordPress max memory limit. * * @return array { * Report data. * * @type string $value WordPress max memory limit. * } */ public function get_max_memory_limit() { return [ 'value' => (string) WP_MAX_MEMORY_LIMIT, ]; } /** * Get WordPress version. * * Retrieve the WordPress version. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value WordPress version. * } */ public function get_version() { return [ 'value' => get_bloginfo( 'version' ), ]; } /** * Is multisite. * * Whether multisite is enabled or not. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value Yes if multisite is enabled, No otherwise. * } */ public function get_is_multisite() { return [ 'value' => is_multisite() ? 'Yes' : 'No', ]; } /** * Get site URL. * * Retrieve WordPress site URL. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value WordPress site URL. * } */ public function get_site_url() { return [ 'value' => get_site_url(), ]; } /** * Get home URL. * * Retrieve WordPress home URL. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value WordPress home URL. * } */ public function get_home_url() { return [ 'value' => get_home_url(), ]; } /** * Get permalink structure. * * Retrieve the permalink structure * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value WordPress permalink structure. * } */ public function get_permalink_structure() { global $wp_rewrite; $structure = $wp_rewrite->permalink_structure; if ( ! $structure ) { $structure = 'Plain'; } return [ 'value' => $structure, ]; } /** * Get site language. * * Retrieve the site language. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value WordPress site language. * } */ public function get_language() { return [ 'value' => get_locale(), ]; } /** * Get PHP `max_upload_size`. * * Retrieve the value of maximum upload file size defined in `php.ini` configuration file. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value Maximum upload file size allowed. * } */ public function get_max_upload_size() { return [ 'value' => size_format( wp_max_upload_size() ), ]; } /** * Get WordPress timezone. * * Retrieve WordPress timezone. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value WordPress timezone. * } */ public function get_timezone() { $timezone = get_option( 'timezone_string' ); if ( ! $timezone ) { $timezone = get_option( 'gmt_offset' ); } return [ 'value' => $timezone, ]; } /** * Get WordPress administrator email. * * Retrieve WordPress administrator email. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value WordPress administrator email. * } */ public function get_admin_email() { return [ 'value' => get_option( 'admin_email' ), ]; } /** * Get debug mode. * * Whether WordPress debug mode is enabled or not. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value Active if debug mode is enabled, Inactive otherwise. * } */ public function get_debug_mode() { return [ 'value' => WP_DEBUG ? 'Active' : 'Inactive', ]; } } system-info/reporters/base.php 0000644 00000012512 15252521347 0012500 0 ustar 00 <?php namespace Elementor\Modules\System_Info\Reporters; use Elementor\Modules\System_Info\Helpers\Model_Helper; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor base reporter. * * A base abstract class that provides the needed properties and methods to * manage and handle reporter in inheriting classes. * * @since 2.9.0 * @abstract */ abstract class Base { /** * Reporter properties. * * Holds the list of all the properties of the report. * * @access protected * @static * * @var array */ protected $_properties; /** * Get report title. * * Retrieve the title of the report. * * @since 2.9.0 * @access public * @abstract */ abstract public function get_title(); /** * Get report fields. * * Retrieve the required fields for the report. * * @since 2.9.0 * @access public * @abstract */ abstract public function get_fields(); /** * Is report enabled. * * Whether the report is enabled. * * @since 2.9.0 * @access public * * @return bool Whether the report is enabled. */ public function is_enabled() { return true; } public function print_html() { foreach ( $this->get_report( 'html' ) as $field ) { $warning_class = ! empty( $field['warning'] ) ? ' class="elementor-warning"' : ''; $log_label = ! empty( $field['label'] ) ? $field['label'] . ':' : ''; ?> <tr<?php Utils::print_unescaped_internal_string( $warning_class ); ?>> <td><?php Utils::print_unescaped_internal_string( $log_label ); ?></td> <td><?php Utils::print_unescaped_internal_string( $field['value'] ); ?></td> <td><?php if ( ! empty( $field['recommendation'] ) ) : Utils::print_unescaped_internal_string( $field['recommendation'] ); endif; ?></td> </tr> <?php } } public function print_html_label( $label ) { Utils::print_unescaped_internal_string( $label ); } public function print_raw( $tabs_count ) { $indent = str_repeat( "\t", $tabs_count - 1 ); $report = $this->get_report( 'raw' ); echo PHP_EOL . $indent . '== ' . $this->get_title() . ' =='; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo PHP_EOL; foreach ( $report as $field_name => $field ) : $sub_indent = str_repeat( "\t", $tabs_count ); $label = $field['label']; if ( ! empty( $label ) ) { $label .= ': '; } echo "{$sub_indent}{$label}{$field['value']}" . PHP_EOL; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped endforeach; } /** * Get report. * * Retrieve the report with all it's containing fields. * * @since 2.9.0 * @access public * * @return \WP_Error | array { * Report fields. * * @type string $name Field name. * @type string $label Field label. * } */ final public function get_report( $format = '' ) { $result = []; $format = ( empty( $format ) ) ? '' : $format . '_'; foreach ( $this->get_fields() as $field_name => $field_label ) { $method = 'get_' . $format . $field_name; if ( ! method_exists( $this, $method ) ) { $method = 'get_' . $field_name; // fallback: if ( ! method_exists( $this, $method ) ) { return new \WP_Error( sprintf( "Getter method for the field '%s' wasn't found in %s.", $field_name, get_called_class() ) ); } } $reporter_field = [ 'name' => $field_name, 'label' => $field_label, ]; $reporter_field = array_merge( $reporter_field, $this->$method() ); $result[ $field_name ] = $reporter_field; } return $result; } /** * Get properties keys. * * Retrieve the keys of the properties. * * @since 2.9.0 * @access public * @static * * @return array { * Property keys. * * @type string $name Property name. * @type string $fields Property fields. * } */ public static function get_properties_keys() { return [ 'name', 'format', 'fields', ]; } /** * Filter possible properties. * * Retrieve possible properties filtered by property keys. * * @since 2.9.0 * @access public * @static * * @param array $properties Properties to filter. * * @return array Possible properties filtered by property keys. */ final public static function filter_possible_properties( $properties ) { return Model_Helper::filter_possible_properties( self::get_properties_keys(), $properties ); } /** * Set properties. * * Add/update properties to the report. * * @since 2.9.0 * @access public * * @param array $key Property key. * @param array $value Optional. Property value. Default is `null`. */ final public function set_properties( $key, $value = null ) { if ( is_array( $key ) ) { $key = self::filter_possible_properties( $key ); foreach ( $key as $sub_key => $sub_value ) { $this->set_properties( $sub_key, $sub_value ); } return; } if ( ! in_array( $key, self::get_properties_keys(), true ) ) { return; } $this->_properties[ $key ] = $value; } /** * Reporter base constructor. * * Initializing the reporter base class. * * @since 2.9.0 * @access public * * @param array $properties Optional. Properties to filter. Default is `null`. */ public function __construct( $properties = null ) { $this->_properties = array_fill_keys( self::get_properties_keys(), null ); if ( $properties ) { $this->set_properties( $properties, null ); } } } system-info/reporters/network-plugins.php 0000644 00000004275 15252521347 0014745 0 ustar 00 <?php namespace Elementor\Modules\System_Info\Reporters; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor network plugins report. * * Elementor system report handler class responsible for generating a report for * network plugins. * * @since 1.0.0 */ class Network_Plugins extends Base_Plugin { /** * Network plugins. * * Holds the sites network plugins list. * * @since 1.0.0 * @access private * * @var array */ private $plugins; /** * Get network plugins reporter title. * * Retrieve network plugins reporter title. * * @since 1.0.0 * @access public * * @return string Reporter title. */ public function get_title() { return 'Network Plugins'; } /** * Get active network plugins. * * Retrieve the active network plugins from the list of active site-wide plugins. * * @since 2.0.0 * @access private * * @return array Active network plugins. */ private function get_network_plugins() { if ( ! $this->plugins ) { $active_plugins = get_site_option( 'active_sitewide_plugins' ); $this->plugins = array_intersect_key( get_plugins(), $active_plugins ); } return $this->plugins; } /** * Is enabled. * * Whether there are active network plugins or not. * * @since 1.0.0 * @access public * * @return bool True if the site has active network plugins, False otherwise. */ public function is_enabled() { if ( ! is_multisite() ) { return false; } return (bool) $this->get_network_plugins(); } /** * Get network plugins report fields. * * Retrieve the required fields for the network plugins report. * * @since 1.0.0 * @access public * * @return array Required report fields with field ID and field label. */ public function get_fields() { return [ 'network_active_plugins' => 'Network Plugins', ]; } /** * Get active network plugins. * * Retrieve the sites active network plugins. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The active network plugins list. * } */ public function get_network_active_plugins() { return [ 'value' => $this->get_network_plugins(), ]; } } system-info/reporters/user.php 0000644 00000003761 15252521347 0012552 0 ustar 00 <?php namespace Elementor\Modules\System_Info\Reporters; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor user report. * * Elementor system report handler class responsible for generating a report for * the user. * * @since 1.0.0 */ class User extends Base { public function is_enabled() { return (bool) wp_get_current_user()->ID; } /** * Get user reporter title. * * Retrieve user reporter title. * * @since 1.0.0 * @access public * * @return string Reporter title. */ public function get_title() { return 'User'; } /** * Get user report fields. * * Retrieve the required fields for the user report. * * @since 1.0.0 * @access public * * @return array Required report fields with field ID and field label. */ public function get_fields() { return [ 'role' => 'Role', 'locale' => 'WP Profile lang', 'agent' => 'User Agent', ]; } /** * Get user role. * * Retrieve the user role. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value The user role. * } */ public function get_role() { $role = null; $current_user = wp_get_current_user(); if ( ! empty( $current_user->roles ) ) { $role = $current_user->roles[0]; } return [ 'value' => $role, ]; } /** * Get user profile language. * * Retrieve the user profile language. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value User profile language. * } */ public function get_locale() { return [ 'value' => get_bloginfo( 'language' ), ]; } /** * Get user agent. * * Retrieve user agent. * * @since 1.0.0 * @access public * * @return array { * Report data. * * @type string $value HTTP user agent. * } */ public function get_agent() { return [ 'value' => esc_html( Utils::get_super_global_value( $_SERVER, 'HTTP_USER_AGENT' ) ), ]; } } system-info/helpers/model-helper.php 0000644 00000003312 15252521347 0013556 0 ustar 00 <?php namespace Elementor\Modules\System_Info\Helpers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor model helper. * * Elementor model helper handler class is responsible for filtering properties. * * @since 1.0.0 */ final class Model_Helper { /** * Model helper constructor. * * Initializing the model helper class. * * @since 1.0.0 * @access private */ private function __construct() {} /** * Filter possible properties. * * Retrieve possible properties filtered by property intersect key. * * @since 1.0.0 * @access public * @static * * @param array $possible_properties All the possible properties. * @param array $properties Properties to filter. * * @return array Possible properties filtered by property intersect key. */ public static function filter_possible_properties( $possible_properties, $properties ) { $properties_keys = array_flip( $possible_properties ); return array_intersect_key( $properties, $properties_keys ); } /** * Prepare properties. * * Combine the possible properties with the user properties and filter them. * * @since 1.0.0 * @access public * @static * * @param array $possible_properties All the possible properties. * @param array $user_properties User properties. * * @return array Possible properties and user properties filtered by property intersect key. */ public static function prepare_properties( $possible_properties, $user_properties ) { $properties = array_fill_keys( $possible_properties, null ); $properties = array_merge( $properties, $user_properties ); return self::filter_possible_properties( $possible_properties, $properties ); } } system-info/module.php 0000644 00000021760 15252521347 0011033 0 ustar 00 <?php namespace Elementor\Modules\System_Info; use Elementor\Core\Base\Module as BaseModule; use Elementor\Modules\System_Info\Reporters\Base; use Elementor\Modules\System_Info\Helpers\Model_Helper; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\Modules\System_Info\AdminMenuItems\Editor_One_System_Info_Menu; use Elementor\Modules\System_Info\AdminMenuItems\Editor_One_System_Menu; use Elementor\Plugin; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor system info module. * * Elementor system info module handler class is responsible for registering and * managing Elementor system info reports. * * @since 2.9.0 */ class Module extends BaseModule { /** * Get module name. * * Retrieve the system info module name. * * @since 2.9.0 * @access public * * @return string Module name. */ public function get_name() { return 'system-info'; } /** * Required user capabilities. * * Holds the user capabilities required to manage Elementor menus. * * @since 2.9.0 * @access private * * @var string */ private $capability = 'manage_options'; /** * Elementor system info reports. * * Holds an array of available reports in Elementor system info page. * * @since 2.9.0 * @access private * * @var array */ private static $reports = [ 'server' => [], 'wordpress' => [], 'theme' => [], 'user' => [], 'plugins' => [], 'network_plugins' => [], 'mu_plugins' => [], ]; public function get_capability() { return $this->capability; } /** * Main system info page constructor. * * Initializing Elementor system info page. * * @since 2.9.0 * @access public */ public function __construct() { $this->add_actions(); } /** * Get default settings. * * Retrieve the default settings. Used to reset the report settings on * initialization. * * @since 2.9.0 * @access protected * * @return array Default settings. */ protected function get_init_settings() { $settings = []; $reporter_properties = Base::get_properties_keys(); array_push( $reporter_properties, 'category', 'name', 'class_name' ); $settings['reporter_properties'] = $reporter_properties; $settings['reportFilePrefix'] = ''; return $settings; } /** * Add actions. * * Register filters and actions for the main system info page. * * @since 2.9.0 * @access private */ private function add_actions() { add_action( 'elementor/editor-one/menu/register', function ( Menu_Data_Provider $menu_data_provider ) { $this->register_editor_one_menu( $menu_data_provider ); } ); add_action( 'wp_ajax_elementor_system_info_download_file', [ $this, 'download_file' ] ); } private function register_editor_one_menu( Menu_Data_Provider $menu_data_provider ) { $menu_data_provider->register_menu( new Editor_One_System_Menu() ); $menu_data_provider->register_menu( new Editor_One_System_Info_Menu() ); } /** * Display page. * * Output the content for the main system info page. * * @since 2.9.0 * @access public */ public function display_page() { $reports_info = self::get_allowed_reports(); $reports = $this->load_reports( $reports_info ); ?> <div id="elementor-system-info"> <div class="elementor-system-info-header"> <h3 class="wp-heading-inline"><?php echo esc_html__( 'System Info', 'elementor' ); ?></h3> <form action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>" method="post"> <input type="hidden" name="action" value="elementor_system_info_download_file"> <input type="submit" data-id="elementor-system-info-download-file" class="button button-primary" value="<?php echo esc_attr__( 'Download System Info', 'elementor' ); ?>"> </form> </div> <div><?php $this->print_report( $reports, 'html' ); ?></div> <h3><?php echo esc_html__( 'Copy & Paste Info', 'elementor' ); ?></h3> <div id="elementor-system-info-raw"> <label id="elementor-system-info-raw-code-label" for="elementor-system-info-raw-code"><?php echo esc_html__( 'You can copy the below info as simple text with Ctrl+C / Ctrl+V:', 'elementor' ); ?></label> <textarea id="elementor-system-info-raw-code" readonly> <?php $this->print_report( $reports, 'raw' ); ?> </textarea> <script> var textarea = document.getElementById( 'elementor-system-info-raw-code' ); var selectRange = function() { textarea.setSelectionRange( 0, textarea.value.length ); }; textarea.onfocus = textarea.onblur = textarea.onclick = selectRange; textarea.onfocus(); </script> </div> <hr> <form action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>" method="post"> <input type="hidden" name="action" value="elementor_system_info_download_file"> <input type="submit" data-id="elementor-system-info-download-file" class="button button-primary" value="<?php echo esc_attr__( 'Download System Info', 'elementor' ); ?>"> </form> </div> <?php } /** * Download file. * * Download the reports files. * * Fired by `wp_ajax_elementor_system_info_download_file` action. * * @since 2.9.0 * @access public */ public function download_file() { if ( ! current_user_can( $this->capability ) ) { wp_die( esc_html__( 'You do not have permission to download this file.', 'elementor' ) ); } $reports_info = self::get_allowed_reports(); $reports = $this->load_reports( $reports_info ); $domain = parse_url( site_url(), PHP_URL_HOST ); header( 'Content-Type: text/plain' ); header( 'Content-Disposition:attachment; filename=system-info-' . $domain . '-' . gmdate( 'd-m-Y' ) . '.txt' ); $this->print_report( $reports ); die; } /** * Get report class. * * Retrieve the class of the report for any given report type. * * @since 2.9.0 * @access public * * @param string $reporter_type The type of the report. * * @return string The class of the report. */ public function get_reporter_class( $reporter_type ) { return __NAMESPACE__ . '\Reporters\\' . ucfirst( $reporter_type ); } /** * Load reports. * * Retrieve the system info reports. * * @since 2.9.0 * @access public * * @param array $reports An array of system info reports. * * @return array An array of system info reports. */ public function load_reports( $reports ) { $result = []; foreach ( $reports as $report_name => $report_info ) { $reporter_params = [ 'name' => $report_name, ]; $reporter_params = array_merge( $reporter_params, $report_info ); $reporter = $this->create_reporter( $reporter_params ); if ( ! $reporter instanceof Base ) { continue; } $result[ $report_name ] = [ 'report' => $reporter, 'label' => $reporter->get_title(), ]; if ( ! empty( $report_info['sub'] ) ) { $result[ $report_name ]['sub'] = $this->load_reports( $report_info['sub'] ); } } return $result; } /** * Create a report. * * Register a new report that will be displayed in Elementor system info page. * * @param array $properties Report properties. * * @return \WP_Error|false|Base Base instance if the report was created, * False or WP_Error otherwise. * @since 2.9.0 * @access public */ public function create_reporter( array $properties ) { $properties = Model_Helper::prepare_properties( $this->get_settings( 'reporter_properties' ), $properties ); $reporter_class = $properties['class_name'] ? $properties['class_name'] : $this->get_reporter_class( $properties['name'] ); $reporter = new $reporter_class( $properties ); if ( ! ( $reporter instanceof Base ) ) { return new \WP_Error( 'Each reporter must to be an instance or sub-instance of `Base` class.' ); } if ( ! $reporter->is_enabled() ) { return false; } return $reporter; } /** * Print report. * * Output the system info page reports using an output template. * * @since 2.9.0 * @access public * * @param array $reports An array of system info reports. * @param string $template Output type from the templates folder. Available * templates are `raw` and `html`. Default is `raw`. */ public function print_report( $reports, $template = 'raw' ) { static $tabs_count = 0; $template_path = __DIR__ . '/templates/' . $template . '.php'; require $template_path; } /** * Get allowed reports. * * Retrieve the available reports in Elementor system info page. * * @since 2.9.0 * @access public * @static * * @return array Available reports in Elementor system info page. */ public static function get_allowed_reports() { do_action( 'elementor/system_info/get_allowed_reports' ); return self::$reports; } /** * Add report. * * Register a new report to Elementor system info page. * * @since 2.9.0 * @access public * @static * * @param string $report_name The name of the report. * @param array $report_info Report info. */ public static function add_report( $report_name, $report_info ) { self::$reports[ $report_name ] = $report_info; } } system-info/admin-menu-items/editor-one-system-info-menu.php 0000644 00000002117 15252521347 0020174 0 ustar 00 <?php namespace Elementor\Modules\System_Info\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_System_Info_Menu implements Menu_Item_Interface, Admin_Menu_Item_With_Page { public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'System Info', 'elementor' ); } public function get_position(): int { return 10; } public function get_slug(): string { return 'elementor-system-info'; } public function get_group_id(): string { return Menu_Config::SYSTEM_GROUP_ID; } public function get_page_title() { return $this->get_label(); } public function render() { Plugin::$instance->system_info->display_page(); } } system-info/admin-menu-items/editor-one-system-menu.php 0000644 00000001713 15252521347 0017244 0 ustar 00 <?php namespace Elementor\Modules\System_Info\AdminMenuItems; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Third_Level_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_System_Menu implements Menu_Item_Third_Level_Interface { public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'System', 'elementor' ); } public function get_position(): int { return 90; } public function get_slug(): string { return 'elementor-system'; } public function get_icon(): string { return 'file-settings'; } public function get_group_id(): string { return Menu_Config::SYSTEM_GROUP_ID; } public function has_children(): bool { return true; } } system-info/templates/html.php 0000644 00000001057 15252521347 0012505 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @var array $reports */ foreach ( $reports as $report_name => $report ) : ?> <div class="elementor-system-info-section elementor-system-info-<?php echo esc_attr( $report_name ); ?>"> <table class="widefat"> <thead> <tr> <th><?php $report['report']->print_html_label( ( $report['label'] ) ); ?></th> <th></th> <th></th> </tr> </thead> <tbody> <?php $report['report']->print_html(); ?> </tbody> </table> </div> <?php endforeach; system-info/templates/raw.php 0000644 00000000572 15252521347 0012333 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @var array $reports * @var int $tabs_count */ $tabs_count++; foreach ( $reports as $report_name => $report ) : $report['report']->print_raw( $tabs_count ); if ( ! empty( $report['sub'] ) ) : $this->print_report( $report['sub'], $template, true ); endif; endforeach; $tabs_count--; lazyload/module.php 0000644 00000005427 15252521347 0010377 0 ustar 00 <?php namespace Elementor\Modules\LazyLoad; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { public function get_name() { return 'lazyload'; } public function __construct() { parent::__construct(); add_action( 'init', [ $this, 'init' ] ); } public function init() { if ( ! $this->is_lazy_load_background_images_enabled() ) { return; } add_action( 'wp_head', function() { if ( ! $this->should_lazy_load_background_images() ) { return; } ?> <style> .e-con.e-parent:nth-of-type(n+4):not(.e-lazyloaded):not(.e-no-lazyload), .e-con.e-parent:nth-of-type(n+4):not(.e-lazyloaded):not(.e-no-lazyload) * { background-image: none !important; } @media screen and (max-height: 1024px) { .e-con.e-parent:nth-of-type(n+3):not(.e-lazyloaded):not(.e-no-lazyload), .e-con.e-parent:nth-of-type(n+3):not(.e-lazyloaded):not(.e-no-lazyload) * { background-image: none !important; } } @media screen and (max-height: 640px) { .e-con.e-parent:nth-of-type(n+2):not(.e-lazyloaded):not(.e-no-lazyload), .e-con.e-parent:nth-of-type(n+2):not(.e-lazyloaded):not(.e-no-lazyload) * { background-image: none !important; } } </style> <?php } ); add_action( 'wp_footer', function() { if ( ! $this->should_lazy_load_background_images() ) { return; } ?> <script> ( () => { const lazyloadRunObserver = () => { const lazyloadBackgrounds = document.querySelectorAll( `.e-con.e-parent:not(.e-lazyloaded)` ); const lazyloadBackgroundObserver = new IntersectionObserver( ( entries ) => { entries.forEach( ( entry ) => { if ( entry.isIntersecting ) { let lazyloadBackground = entry.target; if( lazyloadBackground ) { lazyloadBackground.classList.add( 'e-lazyloaded' ); } lazyloadBackgroundObserver.unobserve( entry.target ); } }); }, { rootMargin: '200px 0px 200px 0px' } ); lazyloadBackgrounds.forEach( ( lazyloadBackground ) => { lazyloadBackgroundObserver.observe( lazyloadBackground ); } ); }; const events = [ 'DOMContentLoaded', 'elementor/lazyload/observe', ]; events.forEach( ( event ) => { document.addEventListener( event, lazyloadRunObserver ); } ); } )(); </script> <?php } ); } private function should_lazy_load_background_images(): bool { return ! is_admin() && ! Plugin::$instance->preview->is_preview_mode() && ! Plugin::$instance->editor->is_edit_mode(); } private static function is_lazy_load_background_images_enabled(): bool { return '1' === get_option( 'elementor_lazy_load_background_images', '1' ); } } container-converter/module.php 0000644 00000010124 15252521347 0012535 0 ustar 00 <?php namespace Elementor\Modules\ContainerConverter; use Elementor\Controls_Manager; use Elementor\Controls_Stack; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends \Elementor\Core\Base\Module { // Event name dispatched by the buttons. const EVENT_NAME = 'elementorContainerConverter:convert'; /** * Retrieve the module name. * * @return string */ public function get_name() { return 'container-converter'; } /** * Determine whether the module is active. * * @return bool */ public static function is_active() { return Plugin::$instance->experiments->is_feature_active( 'container' ); } /** * Enqueue the module scripts. * * @return void */ public function enqueue_scripts() { wp_enqueue_script( 'container-converter', $this->get_js_assets_url( 'container-converter' ), [ 'elementor-editor' ], ELEMENTOR_VERSION, true ); } /** * Enqueue the module styles. * * @return void */ public function enqueue_styles() { wp_enqueue_style( 'container-converter', $this->get_css_assets_url( 'modules/container-converter/editor' ), [], ELEMENTOR_VERSION ); } /** * Add a convert button to sections. * * @param \Elementor\Controls_Stack $controls_stack * * @return void */ protected function add_section_convert_button( Controls_Stack $controls_stack ) { if ( ! Plugin::$instance->editor->is_edit_mode() ) { return; } $controls_stack->start_injection( [ 'of' => '_title', ] ); $controls_stack->add_control( 'convert_to_container', [ 'type' => Controls_Manager::BUTTON, 'label' => esc_html__( 'Convert to container', 'elementor' ), 'text' => esc_html__( 'Convert', 'elementor' ), 'button_type' => 'default', 'description' => esc_html__( 'Copies all of the selected sections and columns and pastes them in a container beneath the original.', 'elementor' ), 'separator' => 'after', 'event' => static::EVENT_NAME, ] ); $controls_stack->end_injection(); } /** * Add a convert button to page settings. * * @param \Elementor\Controls_Stack $controls_stack * * @return void */ protected function add_page_convert_button( Controls_Stack $controls_stack ) { if ( ! Plugin::$instance->editor->is_edit_mode() || ! $this->page_contains_sections( $controls_stack ) || ! Plugin::$instance->role_manager->user_can( 'design' ) ) { return; } $controls_stack->start_injection( [ 'of' => 'post_title', 'at' => 'before', ] ); $controls_stack->add_control( 'convert_to_container', [ 'type' => Controls_Manager::BUTTON, 'label' => esc_html__( 'Convert to container', 'elementor' ), 'text' => esc_html__( 'Convert', 'elementor' ), 'button_type' => 'default', 'description' => esc_html__( 'Copies all of the selected sections and columns and pastes them in a container beneath the original.', 'elementor' ), 'separator' => 'after', 'event' => static::EVENT_NAME, ] ); $controls_stack->end_injection(); } /** * Checks if document has any Section elements. * * @param \Elementor\Controls_Stack $controls_stack * * @return bool */ protected function page_contains_sections( $controls_stack ) { $data = $controls_stack->get_elements_data(); if ( ! is_array( $data ) ) { return false; } foreach ( $data as $element ) { if ( isset( $element['elType'] ) && 'section' === $element['elType'] ) { return true; } } return false; } /** * Initialize the Container-Converter module. * * @return void */ public function __construct() { add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); add_action( 'elementor/editor/after_enqueue_styles', [ $this, 'enqueue_styles' ] ); add_action( 'elementor/element/section/section_layout/after_section_end', function ( Controls_Stack $controls_stack ) { $this->add_section_convert_button( $controls_stack ); } ); add_action( 'elementor/documents/register_controls', function ( Controls_Stack $controls_stack ) { $this->add_page_convert_button( $controls_stack ); } ); } } nested-elements/base/widget-nested-base.php 0000644 00000007173 15252521347 0014754 0 ustar 00 <?php namespace Elementor\Modules\NestedElements\Base; use Elementor\Plugin; use Elementor\Widget_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Used to create a new widget that can be nested inside other widgets. */ abstract class Widget_Nested_Base extends Widget_Base { /** * Get default children elements structure. * * @return array */ abstract protected function get_default_children_elements(); /** * Get repeater title setting key name. * * @return string */ abstract protected function get_default_repeater_title_setting_key(); /** * Get default children title for the navigator, using `%d` as index in the format. * * @note The title in this method is used to set the default title for each created child in nested element. * for handling the children title for new created widget(s), use `get_default_children_elements()` method, * eg: * [ * 'elType' => 'container', * 'settings' => [ * '_title' => __( 'Tab #1', 'elementor' ), * ], * ], * @return string */ protected function get_default_children_title() { /* translators: %d: Item index. */ return esc_html__( 'Item #%d', 'elementor' ); } /** * Get default children placeholder selector, Empty string, means will be added at the end view. * * @return string */ protected function get_default_children_placeholder_selector() { return ''; } protected function get_default_children_container_placeholder_selector() { return ''; } protected function is_dynamic_content(): bool { return false; } /** * @inheritDoc * * To support nesting. */ protected function _get_default_child_type( array $element_data ) { return Plugin::$instance->elements_manager->get_element_types( $element_data['elType'] ); } /** * @inheritDoc * * Adding new 'defaults' config for handling children elements. */ protected function get_initial_config() { return array_merge( parent::get_initial_config(), [ 'defaults' => [ 'elements' => $this->get_default_children_elements(), 'elements_title' => $this->get_default_children_title(), 'elements_placeholder_selector' => $this->get_default_children_placeholder_selector(), 'child_container_placeholder_selector' => $this->get_default_children_container_placeholder_selector(), 'repeater_title_setting' => $this->get_default_repeater_title_setting_key(), ], 'support_nesting' => true, ] ); } /** * @inheritDoc * * Each element including its children elements. */ public function get_raw_data( $with_html_content = false ) { $elements = []; $data = $this->get_data(); $children = $this->get_children(); foreach ( $children as $child ) { $child_raw_data = $child->get_raw_data( $with_html_content ); $elements[] = $child_raw_data; } return [ 'id' => $this->get_id(), 'elType' => $data['elType'], 'widgetType' => $data['widgetType'], 'settings' => $data['settings'], 'elements' => $elements, ]; } /** * Print child, helper method to print the child element. * * @param int $index */ public function print_child( $index ) { $children = $this->get_children(); if ( ! empty( $children[ $index ] ) ) { $children[ $index ]->print_element(); } } protected function content_template_single_repeater_item() {} public function print_template() { parent::print_template(); if ( $this->get_initial_config()['support_improved_repeaters'] ?? false ) { ?> <script type="text/html" id="tmpl-elementor-<?php echo esc_attr( $this->get_name() ); ?>-content-single"> <?php $this->content_template_single_repeater_item(); ?> </script> <?php } } } nested-elements/module.php 0000644 00000002765 15252521347 0011656 0 ustar 00 <?php namespace Elementor\Modules\NestedElements; use Elementor\Core\Experiments\Manager as Experiments_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends \Elementor\Core\Base\Module { const EXPERIMENT_NAME = 'nested-elements'; public static function get_experimental_data() { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Nested Elements', 'elementor' ), 'description' => sprintf( '%1$s <a href="https://go.elementor.com/wp-dash-nested-elements/" target="_blank">%2$s</a>', esc_html__( 'Create a rich user experience by layering widgets together inside "Nested" Tabs, etc. When turned on, we’ll automatically enable new nested features. Your old widgets won’t be affected.', 'elementor' ), esc_html__( 'Learn more', 'elementor' ) ), 'release_status' => Experiments_Manager::RELEASE_STATUS_STABLE, 'default' => Experiments_Manager::STATE_ACTIVE, 'dependencies' => [ 'container', ], ]; } public function get_name() { return 'nested-elements'; } public function __construct() { parent::__construct(); add_action( 'elementor/controls/register', function ( $controls_manager ) { $controls_manager->register( new Controls\Control_Nested_Repeater() ); } ); add_action( 'elementor/editor/before_enqueue_scripts', function () { wp_enqueue_script( $this->get_name(), $this->get_js_assets_url( $this->get_name() ), [ 'elementor-common', ], ELEMENTOR_VERSION, true ); } ); } } nested-elements/controls/control-nested-repeater.php 0000644 00000000744 15252521347 0016774 0 ustar 00 <?php namespace Elementor\Modules\NestedElements\Controls; use Elementor\Control_Repeater; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Changing the default repeater control behavior for custom item title defaults. * For custom management of nested repeater controls. */ class Control_Nested_Repeater extends Control_Repeater { const CONTROL_TYPE = 'nested-elements-repeater'; public function get_type() { return static::CONTROL_TYPE; } } elementor-counter/module.php 0000644 00000003535 15252521347 0012225 0 ustar 00 <?php namespace Elementor\Modules\ElementorCounter; use Elementor\Core\Isolation\Elementor_Counter_Adapter_Interface; use Elementor\Core\Isolation\Wordpress_Adapter; use Elementor\Core\Isolation\Wordpress_Adapter_Interface; use Elementor\Core\Base\Module as BaseModule; if ( ! defined( 'ABSPATH' ) ) { exit; } class Module extends BaseModule implements Elementor_Counter_Adapter_Interface { const EDITOR_COUNTER_KEY = 'e_editor_counter'; private ?Wordpress_Adapter_Interface $wordpress_adapter = null; private static $should_count_editor = true; public function get_name() { return 'elementor-counter'; } public function __construct( ?Wordpress_Adapter_Interface $wordpress_adapter = null ) { parent::__construct(); $this->wordpress_adapter = $wordpress_adapter ?? new Wordpress_Adapter(); if ( self::$should_count_editor ) { add_action( 'elementor/editor/init', function () { $this->increment( self::EDITOR_COUNTER_KEY ); }, 10 ); self::$should_count_editor = false; } } /** * @param self::EDITOR_COUNTER_KEY $key * * @return int | null */ public function get_count( $key ): ?int { return $this->is_key_allowed( $key ) ? (int) $this->wordpress_adapter->get_option( $key, 0 ) : null; } /** * @param self::EDITOR_COUNTER_KEY $key * @param int $count */ public function set_count( $key, $count = 0 ): void { if ( ! $this->is_key_allowed( $key ) || ! is_int( $count ) ) { return; } $this->wordpress_adapter->update_option( $key, $count ); } /** * @param self::EDITOR_COUNTER_KEY $key */ public function increment( $key ): void { if ( ! $this->is_key_allowed( $key ) ) { return; } $count = $this->get_count( $key ); $this->set_count( $key, $count + 1 ); } public function is_key_allowed( $key ): bool { return in_array( $key, [ self::EDITOR_COUNTER_KEY ] ); } } global-classes/atomic-global-styles.php 0000644 00000031774 15252521347 0014225 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity; use Elementor\Plugin; use Elementor\Modules\AtomicWidgets\Styles\Atomic_Styles_Manager; class Atomic_Global_Styles { const STYLES_KEY = 'global'; const RELATED_KEY = 'related'; const RELATED_REVERSE_KEY = 'related-reverse'; private Global_Classes_Relations $relations; public function __construct( Global_Classes_Relations $relations ) { $this->relations = $relations; } public function register_hooks() { add_action( 'elementor/atomic-widgets/styles/register', fn( Atomic_Styles_Manager $styles_manager, array $post_ids ) => $this->register_styles( $styles_manager, $post_ids ), 20, 2 ); add_action( 'elementor/global_classes/update', fn( string $context, array $changes ) => $this->invalidate_cache_for_updated_classes( $context, $changes ), 10, 2 ); add_action( 'deleted_post', fn( $post_id ) => $this->on_kit_delete( $post_id ) ); add_action( 'elementor/core/files/clear_cache', fn() => $this->invalidate_all_cache(), ); add_filter( 'elementor/atomic-widgets/settings/transformers/classes', fn( $value ) => $this->transform_classes_names( $value ) ); add_action( 'elementor/document/after_save', fn( $document ) => $this->on_document_save( $document ) ); } private function register_styles( Atomic_Styles_Manager $styles_manager, array $post_ids ) { $context = $this->get_context(); $parent_to_embedded = []; $visited = []; foreach ( $post_ids as $post_id ) { $this->resolve_embedded_post_descendants( (int) $post_id, $parent_to_embedded, $visited ); } $this->persist_relation_maps( $parent_to_embedded, $context ); $all_embedded = []; foreach ( $parent_to_embedded as $children ) { $all_embedded = array_merge( $all_embedded, $children ); } $all_embedded = array_flip( array_unique( $all_embedded ) ); // use as set foreach ( $post_ids as $post_id ) { $post_id_int = (int) $post_id; if ( isset( $all_embedded[ $post_id_int ] ) ) { // This post is embedded inside another rendered post – skip it so // it doesn't produce its own global-{id}-*.css file. continue; } $embedded_ids = $parent_to_embedded[ $post_id_int ] ?? []; $aggregate_ids = array_merge( [ $post_id_int ], $embedded_ids ); $get_styles = fn() => $this->get_document_global_styles( $aggregate_ids, $context ); $styles_manager->register( [ $this->get_cache_root_key(), $post_id, $context ], $get_styles ); } } /** * Recursively resolve embedded post descendants for a parent post id. * * Applies the `elementor/document/related_posts` filter transitively and * guards against cycles with the shared $visited set. * * @param int $pid Parent post id being inspected. * @param array<int,int[]> $parent_to_embedded Accumulated forward map. * @param array<int,true> $visited Cycle guard. * @return int[] Embedded descendant post ids to merge into $pid's global styles. */ private function resolve_embedded_post_descendants( int $pid, array &$parent_to_embedded, array &$visited ): array { if ( isset( $visited[ $pid ] ) ) { return $parent_to_embedded[ $pid ] ?? []; } $visited[ $pid ] = true; $related = (array) apply_filters( 'elementor/document/related_posts', [], $pid ); $related = array_values( array_unique( array_map( 'intval', array_filter( $related, 'is_numeric' ) ) ) ); $all_related = $related; foreach ( $related as $related_post ) { $further_related_posts = $this->resolve_embedded_post_descendants( $related_post, $parent_to_embedded, $visited ); $all_related = array_values( array_unique( array_merge( $all_related, $further_related_posts ) ) ); } $parent_to_embedded[ $pid ] = $all_related; return $all_related; } /** * Returns the merged, ordered global class styles for one or more post ids. * * @param int[] $post_ids One or more post ids whose classes should be merged. * @param string $context Frontend or preview context. * @return array Ordered style items ready for serialization. */ private function get_document_global_styles( array $post_ids, string $context ): array { $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context; $class_ids = []; foreach ( $post_ids as $pid ) { $ids = $this->relations->set_preview( $is_preview )->get_styles_by_post( (int) $pid ); $class_ids = array_merge( $class_ids, $ids ); } $class_ids = array_values( array_unique( $class_ids ) ); if ( empty( $class_ids ) ) { return []; } $repository = Global_Classes_Repository::make(); if ( $is_preview ) { $repository->set_preview( true ); } $global_order = $repository->all_labels(); $ordered_class_ids = array_values( array_intersect( array_keys( $global_order ), $class_ids ) ); if ( empty( $ordered_class_ids ) ) { return []; } $items = $repository->get_by_ids( $ordered_class_ids ); $reversed_order = array_reverse( $ordered_class_ids ); $styles = []; foreach ( $reversed_order as $class_id ) { $item = $items[ $class_id ] ?? null; if ( ! $item ) { continue; } $resolved_label = $global_order[ $class_id ] ?? $item['label']; $item['id'] = $resolved_label; $item['label'] = $resolved_label; $styles[] = $item; } return $styles; } /** * Persist the parent-to-child and child-to-parent relation maps. * * @param array<int,int[]> $parent_to_embedded Forward map: parent_id => child_ids[]. */ private function persist_relation_maps( array $parent_to_embedded, string $context ): void { $cache_validity = new Cache_Validity(); foreach ( $parent_to_embedded as $parent => $new_children ) { $forward_path = [ $this->get_cache_root_key( self::RELATED_KEY ), $parent, $context ]; $old_related_posts = array_map( 'intval', (array) ( $cache_validity->get_meta( $forward_path ) ?? [] ) ); $new_related_posts = array_map( 'intval', $new_children ); $added_posts = array_diff( $new_related_posts, $old_related_posts ); $removed_posts = array_diff( $old_related_posts, $new_related_posts ); $cache_validity->validate( $forward_path, $new_related_posts ); foreach ( $added_posts as $added_post ) { $this->add_reverse_relation( $cache_validity, $added_post, $parent, $context ); } foreach ( $removed_posts as $removed_post ) { $this->remove_reverse_relation( $cache_validity, $removed_post, $parent, $context ); } } } private function add_reverse_relation( Cache_Validity $cache_validity, int $child, int $parent, string $context ): void { $reverse_path = [ $this->get_cache_root_key( self::RELATED_REVERSE_KEY ), $child, $context ]; $existing = array_map( 'intval', (array) ( $cache_validity->get_meta( $reverse_path ) ?? [] ) ); if ( in_array( $parent, $existing, true ) ) { return; } $cache_validity->validate( $reverse_path, array_values( array_merge( $existing, [ $parent ] ) ) ); } private function remove_reverse_relation( Cache_Validity $cache_validity, int $child, int $parent, string $context ): void { $reverse_path = [ $this->get_cache_root_key( self::RELATED_REVERSE_KEY ), $child, $context ]; $existing = array_map( 'intval', (array) ( $cache_validity->get_meta( $reverse_path ) ?? [] ) ); $pruned = array_values( array_filter( $existing, fn( int $p ) => $p !== $parent ) ); $cache_validity->validate( $reverse_path, $pruned ); } /** * Look up all parent post ids that declared $child_post_id as embedded. * * @param int $child_post_id * @return int[] */ private function get_parent_post_ids( int $child_post_id, string $context ): array { $cache_validity = new Cache_Validity(); $parents = $cache_validity->get_meta( [ $this->get_cache_root_key( self::RELATED_REVERSE_KEY ), $child_post_id, $context, ] ); if ( ! is_array( $parents ) ) { return []; } return array_values( array_unique( array_map( 'intval', $parents ) ) ); } /** * Walk the reverse relation map transitively to collect every ancestor * post that embeds $post_id (direct parent, grandparent, etc.). * * @param int $post_id * @return int[] */ private function get_ancestor_post_ids( int $post_id, string $context ): array { $ancestors = []; $visited = []; $queue = [ $post_id ]; while ( ! empty( $queue ) ) { $current = array_shift( $queue ); foreach ( $this->get_parent_post_ids( $current, $context ) as $parent_id ) { if ( isset( $visited[ $parent_id ] ) ) { continue; } $visited[ $parent_id ] = true; $ancestors[] = $parent_id; $queue[] = $parent_id; } } return array_values( array_unique( array_map( 'intval', $ancestors ) ) ); } private function on_kit_delete( $post_id ) { if ( ! Plugin::$instance->kits_manager->is_kit( $post_id ) ) { return; } $this->invalidate_all_cache(); } /** * When an embedded post (component/template) is saved, its own CSS cache * is cleared by Global_Classes_Relations. We additionally need to clear * the parent's global CSS so it is regenerated with the updated child content. */ private function on_document_save( $document ): void { $post_id = (int) $document->get_main_id(); $context = $this->get_context(); $cache_validity = new Cache_Validity(); $cache_validity->invalidate( [ $this->get_cache_root_key( self::RELATED_KEY ), $post_id, $context ] ); foreach ( $this->get_ancestor_post_ids( $post_id, $context ) as $ancestor_id ) { $this->invalidate_document_cache( $ancestor_id, $context ); $cache_validity->invalidate( [ $this->get_cache_root_key( self::RELATED_KEY ), $ancestor_id, $context ] ); } } private function invalidate_cache_for_updated_classes( string $context, array $changes ) { if ( isset( $changes['order'] ) && $changes['order'] ) { $this->invalidate_all_cache( $context ); return; } $affected = array_unique( array_merge( $changes['added'] ?? [], $changes['deleted'] ?? [], $changes['modified'] ?? [] ) ); if ( empty( $affected ) ) { return; } $document_ids = []; $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context; if ( ! empty( $changes['affected_post_ids'] ) ) { foreach ( $changes['affected_post_ids'] as $post_id ) { $document_ids[ (int) $post_id ] = true; } } foreach ( $affected as $class_id ) { foreach ( $this->relations->set_preview( $is_preview )->get_posts_by_style( $class_id ) as $doc_id ) { $document_ids[ $doc_id ] = true; } } if ( empty( $document_ids ) ) { return; } // Also include ancestor posts of the directly-affected documents so that // aggregated CSS bundles are regenerated when a descendant class changes. $with_parents = $document_ids; foreach ( array_keys( $document_ids ) as $doc_id ) { foreach ( $this->get_ancestor_post_ids( (int) $doc_id, $context ) as $ancestor_id ) { $with_parents[ $ancestor_id ] = true; } } foreach ( array_keys( $with_parents ) as $post_id ) { $this->invalidate_document_cache( $post_id, $context ); } } private function invalidate_document_cache( int $post_id, ?string $context = null ) { if ( empty( $context ) ) { do_action( 'elementor/atomic-widgets/styles/clear', [ $this->get_cache_root_key(), $post_id ] ); do_action( 'elementor/atomic-widgets/styles/clear', [ $this->get_cache_root_key( self::RELATED_KEY ), $post_id ] ); do_action( 'elementor/atomic-widgets/styles/clear', [ $this->get_cache_root_key( self::RELATED_REVERSE_KEY ), $post_id ] ); } else { do_action( 'elementor/atomic-widgets/styles/clear', [ $this->get_cache_root_key(), $post_id, $context ] ); do_action( 'elementor/atomic-widgets/styles/clear', [ $this->get_cache_root_key( self::RELATED_KEY ), $post_id, $context ] ); do_action( 'elementor/atomic-widgets/styles/clear', [ $this->get_cache_root_key( self::RELATED_REVERSE_KEY ), $post_id, $context ] ); } } private function invalidate_all_cache( ?string $context = null ) { if ( empty( $context ) || Global_Classes_Repository::CONTEXT_FRONTEND === $context ) { do_action( 'elementor/atomic-widgets/styles/clear', [ $this->get_cache_root_key() ] ); $cache_validity = new Cache_Validity(); $cache_validity->invalidate( [ $this->get_cache_root_key( self::RELATED_KEY ) ] ); $cache_validity->invalidate( [ $this->get_cache_root_key( self::RELATED_REVERSE_KEY ) ] ); return; } do_action( 'elementor/atomic-widgets/styles/clear', [ $this->get_cache_root_key(), $context ] ); } private function transform_classes_names( $ids ) { $labels = Global_Classes_Repository::make() ->set_preview( $this->is_preview() ) ->all_labels(); return array_map( static function( $id ) use ( $labels ) { return $labels[ $id ] ?? $id; }, $ids ); } private function get_context(): string { return $this->is_preview() ? Global_Classes_Repository::CONTEXT_PREVIEW : Global_Classes_Repository::CONTEXT_FRONTEND; } private function is_preview(): bool { return Plugin::$instance->preview->is_editor_or_preview(); } private function get_cache_root_key( string $key = null ): string { return $key ? self::STYLES_KEY . '_' . $key : self::STYLES_KEY; } } global-classes/global-classes-cleanup.php 0000644 00000004730 15252521347 0014502 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Core\Base\Document; use Elementor\Core\Utils\Collection; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\GlobalClasses\Utils\Atomic_Elements_Utils; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Global_Classes_Cleanup { public function register_hooks() { add_action( 'elementor/global_classes/cleanup', fn( array $deleted_class_ids, array $affected_post_ids ) => $this->on_classes_deleted( $deleted_class_ids, $affected_post_ids ), 10, 2 ); } private function on_classes_deleted( array $deleted_class_ids, array $affected_post_ids ) { foreach ( $affected_post_ids as $post_id ) { $document = Plugin::$instance->documents->get( $post_id ); if ( ! $document ) { continue; } $elements_data = $document->get_json_meta( Document::ELEMENTOR_DATA_META_KEY ); $this->unapply_deleted_classes( $document, $elements_data, $deleted_class_ids ); } } private function unapply_deleted_classes( $document, $elements_data, $deleted_classes_ids ) { $elements_data = Plugin::$instance->db->iterate_data( $elements_data, function( $element_data ) use ( $deleted_classes_ids ) { $element_type = Atomic_Elements_Utils::get_element_type( $element_data ); $element_instance = Atomic_Elements_Utils::get_element_instance( $element_type ); if ( ! Atomic_Elements_Utils::is_atomic_element( $element_instance ) ) { return $element_data; } /** @var Atomic_Element_Base | Atomic_Widget_Base $element_instance */ return $this->unapply_classes_from_element( $element_instance->get_props_schema(), $element_data, $deleted_classes_ids ); } ); $document->update_json_meta( Document::ELEMENTOR_DATA_META_KEY, $elements_data ); } private function unapply_classes_from_element( $props_schema, $element_data, $deleted_classes_ids ) { foreach ( $props_schema as $settings_key => $prop ) { if ( ! Atomic_Elements_Utils::is_classes_prop( $prop ) ) { continue; } $current_classes = $element_data['settings'][ $settings_key ] ?? null; if ( ! $current_classes ) { continue; } $element_data['settings'][ $settings_key ]['value'] = Collection::make( $current_classes['value'] ) ->filter( fn( $class_name ) => ! in_array( $class_name, $deleted_classes_ids, true ) ) ->values(); } return $element_data; } } global-classes/concerns/has-preview-context.php 0000644 00000001701 15252521347 0015703 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Concerns; if ( ! defined( 'ABSPATH' ) ) { exit; } trait Has_Preview_Context { private bool $is_preview = false; public function set_preview( bool $is_preview = true ): self { if ( $is_preview === $this->is_preview ) { return $this; } $this->is_preview = $is_preview; $this->on_preview_change(); return $this; } protected function is_preview(): bool { return $this->is_preview; } protected function get_context_key( string $key ): string { $map = $this->get_context_keys()[ $key ] ?? null; if ( null === $map ) { throw new \InvalidArgumentException( sprintf( 'Unknown context key: %s', esc_html( $key ) ) ); } return $this->is_preview ? $map['preview'] : $map['frontend']; } protected function get_context_keys(): array { if ( empty( $this->context_keys ) ) { return []; } return $this->context_keys; } protected function on_preview_change(): void { } } global-classes/concerns/has-kit-dependency.php 0000644 00000000722 15252521347 0015445 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Concerns; use Elementor\Core\Kits\Documents\Kit; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } trait Has_Kit_Dependency { private ?Kit $kit = null; public function set_kit( Kit $kit ): self { $this->kit = $kit; return $this; } protected function get_kit(): ?Kit { if ( ! $this->kit ) { $this->kit = Plugin::$instance->kits_manager->get_active_kit(); } return $this->kit; } } global-classes/utils/template-library-global-classes-element-transformer.php 0000644 00000011325 15252521347 0023455 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Utils; use Elementor\Core\Utils\Template_Library_Element_Iterator; use Elementor\Core\Utils\Template_Library_Import_Export_Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Template_Library_Global_Classes_Element_Transformer { public static function rewrite_elements_classes_ids( array $elements, array $id_map ): array { if ( empty( $elements ) || empty( $id_map ) ) { return $elements; } return Template_Library_Element_Iterator::iterate( $elements, function ( $element_data ) use ( $id_map ) { $class_values = $element_data['settings']['classes']['value'] ?? null; if ( ! is_array( $class_values ) ) { return $element_data; } $element_data['settings']['classes']['value'] = self::map_class_values( $class_values, $id_map ); return $element_data; } ); } public static function flatten_elements_classes( array $elements, array $global_classes, ?array $only_ids = null ): array { $items = $global_classes['items'] ?? []; if ( empty( $elements ) || empty( $items ) ) { return $elements; } $ids_to_flatten = null !== $only_ids ? array_fill_keys( $only_ids, true ) : null; return Template_Library_Element_Iterator::iterate( $elements, function ( $element_data ) use ( $items, $ids_to_flatten ) { $class_values = $element_data['settings']['classes']['value'] ?? null; if ( ! is_array( $class_values ) || empty( $class_values ) ) { return $element_data; } [ $updated_values, $element_styles ] = self::flatten_class_values( $class_values, $element_data, $items, $ids_to_flatten ); $element_data['settings']['classes']['value'] = array_values( array_unique( $updated_values ) ); $element_data['styles'] = $element_styles; return $element_data; } ); } private static function map_class_values( array $class_values, array $id_map ): array { $updated_values = []; foreach ( $class_values as $class_id ) { if ( ! is_string( $class_id ) || '' === $class_id ) { continue; } $updated_values[] = $id_map[ $class_id ] ?? $class_id; } return array_values( array_unique( $updated_values ) ); } private static function flatten_class_values( array $class_values, array $element_data, array $items, ?array $ids_to_flatten ): array { $updated_values = []; $element_styles = $element_data['styles'] ?? []; $local_style_id = self::find_existing_local_style_id( $element_styles ); $local_style_used = false; foreach ( $class_values as $class_id ) { if ( ! is_string( $class_id ) || '' === $class_id ) { continue; } if ( self::should_flatten_class_id( $class_id, $items, $ids_to_flatten ) ) { $incoming_variants = $items[ $class_id ]['variants'] ?? []; if ( null === $local_style_id ) { $local_style_id = self::create_local_class_id( $element_data ); $element_styles[ $local_style_id ] = self::build_local_class_style( $local_style_id, [] ); } $element_styles[ $local_style_id ]['variants'] = array_merge( $element_styles[ $local_style_id ]['variants'], $incoming_variants ); if ( ! $local_style_used ) { $updated_values[] = $local_style_id; $local_style_used = true; } continue; } $is_global = self::is_global_class_id( $class_id ); if ( ! $is_global || ( null !== $ids_to_flatten && isset( $items[ $class_id ] ) ) ) { $updated_values[] = $class_id; } } return [ $updated_values, $element_styles ]; } private static function find_existing_local_style_id( array $element_styles ): ?string { foreach ( $element_styles as $style_id => $style ) { if ( isset( $style['label'] ) && Template_Library_Import_Export_Utils::LOCAL_CLASS_LABEL === $style['label'] ) { return $style_id; } } return null; } private static function is_global_class_id( string $class_id ): bool { return str_starts_with( $class_id, Template_Library_Import_Export_Utils::GLOBAL_CLASS_ID_PREFIX ); } private static function should_flatten_class_id( string $class_id, array $items, ?array $ids_to_flatten ): bool { if ( ! isset( $items[ $class_id ] ) ) { return false; } if ( null !== $ids_to_flatten && ! isset( $ids_to_flatten[ $class_id ] ) ) { return false; } return true; } private static function create_local_class_id( array $element_data ): string { return Template_Library_Import_Export_Utils::LOCAL_CLASS_ID_PREFIX . substr( $element_data['id'] ?? '', 0, 8 ) . '-' . Template_Library_Import_Export_Utils::generate_random_string(); } private static function build_local_class_style( string $local_id, array $variants ): array { return [ 'id' => $local_id, 'label' => Template_Library_Import_Export_Utils::LOCAL_CLASS_LABEL, 'type' => 'class', 'variants' => $variants, ]; } } global-classes/utils/template-library-global-classes-snapshot-builder.php 0000644 00000013331 15252521347 0022746 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Utils; use Elementor\Core\Utils\Template_Library_Element_Iterator; use Elementor\Core\Utils\Template_Library_Import_Export_Utils; use Elementor\Core\Utils\Template_Library_Snapshot_Processor; use Elementor\Modules\GlobalClasses\Global_Classes_Parser; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Modules\GlobalClasses\Global_Classes_Rest_Api; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Template_Library_Global_Classes_Snapshot_Builder extends Template_Library_Snapshot_Processor { private static ?self $instance = null; public static function make(): self { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } public static function extract_used_class_ids_from_elements( array $elements ): array { $ids = []; if ( empty( $elements ) ) { return []; } Template_Library_Element_Iterator::iterate( $elements, function ( $element_data ) use ( &$ids ) { $class_values = $element_data['settings']['classes']['value'] ?? []; if ( is_array( $class_values ) ) { foreach ( $class_values as $class_id ) { if ( is_string( $class_id ) && '' !== $class_id ) { $ids[] = $class_id; } } } return $element_data; } ); return array_values( array_unique( $ids ) ); } public static function build_snapshot_for_ids( array $ids ): ?array { if ( empty( $ids ) || ! self::make()->can_access_repository() ) { return null; } $ids = Template_Library_Import_Export_Utils::normalize_string_ids( $ids ); if ( empty( $ids ) ) { return null; } $repository = Global_Classes_Repository::make()->set_preview( false ); $order = $repository->get_order(); $filtered_order = array_values( array_filter( $order, fn( $id ) => in_array( $id, $ids, true ) ) ); $filtered_items = $repository->get_by_ids( $ids ); if ( empty( $filtered_items ) ) { return null; } return self::parse_snapshot_or_null( [ 'items' => $filtered_items, 'order' => $filtered_order, ] ); } public static function build_snapshot_for_elements( array $elements ): ?array { $ids = self::extract_used_class_ids_from_elements( $elements ); if ( empty( $ids ) ) { return null; } return self::build_snapshot_for_ids( $ids ); } public static function merge_snapshot_and_get_id_map( array $snapshot ): array { return self::make()->merge_and_get_id_map( $snapshot ); } public static function create_snapshot_as_new( array $snapshot ): array { return self::make()->create_all_as_new( $snapshot ); } protected function is_matching_item( array $existing_item, array $incoming_item ): bool { // For global classes, if the labels match, we consider them the same item // when merging, so we reuse the existing class and ignore incoming variants or extra fields. return true; } protected function normalize_for_comparison( array $item ): array { $id = $item['id'] ?? ''; if ( '' === $id ) { return $item; } $parsed = self::parse_snapshot_or_null( [ 'items' => [ $id => $item ], 'order' => [ $id ], ] ); if ( null !== $parsed && isset( $parsed['items'][ $id ] ) ) { return $parsed['items'][ $id ]; } return $item; } protected function get_item_prefix(): string { return Template_Library_Import_Export_Utils::GLOBAL_CLASS_ID_PREFIX; } protected function get_max_items(): int { return Global_Classes_Rest_Api::MAX_ITEMS; } protected function can_access_repository(): bool { return class_exists( Global_Classes_Repository::class ) && $this->has_active_kit(); } protected function load_current_data(): array { $repository = Global_Classes_Repository::make()->set_preview( true ); $labels = $repository->all_labels(); $items = []; foreach ( $labels as $id => $label ) { $items[ $id ] = [ 'id' => $id, 'label' => $label, ]; } return [ 'items' => $items, 'order' => $repository->get_order(), ]; } protected function parse_incoming_snapshot( array $snapshot ): ?array { return self::parse_snapshot_or_null( $snapshot ); } protected function get_incoming_items( array $parsed_snapshot ): array { $items = []; $order = $parsed_snapshot['order'] ?? array_keys( $parsed_snapshot['items'] ?? [] ); foreach ( $order as $id ) { if ( isset( $parsed_snapshot['items'][ $id ] ) ) { $items[ $id ] = $parsed_snapshot['items'][ $id ]; } } return $items; } protected function count_current_items( array $items ): int { return count( $items ); } protected function save_data( array $data, array $metadata ): array { $new_items = $data['new_items'] ?? []; $order = $data['order'] ?? []; if ( empty( $new_items ) ) { return [ 'global_classes' => [ 'added_items' => [], 'added_items_order' => [], ], ]; } $repository = Global_Classes_Repository::make()->set_preview( false ); $added_ids = array_keys( $new_items ); $repository->apply_changes( $new_items, [ 'added' => $added_ids, 'order' => true, ], $order ); return [ 'global_classes' => [ 'added_items_order' => $added_ids, 'added_items' => $new_items, ], ]; } protected function prepare_item_for_save( array $item, string $target_id ): array { $item['id'] = $target_id; return $item; } private function has_active_kit(): bool { return (bool) Plugin::instance()->kits_manager->get_active_kit(); } private static function parse_snapshot_or_null( array $snapshot ): ?array { $snapshot['order'] = Global_Classes_Parser::sanitize_order( $snapshot['items'] ?? [], $snapshot['order'] ?? [] ); $parse_result = Global_Classes_Parser::make()->parse( $snapshot ); if ( ! $parse_result->is_valid() ) { return null; } return $parse_result->unwrap(); } } global-classes/utils/template-library-global-classes.php 0000644 00000004113 15252521347 0017463 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Utils; use Elementor\Core\Utils\Template_Library_Import_Export_Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Template_Library_Global_Classes { public static function add_global_classes_snapshot( array $snapshots, $content, $template_id, array $export_data ): array { if ( ! is_array( $content ) ) { return $snapshots; } if ( ! empty( $snapshots['global_classes'] ) ) { return $snapshots; } $snapshot = Template_Library_Global_Classes_Snapshot_Builder::build_snapshot_for_elements( $content ); if ( ! empty( $snapshot ) ) { $snapshots['global_classes'] = $snapshot; } return $snapshots; } public static function extract_global_classes_from_data( array $snapshots, array $decoded_data, array $data ): array { $snapshot = $decoded_data['global_classes'] ?? null; if ( ! empty( $snapshot ) && is_array( $snapshot ) ) { $snapshots['global_classes'] = $snapshot; } return $snapshots; } public static function process_global_classes_import( array $result, string $import_mode, array $data ): array { $snapshot = $data['global_classes'] ?? null; if ( empty( $snapshot ) || ! is_array( $snapshot ) ) { return $result; } $snapshot = apply_filters( 'elementor/global_classes/import/transform_snapshot', $snapshot, $import_mode, $result, $data ); $processed = Template_Library_Import_Export_Utils::process_import_by_mode( $import_mode, $result['content'], $snapshot, [ Template_Library_Global_Classes_Snapshot_Builder::class, 'merge_snapshot_and_get_id_map' ], [ Template_Library_Global_Classes_Snapshot_Builder::class, 'create_snapshot_as_new' ], [ Template_Library_Global_Classes_Element_Transformer::class, 'rewrite_elements_classes_ids' ], [ Template_Library_Global_Classes_Element_Transformer::class, 'flatten_elements_classes' ] ); $result['content'] = $processed['content']; $result['updated_global_classes'] = $processed['operation_result']['global_classes'] ?? null; $result['classes_to_flatten'] = $processed['ids_to_flatten']; return $result; } } global-classes/utils/global-class-data-normalizer.php 0000644 00000001714 15252521347 0016753 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Global_Class_Data_Normalizer { public static function normalize_styles( array $raw_items ): array { $normalized = []; foreach ( $raw_items as $class_id => $class_data ) { $normalized[ $class_id ] = self::normalize_style( $class_id, $class_data ); } return $normalized; } public static function normalize_style( string $class_id, array $class_data ): array { return array_merge( [ 'id' => $class_id, 'label' => $class_data['label'] ?? $class_id, ], self::normalize_style_fields( $class_data ) ); } public static function normalize_style_fields( array $item ): array { $data = [ 'type' => $item['type'] ?? 'class', 'variants' => $item['variants'] ?? [], ]; if ( array_key_exists( 'sync_to_v3', $item ) ) { $data['sync_to_v3'] = (bool) $item['sync_to_v3']; } return $data; } } global-classes/utils/kit-utils.php 0000644 00000001744 15252521347 0013251 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Utils; use Elementor\Core\Base\Document; use Elementor\Core\Kits\Documents\Kit; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Kit_Utils { /** * Returns all kit documents on the current site. * * @return Kit[] */ public static function get_all_kit_documents(): array { $kit_ids = get_posts( [ 'post_type' => Source_Local::CPT, 'post_status' => 'any', 'fields' => 'ids', 'posts_per_page' => -1, 'no_found_rows' => true, 'update_post_meta_cache' => false, 'meta_query' => [ [ 'key' => Document::TYPE_META_KEY, 'value' => 'kit', ], ], ] ); $kits = []; foreach ( $kit_ids as $kit_id ) { $kit = Plugin::$instance->kits_manager->get_kit( $kit_id ); if ( $kit ) { $kits[] = $kit; } } return $kits; } } global-classes/utils/atomic-elements-utils.php 0000644 00000003735 15252521347 0015552 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Utils; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Utils\Utils as Atomic_Utils; use Elementor\Modules\AtomicWidgets\PropTypeMigrations\Schema_Resolver; use Elementor\Plugin; class Atomic_Elements_Utils { public static function is_classes_prop( $prop ) { // phpcs:ignore return 'plain' === $prop::$KIND && 'classes' === $prop->get_key(); } public static function collect_class_ids_from_element_data( array $element_data ): array { $element_type = self::get_element_type( $element_data ); $element_instance = self::get_element_instance( $element_type ); if ( ! Atomic_Utils::is_atomic( $element_instance ) ) { return []; } $schema = Schema_Resolver::get_widget_schema( $element_type ); $settings = $element_data['settings'] ?? []; $class_ids = []; foreach ( $schema as $settings_key => $prop ) { if ( ! self::is_classes_prop( $prop ) ) { continue; } $values = $settings[ $settings_key ]['value'] ?? []; if ( ! is_array( $values ) ) { continue; } foreach ( $values as $class_id ) { if ( is_string( $class_id ) && '' !== $class_id ) { $class_ids[] = $class_id; } } } return $class_ids; } public static function get_element_type( $element ) { return 'widget' === $element['elType'] ? $element['widgetType'] : $element['elType']; } public static function get_element_instance( $element_type ) { $widget = Plugin::instance()->widgets_manager->get_widget_types( $element_type ); $element = Plugin::instance()->elements_manager->get_element_types( $element_type ); return $widget ?? $element; } public static function is_atomic_element( $element_instance ) { if ( ! $element_instance ) { return false; } return ( $element_instance instanceof Atomic_Element_Base || $element_instance instanceof Atomic_Widget_Base ); } } global-classes/import-export-utils/import-utils.php 0000644 00000023576 15252521347 0016632 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\ImportExportUtils; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Modules\GlobalClasses\Global_Class_Post; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Modules\GlobalClasses\Global_Classes_REST_API; use Elementor\Modules\AtomicWidgets\Parsers\Style_Parser; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; use Elementor\Modules\AtomicWidgets\PropTypeMigrations\Migrations_Orchestrator; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Import_Utils { const ORDER_FILE = 'order.json'; const DEFAULT_CONFLICT_RESOLUTION = 'skip'; const ERROR_NOT_ARRAY = 'not_array'; const ERROR_MISSING_FIELDS = 'missing_fields'; const ERROR_FILE_NOT_FOUND = 'file_not_found'; const ERROR_INVALID_JSON = 'invalid_json'; const ERROR_INVALID_PROPS = 'invalid_props'; const ERROR_ID_MISMATCH = 'id_mismatch'; const ERROR_LIMIT_REACHED = 'limit_reached'; const EMPTY_RESULT = [ 'created' => [], 'renamed' => [], 'replaced' => [], 'skipped' => [], 'failed' => [], ]; public static function import_classes( string $classes_dir, array $options = [] ) { $order_file = rtrim( $classes_dir, '/' ) . '/' . self::ORDER_FILE; $active_kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $active_kit || ! is_dir( $classes_dir ) || ! file_exists( $order_file ) ) { return self::EMPTY_RESULT; } $conflict_resolution = $options['conflict_resolution'] ?? self::DEFAULT_CONFLICT_RESOLUTION; $classes_repository = Global_Classes_Repository::make( $active_kit ); $classes_repository->set_preview( false ); $imported_classes_order = json_decode( file_get_contents( $order_file ), true ); if ( ! is_array( $imported_classes_order ) ) { throw new \Exception( 'Invalid file: order.json is not valid JSON.' ); } if ( empty( $imported_classes_order ) ) { return self::EMPTY_RESULT; } global $wpdb; $wpdb->query( 'START TRANSACTION' ); try { [ 'result' => $result, 'changes' => $changes ] = self::do_import( $classes_repository, $classes_dir, $imported_classes_order, $conflict_resolution ); $wpdb->query( 'COMMIT' ); } catch ( \Throwable $e ) { $wpdb->query( 'ROLLBACK' ); throw $e; } if ( $changes ) { if ( function_exists( 'wp_cache_flush_runtime' ) ) { wp_cache_flush_runtime(); } do_action( 'elementor/global_classes/update', Global_Classes_Repository::CONTEXT_FRONTEND, $changes ); } return $result; } private static function do_import( Global_Classes_Repository $classes_repository, string $classes_dir, array $imported_classes_order, string $conflict_resolution ): array { $added_classes_order = []; $added_classes_labels = []; $modified_classes = []; $deleted_classes = []; $previous_order = $classes_repository->get_order(); $order_set = array_flip( $previous_order ); $style_parser = Style_Parser::make( Style_Schema::get() ); $classes_dir = rtrim( $classes_dir, '/' ); if ( 'override-all' === $conflict_resolution ) { $deleted_classes = $previous_order; $classes_repository->delete_all(); $previous_order = []; $order_set = []; } $label_to_id_map = self::build_label_to_id_map_from_labels( $classes_repository->all_labels() ); $result = self::EMPTY_RESULT; foreach ( $imported_classes_order as $import_entry ) { [ 'is_valid' => $is_valid, 'error' => $validation_error ] = self::validate_class_entry( $import_entry ); if ( ! $is_valid ) { $result['failed'][] = [ 'import_entry' => $import_entry, 'error' => $validation_error, ]; continue; } $action = self::resolve_item_action( $import_entry, $label_to_id_map, $conflict_resolution ); if ( 'skip' === $action ) { $result['skipped'][] = [ 'import_entry' => $import_entry ]; continue; } if ( 'replace' !== $action && count( $order_set ) >= Global_Classes_REST_API::MAX_ITEMS ) { $result['failed'][] = [ 'import_entry' => $import_entry, 'error' => self::ERROR_LIMIT_REACHED, ]; continue; } $class_file = $classes_dir . '/' . $import_entry['id'] . '.json'; if ( ! file_exists( $class_file ) ) { $result['failed'][] = [ 'import_entry' => $import_entry, 'error' => self::ERROR_FILE_NOT_FOUND, ]; continue; } $raw_item = json_decode( file_get_contents( $class_file ), true ); if ( ! is_array( $raw_item ) ) { $result['failed'][] = [ 'import_entry' => $import_entry, 'error' => self::ERROR_INVALID_JSON, ]; continue; } [ 'is_valid' => $is_valid, 'error' => $sanitize_error, 'sanitized' => $sanitized_item ] = self::sanitize_item( $import_entry['id'], $raw_item, $style_parser ); if ( ! $is_valid ) { $result['failed'][] = [ 'import_entry' => $import_entry, 'error' => $sanitize_error, ]; continue; } if ( 'replace' === $action ) { $existing_id = $label_to_id_map[ strtolower( $import_entry['label'] ) ]; self::replace_existing_class( $existing_id, $sanitized_item ); $modified_classes[] = $existing_id; $result['replaced'][] = [ 'import_entry' => $import_entry, 'result_entry' => [ 'id' => $existing_id, 'label' => $import_entry['label'], ], ]; continue; } if ( 'rename' === $action ) { $existing_labels = array_keys( $label_to_id_map ); $new_label = ImportExportUtils::resolve_label_conflict( $import_entry['label'], $existing_labels ); $sanitized_item['label'] = $new_label; } $new_id = $sanitized_item['id']; if ( isset( $order_set[ $new_id ] ) ) { $new_id = self::generate_unique_id( $order_set ); $sanitized_item['id'] = $new_id; } self::create_new_class( $sanitized_item ); $order_set[ $new_id ] = true; $added_classes_order[] = $new_id; $added_classes_labels[ $new_id ] = $sanitized_item['label']; $label_to_id_map[ strtolower( $sanitized_item['label'] ) ] = $new_id; $result_entry = [ 'id' => $new_id, 'label' => $sanitized_item['label'], ]; if ( 'rename' === $action ) { $result['renamed'][] = [ 'import_entry' => $import_entry, 'result_entry' => $result_entry, ]; } else { $result['created'][] = [ 'import_entry' => $import_entry, 'result_entry' => $result_entry, ]; } } $changes = null; $has_changes = ! empty( $added_classes_order ) || ! empty( $modified_classes ) || ! empty( $deleted_classes ); if ( $has_changes ) { $new_order = array_merge( $added_classes_order, $previous_order ); $classes_repository->update_order_and_labels( $new_order, $added_classes_labels ); $changes = [ 'added' => $added_classes_order, 'deleted' => $deleted_classes, 'modified' => $modified_classes, 'order' => count( $added_classes_order ) > 0 || count( $deleted_classes ) > 0, ]; } return [ 'result' => $result, 'changes' => $changes, ]; } private static function create_new_class( array $sanitized_item ): void { $created = Global_Class_Post::create( $sanitized_item['id'], $sanitized_item['label'], $sanitized_item ); if ( $created ) { clean_post_cache( $created->get_post_id() ); } else { throw new \Exception( 'Failed to create new class: ' . esc_html( $sanitized_item['id'] ) . ' with label: ' . esc_html( $sanitized_item['label'] ) ); } } private static function replace_existing_class( string $existing_id, array $sanitized_item ): void { $post = Global_Class_Post::find_by_class_id( $existing_id ); if ( ! $post ) { throw new \Exception( 'Failed to find existing class: ' . esc_html( $existing_id ) ); } $post->set_preview( false ); $post->update_data( $sanitized_item ); Migrations_Orchestrator::clear_entity_migration_cache( $post->get_post_id(), Global_Classes_Repository::META_KEY_FRONTEND ); clean_post_cache( $post->get_post_id() ); } private static function validate_class_entry( $class_entry ): array { if ( ! is_array( $class_entry ) ) { return [ 'is_valid' => false, 'error' => self::ERROR_NOT_ARRAY, ]; } $missing_fields = []; foreach ( [ 'id', 'label' ] as $field ) { if ( ! isset( $class_entry[ $field ] ) || ! is_string( $class_entry[ $field ] ) ) { $missing_fields[] = $field; } } if ( ! empty( $missing_fields ) ) { return [ 'is_valid' => false, 'error' => self::ERROR_MISSING_FIELDS . ':' . implode( ',', $missing_fields ), ]; } return [ 'is_valid' => true, 'error' => null, ]; } private static function resolve_item_action( array $class_entry, array $existing_label_to_id, string $conflict_resolution ): string { $label_lower = strtolower( $class_entry['label'] ); $has_conflict = isset( $existing_label_to_id[ $label_lower ] ); if ( ! $has_conflict ) { return 'new'; } switch ( $conflict_resolution ) { case 'skip': return 'skip'; case 'replace': return 'replace'; case 'merge': return 'rename'; default: return self::DEFAULT_CONFLICT_RESOLUTION; } } private static function sanitize_item( string $item_id, array $item, Style_Parser $style_parser ): array { $item_result = $style_parser->parse( $item ); if ( ! $item_result->is_valid() ) { return [ 'is_valid' => false, 'error' => self::ERROR_INVALID_PROPS, 'sanitized' => null, ]; } $sanitized_item = $item_result->unwrap(); if ( $item_id !== $sanitized_item['id'] ) { return [ 'is_valid' => false, 'error' => self::ERROR_ID_MISMATCH, 'sanitized' => null, ]; } return [ 'is_valid' => true, 'error' => null, 'sanitized' => $sanitized_item, ]; } private static function build_label_to_id_map_from_labels( array $id_to_label ): array { $map = []; foreach ( $id_to_label as $id => $label ) { $map[ strtolower( $label ) ] = $id; } return $map; } private static function generate_unique_id( array $order_set ): string { do { $id = 'g-' . substr( bin2hex( random_bytes( 4 ) ), 0, 7 ); } while ( isset( $order_set[ $id ] ) ); return $id; } } global-classes/import-export-utils/legacy-import-utils.php 0000644 00000010414 15252521347 0020057 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\ImportExportUtils; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Modules\GlobalClasses\Global_Classes_Parser; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Legacy_Import_Utils { public static function import_classes( string $global_classes_file, string $conflict_resolution ): array { $global_classes = ImportExportUtils::read_json_file( $global_classes_file ); $active_kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $active_kit || ! $global_classes ) { return Import_Utils::EMPTY_RESULT; } $global_classes_result = Global_Classes_Parser::make()->parse( $global_classes ); if ( ! $global_classes_result->is_valid() ) { throw new \Exception( 'Invalid global classes file: ' . esc_html( $global_classes_result->errors()->to_string() ) ); } $imported_classes = $global_classes_result->unwrap(); if ( empty( $imported_classes['items'] ) ) { return Import_Utils::EMPTY_RESULT; } $classes_repository = Global_Classes_Repository::make( $active_kit ); $classes_repository->set_preview( false ); if ( 'override-all' === $conflict_resolution ) { $ids_to_delete = $classes_repository->get_order(); $imported_items = $imported_classes['items']; $imported_order = $imported_classes['order']; $added_ids = array_keys( $imported_items ); $classes_repository->apply_changes( $imported_items, [ 'added' => $added_ids, 'deleted' => $ids_to_delete, 'order' => true, ], $imported_order ); $result = Import_Utils::EMPTY_RESULT; foreach ( $imported_classes['order'] as $id ) { if ( ! isset( $imported_classes['items'][ $id ] ) ) { continue; } $item = $imported_classes['items'][ $id ]; $entry = [ 'id' => $id, 'label' => $item['label'] ?? $id, ]; $result['created'][] = [ 'import_entry' => $entry, 'result_entry' => $entry, ]; } return $result; } $existing_labels = $classes_repository->all_labels(); $existing_order = $classes_repository->get_order(); $existing_ids_set = array_flip( $existing_order ); $existing_label_keys = array_values( array_map( 'strtolower', $existing_labels ) ); $imported_items = $imported_classes['items'] ?? []; $imported_order = $imported_classes['order'] ?? []; $items_to_add = []; $added_order = []; $result = Import_Utils::EMPTY_RESULT; foreach ( $imported_order as $imported_id ) { if ( ! isset( $imported_items[ $imported_id ] ) ) { continue; } $imported_class = $imported_items[ $imported_id ]; $new_id = $imported_id; if ( isset( $existing_ids_set[ $new_id ] ) || isset( $items_to_add[ $new_id ] ) ) { $all_ids = array_merge( array_keys( $existing_ids_set ), array_keys( $items_to_add ) ); $new_id = self::generate_unique_id( $all_ids ); } $original_label = $imported_class['label'] ?? $imported_id; $new_label = ImportExportUtils::resolve_label_conflict( $original_label, $existing_label_keys ); $existing_label_keys[] = strtolower( $new_label ); $imported_class['id'] = $new_id; $imported_class['label'] = $new_label; $items_to_add[ $new_id ] = $imported_class; $added_order[] = $new_id; $import_entry = [ 'id' => $imported_id, 'label' => $original_label, ]; $result_entry = [ 'id' => $new_id, 'label' => $new_label, ]; $was_renamed = strtolower( $new_label ) !== strtolower( $original_label ); if ( $was_renamed ) { $result['renamed'][] = [ 'import_entry' => $import_entry, 'result_entry' => $result_entry, ]; } else { $result['created'][] = [ 'import_entry' => $import_entry, 'result_entry' => $result_entry, ]; } } if ( ! empty( $items_to_add ) ) { $final_order = array_merge( $added_order, $existing_order ); $added_ids = array_keys( $items_to_add ); $classes_repository->apply_changes( $items_to_add, [ 'added' => $added_ids, 'order' => true, ], $final_order ); } return $result; } private static function generate_unique_id( array $existing_ids ): string { return Utils::generate_id( 'g-', $existing_ids ); } } global-classes/module.php 0000644 00000012706 15252521347 0011451 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Modules\AtomicWidgets\Module as Atomic_Widgets_Module; use Elementor\Modules\DesignSystemSync\Classes\Global_Classes_Sync_Map; use Elementor\Modules\GlobalClasses\Database\Global_Classes_Database_Updater; use Elementor\Modules\GlobalClasses\ImportExport\Import_Export; use Elementor\Modules\GlobalClasses\ImportExportCustomization\Import_Export_Customization; use Elementor\Modules\GlobalClasses\Utils\Template_Library_Global_Classes; use Elementor\Modules\GlobalClasses\Usage\Global_Classes_Usage; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const NAME = 'e_classes'; const ENFORCE_CAPABILITIES_EXPERIMENT = 'global_classes_should_enforce_capabilities'; // TODO: Add global classes package const PACKAGES = [ 'editor-global-classes', ]; public function get_name() { return 'global-classes'; } public function __construct() { parent::__construct(); $this->register_features(); $is_feature_active = Plugin::$instance->experiments->is_feature_active( self::NAME ); $is_atomic_widgets_active = Plugin::$instance->experiments->is_feature_active( Atomic_Widgets_Module::EXPERIMENT_NAME ); // TODO: When the `e_atomic_elements` feature is not hidden, add it as a dependency if ( $is_feature_active && $is_atomic_widgets_active ) { ( new Global_Class_Post_Type() )->register(); ( new Global_Classes_Post_IDs() )->register_hooks(); $relations = new Global_Classes_Relations(); $relations->register_hooks(); add_filter( 'elementor/editor/v2/packages', fn( $packages ) => $this->add_packages( $packages ) ); ( new Global_Classes_Usage() )->register_hooks(); ( new Global_Classes_REST_API() )->register_hooks(); ( new Atomic_Global_Styles( $relations ) )->register_hooks(); ( new Global_Classes_Cleanup() )->register_hooks(); ( new Import_Export() )->register_hooks(); ( new Import_Export_Customization() )->register_hooks(); ( new Global_Classes_Database_Updater() )->register(); add_filter( 'elementor/template_library/export/build_snapshots', [ Template_Library_Global_Classes::class, 'add_global_classes_snapshot' ], 10, 4 ); add_filter( 'elementor/template_library/get_data/extract_snapshots', [ Template_Library_Global_Classes::class, 'extract_global_classes_from_data' ], 10, 3 ); add_filter( 'elementor/template_library/import/process_content', [ Template_Library_Global_Classes::class, 'process_global_classes_import' ], 20, 3 ); add_filter( 'elementor/kit/meta_to_preserve_on_kit_import', [ $this, 'add_meta_to_preserve_on_kit_import' ] ); add_action( 'elementor/kit/after_new_kit_created', [ $this, 'create_global_classes_posts_for_new_kit' ], 10, 1 ); } } public function add_meta_to_preserve_on_kit_import( array $meta_keys ): array { return array_merge( $meta_keys, [ Global_Classes_Order::META_KEY, Global_Classes_Labels::META_KEY_FRONTEND, Global_Classes_Labels::META_KEY_PREVIEW, Global_Classes_Relations::META_KEY_FRONTEND, Global_Classes_Relations::META_KEY_PREVIEW, Global_Classes_Relations::META_KEY_USAGE_INDEXED_FRONTEND, Global_Classes_Relations::META_KEY_USAGE_INDEXED_PREVIEW, Global_Classes_Relations::META_KEY_CLASS_RELATED_POSTS_FRONTEND, Global_Classes_Relations::META_KEY_CLASS_RELATED_POSTS_PREVIEW, Global_Classes_Sync_Map::META_KEY, ] ); } /** * Duplicates global classes posts from the previous kit to the new kit, after a new kit is created. * So each kit has its own, separate, global classes posts, and editing one kit's classes will not affect the other kits. * * @param array $params The parameters passed to the action - 'new_kit_id' and 'previous_kit_id'. * @return void */ public function create_global_classes_posts_for_new_kit( array $params ): void { [ 'new_kit_id' => $new_kit_id, 'previous_kit_id' => $previous_kit_id ] = $params; $previous_kit = Plugin::$instance->kits_manager->get_kit( $previous_kit_id ); $new_kit = Plugin::$instance->kits_manager->get_kit( $new_kit_id ); if ( ! $previous_kit || ! $new_kit ) { return; } $all_classes = Global_Classes_Repository::make( $previous_kit )->get_order(); foreach ( $all_classes as $class_id ) { Global_Class_Post::clone_to_other_kit( $class_id, $previous_kit, $new_kit ); } } private function register_features() { Plugin::$instance->experiments->add_feature([ 'name' => self::NAME, 'title' => esc_html__( 'Global Classes', 'elementor' ), 'description' => esc_html__( 'Enable global CSS classes.', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_INACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_ALPHA, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '4.0.0', ], ]); Plugin::$instance->experiments->add_feature([ 'name' => self::ENFORCE_CAPABILITIES_EXPERIMENT, 'title' => esc_html__( 'Enforce global classes capabilities', 'elementor' ), 'description' => esc_html__( 'Enforce global classes capabilities.', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_ACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_DEV, ]); } private function add_packages( $packages ) { return array_merge( $packages, self::PACKAGES ); } } global-classes/global-classes-parser.php 0000644 00000010231 15252521347 0014340 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Core\Utils\Api\Parse_Result; use Elementor\Modules\AtomicWidgets\Parsers\Style_Parser; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; class Global_Classes_Parser { private ?Style_Parser $style_parser = null; public static function make() { return new static(); } private function get_style_parser(): Style_Parser { if ( null === $this->style_parser ) { $this->style_parser = Style_Parser::make( Style_Schema::get() ); } return $this->style_parser; } public function parse( $data ): Parse_Result { $result = Parse_Result::make(); if ( ! isset( $data['items'] ) ) { $result->errors()->add( 'items', 'missing' ); return $result; } if ( ! isset( $data['order'] ) ) { $result->errors()->add( 'order', 'missing' ); return $result; } $items = $data['items']; $order = $data['order']; if ( ! is_array( $items ) ) { $result->errors()->add( 'items', 'invalid' ); return $result; } if ( ! is_array( $order ) ) { $result->errors()->add( 'order', 'invalid' ); return $result; } $items_result = $this->parse_items( $items ); if ( ! $items_result->is_valid() ) { $result->errors()->merge( $items_result->errors(), 'items' ); return $result; } $sanitized_items = $items_result->unwrap(); $order_result = $this->parse_order( $order, array_keys( $sanitized_items ) ); if ( ! $order_result->is_valid() ) { $result->errors()->merge( $order_result->errors(), 'order' ); return $result; } $sanitized_order = $order_result->unwrap(); return $result->wrap( [ 'items' => $sanitized_items, 'order' => $sanitized_order, ] ); } public function parse_items( array $items ) { $sanitized_items = []; $result = Parse_Result::make(); $style_parser = $this->get_style_parser(); foreach ( $items as $item_id => $item ) { $item_result = $style_parser->parse( $item ); if ( ! $item_result->is_valid() ) { $result->errors()->merge( $item_result->errors(), $item_id ); continue; } $sanitized_item = $item_result->unwrap(); if ( $item_id !== $sanitized_item['id'] ) { $result->errors()->add( "$item_id.id", 'mismatching_value' ); continue; } $sanitized_items[ $sanitized_item['id'] ] = $sanitized_item; } return $result->wrap( $sanitized_items ); } public function parse_order( array $order, array $final_item_ids ) { $result = Parse_Result::make(); $expected_ids = array_values( $final_item_ids ); $order_unique = array_values( array_unique( array_filter( $order, 'is_string' ) ) ); $missing_ids = array_diff( $expected_ids, $order_unique ); $excess_ids = array_diff( $order_unique, $expected_ids ); foreach ( $missing_ids as $id ) { $result->errors()->add( $id, 'missing' ); } foreach ( $excess_ids as $id ) { $result->errors()->add( $id, 'excess' ); } return $result->is_valid() ? $result->wrap( $order_unique ) : $result; } public static function check_for_duplicate_labels( array $label_by_id, array $deleted_ids, array $items, array $new_items_ids ) { if ( empty( $new_items_ids ) ) { return []; } $new_added_items = array_filter( $items, fn( $item ) => in_array( $item['id'], $new_items_ids, true ) ); $duplicates = []; foreach ( $new_added_items as $item ) { $item_id = $item['id']; $label = $item['label']; foreach ( $label_by_id as $other_id => $other_label ) { if ( in_array( $other_id, $deleted_ids, true ) ) { continue; } if ( $other_id === $item_id ) { continue; } if ( $other_label === $label ) { $duplicates[] = [ 'item_id' => $item_id, 'label' => $label, ]; break; } } } return $duplicates; } public static function sanitize_order( array $items, array $order ): array { if ( empty( $items ) ) { return []; } $item_ids = array_keys( $items ); $order = array_filter( $order, 'is_string' ); $order = array_unique( $order ); $order_existing = array_values( array_intersect( $order, $item_ids ) ); $missing = array_diff( $item_ids, $order_existing ); sort( $missing, SORT_STRING ); return array_merge( $order_existing, $missing ); } } global-classes/global-classes-labels.php 0000644 00000006702 15252521347 0014316 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\GlobalClasses\Concerns\Has_Kit_Dependency; use Elementor\Modules\GlobalClasses\Concerns\Has_Preview_Context; if ( ! defined( 'ABSPATH' ) ) { exit; } class Global_Classes_Labels { use Has_Kit_Dependency; use Has_Preview_Context; const META_KEY_FRONTEND = '_elementor_global_classes_labels'; const META_KEY_PREVIEW = '_elementor_global_classes_labels_preview'; const META_KEY = self::META_KEY_FRONTEND; protected array $context_keys = [ 'labels' => [ 'frontend' => self::META_KEY_FRONTEND, 'preview' => self::META_KEY_PREVIEW, ], ]; private ?array $cache = null; private function __construct() { } public static function make( Kit $kit ): self { return ( new self() )->set_kit( $kit ); } protected function on_preview_change(): void { $this->cache = null; } public function get_labels(): array { $stored = $this->read_stored(); if ( empty( $stored ) ) { return []; } return $stored; } public function get_ordered_labels(): array { $order = Global_Classes_Order::make( $this->get_kit() )->set_preview( $this->is_preview() )->get_order(); $map = $this->get_labels(); if ( $this->is_preview() ) { $frontend_map = self::make( $this->get_kit() )->get_labels(); foreach ( $order as $id ) { if ( ! isset( $map[ $id ] ) && isset( $frontend_map[ $id ] ) ) { $map[ $id ] = $frontend_map[ $id ]; } } } $result = []; foreach ( $order as $id ) { if ( isset( $map[ $id ] ) ) { $result[ $id ] = $map[ $id ]; } } return $result; } public static function generate_unique_label( string $label, array $existing_labels ): string { $prefix = 'DUP_'; $max_length = 50; $has_prefix = str_starts_with( $label, $prefix ); if ( $has_prefix ) { $base = substr( $label, strlen( $prefix ) ); $counter = 1; $candidate = $prefix . $base . $counter; while ( in_array( $candidate, $existing_labels, true ) ) { $candidate = $prefix . $base . ( ++$counter ); } if ( strlen( $candidate ) > $max_length ) { $base = substr( $base, 0, $max_length - strlen( $prefix . $counter ) ); $candidate = $prefix . $base . $counter; } return $candidate; } $available_length = strlen( $label ); $candidate = $prefix . $label; if ( strlen( $candidate ) > $max_length ) { $available_length = $max_length - strlen( $prefix ); $candidate = $prefix . substr( $label, 0, $available_length ); } $base = substr( $label, 0, $available_length ); $counter = 1; while ( in_array( $candidate, $existing_labels, true ) ) { $candidate = $prefix . $base . $counter; if ( strlen( $candidate ) > $max_length ) { $base = substr( $label, 0, $max_length - strlen( $prefix . $counter ) ); $candidate = $prefix . $base . $counter; } ++$counter; } return $candidate; } public function set_labels( array $id_to_label ): bool { $kit = $this->get_kit(); if ( ! $kit ) { return false; } $result = $kit->update_meta( $this->get_context_key( 'labels' ), $id_to_label ); $this->cache = $id_to_label; return false !== $result; } private function read_stored(): array { if ( null !== $this->cache ) { return $this->cache; } $kit = $this->get_kit(); if ( ! $kit ) { $this->cache = []; return []; } $raw = $kit->get_meta( $this->get_context_key( 'labels' ) ); $this->cache = is_array( $raw ) ? $raw : []; return $this->cache; } } global-classes/database/global-classes-database-updater.php 0000644 00000001700 15252521347 0020017 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Database; use Elementor\Core\Database\Base_Database_Updater; use Elementor\Modules\GlobalClasses\Database\Migrations\Add_Capabilities; use Elementor\Modules\GlobalClasses\Database\Migrations\Migrate_All_Kits_Post_IDs; use Elementor\Modules\GlobalClasses\Database\Migrations\Migrate_To_Posts; use Elementor\Modules\GlobalClasses\Database\Migrations\Reconcile_Downgraded_Posts; class Global_Classes_Database_Updater extends Base_Database_Updater { const DB_VERSION = 4; const OPTION_NAME = 'elementor_global_classes_db_version'; protected function get_migrations(): array { return [ 1 => new Add_Capabilities(), 2 => new Migrate_To_Posts(), 3 => new Reconcile_Downgraded_Posts(), 4 => new Migrate_All_Kits_Post_IDs(), ]; } protected function get_db_version() { return static::DB_VERSION; } protected function get_db_version_option_name(): string { return static::OPTION_NAME; } } global-classes/database/migrations/add-capabilities.php 0000644 00000001576 15252521347 0017266 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Database\Migrations; use Elementor\Core\Database\Base_Migration; class Add_Capabilities extends Base_Migration { const UPDATE_CLASS = 'elementor_global_classes_update_class'; const REMOVE_CSS_CLASS = 'elementor_global_classes_remove_class'; const APPLY_CSS_CLASS = 'elementor_global_classes_apply_class'; public function up() { $capabilities = [ self::UPDATE_CLASS => [ 'administrator' ], self::REMOVE_CSS_CLASS => [ 'administrator', 'editor', 'author', 'contributor', 'shop_manager' ], self::APPLY_CSS_CLASS => [ 'administrator', 'editor', 'author', 'contributor', 'shop_manager' ], ]; foreach ( $capabilities as $capability => $roles ) { foreach ( $roles as $role_name ) { $role = get_role( $role_name ); if ( $role ) { $role->add_cap( $capability ); } } } } } global-classes/database/migrations/migrate-all-kits-post-ids.php 0000644 00000022231 15252521347 0021004 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Database\Migrations; use Elementor\Core\Database\Base_Migration; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\GlobalClasses\Global_Class_Post; use Elementor\Modules\GlobalClasses\Global_Class_Post_Type; use Elementor\Modules\GlobalClasses\Global_Classes_Order; use Elementor\Modules\GlobalClasses\Global_Classes_Post_IDs; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Modules\GlobalClasses\Utils\Global_Class_Data_Normalizer; use Elementor\Modules\GlobalClasses\Utils\Kit_Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Ensures every kit on the site has a complete, exclusive _elementor_global_classes_post_ids map. * * Pass A — Kits that were never migrated (no order meta, but have the old aggregated meta): * Runs the same migrate-to-posts logic that previously applied only to the active kit. * * Pass B — Kits that are already migrated but may be missing some entries in their post-id map * (possible due to the old lazy-backfill being shared across kits): * Fills in missing class_id → post_id entries using a conflict-aware resolver that * never reuses a post_id already claimed by another kit. * * Pass C — Ensures no single post_id is referenced by more than one kit's map. * For each shared post_id, the kit with the smallest ID is kept as the owner; all * other ("loser") kits get a freshly created CPT post cloned from the current data. */ class Migrate_All_Kits_Post_IDs extends Base_Migration { public function up(): void { Global_Class_Post_Type::ensure_registered(); $kits = Kit_Utils::get_all_kit_documents(); if ( empty( $kits ) ) { return; } // Pass A: restructure kits that still use the old aggregate meta. $this->restructure_unmigrated_kits( $kits ); // Pass B: fill any gaps in the post-id map for each kit. $claimed_post_ids = $this->build_claimed_post_ids( $kits ); $this->fill_missing_post_ids( $kits, $claimed_post_ids ); // Pass C: break any remaining sharing — one post_id must belong to exactly one kit. $this->deduplicate_shared_post_ids( $kits ); } // ------------------------------------------------------------------------- // Pass A // ------------------------------------------------------------------------- /** * @param Kit[] $kits */ private function restructure_unmigrated_kits( array $kits ): void { foreach ( $kits as $kit ) { Migrate_To_Posts::migrate_kit( $kit ); } } // ------------------------------------------------------------------------- // Pass B // ------------------------------------------------------------------------- /** * Build the union of all post_ids already mapped by any kit on this site. * * @param Kit[] $kits * @return array<int, true> post_id => true */ private function build_claimed_post_ids( array $kits ): array { $claimed_post_ids = []; foreach ( $kits as $kit ) { $map = $this->read_post_ids_map( $kit ); foreach ( $map as $post_id ) { $claimed_post_ids[ (int) $post_id ] = true; } } return $claimed_post_ids; } /** * @param Kit[] $kits * @param array<int, true> $claimed_post_ids Mutable — updated as we resolve entries. */ private function fill_missing_post_ids( array $kits, array &$claimed_post_ids ): void { foreach ( $kits as $kit ) { $order = Global_Classes_Order::make( $kit )->set_preview( false )->get_order(); if ( empty( $order ) ) { continue; } $map = $this->read_post_ids_map( $kit ); $missing = array_diff( $order, array_keys( $map ) ); if ( empty( $missing ) ) { continue; } $aggregate_items = Migrate_To_Posts::get_aggregate_global_classes( $kit )['items'] ?? []; $resolved = []; foreach ( $missing as $class_id ) { $post_id = $this->resolve_post_id_for_class( $class_id, $kit, $aggregate_items, $claimed_post_ids ); if ( null !== $post_id ) { $resolved[ $class_id ] = $post_id; $claimed_post_ids[ $post_id ] = true; } } if ( ! empty( $resolved ) ) { Global_Classes_Post_IDs::make( $kit )->set_many( $resolved ); } } } /** * Find or create a post_id for a class_id that belongs exclusively to $kit. * * Priority: * 1. Find an existing CPT post for this class_id that is not yet claimed by any kit. * 2. Create a fresh CPT post from the kit's aggregate data. * 3. If no aggregate data exists, skip (returns null). * * @param string $class_id * @param ?Kit $kit * @param array<string, array> $aggregate_items * @param array<int, true> $claimed_post_ids Passed by reference so the caller can mark reused ids. * @return int|null */ private function resolve_post_id_for_class( string $class_id, ?Kit $kit, array $aggregate_items, array &$claimed_post_ids ): ?int { // Try to reuse an unclaimed CPT post for this class_id. $candidates = $this->query_cpt_post_ids_for_class( $class_id ); foreach ( $candidates as $candidate_id ) { if ( ! isset( $claimed_post_ids[ $candidate_id ] ) ) { return $candidate_id; } } // All candidates are claimed (or there are none) — create a fresh post. if ( empty( $aggregate_items[ $class_id ] ) ) { if ( empty( $candidates ) ) { // Nothing to create from; skip. return null; } $source_post = Global_Class_Post::from_post_id( end( $candidates ), false ); if ( ! $source_post ) { return null; } $created = Global_Class_Post::create( $class_id, $source_post->get_label(), $source_post->get_data( true ), $kit ); if ( ! $created ) { return null; } return $created->get_post_id(); } $item = $aggregate_items[ $class_id ]; $data = Global_Class_Data_Normalizer::normalize_style_fields( $item ); $label = $item['label'] ?? $class_id; $created = Global_Class_Post::create( $class_id, $label, $data, $kit ); if ( ! $created ) { return null; } return $created->get_post_id(); } /** * Query all published CPT post IDs that carry the given class_id, ordered ascending. * * @return int[] */ private function query_cpt_post_ids_for_class( string $class_id ): array { global $wpdb; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $sql = $wpdb->prepare( "SELECT pm.post_id FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s AND pm.meta_value = %s AND p.post_type = %s AND p.post_status = %s ORDER BY pm.post_id ASC", Global_Class_Post::META_KEY_ID, $class_id, Global_Class_Post_Type::CPT, 'publish' ); // phpcs:enable // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- prepared above. $rows = $wpdb->get_col( $sql ); return array_map( 'intval', $rows ); } // ------------------------------------------------------------------------- // Pass C // ------------------------------------------------------------------------- /** * @param Kit[] $kits */ private function deduplicate_shared_post_ids( array $kits ): void { // Build: post_id -> [(kit, class_id), ...] sorted by kit ID asc (lowest = owner). $post_id_to_references = $this->build_post_id_reference_map( $kits ); foreach ( $post_id_to_references as $post_id => $references ) { if ( count( $references ) <= 1 ) { continue; } // First reference (lowest kit ID) is the owner. All others are losers. $losers = array_slice( $references, 1 ); $source_post = Global_Class_Post::from_post_id( $post_id, false ); if ( ! $source_post ) { continue; } $source_data = $source_post->get_data( true ); $source_label = $source_post->get_label(); foreach ( $losers as [ 'kit' => $loser_kit, 'class_id' => $class_id ] ) { Global_Class_Post::create( $class_id, $source_label, $source_data, $loser_kit ); } } } /** * Returns an array keyed by post_id. Each value is a list of references: * [ ['kit' => Kit, 'class_id' => string], ... ] * sorted by kit post ID ascending so index 0 is always the "owner". * * @param Kit[] $kits Already sorted ascending by ID (Kit_Utils::get_all_kit_documents uses get_posts default order). * @return array<int, array<int, array{kit: Kit, class_id: string}>> */ private function build_post_id_reference_map( array $kits ): array { // Sort kits ascending so the first reference is always the lowest-ID kit. usort( $kits, fn( $a, $b ) => $a->get_id() <=> $b->get_id() ); $map = []; foreach ( $kits as $kit ) { $post_ids_map = $this->read_post_ids_map( $kit ); foreach ( $post_ids_map as $class_id => $post_id ) { $post_id = (int) $post_id; $map[ $post_id ][] = [ 'kit' => $kit, 'class_id' => (string) $class_id, ]; } } return $map; } // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- /** * Read the raw class_id -> post_id map stored on a kit's meta. * * @return array<string, int> */ private function read_post_ids_map( Kit $kit ): array { $raw = $kit->get_meta( Global_Classes_Post_IDs::META_KEY ); return is_array( $raw ) ? $raw : []; } } global-classes/database/migrations/migrate-to-posts.php 0000644 00000006145 15252521347 0017322 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Database\Migrations; use Elementor\Core\Database\Base_Migration; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\GlobalClasses\Concerns\Has_Kit_Dependency; use Elementor\Modules\GlobalClasses\Global_Class_Post_Type; use Elementor\Modules\GlobalClasses\Global_Classes_Order; use Elementor\Modules\GlobalClasses\Global_Classes_Post_IDs; use Elementor\Modules\GlobalClasses\Global_Classes_Relations; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Modules\GlobalClasses\Utils\Global_Class_Data_Normalizer; use Elementor\Modules\GlobalClasses\Utils\Kit_Utils; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Migrate_To_Posts extends Base_Migration { use Has_Kit_Dependency; public function up() { Global_Class_Post_Type::ensure_registered(); $active_kit = $this->get_kit(); foreach ( Kit_Utils::get_all_kit_documents() as $kit ) { $migrated = $this->migrate_kit( $kit ); if ( $migrated && $active_kit && $kit->get_id() === $active_kit->get_id() ) { self::run_document_tracking( $kit ); } } // We'll comment it out for now as we may prefer to avoid data restoration upon downgrading // $this->cleanup_kit_meta(); } public static function migrate_kit( Kit $kit ): bool { $global_classes = self::get_aggregate_global_classes( $kit ); if ( empty( $global_classes ) || empty( $global_classes['items'] ) ) { return false; } $existing_order = Global_Classes_Order::make( $kit )->set_preview( false )->get_order(); if ( ! empty( $existing_order ) ) { return false; } $raw_items = $global_classes['items']; $order = $global_classes['order'] ?? array_keys( $raw_items ); $items = Global_Class_Data_Normalizer::normalize_styles( $raw_items ); Global_Classes_Repository::make( $kit )->put( $items, $order ); return true; } public static function get_aggregate_global_classes( ?Kit $kit = null ): array { $empty_result = [ 'items' => [], 'order' => [], ]; if ( ! $kit ) { return $empty_result; } return $kit->get_json_meta( Global_Classes_Repository::META_KEY_FRONTEND ) ?? $empty_result; } public static function run_document_tracking( ?Kit $kit ): void { if ( ! $kit ) { return; } $valid_class_ids = Global_Classes_Order::make( $kit )->set_preview( false )->get_order(); if ( empty( $valid_class_ids ) ) { return; } $relations = new Global_Classes_Relations(); Plugin::$instance->db->iterate_elementor_documents( function ( $document ) use ( $relations, $valid_class_ids ) { $post_id = $document->get_main_id(); $used_class_ids = $relations->collect_class_ids_from_post( $post_id, $valid_class_ids ); if ( ! empty( $used_class_ids ) ) { $relations->set_styles_for_post( $post_id, $used_class_ids ); } } ); } private function cleanup_kit_meta(): void { $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return; } $kit->delete_meta( Global_Classes_Repository::META_KEY_FRONTEND ); $kit->delete_meta( Global_Classes_Repository::META_KEY_PREVIEW ); } } global-classes/database/migrations/reconcile-downgraded-posts.php 0000644 00000014366 15252521347 0021335 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Database\Migrations; use Elementor\Core\Database\Base_Migration; use Elementor\Core\Upgrade\Manager; use Elementor\Modules\GlobalClasses\Concerns\Has_Kit_Dependency; use Elementor\Modules\GlobalClasses\Global_Class_Post; use Elementor\Modules\GlobalClasses\Global_Class_Post_Type; use Elementor\Modules\GlobalClasses\Global_Classes_Labels; use Elementor\Modules\GlobalClasses\Global_Classes_Order; use Elementor\Modules\GlobalClasses\Utils\Global_Class_Data_Normalizer; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Reconciles global class posts for users who already ran the posts migration. * * Upgrade states: * - A: still on aggregate structure, handled by `Migrate_To_Posts` (which now sets an initial edit timestamp). * - B: upgraded, downgraded to the aggregated structure, then edited classes. * - C: upgraded and kept editing classes in posts. * * B and C already have DB version 2, so they skip `Migrate_To_Posts`. * This migration aligns aggregate kit meta with class posts, for group B */ class Reconcile_Downgraded_Posts extends Base_Migration { use Has_Kit_Dependency; private const QUERY_LIMIT = 10; // Just a safety measurement to make sure we have a big-enough sample size of new edits' versions private const POST_STORAGE_INTRO_VERSION = '4.1.0'; private $should_overwrite = null; public function up() { Global_Class_Post_Type::ensure_registered(); $kit = $this->get_kit(); if ( ! $kit ) { return; } $aggregate = Migrate_To_Posts::get_aggregate_global_classes( $kit ); if ( empty( $aggregate ) || empty( $aggregate['items'] ) ) { return; } $items = Global_Class_Data_Normalizer::normalize_styles( $aggregate['items'] ); $order = $aggregate['order'] ?? array_keys( $items ); $touched_any = $this->reconcile_posts( $items ); if ( ! $touched_any ) { return; } $this->sync_order_and_labels( $kit, $items, $order ); Migrate_To_Posts::run_document_tracking( $kit ); } private function reconcile_posts( array $items ): bool { $post_map = $this->build_post_map( $items ); if ( null === $post_map ) { return false; } if ( ! $this->should_overwrite_existing_posts() ) { return false; } return $this->apply_reconciliation( $items, $post_map ); } /** * @return array<string, Global_Class_Post|null>|null Null when a Group A post is detected (edit timestamp found). */ private function build_post_map( array $items ): ?array { $post_map = []; foreach ( $items as $class_id => $item ) { $post = Global_Class_Post::find_by_class_id( $class_id, false ); if ( $post && $post->has_edit_timestamp() ) { $this->set_should_overwrite( false ); return null; } $post_map[ $class_id ] = $post; } return $post_map; } private function apply_reconciliation( array $items, array $post_map ): bool { $touched_any = false; foreach ( $items as $class_id => $item ) { $post = $post_map[ $class_id ]; $normalized_item = Global_Class_Data_Normalizer::normalize_style( $class_id, $item ); if ( ! $post ) { $created = Global_Class_Post::create( $normalized_item['id'], $normalized_item['label'], $normalized_item ); if ( $created ) { $touched_any = true; } continue; } $normalized_data = Global_Class_Data_Normalizer::normalize_style_fields( $normalized_item ); $post->update_data( $normalized_data ); $post->update_label( $normalized_item['label'] ); $touched_any = true; } return $touched_any; } private function set_should_overwrite( bool $should_overwrite ): void { $this->should_overwrite = $should_overwrite; } private function should_overwrite_existing_posts(): bool { if ( null !== $this->should_overwrite ) { return $this->should_overwrite; } $history = Manager::get_installs_history(); $intro_ts = $history[ self::POST_STORAGE_INTRO_VERSION ] ?? null; if ( ! $intro_ts ) { $this->set_should_overwrite( true ); return true; } global $wpdb; $cutoff = gmdate( 'Y-m-d H:i:s', (int) $intro_ts ); $cpt = Global_Class_Post_Type::CPT; $versions = $wpdb->get_col( $wpdb->prepare( "SELECT pm.meta_value FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id AND p.post_type <> %s WHERE pm.meta_key = '_elementor_version' AND pm.meta_value <> %s AND p.post_modified_gmt > %s ORDER BY pm.meta_value ASC LIMIT %d", $cpt, self::POST_STORAGE_INTRO_VERSION, $cutoff, self::QUERY_LIMIT ) ); foreach ( $versions as $version ) { if ( ! is_string( $version ) || '' === $version ) { continue; } if ( version_compare( $version, self::POST_STORAGE_INTRO_VERSION, '<' ) ) { $this->should_overwrite = true; return true; } } $this->set_should_overwrite( false ); return false; } private function sync_order_and_labels( $kit, array $items, array $order ): void { $aggregated_ids = array_keys( $items ); $frontend_order = Global_Classes_Order::make( $kit )->set_preview( false ); $merged_order = array_values( array_unique( array_merge( $order, array_diff( $frontend_order->get_order(), $aggregated_ids ) ) ) ); $frontend_order->set_order( $merged_order ); $frontend_labels = Global_Classes_Labels::make( $kit )->set_preview( false ); $label_map = $frontend_labels->get_labels(); $label_to_id = array_flip( $label_map ); foreach ( $merged_order as $id ) { $class_post = Global_Class_Post::find_by_class_id( $id, false ); if ( $class_post && $class_post->was_edited() ) { $candidate = $class_post->get_label(); } elseif ( isset( $items[ $id ] ) ) { $candidate = $items[ $id ]['label']; } else { continue; } // Original style using the current style's label $owner_id = $label_to_id[ $candidate ] ?? null; if ( $owner_id === $id ) { continue; } if ( null !== $owner_id ) { // Duplicate label - generate a unique one $candidate = Global_Classes_Labels::generate_unique_label( $candidate, array_values( $label_map ) ); } if ( $class_post && $class_post->get_label() !== $candidate ) { $class_post->update_label( $candidate ); } unset( $label_to_id[ $label_map[ $id ] ?? '' ] ); $label_map[ $id ] = $candidate; $label_to_id[ $candidate ] = $id; } $frontend_labels->set_labels( $label_map ); } } global-classes/global-class-post.php 0000644 00000020200 15252521347 0013476 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Modules\AtomicWidgets\PropTypeMigrations\Migrations_Orchestrator; use Elementor\Modules\GlobalClasses\Concerns\Has_Preview_Context; use Elementor\Plugin; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\GlobalClasses\Utils\Global_Class_Data_Normalizer; use WP_Post; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Global_Class_Post { use Has_Preview_Context; const META_KEY_VERSION = '_elementor_version'; const META_KEY_ID = '_elementor_global_class_id'; const META_KEY_DATA = '_elementor_global_class_data'; const META_KEY_DATA_PREVIEW = '_elementor_global_class_data_preview'; const META_KEY_EDITED = '_elementor_global_class_edited'; protected array $context_keys = [ 'data' => [ 'frontend' => self::META_KEY_DATA, 'preview' => self::META_KEY_DATA_PREVIEW, ], ]; private WP_Post $post; private function __construct( WP_Post $post ) { $this->post = $post; } public static function from_post( WP_Post $post, bool $is_preview = false ): self { return ( new static( $post ) )->set_preview( $is_preview ); } public static function from_post_id( int $post_id, bool $is_preview = false ): ?self { $post = get_post( $post_id ); if ( ! $post || Global_Class_Post_Type::CPT !== $post->post_type ) { return null; } return ( new static( $post ) )->set_preview( $is_preview ); } public static function find_by_class_id( string $class_id, bool $is_preview = false, ?Kit $kit = null ): ?self { $kit = $kit ?? Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return null; } $post_id = Global_Classes_Post_IDs::make( $kit )->get_post_id( $class_id ); if ( ! $post_id ) { return null; } return self::from_post_id( $post_id, $is_preview ); } public function get_post_id(): int { return $this->post->ID; } public function get_class_id(): string { $class_id = get_post_meta( $this->post->ID, self::META_KEY_ID, true ); if ( ! $class_id ) { return ''; } return $class_id; } public function get_label(): string { return $this->post->post_title; } public function get_data( bool $skip_migration = false ): array { $data = $this->get_context_data(); $meta_key = $this->get_context_key( 'data' ); // Empty preview (draft) - use frontend (published) data if ( empty( $data ) && $this->is_preview() ) { $data = $this->get_frontend_data(); $meta_key = self::META_KEY_DATA; } if ( ! empty( $data ) && ( ! $skip_migration ) ) { $this->migrate_data( $data, $meta_key ); } return $data; } private function migrate_data( array &$data, string $meta_key ): void { if ( ! Migrations_Orchestrator::is_active() ) { return; } $post_id = $this->post->ID; Migrations_Orchestrator::make()->migrate( $data, $post_id, $meta_key, function ( $migrated ) use ( $post_id, $meta_key ) { update_post_meta( $post_id, $meta_key, $migrated ); clean_post_cache( $post_id ); } ); } public function to_array( bool $skip_migration = false ): array { $data = $this->get_data( $skip_migration ); $class_id = $this->get_class_id(); return Global_Class_Data_Normalizer::normalize_style( $class_id, array_merge( $data, [ 'label' => $this->get_label() ] ) ); } public function was_edited(): bool { $last_edited = $this->get_last_edited_timestamp(); $creation = $this->get_creation_timestamp(); return $last_edited > $creation; } public function has_edit_timestamp(): bool { return $this->get_last_edited_timestamp() > 0; } private function get_creation_timestamp(): int { $dt = get_post_datetime( $this->post, 'date', 'gmt' ); if ( $dt ) { return (int) $dt->format( 'U' ); } return (int) strtotime( (string) $this->post->post_date ); } private function get_last_edited_timestamp(): int { $raw = get_post_meta( $this->post->ID, self::META_KEY_EDITED, true ); if ( self::is_timestamp( $raw ) ) { return (int) $raw; } return 0; } private static function is_timestamp( $value ): bool { if ( ! is_numeric( $value ) ) { return false; } return (int) $value > 0; } protected function get_current_timestamp(): int { return (int) time(); } public function update_label( string $label ): bool { $result = wp_update_post( [ 'ID' => $this->post->ID, 'post_title' => $label, ] ); if ( ! is_wp_error( $result ) && ! $this->is_preview() ) { update_post_meta( $this->post->ID, self::META_KEY_EDITED, $this->get_current_timestamp() ); } return ! is_wp_error( $result ); } private function get_context_data(): array { $data = get_post_meta( $this->post->ID, $this->get_context_key( 'data' ), true ); return is_array( $data ) ? $data : []; } private function get_frontend_data(): array { $data = get_post_meta( $this->post->ID, self::META_KEY_DATA, true ); return is_array( $data ) ? $data : []; } private function get_preview_data(): array { $data = get_post_meta( $this->post->ID, self::META_KEY_DATA_PREVIEW, true ); return is_array( $data ) ? $data : []; } private function get_version(): string { $version = get_post_meta( $this->post->ID, self::META_KEY_VERSION, true ); return is_string( $version ) ? $version : ''; } public function update_data( array $data, string $version = ELEMENTOR_VERSION ): bool { $meta_key = $this->get_context_key( 'data' ); $result = update_post_meta( $this->post->ID, $meta_key, $data ); if ( ! $this->is_preview() ) { delete_post_meta( $this->post->ID, self::META_KEY_DATA_PREVIEW ); } update_post_meta( $this->post->ID, self::META_KEY_VERSION, $version ); if ( ! $this->is_preview() ) { update_post_meta( $this->post->ID, self::META_KEY_EDITED, $this->get_current_timestamp() ); } return false !== $result; } public static function create( string $class_id, string $label, array $data, ?Kit $kit = null, string $version = ELEMENTOR_VERSION ): ?self { $post_id = wp_insert_post( [ 'post_type' => Global_Class_Post_Type::CPT, 'post_title' => $label, 'post_status' => 'publish', ] ); if ( is_wp_error( $post_id ) || ! $post_id ) { return null; } $normalized_data = Global_Class_Data_Normalizer::normalize_style_fields( $data ); update_post_meta( $post_id, self::META_KEY_ID, $class_id ); update_post_meta( $post_id, self::META_KEY_DATA, $normalized_data ); update_post_meta( $post_id, self::META_KEY_VERSION, $version ); $kit = $kit ?? Plugin::$instance->kits_manager->get_active_kit(); if ( $kit ) { Global_Classes_Post_IDs::make( $kit )->set( $class_id, (int) $post_id ); } $instance = self::from_post_id( $post_id ); if ( $instance ) { update_post_meta( $post_id, self::META_KEY_EDITED, $instance->get_creation_timestamp() ); } return $instance; } public function delete(): bool { $result = wp_delete_post( $this->post->ID, true ); return false !== $result; } public static function clone_to_other_kit( string $style_id, Kit $source_kit, Kit $target_kit ): ?Global_Class_Post { $source_post = self::find_by_class_id( $style_id, false, $source_kit ); if ( ! $source_post ) { return null; } $new_post_id = wp_insert_post( [ 'post_type' => Global_Class_Post_Type::CPT, 'post_title' => $source_post->get_label(), 'post_status' => 'publish', ] ); if ( is_wp_error( $new_post_id ) || ! $new_post_id ) { return null; } update_post_meta( $new_post_id, self::META_KEY_ID, $style_id ); update_post_meta( $new_post_id, self::META_KEY_VERSION, $source_post->get_version() ); $frontend_data = $source_post->get_frontend_data(); $preview_data = $source_post->get_preview_data(); $last_edited_timestamp = get_post_meta( $source_post->get_post_id(), self::META_KEY_EDITED, true ); if ( ! empty( $frontend_data ) ) { update_post_meta( $new_post_id, self::META_KEY_DATA, $frontend_data ); } if ( ! empty( $preview_data ) ) { update_post_meta( $new_post_id, self::META_KEY_DATA_PREVIEW, $preview_data ); } if ( $last_edited_timestamp ) { update_post_meta( $new_post_id, self::META_KEY_EDITED, $last_edited_timestamp ); } Global_Classes_Post_IDs::make( $target_kit )->set( $style_id, (int) $new_post_id ); return self::from_post_id( $new_post_id ); } } global-classes/usage/global-classes-usage.php 0000644 00000002171 15252521347 0015260 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Usage; use Elementor\Core\Utils\Collection; use Elementor\Modules\GlobalClasses\Usage\Applied_Global_Classes_Usage; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; class Global_Classes_Usage { const MIN_CLASSES_COUNT = 1; public function register_hooks() { add_filter( 'elementor/tracker/send_tracking_data_params', fn( $params ) => $this->add_tracking_data( $params ) ); } private function add_tracking_data( $params ) { $params['usages']['global_classes']['total_count'] = count( Global_Classes_Repository::make()->all_labels() ); if ( 0 === $params['usages']['global_classes']['total_count'] ) { return $params; } $applied_global_classes_usage = ( new Applied_Global_Classes_Usage() )->get(); $applied_global_classes_usage = Collection::make( $applied_global_classes_usage ) ->filter( fn( $count ) => $count <= self::MIN_CLASSES_COUNT ) ->keys() ->count(); if ( ! empty( $applied_global_classes_usage ) ) { $params['usages']['global_classes']['low_usage_global_classes_count'] = $applied_global_classes_usage; } return $params; } } global-classes/usage/css-class-usage.php 0000644 00000005525 15252521347 0014266 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Usage; /** * Tracks usage of a specific global CSS class across multiple Elementor documents. */ class Css_Class_Usage { /** @var string */ private string $class_id; /** @var int */ private int $total = 0; /** * @var array<int, array{ * title: string, * total: int, * elements: string[], * type?: string * }> */ private array $pages = []; /** * Constructor. * * @param string $class_id Global CSS class ID. */ public function __construct( string $class_id ) { $this->class_id = $class_id; } /** * Track usage of this class on a specific document and element. * * @param int $page_id Document ID. * @param string $page_title Document title. * @param string $element_id Element ID using this class. * @param string|null $document_type Optional document type (e.g. header, footer, etc). */ public function track_usage( int $page_id, string $page_title, string $element_id, ?string $document_type = null ): void { ++$this->total; if ( ! isset( $this->pages[ $page_id ] ) ) { $this->pages[ $page_id ] = [ 'title' => $page_title, 'total' => 0, 'elements' => [], ]; if ( $document_type ) { $this->pages[ $page_id ]['type'] = $document_type; } } ++$this->pages[ $page_id ]['total']; $this->pages[ $page_id ]['elements'][] = $element_id; } /** * Merge usage data from another instance with the same class ID. * * @param Css_Class_Usage $other The other usage object to merge in. * * @throws \InvalidArgumentException If the class IDs do not match. */ public function merge( Css_Class_Usage $other ): void { if ( $other->get_class_id() !== $this->class_id ) { throw new \InvalidArgumentException( 'Mismatched class ID' ); } $this->total += $other->get_total_usage(); foreach ( $other->get_pages() as $page_id => $data ) { if ( ! isset( $this->pages[ $page_id ] ) ) { $this->pages[ $page_id ] = $data; } else { $this->pages[ $page_id ]['total'] += $data['total']; $this->pages[ $page_id ]['elements'] = array_merge( $this->pages[ $page_id ]['elements'], $data['elements'] ); if ( empty( $this->pages[ $page_id ]['type'] ) && ! empty( $data['type'] ) ) { $this->pages[ $page_id ]['type'] = $data['type']; } } } } /** * Get the global class ID this instance tracks. * * @return string */ public function get_class_id(): string { return $this->class_id; } /** * Get the total number of elements using this class. * * @return int */ public function get_total_usage(): int { return $this->total; } /** * Get a map of document usages. * * @return array<int, array{title: string, total: int, elements: string[], type?: string}> */ public function get_pages(): array { return $this->pages; } } global-classes/usage/applied-global-classes-usage.php 0000644 00000005715 15252521347 0016703 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Usage; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Collects and exposes usage data for all global CSS classes across Elementor documents. */ class Applied_Global_Classes_Usage { /** * Document types that should be excluded from usage reporting. * * @var string[] */ private array $excluded_types = [ 'e-flexbox', 'template' ]; /** * Tracks usage for each global class. * * @var array<string, Css_Class_Usage> */ private array $class_usages = []; /** * Returns the total usage count per class ID (excluding template-only classes). * * @return array<string, int> */ public function get(): array { $this->build_class_usages(); $result = []; foreach ( $this->class_usages as $class_id => $usage ) { if ( $usage->get_total_usage() > 0 ) { $result[ $class_id ] = $usage->get_total_usage(); } } return $result; } /** * Returns detailed usage information per class ID. * Each class ID maps to a list of document usages (excluding excluded types). * * @return array<string, array{ * pageId: int, * title: string, * type: string, * total: int, * elements: string[] * }> */ public function get_detailed_usage(): array { $this->build_class_usages(); $result = []; foreach ( $this->class_usages as $class_id => $usage ) { $pages = $usage->get_pages(); $filtered_pages = array_filter( $pages, fn( $page_data ) => ! in_array( $page_data['type'], $this->excluded_types, true ) ); if ( empty( $filtered_pages ) ) { continue; } foreach ( $filtered_pages as $page_id => $page_data ) { $result[ $class_id ][] = [ 'pageId' => $page_id, 'title' => $page_data['title'], 'type' => $page_data['type'], 'total' => $page_data['total'], 'elements' => $page_data['elements'], ]; } } return $result; } /** * Builds the internal usage map from all Elementor documents. * * This method initializes and aggregates class usage from all relevant documents, * merging duplicate class IDs found in multiple pages. */ private function build_class_usages(): void { $this->class_usages = []; $class_ids = array_keys( Global_Classes_Repository::make()->all_labels() ); $class_id_set = array_fill_keys( $class_ids, true ); Plugin::$instance->db->iterate_elementor_documents( function ( $document ) use ( $class_id_set ) { $usage = new Document_Usage( $document, $class_id_set ); $usage->analyze(); foreach ( $usage->get_usages() as $class_id => $class_usage ) { if ( ! isset( $class_id_set[ $class_id ] ) ) { continue; } if ( ! isset( $this->class_usages[ $class_id ] ) ) { $this->class_usages[ $class_id ] = $class_usage; } else { $this->class_usages[ $class_id ]->merge( $class_usage ); } } } ); } } global-classes/usage/document-usage.php 0000644 00000004451 15252521347 0014206 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\Usage; use Elementor\Core\Base\Document as ElementorDocument; use Elementor\Plugin; /** * Tracks usage of global CSS classes within a specific Elementor document. */ class Document_Usage { /** @var ElementorDocument */ private ElementorDocument $document; /** * Map of known global class ID => true for O(1) membership checks. * * @var array<string, true> */ private array $valid_class_id_set; /** @var array<string, Css_Class_Usage> */ private array $usages = []; /** * Constructor. * * @param ElementorDocument $document The Elementor document object. * @param array<string, true> $valid_class_id_set Known global class IDs (e.g. from array_fill_keys). */ public function __construct( ElementorDocument $document, array $valid_class_id_set ) { $this->document = $document; $this->valid_class_id_set = $valid_class_id_set; } /** * Analyze the document to find and record usage of global CSS classes. */ public function analyze(): void { $page_id = $this->document->get_main_id(); $page_title = $this->document->get_post()->post_title; $elements_data = $this->document->get_elements_raw_data() ?? []; $document_type = $this->document->get_type(); if ( empty( $document_type ) ) { $document_type = get_post_type( $page_id ) ?? 'unknown'; } if ( empty( $elements_data ) ) { return; } Plugin::$instance->db->iterate_data( $elements_data, function ( $element_data ) use ( $page_id, $page_title, $document_type ) { $class_values = $element_data['settings']['classes']['value'] ?? []; if ( empty( $class_values ) || ! is_array( $class_values ) ) { return; } foreach ( $class_values as $class_id ) { if ( ! isset( $this->valid_class_id_set[ $class_id ] ) ) { continue; } if ( ! isset( $this->usages[ $class_id ] ) ) { $this->usages[ $class_id ] = new Css_Class_Usage( $class_id ); } $this->usages[ $class_id ]->track_usage( $page_id, $page_title, $element_data['id'] ?? 'unknown', $document_type ); } } ); } /** * Get all recorded usages of global CSS classes in this document. * * @return array<string, Css_Class_Usage> */ public function get_usages(): array { return $this->usages; } } global-classes/import-export/import-export.php 0000644 00000001045 15252521347 0015640 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\ImportExport; use Elementor\App\Modules\ImportExport\Processes\Export; use Elementor\App\Modules\ImportExport\Processes\Import; class Import_Export { const FILE_NAME = 'global-classes'; public function register_hooks() { add_action( 'elementor/import-export/export-kit', function ( Export $export ) { $export->register( new Export_Runner() ); } ); add_action( 'elementor/import-export/import-kit', function ( Import $import ) { $import->register( new Import_Runner() ); } ); } } global-classes/import-export/export-runner.php 0000644 00000002351 15252521347 0015640 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\ImportExport; use Elementor\App\Modules\ImportExport\Runners\Export\Export_Runner_Base; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Modules\GlobalClasses\Global_Classes_Parser; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Export_Runner extends Export_Runner_Base { public static function get_name(): string { return 'global-classes'; } public function should_export( array $data ) { // Same as the site-settings runner. return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) ); } public function export( array $data ) { $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return [ 'manifest' => [], 'files' => [], ]; } $global_classes = Global_Classes_Repository::make( $kit )->all()->get(); $global_classes_result = Global_Classes_Parser::make()->parse( $global_classes ); if ( ! $global_classes_result->is_valid() ) { return [ 'manifest' => [], 'files' => [], ]; } return [ 'files' => [ 'path' => Import_Export::FILE_NAME, 'data' => $global_classes_result->unwrap(), ], ]; } } global-classes/import-export/import-runner.php 0000644 00000003205 15252521347 0015630 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\ImportExport; use Elementor\App\Modules\ImportExport\Runners\Import\Import_Runner_Base; use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils; use Elementor\Modules\GlobalClasses\Global_Classes_Parser; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Import_Runner extends Import_Runner_Base { public static function get_name(): string { return 'global-classes'; } public function should_import( array $data ) { // Same as the site-settings runner. return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) && ! empty( $data['site_settings']['settings'] ) && ! empty( $data['extracted_directory_path'] ) ); } public function import( array $data, array $imported_data ) { $kit = Plugin::$instance->kits_manager->get_active_kit(); $file_name = Import_Export::FILE_NAME; $global_classes = ImportExportUtils::read_json_file( "{$data['extracted_directory_path']}/{$file_name}.json" ); if ( ! $kit || ! $global_classes ) { return []; } $global_classes['order'] = Global_Classes_Parser::sanitize_order( $global_classes['items'] ?? [], $global_classes['order'] ?? [] ); $global_classes_result = Global_Classes_Parser::make()->parse( $global_classes ); if ( ! $global_classes_result->is_valid() ) { return []; } $global_classes = $global_classes_result->unwrap(); Global_Classes_Repository::make( $kit )->put( $global_classes['items'], $global_classes['order'] ); return $global_classes; } } global-classes/global-classes-rest-api.php 0000644 00000032752 15252521347 0014604 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Core\Kits\Documents\Kit; use Elementor\Core\Utils\Api\Error_Builder; use Elementor\Core\Utils\Api\Response_Builder; use Elementor\Modules\GlobalClasses\Database\Migrations\Add_Capabilities; use Elementor\Modules\GlobalClasses\Usage\Applied_Global_Classes_Usage; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Global_Classes_REST_API { const API_NAMESPACE = 'elementor/v1'; const API_BASE = 'global-classes'; const API_BASE_USAGE = self::API_BASE . '/usage'; const API_BASE_POST = self::API_BASE . '/post'; const API_BASE_STYLES = self::API_BASE . '/styles'; const MAX_ITEMS = 1000; private ?Global_Classes_Repository $repository = null; private ?Global_Classes_Relations $relations = null; private ?Kit $kit = null; public function register_hooks() { add_action( 'rest_api_init', fn() => $this->register_routes() ); } public function invalidate_cache() { $this->kit = null; $this->repository = null; $this->relations = null; } private function get_kit(): ?Kit { if ( ! $this->kit ) { $this->kit = Plugin::$instance->kits_manager->get_active_kit(); } return $this->kit; } private function get_repository() { if ( ! $this->repository ) { $this->repository = new Global_Classes_Repository( $this->get_kit() ); } return $this->repository; } private function get_classes_relations(): Global_Classes_Relations { if ( ! $this->relations ) { $this->relations = new Global_Classes_Relations(); } return $this->relations; } private function register_routes() { // cache invalidation at this point is solely for tests, in particular - Test_Global_Classes_Rest_Api $this->invalidate_cache(); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [ [ 'methods' => 'GET', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->all( $request ) ), 'permission_callback' => fn() => is_user_logged_in(), 'args' => [ 'context' => [ 'type' => 'string', 'required' => false, 'default' => Global_Classes_Repository::CONTEXT_FRONTEND, 'enum' => [ Global_Classes_Repository::CONTEXT_FRONTEND, Global_Classes_Repository::CONTEXT_PREVIEW, ], ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE_POST, [ [ 'methods' => 'GET', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->styles_for_post( $request ) ), 'permission_callback' => fn() => is_user_logged_in(), 'args' => [ 'context' => [ 'type' => 'string', 'required' => false, 'default' => Global_Classes_Repository::CONTEXT_FRONTEND, 'enum' => [ Global_Classes_Repository::CONTEXT_FRONTEND, Global_Classes_Repository::CONTEXT_PREVIEW, ], ], 'post_id' => [ 'type' => 'integer', 'required' => true, ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE_STYLES, [ [ 'methods' => 'GET', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->styles_by_ids( $request ) ), 'permission_callback' => fn() => is_user_logged_in(), 'args' => [ 'context' => [ 'type' => 'string', 'required' => false, 'default' => Global_Classes_Repository::CONTEXT_FRONTEND, 'enum' => [ Global_Classes_Repository::CONTEXT_FRONTEND, Global_Classes_Repository::CONTEXT_PREVIEW, ], ], 'ids' => [ 'type' => 'string', 'required' => true, 'description' => 'Comma-separated list of global class IDs', ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE_USAGE, [ [ 'methods' => 'GET', 'callback' => fn() => $this->route_wrapper( fn() => $this->get_usage() ), 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'context' => [ 'type' => 'string', 'required' => false, 'default' => Global_Classes_Repository::CONTEXT_FRONTEND, 'enum' => [ Global_Classes_Repository::CONTEXT_FRONTEND, Global_Classes_Repository::CONTEXT_PREVIEW, ], ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [ [ 'methods' => 'PUT', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->put( $request ) ), 'permission_callback' => fn() => current_user_can( Add_Capabilities::UPDATE_CLASS ), 'args' => [ 'context' => [ 'type' => 'string', 'required' => false, 'default' => Global_Classes_Repository::CONTEXT_FRONTEND, 'enum' => [ Global_Classes_Repository::CONTEXT_FRONTEND, Global_Classes_Repository::CONTEXT_PREVIEW, ], ], 'changes' => [ 'type' => 'object', 'required' => true, 'additionalProperties' => false, 'properties' => [ 'added' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'string' ], ], 'deleted' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'string' ], ], 'modified' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'string' ], ], 'order' => [ 'type' => 'boolean', 'required' => false, ], ], ], 'items' => [ 'required' => true, 'type' => 'object', 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'type' => 'string', 'required' => true, ], 'variants' => [ 'type' => 'array', 'required' => true, ], 'type' => [ 'type' => 'string', 'enum' => [ 'class' ], 'required' => true, ], 'label' => [ 'type' => 'string', 'required' => true, ], ], ], ], 'order' => [ 'required' => true, 'type' => 'array', 'items' => [ 'type' => 'string', ], ], ], ], ] ); } private function all( \WP_REST_Request $request ) { $context = $request->get_param( 'context' ); $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context; $label_by_id = $this->get_repository()->set_preview( $is_preview )->all_labels(); $list = []; foreach ( $label_by_id as $id => $label ) { $list[] = [ 'id' => $id, 'label' => $label, ]; } return Response_Builder::make( $list )->build(); } private function styles_for_post( \WP_REST_Request $request ) { $context = $request->get_param( 'context' ); $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context; $post_id = (int) $request->get_param( 'post_id' ); $document_class_ids = $this->get_classes_relations()->set_preview( $is_preview )->get_styles_by_post( $post_id ); if ( empty( $document_class_ids ) ) { return Response_Builder::make( (object) [] )->set_meta( [ 'order' => [] ] )->build(); } $repository = $this->get_repository()->set_preview( $is_preview ); $global_order = array_keys( $repository->all_labels() ); $filtered_order = array_values( array_intersect( $global_order, $document_class_ids ) ); $items = $repository->get_by_ids( $document_class_ids ); $result = []; foreach ( $document_class_ids as $id ) { $result[ $id ] = $items[ $id ] ?? null; } return Response_Builder::make( (object) $result ) ->set_meta( [ 'order' => $filtered_order ] ) ->build(); } private function styles_by_ids( \WP_REST_Request $request ) { $context = $request->get_param( 'context' ); $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context; $ids_param = $request->get_param( 'ids' ); $requested_ids = array_map( 'trim', explode( ',', $ids_param ) ); $requested_ids = array_filter( $requested_ids ); if ( empty( $requested_ids ) ) { return Response_Builder::make( (object) [] )->set_meta( [ 'order' => [] ] )->build(); } $repository = $this->get_repository()->set_preview( $is_preview ); $global_order = array_keys( $repository->all_labels() ); $filtered_order = array_values( array_intersect( $global_order, $requested_ids ) ); $items = $repository->get_by_ids( $requested_ids ); $result = []; foreach ( $requested_ids as $id ) { $result[ $id ] = $items[ $id ] ?? null; } return Response_Builder::make( (object) $result ) ->set_meta( [ 'order' => $filtered_order ] ) ->build(); } private function get_usage() { $classes_usage = ( new Applied_Global_Classes_Usage() )->get_detailed_usage(); return Response_Builder::make( (object) $classes_usage )->build(); } private function put( \WP_REST_Request $request ) { $context = $request->get_param( 'context' ); $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context; $changes = $request->get_param( 'changes' ) ?? []; $added_ids = $changes['added'] ?? []; $deleted_ids = $changes['deleted'] ?? []; $order = $request->get_param( 'order' ) ?? []; $repository = $this->get_repository()->set_preview( $is_preview ); $all_label_by_id = $repository->all_labels(); $existing_label_list = $this->global_classes_existing_label_list( $all_label_by_id, $deleted_ids ); $total_count = count( $all_label_by_id ) - count( $deleted_ids ) + count( $added_ids ); $parser = Global_Classes_Parser::make(); $items_result = $parser->parse_items( $request->get_param( 'items' ) ?? [] ); if ( ! $items_result->is_valid() ) { return Error_Builder::make( 'invalid_items' ) ->set_status( 400 ) ->set_message( 'Invalid items: ' . $items_result->errors()->to_string() ) ->build(); } $touched_items = $items_result->unwrap(); if ( $total_count > self::MAX_ITEMS ) { return Error_Builder::make( 'global_classes_limit_exceeded' ) ->set_status( 400 ) ->set_meta( [ 'current_count' => $total_count, 'max_allowed' => self::MAX_ITEMS, ] ) ->set_message( sprintf( /* translators: %d: Maximum allowed items. */ __( 'Global classes limit exceeded. Maximum allowed: %d', 'elementor' ), self::MAX_ITEMS ) ) ->build(); } $duplicated_labels = Global_Classes_Parser::check_for_duplicate_labels( $all_label_by_id, $deleted_ids, $touched_items, $added_ids ); $duplicate_validation_result = null; if ( ! empty( $duplicated_labels ) ) { $modified_labels = $this->handle_duplicates( $duplicated_labels, $existing_label_list ); $duplicate_validation_result = $modified_labels; foreach ( $modified_labels as $item_id => $labels ) { $touched_items[ $item_id ]['label'] = $labels['modified']; } } $final_item_ids = array_keys( $this->merge_touched_with_existing_labels( $all_label_by_id, $touched_items, $deleted_ids ) ); $final_item_ids_set = array_flip( $final_item_ids ); $order_set = array_flip( $order ); $order = array_values( array_filter( $order, fn( $id ) => isset( $final_item_ids_set[ $id ] ) ) ); $missing_from_order = array_values( array_filter( $final_item_ids, fn( $id ) => ! isset( $order_set[ $id ] ) ) ); $order = array_merge( $order, $missing_from_order ); $order_result = $parser->parse_order( $order, $final_item_ids ); if ( ! $order_result->is_valid() ) { return Error_Builder::make( 'invalid_order' ) ->set_status( 400 ) ->set_message( 'Invalid order: ' . $order_result->errors()->to_string() ) ->build(); } $repository->apply_changes( $touched_items, [ 'added' => $added_ids, 'deleted' => $changes['deleted'] ?? [], 'modified' => $changes['modified'] ?? [], 'order' => isset( $changes['order'] ) && $changes['order'], // boolean indicating if the order has changed ], $order_result->unwrap() ); if ( $duplicate_validation_result ) { return Response_Builder::make( [ 'code' => 'DUPLICATED_LABEL', 'modifiedLabels' => $duplicate_validation_result, ] )->build(); } return Response_Builder::make()->no_content()->build(); } private function global_classes_existing_label_list( array $label_by_id, array $deleted_ids ): array { $labels = []; foreach ( $label_by_id as $id => $label ) { if ( in_array( $id, $deleted_ids, true ) ) { continue; } $labels[] = $label; } return $labels; } private function merge_touched_with_existing_labels( array $label_by_id, array $touched_items, array $deleted_ids ): array { $final = []; foreach ( $label_by_id as $id => $label ) { if ( in_array( $id, $deleted_ids, true ) ) { continue; } if ( isset( $touched_items[ $id ] ) ) { $final[ $id ] = $touched_items[ $id ]; } else { $final[ $id ] = [ 'id' => $id, 'label' => $label, 'type' => 'class', 'variants' => [], ]; } } foreach ( $touched_items as $id => $item ) { if ( ! isset( $final[ $id ] ) ) { $final[ $id ] = $item; } } return $final; } private function route_wrapper( callable $cb ) { try { $response = $cb(); } catch ( \Exception $e ) { return Error_Builder::make( 'unexpected_error' ) ->set_message( __( 'Something went wrong', 'elementor' ) ) ->build(); } return $response; } private function handle_duplicates( array $duplicate_labels, array $existing_labels ) { $modified_labels = []; foreach ( $duplicate_labels as $duplicate_label ) { $item_id = $duplicate_label['item_id']; $original_label = $duplicate_label['label']; $modified_label = Global_Classes_Labels::generate_unique_label( $original_label, $existing_labels ); $modified_labels[ $item_id ] = [ 'original' => $original_label, 'modified' => $modified_label, ]; } return $modified_labels; } } global-classes/global-classes.php 0000644 00000001643 15252521347 0013055 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Core\Utils\Collection; class Global_Classes { private Collection $items; private Collection $order; private Collection $ordered_items; public static function make( array $items = [], array $order = [] ) { return new static( $items, $order ); } private function __construct( array $data = [], array $order = [] ) { $this->items = Collection::make( $data ); $this->order = Collection::make( $order ); $this->ordered_items = $this->order ->map( fn( $id ) => $data[ $id ] ?? null ) ->filter( fn( $item ) => null !== $item ); } public function get_items() { return $this->items; } public function get_order() { return $this->order; } public function get_ordered_items() { return $this->ordered_items; } public function get() { return [ 'items' => $this->get_items()->all(), 'order' => $this->get_order()->all(), ]; } } global-classes/global-classes-post-ids.php 0000644 00000006523 15252521347 0014617 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\GlobalClasses\Concerns\Has_Kit_Dependency; use Elementor\Modules\GlobalClasses\Utils\Kit_Utils; use WP_Post; if ( ! defined( 'ABSPATH' ) ) { exit; } class Global_Classes_Post_IDs { use Has_Kit_Dependency; const META_KEY = '_elementor_global_classes_post_ids'; private ?array $cache = null; public static function make( ?Kit $kit = null ): self { $instance = new self(); if ( null !== $kit ) { $instance->set_kit( $kit ); } return $instance; } public function register_hooks(): void { add_action( 'deleted_post', [ self::class, 'on_deleted_post' ], 10, 2 ); } public static function on_deleted_post( int $post_id, WP_Post $post ): void { if ( Global_Class_Post_Type::CPT !== $post->post_type ) { return; } foreach ( Kit_Utils::get_all_kit_documents() as $kit ) { self::make( $kit )->remove_post_id( $post_id ); } } public function get_post_id( string $class_id ): ?int { $map = $this->read_map(); if ( ! isset( $map[ $class_id ] ) ) { return null; } $post_id = (int) $map[ $class_id ]; if ( get_post( $post_id ) ) { return $post_id; } $this->remove_post_id( $post_id ); return null; } public function get_post_ids( array $class_ids ): array { if ( empty( $class_ids ) ) { return []; } $map = $this->read_map(); $resolved = []; foreach ( $class_ids as $class_id ) { if ( ! isset( $map[ $class_id ] ) ) { continue; } $post_id = (int) $map[ $class_id ]; if ( get_post( $post_id ) ) { $resolved[ $class_id ] = $post_id; } else { $this->remove_post_id( $post_id ); } } return $resolved; } public function set( string $class_id, int $post_id ): void { $this->set_many( [ $class_id => $post_id ] ); } public function set_many( array $class_id_to_post_id ): void { if ( empty( $class_id_to_post_id ) ) { return; } $map = $this->read_map(); $changed = false; foreach ( $class_id_to_post_id as $class_id => $post_id ) { $post_id = (int) $post_id; if ( ! is_string( $class_id ) || '' === $class_id || $post_id <= 0 ) { continue; } if ( ! isset( $map[ $class_id ] ) || (int) $map[ $class_id ] !== $post_id ) { $map[ $class_id ] = $post_id; $changed = true; } } if ( $changed ) { $this->write_map( $map ); } } public function remove_class_id( string $class_id ): void { $map = $this->read_map(); if ( ! isset( $map[ $class_id ] ) ) { return; } unset( $map[ $class_id ] ); $this->write_map( $map ); } public function remove_post_id( int $post_id ): void { $map = $this->read_map(); $filtered = array_filter( $map, fn( $id ) => (int) $id !== $post_id ); if ( count( $filtered ) === count( $map ) ) { return; } $this->write_map( $filtered ); } private function read_map(): array { if ( null !== $this->cache ) { return $this->cache; } $kit = $this->get_kit(); if ( ! $kit ) { $this->cache = []; return []; } $raw = $kit->get_meta( self::META_KEY ); $this->cache = is_array( $raw ) ? $raw : []; return $this->cache; } private function write_map( array $map ): bool { $kit = $this->get_kit(); if ( ! $kit ) { return false; } $result = $kit->update_meta( self::META_KEY, $map ); $this->cache = $map; return false !== $result; } } global-classes/global-classes-repository.php 0000644 00000035204 15252521347 0015272 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\DesignSystemSync\Classes\Global_Classes_Sync_Map; use Elementor\Modules\GlobalClasses\Concerns\Has_Kit_Dependency; use Elementor\Modules\GlobalClasses\Concerns\Has_Preview_Context; use Elementor\Modules\GlobalClasses\Utils\Global_Class_Data_Normalizer; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Global_Classes_Repository { use Has_Kit_Dependency; use Has_Preview_Context; const META_KEY_FRONTEND = '_elementor_global_classes'; const META_KEY_PREVIEW = '_elementor_global_classes_preview'; const CONTEXT_FRONTEND = 'frontend'; const CONTEXT_PREVIEW = 'preview'; const READ_BATCH_SIZE = 100; const PERSIST_BATCH_SIZE = 100; protected array $context_keys = [ 'event' => [ 'frontend' => self::CONTEXT_FRONTEND, 'preview' => self::CONTEXT_PREVIEW, ], ]; private ?Global_Classes $cache = null; public function __construct( ?Kit $kit = null ) { if ( null !== $kit ) { $this->set_kit( $kit ); } } public static function make( ?Kit $kit = null ): Global_Classes_Repository { return new self( $kit ); } protected function on_preview_change(): void { $this->cache = null; } /** * This method may be too heavy to use * Be mindful as this call would cause the server to freeze for as much time as needed until it fetches * all global classes */ public function all( bool $force = false ): Global_Classes { if ( ! $force && null !== $this->cache ) { return $this->cache; } $this->cache = $this->all_from_posts(); return $this->cache; } public function all_labels(): array { return $this->labels()->get_ordered_labels(); } public function get_order(): array { return Global_Classes_Order::make( $this->get_kit() )->set_preview( $this->is_preview() )->get_order(); } public function update_order_and_labels( array $order, array $new_labels ): void { Global_Classes_Order::make( $this->get_kit() ) ->set_preview( $this->is_preview() ) ->set_order( $order ); $labels = $this->labels(); $existing_labels = $labels->get_labels(); foreach ( $new_labels as $id => $label ) { $existing_labels[ $id ] = $label; } $labels->set_labels( $existing_labels ); if ( ! $this->is_preview() ) { Global_Classes_Order::make( $this->get_kit() ) ->set_preview( true ) ->set_order( $order ); $this->clear_preview_labels_for_ids( array_keys( $new_labels ) ); } $this->cache = null; } private function labels(): Global_Classes_Labels { return Global_Classes_Labels::make( $this->get_kit() )->set_preview( $this->is_preview() ); } public function get( string $class_id ): ?array { $post = Global_Class_Post::find_by_class_id( $class_id, $this->is_preview(), $this->get_kit() ); return $post ? $post->to_array() : null; } public function get_by_ids( array $class_ids ): array { if ( empty( $class_ids ) ) { return []; } $post_ids = Global_Classes_Post_IDs::make( $this->get_kit() )->get_post_ids( $class_ids ); $items = []; foreach ( $post_ids as $class_id => $post_id ) { $post = Global_Class_Post::from_post_id( $post_id, $this->is_preview() ); if ( $post ) { $items[ $class_id ] = $post->to_array(); } } return $items; } public function apply_changes( array $touched_items, array $changes, array $order ): void { $labels = $this->labels(); $before = $labels->get_labels(); $is_preview = $this->is_preview(); $to_delete = $changes['deleted'] ?? []; $to_create = $changes['added'] ?? []; $to_update = $changes['modified'] ?? []; $order_changed = isset( $changes['order'] ) && $changes['order']; $final_label_map = []; foreach ( $order as $id ) { if ( isset( $touched_items[ $id ] ) ) { $final_label_map[ $id ] = $touched_items[ $id ]['label']; } elseif ( isset( $before[ $id ] ) ) { $final_label_map[ $id ] = $before[ $id ]; } } $affected_post_ids = $this->get_posts_affected_by_deletion( $to_delete ); $this->persist_class_batch_mutations( $to_delete, $to_create, $to_update, $touched_items, $is_preview ); $classes_order = Global_Classes_Order::make( $this->get_kit() )->set_preview( $this->is_preview() ); $classes_order->set_order( $order ); $labels->set_labels( $final_label_map ); if ( ! $is_preview ) { Global_Classes_Sync_Map::make( $this->get_kit() )->apply_changes( $touched_items, $to_delete ); Global_Classes_Order::make( $this->get_kit() ) ->set_preview( true ) ->set_order( $order ); $this->bulk_clear_preview_meta( array_values( $to_update ) ); $this->clear_preview_labels_for_ids( array_merge( array_values( $to_create ), array_values( $to_update ), array_values( $to_delete ) ) ); } $this->cache = null; $this->flush_runtime_cache(); do_action( 'elementor/global_classes/update', $this->get_context_key( 'event' ), [ 'added' => $to_create, 'deleted' => $to_delete, 'modified' => $to_update, 'order' => $order_changed, 'affected_post_ids' => $affected_post_ids, ] ); if ( ! empty( $to_delete ) && ! $is_preview ) { do_action( 'elementor/global_classes/cleanup', $to_delete, $affected_post_ids ); } } public function each_item( callable $cb, bool $skip_migration = false, int $batch_size = self::READ_BATCH_SIZE ): void { $order = Global_Classes_Order::make( $this->get_kit() )->get_order(); if ( empty( $order ) ) { return; } foreach ( array_chunk( $order, $batch_size ) as $chunk ) { foreach ( $this->iterate_class_posts_for_ids( $chunk ) as $class_post ) { $cb( $class_post->to_array( $skip_migration ) ); } } } public function put( array $items, array $order ) { $current_ids = Global_Classes_Order::make( $this->get_kit() ) ->set_preview( $this->is_preview() ) ->get_order(); $new_ids = array_keys( $items ); $current_order_string = implode( ';', $current_ids ); $deleted_class_ids = array_values( array_diff( $current_ids, $new_ids ) ); $changes = [ 'added' => array_values( array_diff( $new_ids, $current_ids ) ), 'deleted' => $deleted_class_ids, 'modified' => array_values( array_intersect( $new_ids, $current_ids ) ), 'order' => implode( ';', $order ) !== $current_order_string, ]; /** * We collect all affected ids before the put_to_posts execution * as the update mechanism would handle the Global_Classes_Relations as it iterates over the update batches * So once we get to the cleanup phase - we would no longer have the relevant relations * * On top of that - by collecting all affected posts in advance, means we would iterate over each document's elements only once * (as the alternative would be to trigger the cleanup per removed class, but that means we may end up iterating over the same document N times, if all N styles are used in it) */ $affected_post_ids = $this->get_posts_affected_by_deletion( $deleted_class_ids ); $this->put_to_posts( $items, $order, $current_ids ); $this->cache = null; $changes['affected_post_ids'] = $affected_post_ids; do_action( 'elementor/global_classes/update', $this->get_context_key( 'event' ), $changes ); if ( ! empty( $deleted_class_ids ) && ! $this->is_preview() ) { do_action( 'elementor/global_classes/cleanup', $deleted_class_ids, $affected_post_ids ); } } private function get_posts_affected_by_deletion( array $deleted_class_ids ): array { if ( empty( $deleted_class_ids ) ) { return []; } $relations = new Global_Classes_Relations(); $post_ids = []; foreach ( $deleted_class_ids as $class_id ) { $post_ids[] = $relations->get_posts_by_style( $class_id ); } return array_values( array_unique( array_merge( ...$post_ids ) ) ); } private function all_from_posts(): Global_Classes { $order = Global_Classes_Order::make( $this->get_kit() ) ->set_preview( $this->is_preview() ) ->get_order(); if ( empty( $order ) ) { return Global_Classes::make( [], [] ); } $items = []; foreach ( $this->iterate_class_posts_for_ids( $order ) as $class_post ) { $class_data = $class_post->to_array(); $items[ $class_data['id'] ] = $class_data; } $order = Global_Classes_Parser::sanitize_order( $items, $order ); return Global_Classes::make( $items, $order ); } private function put_to_posts( array $items, array $order, array $current_ids ): void { $is_preview = $this->is_preview(); $new_ids = array_keys( $items ); $to_delete = array_diff( $current_ids, $new_ids ); $to_create = array_diff( $new_ids, $current_ids ); $to_update = array_intersect( $new_ids, $current_ids ); $this->persist_class_batch_mutations( $to_delete, $to_create, $to_update, $items, $is_preview ); $classes_order = Global_Classes_Order::make( $this->get_kit() )->set_preview( $this->is_preview() ); $classes_order->set_order( $order ); $label_map = []; foreach ( $order as $id ) { if ( isset( $items[ $id ]['label'] ) ) { $label_map[ $id ] = $items[ $id ]['label']; } } $this->labels()->set_labels( $label_map ); if ( ! $is_preview ) { $touched_ids = array_merge( array_values( $to_create ), array_values( $to_update ) ); $touched_items = array_intersect_key( $items, array_flip( $touched_ids ) ); Global_Classes_Sync_Map::make( $this->get_kit() )->apply_changes( $touched_items, array_values( $to_delete ) ); $this->bulk_clear_preview_meta( array_values( $to_update ) ); $this->clear_preview_labels_for_ids( array_merge( array_values( $to_create ), array_values( $to_update ), array_values( $to_delete ) ) ); } } private function iterate_class_posts_for_ids( array $class_ids ): \Generator { foreach ( array_chunk( $class_ids, self::READ_BATCH_SIZE ) as $chunk ) { $posts = get_posts( [ 'post_type' => Global_Class_Post_Type::CPT, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => Global_Class_Post::META_KEY_ID, 'value' => $chunk, 'compare' => 'IN', ], ], ] ); foreach ( $posts as $post ) { yield Global_Class_Post::from_post( $post, $this->is_preview() ); clean_post_cache( $post->ID ); } unset( $posts ); $this->flush_runtime_cache(); } } private function persist_class_batch_mutations( array $to_delete, array $to_create, array $to_update, array $items_by_id, bool $is_preview ): void { $relations = new Global_Classes_Relations(); $post_ids_map = Global_Classes_Post_IDs::make( $this->get_kit() ); $ids_to_resolve = array_values( array_merge( array_values( $to_delete ), array_values( $to_update ), array_values( $to_create ) ) ); $post_ids = $post_ids_map->get_post_ids( $ids_to_resolve ); $this->each_class_id_batch( array_values( $to_delete ), function ( string $class_id ) use ( $is_preview, $relations, $post_ids ) { $post = isset( $post_ids[ $class_id ] ) ? Global_Class_Post::from_post_id( $post_ids[ $class_id ], false ) : null; if ( ! $post ) { return; } if ( $is_preview ) { $post->set_preview( true ); $post->update_data( [] ); clean_post_cache( $post->get_post_id() ); } else { $relations->clear_class_relations( $class_id ); $post->delete(); } } ); $this->each_class_id_batch( $to_create, function ( string $class_id ) use ( $items_by_id, $is_preview, $post_ids, $post_ids_map ) { if ( ! isset( $items_by_id[ $class_id ] ) ) { return; } $item = $items_by_id[ $class_id ]; $data = Global_Class_Data_Normalizer::normalize_style_fields( $item ); $kit = $this->get_kit(); $existing_post_id = $post_ids[ $class_id ] ?? null; $existing_post = $existing_post_id ? Global_Class_Post::from_post_id( $existing_post_id, $is_preview ) : null; if ( $existing_post ) { $existing_post->update_data( $data ); if ( ! $is_preview ) { $existing_post->update_label( $item['label'] ); } clean_post_cache( $existing_post->get_post_id() ); return; } $created = Global_Class_Post::create( $class_id, $item['label'], $data, $kit ); if ( $created ) { $post_ids_map->set( $class_id, $created->get_post_id() ); clean_post_cache( $created->get_post_id() ); } } ); $this->each_class_id_batch( $to_update, function ( string $class_id ) use ( $items_by_id, $is_preview, $post_ids ) { if ( ! isset( $items_by_id[ $class_id ] ) || ! isset( $post_ids[ $class_id ] ) ) { return; } $item = $items_by_id[ $class_id ]; $post = Global_Class_Post::from_post_id( $post_ids[ $class_id ], $is_preview ); if ( ! $post ) { return; } $data = Global_Class_Data_Normalizer::normalize_style_fields( $item ); $post->update_data( $data ); if ( ! $is_preview ) { $post->update_label( $item['label'] ); } clean_post_cache( $post->get_post_id() ); } ); } public function delete_all(): void { $order = $this->get_order(); $this->each_class_id_batch( $order, function ( string $class_id ) { $post = Global_Class_Post::find_by_class_id( $class_id, false, $this->get_kit() ); if ( $post ) { $post->delete(); } } ); Global_Classes_Order::make( $this->get_kit() )->set_order( [] ); $this->labels()->set_labels( [] ); } private function clear_preview_labels_for_ids( array $class_ids ): void { if ( empty( $class_ids ) ) { return; } $preview_labels = Global_Classes_Labels::make( $this->get_kit() )->set_preview( true ); $labels_map = $preview_labels->get_labels(); if ( empty( $labels_map ) ) { return; } $ids_to_remove = array_intersect( array_unique( $class_ids ), array_keys( $labels_map ) ); if ( empty( $ids_to_remove ) ) { return; } foreach ( $ids_to_remove as $id ) { unset( $labels_map[ $id ] ); } $preview_labels->set_labels( $labels_map ); } private function bulk_clear_preview_meta( array $class_ids ): void { if ( empty( $class_ids ) ) { return; } $post_ids_map = Global_Classes_Post_IDs::make( $this->get_kit() ); foreach ( array_chunk( $class_ids, self::PERSIST_BATCH_SIZE ) as $chunk ) { $post_ids = $post_ids_map->get_post_ids( $chunk ); foreach ( $post_ids as $post_id ) { delete_post_meta( $post_id, Global_Class_Post::META_KEY_DATA_PREVIEW ); clean_post_cache( $post_id ); } $this->flush_runtime_cache(); } } private function each_class_id_batch( $class_ids, callable $callback, int $batch_size = self::PERSIST_BATCH_SIZE ): void { $class_ids = is_array( $class_ids ) ? $class_ids : iterator_to_array( $class_ids, false ); foreach ( array_chunk( array_values( $class_ids ), $batch_size ) as $batch ) { foreach ( $batch as $class_id ) { $callback( $class_id ); } $this->flush_runtime_cache(); } } private function flush_runtime_cache(): void { if ( function_exists( 'wp_cache_flush_runtime' ) ) { wp_cache_flush_runtime(); } } } global-classes/global-classes-order.php 0000644 00000003615 15252521347 0014167 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\GlobalClasses\Concerns\Has_Kit_Dependency; use Elementor\Modules\GlobalClasses\Concerns\Has_Preview_Context; if ( ! defined( 'ABSPATH' ) ) { exit; } class Global_Classes_Order { use Has_Kit_Dependency; use Has_Preview_Context; const META_KEY = '_elementor_global_classes_order'; const META_KEY_PREVIEW = '_elementor_global_classes_order_preview'; protected array $context_keys = [ 'order' => [ 'frontend' => self::META_KEY, 'preview' => self::META_KEY_PREVIEW, ], ]; private ?array $cache = null; private function __construct() { } public static function make( Kit $kit ): self { return ( new self() )->set_kit( $kit ); } public function get_order(): array { $payload = $this->read_kit_meta_payload(); return $payload['order'] ?? []; } public function set_order( array $ids ): bool { $kit = $this->get_kit(); if ( ! $kit ) { return false; } $payload = [ 'order' => array_values( $ids ), ]; $result = $kit->update_meta( $this->get_context_key( 'order' ), $payload ); $this->cache = null; return false !== $result; } public function remove_class_id( string $id ): bool { $order = $this->get_order(); if ( ! in_array( $id, $order, true ) ) { return true; } $order = array_filter( $order, fn( $item ) => $item !== $id ); return $this->set_order( $order ); } private function read_kit_meta_payload(): array { if ( null !== $this->cache ) { return $this->cache; } $kit = $this->get_kit(); if ( ! $kit ) { return []; } $payload = $kit->get_meta( $this->get_context_key( 'order' ) ); if ( $this->is_preview() && empty( $payload ) ) { $payload = self::make( $this->get_kit() )->set_preview( false )->read_kit_meta_payload(); } $this->cache = is_array( $payload ) ? $payload : []; return $this->cache; } } global-classes/global-class-post-type.php 0000644 00000001375 15252521347 0014471 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Global_Class_Post_Type { const CPT = 'e_global_class'; public function register() { add_action( 'init', [ $this, 'register_post_type' ] ); } public function register_post_type() { register_post_type( self::CPT, [ 'label' => esc_html__( 'Global Class', 'elementor' ), 'labels' => [ 'name' => esc_html__( 'Global Classes', 'elementor' ), 'singular_name' => esc_html__( 'Global Class', 'elementor' ), ], 'public' => false, 'supports' => [ 'title' ], ] ); } public static function ensure_registered(): void { if ( ! post_type_exists( self::CPT ) ) { ( new self() )->register_post_type(); } } } global-classes/import-export-customization/runners/import.php 0000644 00000004162 15252521347 0020726 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\ImportExportCustomization\Runners; use Elementor\App\Modules\ImportExportCustomization\Design_System_Import_Context; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Import_Runner_Base; use Elementor\Modules\GlobalClasses\ImportExportCustomization\Import_Export_Customization; use Elementor\Modules\GlobalClasses\ImportExportUtils\Import_Utils; use Elementor\Plugin; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\GlobalClasses\ImportExportUtils\Legacy_Import_Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Import extends Import_Runner_Base { public static function get_name(): string { return 'global-classes'; } public function should_import( array $data ): bool { $import_context = Design_System_Import_Context::from_data( $data ); return ( $import_context->is_included() && ! empty( $data['extracted_directory_path'] ) && $this->is_classes_enabled( $data ) ); } private function is_classes_enabled( array $data ): bool { if ( isset( $data['customization']['settings']['classes'] ) ) { return (bool) $data['customization']['settings']['classes']; } return true; } public function import( array $data, array $imported_data ): array { $import_context = Design_System_Import_Context::from_data( $data ); $conflict_resolution = $import_context->resolve_conflict_resolution( $data, 'classesOverrideAll' ); if ( $this->is_legacy_import_format( $data ) ) { $global_classes_file = $data['extracted_directory_path'] . '/' . Import_Export_Customization::FILE_NAME . '.json'; return Legacy_Import_Utils::import_classes( $global_classes_file, $conflict_resolution ); } $global_classes_dir = $data['extracted_directory_path'] . '/' . Import_Export_Customization::DIRECTORY_NAME; return Import_Utils::import_classes( $global_classes_dir, [ 'conflict_resolution' => $conflict_resolution ] ); } protected function is_legacy_import_format( array $data ): bool { $manifest = $data['manifest']; $elementor_version = $manifest['elementor_version']; return version_compare( $elementor_version, '4.1.0-beta1', '<' ); } } global-classes/import-export-customization/runners/export.php 0000644 00000006034 15252521347 0020735 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\ImportExportCustomization\Runners; use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Export_Runner_Base; use Elementor\Modules\AtomicWidgets\Module as Atomic_Widgets_Module; use Elementor\Modules\GlobalClasses\Global_Classes_Order; use Elementor\Modules\GlobalClasses\Global_Classes_Parser; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Modules\GlobalClasses\ImportExportCustomization\Import_Export_Customization; use Elementor\Modules\GlobalClasses\Module as Global_Classes_Module; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Export extends Export_Runner_Base { const ORDER_FILE = 'order.json'; public static function get_name(): string { return 'global-classes'; } public function should_export( array $data ): bool { return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) && $this->is_classes_enabled( $data ) ); } private function is_classes_enabled( array $data ): bool { if ( ! $this->is_feature_active() ) { return false; } if ( isset( $data['customization']['settings']['classes'] ) ) { return (bool) $data['customization']['settings']['classes']; } return true; } private function is_feature_active(): bool { return Plugin::$instance->experiments->is_feature_active( Global_Classes_Module::NAME ) && Plugin::$instance->experiments->is_feature_active( Atomic_Widgets_Module::EXPERIMENT_NAME ); } public function export( array $data ): array { $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return $this->empty_result(); } $repository = Global_Classes_Repository::make( $kit ); $labels_by_id = []; $files = []; $skip_migration = true; $repository->each_item( static function ( array $class_data ) use ( &$files, &$labels_by_id ) { if ( empty( $class_data['id'] ) || ! is_string( $class_data['id'] ) ) { return; } $class_id = $class_data['id']; $files[] = [ 'path' => Import_Export_Customization::FILE_NAME . '/' . $class_id . '.json', 'data' => wp_json_encode( $class_data ), ]; $labels_by_id[ $class_id ] = $class_data['label'] ?? $class_id; }, $skip_migration ); if ( empty( $files ) ) { return $this->empty_result(); } $files[] = [ 'path' => Import_Export_Customization::FILE_NAME . '/' . self::ORDER_FILE, 'data' => wp_json_encode( $this->build_order_entries( $kit, $labels_by_id ) ), ]; return [ 'files' => $files, 'manifest' => [], ]; } private function build_order_entries( $kit, array $labels_by_id ): array { $repository_order = Global_Classes_Order::make( $kit )->set_preview( false )->get_order(); $sanitized_order = Global_Classes_Parser::sanitize_order( $labels_by_id, $repository_order ); return array_map( static fn( string $id ) => [ 'id' => $id, 'label' => $labels_by_id[ $id ], ], $sanitized_order ); } private function empty_result(): array { return [ 'manifest' => [], 'files' => [], ]; } } global-classes/import-export-customization/import-export-customization.php 0000644 00000001536 15252521347 0023461 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses\ImportExportCustomization; use Elementor\App\Modules\ImportExportCustomization\Processes\Export; use Elementor\App\Modules\ImportExportCustomization\Processes\Import; use Elementor\Modules\GlobalClasses\ImportExportCustomization\Runners\Export as Export_Runner; use Elementor\Modules\GlobalClasses\ImportExportCustomization\Runners\Import as Import_Runner; class Import_Export_Customization { const FILE_NAME = 'global-classes'; const DIRECTORY_NAME = 'global-classes'; public function register_hooks() { add_action( 'elementor/import-export-customization/export-kit', function ( Export $export ) { $export->register( new Export_Runner() ); } ); add_action( 'elementor/import-export-customization/import-kit', function ( Import $import ) { $import->register( new Import_Runner() ); } ); } } global-classes/global-classes-relations.php 0000644 00000023537 15252521347 0015061 0 ustar 00 <?php namespace Elementor\Modules\GlobalClasses; use Elementor\Core\Base\Document; use Elementor\Modules\GlobalClasses\Atomic_Global_Styles; use Elementor\Modules\GlobalClasses\Concerns\Has_Preview_Context; use Elementor\Modules\GlobalClasses\Utils\Atomic_Elements_Utils; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Global_Classes_Relations { use Has_Preview_Context; const META_KEY_FRONTEND = '_elementor_used_global_class'; const META_KEY_PREVIEW = '_elementor_used_global_class_preview'; const META_KEY_USAGE_INDEXED_FRONTEND = '_elementor_global_class_usage_indexed'; const META_KEY_USAGE_INDEXED_PREVIEW = '_elementor_global_class_usage_indexed_preview'; const META_KEY_CLASS_RELATED_POSTS_FRONTEND = '_elementor_global_class_using_documents'; const META_KEY_CLASS_RELATED_POSTS_PREVIEW = '_elementor_global_class_using_documents_preview'; protected array $context_keys = [ 'used_classes' => [ 'frontend' => self::META_KEY_FRONTEND, 'preview' => self::META_KEY_PREVIEW, ], 'usage_indexed' => [ 'frontend' => self::META_KEY_USAGE_INDEXED_FRONTEND, 'preview' => self::META_KEY_USAGE_INDEXED_PREVIEW, ], 'related_posts' => [ 'frontend' => self::META_KEY_CLASS_RELATED_POSTS_FRONTEND, 'preview' => self::META_KEY_CLASS_RELATED_POSTS_PREVIEW, ], ]; public function register_hooks(): void { add_action( 'elementor/document/after_save', fn( Document $document ) => $this->on_document_save( $document ) ); } public function collect_class_ids_from_post( int $post_id, ?array $restrict_to_ids = null ): array { $ids = $this->extract_class_ids_from_post( $post_id ); if ( null === $restrict_to_ids ) { return $ids; } return array_values( array_intersect( $ids, $restrict_to_ids ) ); } public function get_posts_by_style( string $style_id ): array { $from_index = $this->get_posts_from_reverse_index( $style_id ); if ( ! empty( $from_index ) ) { return $from_index; } $post_ids = get_posts( [ 'fields' => 'ids', 'meta_query' => [ [ 'key' => $this->get_context_key( 'used_classes' ), 'value' => $style_id, 'compare' => '=', ], ], 'no_found_rows' => true, 'post_status' => 'any', 'post_type' => 'any', 'posts_per_page' => -1, 'update_post_meta_cache' => false, ] ); return array_map( 'intval', $post_ids ); } public function get_styles_by_post( int $post_id ): array { $stored_ids = $this->get_stored_style_ids( $post_id ); $live_ids = array_values( array_unique( $this->extract_class_ids_from_post( $post_id ) ) ); $has_elementor_data = $this->document_has_elementor_data( $post_id ); $normalize = static function ( array $ids ): string { $ids = array_values( array_unique( $ids ) ); sort( $ids ); return wp_json_encode( $ids ); }; if ( ! $has_elementor_data ) { if ( ! empty( $stored_ids ) ) { $this->mark_usage_indexed( $post_id ); return $stored_ids; } if ( $this->is_usage_indexed( $post_id ) ) { return []; } $this->mark_usage_indexed( $post_id ); return []; } if ( $normalize( $stored_ids ) !== $normalize( $live_ids ) ) { $this->set_styles_for_post( $post_id, $live_ids ); return $live_ids; } if ( ! empty( $live_ids ) ) { $this->mark_usage_indexed( $post_id ); } return $live_ids; } public function clear_class_relations( string $class_id ): void { $post_ids = $this->get_posts_by_style( $class_id ); foreach ( $post_ids as $post_id ) { $stored_ids = $this->get_stored_style_ids( $post_id ); $updated_ids = array_values( array_filter( $stored_ids, fn( $id ) => $id !== $class_id ) ); $this->replace_stored_style_ids( $post_id, $updated_ids ); } } public function clear_post_styles( int $post_id ): void { $saved_preview = $this->is_preview(); try { foreach ( [ false, true ] as $preview_flag ) { $this->set_preview( $preview_flag ); $old_ids = $this->get_stored_style_ids( $post_id ); delete_post_meta( $post_id, $this->get_context_key( 'used_classes' ) ); delete_post_meta( $post_id, $this->get_context_key( 'usage_indexed' ) ); foreach ( $old_ids as $class_id ) { $this->unlink_post_from_class( $class_id, $post_id ); } } } finally { $this->set_preview( $saved_preview ); } } public function set_styles_for_post( int $post_id, array $style_ids ): void { $old_ids = $this->get_stored_style_ids( $post_id ); foreach ( array_diff( $old_ids, $style_ids ) as $class_id ) { $this->unlink_post_from_class( $class_id, $post_id ); } $this->replace_stored_style_ids( $post_id, $style_ids ); foreach ( array_diff( $style_ids, $old_ids ) as $class_id ) { $this->link_post_to_class( $class_id, $post_id ); } $this->mark_usage_indexed( $post_id ); } private function get_posts_from_reverse_index( string $class_id ): array { $ids = $this->read_reverse_index_for_class( $class_id ); if ( ! empty( $ids ) || Global_Classes_Repository::CONTEXT_PREVIEW !== $this->get_context_key( 'used_classes' ) ) { return $ids; } return ( new self() )->read_reverse_index_for_class( $class_id ); } private function read_reverse_index_for_class( string $class_id ): array { $post = Global_Class_Post::find_by_class_id( $class_id ); if ( ! $post ) { return []; } $ids = get_post_meta( $post->get_post_id(), $this->get_context_key( 'related_posts' ), true ); if ( ! is_array( $ids ) ) { return []; } return array_values( array_unique( array_map( 'intval', $ids ) ) ); } private function link_post_to_class( string $class_id, int $document_post_id ): void { $post = Global_Class_Post::find_by_class_id( $class_id ); if ( ! $post ) { return; } $cpt_id = $post->get_post_id(); $ids = get_post_meta( $cpt_id, $this->get_context_key( 'related_posts' ), true ); $ids = is_array( $ids ) ? array_map( 'intval', $ids ) : []; if ( in_array( $document_post_id, $ids, true ) ) { return; } $ids[] = $document_post_id; update_post_meta( $cpt_id, $this->get_context_key( 'related_posts' ), $ids ); } private function unlink_post_from_class( string $class_id, int $document_post_id ): void { $post = Global_Class_Post::find_by_class_id( $class_id ); if ( ! $post ) { return; } $cpt_id = $post->get_post_id(); $ids = get_post_meta( $cpt_id, $this->get_context_key( 'related_posts' ), true ); if ( ! is_array( $ids ) ) { return; } $ids = array_values( array_filter( array_map( 'intval', $ids ), fn( $id ) => $id !== $document_post_id ) ); update_post_meta( $cpt_id, $this->get_context_key( 'related_posts' ), $ids ); } private function on_document_save( Document $document ): void { $post_id = $document->get_main_id(); static $in_progress = []; if ( isset( $in_progress[ $post_id ] ) ) { return; } $in_progress[ $post_id ] = true; $saved_preview = $this->is_preview(); try { $this->invalidate_document_styles_cache( $post_id ); $this->set_preview( false ); $this->set_styles_for_post( $post_id, $this->extract_class_ids_from_post( $post_id ) ); $this->set_preview( true ); $this->set_styles_for_post( $post_id, $this->extract_class_ids_from_post( $post_id ) ); } finally { $this->set_preview( $saved_preview ); unset( $in_progress[ $post_id ] ); } } private function extract_class_ids_from_post( int $post_id ): array { $used_class_ids = []; $document = $this->get_document_for_post( $post_id ); if ( ! $document ) { return []; } $elements_data = $document->get_elements_data(); if ( empty( $elements_data ) ) { return []; } Plugin::$instance->db->iterate_data( $elements_data, function ( $element_data ) use ( &$used_class_ids ) { $used_class_ids = array_merge( $used_class_ids, Atomic_Elements_Utils::collect_class_ids_from_element_data( $element_data ) ); } ); return array_values( array_unique( $used_class_ids ) ); } private function get_stored_style_ids( int $post_id ): array { $meta_values = get_post_meta( $post_id, $this->get_context_key( 'used_classes' ), false ); if ( ! is_array( $meta_values ) ) { return []; } return array_values( array_unique( $meta_values ) ); } private function replace_stored_style_ids( int $post_id, array $style_ids ): void { delete_post_meta( $post_id, $this->get_context_key( 'used_classes' ) ); $unique_ids = array_unique( $style_ids ); foreach ( $unique_ids as $class_id ) { add_post_meta( $post_id, $this->get_context_key( 'used_classes' ), $class_id ); } } private function is_usage_indexed( int $post_id ): bool { return '1' === get_post_meta( $post_id, $this->get_context_key( 'usage_indexed' ), true ); } private function mark_usage_indexed( int $post_id ): void { update_post_meta( $post_id, $this->get_context_key( 'usage_indexed' ), '1' ); } private function invalidate_document_styles_cache( int $post_id ): void { do_action( 'elementor/atomic-widgets/styles/clear', [ Atomic_Global_Styles::STYLES_KEY, $post_id ] ); do_action( 'elementor/atomic-widgets/styles/clear', [ Atomic_Global_Styles::STYLES_KEY, $post_id, Global_Classes_Repository::CONTEXT_PREVIEW ] ); } private function document_has_elementor_data( int $post_id ): bool { $document = $this->get_document_for_post( $post_id ); if ( ! $document ) { return false; } $elements_data = $document->get_elements_data(); return ! empty( $elements_data ); } private function get_document_for_post( int $post_id ): ?Document { $documents = Plugin::$instance->documents; if ( ! $this->is_preview() ) { return $this->get_document_or_null( $documents->get( $post_id ) ); } $document = $documents->get_doc_or_auto_save( $post_id, get_current_user_id() ); if ( ! $document ) { $document = $documents->get( $post_id ); } return $this->get_document_or_null( $document ); } private function get_document_or_null( $document ): ?Document { if ( empty( $document ) ) { return null; } return $document; } } compatibility-tag/compatibility-tag.php 0000644 00000004051 15252521347 0014327 0 ustar 00 <?php namespace Elementor\Modules\CompatibilityTag; use Elementor\Plugin; use Elementor\Core\Utils\Version; use Elementor\Core\Base\Base_Object; use Elementor\Core\Utils\Collection; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Compatibility_Tag extends Base_Object { const PLUGIN_NOT_EXISTS = 'plugin_not_exists'; const HEADER_NOT_EXISTS = 'header_not_exists'; const INVALID_VERSION = 'invalid_version'; const INCOMPATIBLE = 'incompatible'; const COMPATIBLE = 'compatible'; /** * @var string Holds the header that should be checked. */ private $header; /** * Compatibility_Tag constructor. * * @param string $header */ public function __construct( $header ) { $this->header = $header; } /** * Return if plugins is compatible or not. * * @param Version $version * @param array $plugins_names * * @return array * @throws \Exception If an error occurs during compatibility check. */ public function check( Version $version, array $plugins_names ) { return ( new Collection( $plugins_names ) ) ->map_with_keys( function ( $plugin_name ) use ( $version ) { return [ $plugin_name => $this->is_compatible( $version, $plugin_name ) ]; } ) ->all(); } /** * Check single plugin if is compatible or not. * * @param Version $version * @param $plugin_name * * @return string * @throws \Exception If an error occurs during the compatibility check. */ private function is_compatible( Version $version, $plugin_name ) { $plugins = Plugin::$instance->wp->get_plugins(); if ( ! isset( $plugins[ $plugin_name ] ) ) { return self::PLUGIN_NOT_EXISTS; } $requested_plugin = $plugins[ $plugin_name ]; if ( empty( $requested_plugin[ $this->header ] ) ) { return self::HEADER_NOT_EXISTS; } if ( ! Version::is_valid_version( $requested_plugin[ $this->header ] ) ) { return self::INVALID_VERSION; } if ( $version->compare( '>', $requested_plugin[ $this->header ], Version::PART_MAJOR_2 ) ) { return self::INCOMPATIBLE; } return self::COMPATIBLE; } } compatibility-tag/module.php 0000644 00000003251 15252521347 0012173 0 ustar 00 <?php namespace Elementor\Modules\CompatibilityTag; use Elementor\Plugin; use Elementor\Core\Utils\Version; use Elementor\Core\Utils\Collection; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Inspired By WooCommerce. * * @link https://github.com/woocommerce/woocommerce/blob/master/includes/admin/plugin-updates/class-wc-plugin-updates.php */ class Module extends Base_Module { /** * This is the header used by extensions to show testing. * * @var string */ const PLUGIN_VERSION_TESTED_HEADER = 'Elementor tested up to'; /** * @return string */ protected function get_plugin_header() { return static::PLUGIN_VERSION_TESTED_HEADER; } /** * @return string */ protected function get_plugin_label() { return esc_html__( 'Elementor', 'elementor' ); } /** * @return string */ protected function get_plugin_name() { return ELEMENTOR_PLUGIN_BASE; } /** * @return string */ protected function get_plugin_version() { return ELEMENTOR_VERSION; } /** * @return Collection */ protected function get_plugins_to_check() { return parent::get_plugins_to_check() ->merge( $this->get_plugins_with_plugin_title_in_their_name() ); } /** * Get all the plugins that has the name of the current plugin in their name. * * @return Collection */ private function get_plugins_with_plugin_title_in_their_name() { return Plugin::$instance->wp ->get_plugins() ->except( [ 'elementor/elementor.php', 'elementor-beta/elementor-beta.php', 'block-builder/block-builder.php', ] ) ->filter( function ( array $data ) { return false !== strpos( strtolower( $data['Name'] ), 'elementor' ); } ); } } compatibility-tag/compatibility-tag-report.php 0000644 00000010374 15252521347 0015645 0 ustar 00 <?php namespace Elementor\Modules\CompatibilityTag; use Elementor\Plugin; use Elementor\Core\Utils\Version; use Elementor\Core\Utils\Collection; use Elementor\Modules\System_Info\Reporters\Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Compatibility_Tag_Report extends Base { /** * @var Compatibility_Tag */ protected $compatibility_tag_service; /** * @var Version */ protected $plugin_version; /** * @var string */ protected $plugin_label; /** * @var array */ protected $plugins_to_check; /** * Compatibility_Tag_Report constructor. * * @param $properties */ public function __construct( $properties ) { parent::__construct( $properties ); $this->compatibility_tag_service = $this->_properties['fields']['compatibility_tag_service']; $this->plugin_label = $this->_properties['fields']['plugin_label']; $this->plugin_version = $this->_properties['fields']['plugin_version']; $this->plugins_to_check = $this->_properties['fields']['plugins_to_check']; } /** * The title of the report * * @return string */ public function get_title() { return $this->plugin_label . ' - Compatibility Tag'; } /** * Report fields * * @return string[] */ public function get_fields() { return [ 'report_data' => '', ]; } /** * Report data. * * @return string[] */ public function get_report_data() { $compatibility_status = $this->compatibility_tag_service->check( $this->plugin_version, $this->plugins_to_check ); return [ 'value' => $compatibility_status, ]; } public function get_html_report_data() { $compatibility_status = $this->compatibility_tag_service->check( $this->plugin_version, $this->plugins_to_check ); $compatibility_status = $this->get_html_from_compatibility_status( $compatibility_status ); return [ 'value' => $compatibility_status, ]; } public function get_raw_report_data() { $compatibility_status = $this->compatibility_tag_service->check( $this->plugin_version, $this->plugins_to_check ); $compatibility_status = $this->get_raw_from_compatibility_status( $compatibility_status ); return [ 'value' => $compatibility_status, ]; } /** * Merge compatibility status with the plugins data. * * @param array $compatibility_status * * @return Collection */ private function merge_compatibility_status_with_plugins( array $compatibility_status ) { $labels = $this->get_report_labels(); $compatibility_status = ( new Collection( $compatibility_status ) ) ->map( function ( $value ) use ( $labels ) { $status = isset( $labels[ $value ] ) ? $labels[ $value ] : esc_html__( 'Unknown', 'elementor' ); return [ 'compatibility_status' => $status ]; } ); return Plugin::$instance->wp ->get_plugins() ->only( $compatibility_status->keys()->all() ) ->merge_recursive( $compatibility_status ); } /** * Format compatibility status into HTML. * * @param array $compatibility_status * * @return string */ private function get_html_from_compatibility_status( array $compatibility_status ) { return $this->merge_compatibility_status_with_plugins( $compatibility_status ) ->map( function ( array $plugin ) { return "<tr><td> {$plugin['Name']} </td><td> {$plugin['compatibility_status']} </td></tr>"; } ) ->implode( '' ); } /** * Format compatibility status into raw string. * * @param array $compatibility_status * * @return string */ private function get_raw_from_compatibility_status( array $compatibility_status ) { return PHP_EOL . $this->merge_compatibility_status_with_plugins( $compatibility_status ) ->map( function ( array $plugin ) { return "\t{$plugin['Name']}: {$plugin['compatibility_status']}"; } ) ->implode( PHP_EOL ); } /** * @return array */ private function get_report_labels() { return [ Compatibility_Tag::COMPATIBLE => esc_html__( 'Compatible', 'elementor' ), Compatibility_Tag::INCOMPATIBLE => esc_html__( 'Incompatible', 'elementor' ), Compatibility_Tag::HEADER_NOT_EXISTS => esc_html__( 'Compatibility not specified', 'elementor' ), Compatibility_Tag::INVALID_VERSION => esc_html__( 'Compatibility unknown', 'elementor' ), Compatibility_Tag::PLUGIN_NOT_EXISTS => esc_html__( 'Error', 'elementor' ), ]; } } compatibility-tag/base-module.php 0000644 00000007445 15252521347 0013114 0 ustar 00 <?php namespace Elementor\Modules\CompatibilityTag; use Elementor\Plugin; use Elementor\Core\Utils\Version; use Elementor\Core\Utils\Collection; use Elementor\Core\Base\Module as BaseModule; use Elementor\Modules\System_Info\Module as System_Info; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Module extends BaseModule { const MODULE_NAME = 'compatibility-tag'; /** * @var Compatibility_Tag */ private $compatibility_tag_service; /** * @return string */ public function get_name() { return static::MODULE_NAME; } /** * @return Compatibility_Tag */ private function get_compatibility_tag_service() { if ( ! $this->compatibility_tag_service ) { $this->compatibility_tag_service = new Compatibility_Tag( $this->get_plugin_header() ); } return $this->compatibility_tag_service; } /** * Add allowed headers to plugins. * * @param array $headers * @param $compatibility_tag_header * * @return array */ protected function enable_elementor_headers( array $headers, $compatibility_tag_header ) { $headers[] = $compatibility_tag_header; return $headers; } /** * @return Collection */ protected function get_plugins_to_check() { return $this->get_plugins_with_header(); } /** * Append a compatibility message to the update plugin warning. * * @param array $args */ protected function on_plugin_update_message( array $args ) { $new_version = Version::create_from_string( $args['new_version'] ); if ( $new_version->compare( '=', $args['Version'], Version::PART_MAJOR_2 ) ) { return; } $plugins = $this->get_plugins_to_check(); $plugins_compatibility = $this->get_compatibility_tag_service()->check( $new_version, $plugins->keys()->all() ); $plugins = $plugins->filter( function ( $data, $plugin_name ) use ( $plugins_compatibility ) { return Compatibility_Tag::COMPATIBLE !== $plugins_compatibility[ $plugin_name ]; } ); if ( $plugins->is_empty() ) { return; } include __DIR__ . '/views/plugin-update-message-compatibility.php'; } /** * Get all plugins with specific header. * * @return Collection */ private function get_plugins_with_header() { return Plugin::$instance->wp ->get_plugins() ->filter( function ( array $plugin ) { return ! empty( $plugin[ $this->get_plugin_header() ] ); } ); } /** * @return string */ abstract protected function get_plugin_header(); /** * @return string */ abstract protected function get_plugin_label(); /** * @return string */ abstract protected function get_plugin_name(); /** * @return string */ abstract protected function get_plugin_version(); /** * Base_Module constructor. */ public function __construct() { add_filter( 'extra_plugin_headers', function ( array $headers ) { return $this->enable_elementor_headers( $headers, $this->get_plugin_header() ); } ); add_action( 'in_plugin_update_message-' . $this->get_plugin_name(), function ( array $args ) { $this->on_plugin_update_message( $args ); }, 11 /* After the warning message for backup */ ); add_action( 'elementor/system_info/get_allowed_reports', function () { $plugin_short_name = basename( $this->get_plugin_name(), '.php' ); System_Info::add_report( "{$plugin_short_name}_compatibility", [ 'file_name' => __DIR__ . '/compatibility-tag-report.php', 'class_name' => __NAMESPACE__ . '\Compatibility_Tag_Report', 'fields' => [ 'compatibility_tag_service' => $this->get_compatibility_tag_service(), 'plugin_label' => $this->get_plugin_label(), 'plugin_version' => Version::create_from_string( $this->get_plugin_version() ), 'plugins_to_check' => $this->get_plugins_to_check() ->only( get_option( 'active_plugins' ) ) ->keys() ->all(), ], ] ); } ); } } compatibility-tag/views/plugin-update-message-compatibility.php 0000644 00000004242 15252521347 0021113 0 ustar 00 <?php use Elementor\Core\Utils\Version; use Elementor\Core\Utils\Collection; use Elementor\Modules\CompatibilityTag\Base_Module; use Elementor\Modules\CompatibilityTag\Compatibility_Tag; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Those variables were declared in 'in_plugin_update_message' method that included the current view file. * * @var Base_Module $this * @var Version $new_version * @var Collection $plugins * @var array $plugins_compatibility */ ?> <hr class="e-major-update-warning__separator" /> <div class="e-major-update-warning"> <div class="e-major-update-warning__icon"> <i class="eicon-info-circle"></i> </div> <div> <div class="e-major-update-warning__message"> <strong> <?php echo esc_html__( 'Compatibility Alert', 'elementor' ); ?> </strong> - <?php printf( /* translators: 1: Plugin name, 2: Plugin version. */ esc_html__( 'Some of the plugins you’re using have not been tested with the latest version of %1$s (%2$s). To avoid issues, make sure they are all up to date and compatible before updating %1$s.', 'elementor' ), esc_html( $this->get_plugin_label() ), $new_version->__toString() // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); ?> </div> <br /> <table class="e-compatibility-update-table"> <tr> <th><?php echo esc_html__( 'Plugin', 'elementor' ); ?></th> <th><?php /* translators: %s: Elementor plugin name. */ printf( esc_html__( 'Tested up to %s version', 'elementor' ), esc_html( $this->get_plugin_label() ) ); ?></th> </tr> <?php foreach ( $plugins as $plugin_name => $plugin_data ) : ?> <?php if ( in_array( $plugins_compatibility[ $plugin_name ], [ Compatibility_Tag::PLUGIN_NOT_EXISTS, Compatibility_Tag::HEADER_NOT_EXISTS, Compatibility_Tag::INVALID_VERSION, ], true ) ) { $plugin_data[ $this->get_plugin_header() ] = esc_html__( 'Unknown', 'elementor' ); } ?> <tr> <td><?php echo esc_html( $plugin_data['Name'] ); ?></td> <td><?php echo esc_html( $plugin_data[ $this->get_plugin_header() ] ); ?></td> </tr> <?php endforeach ?> </table> </div> </div> apps/admin-apps-page.php 0000644 00000014415 15252521347 0011176 0 ustar 00 <?php namespace Elementor\Modules\Apps; use Elementor\Core\Isolation\Wordpress_Adapter; use Elementor\Core\Isolation\Plugin_Status_Adapter; use Elementor\Includes\EditorAssetsAPI; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin_Apps_Page { const APPS_URL = 'https://assets.elementor.com/apps/v1/apps.json'; private static ?Wordpress_Adapter $wordpress_adapter = null; private static ?Plugin_Status_Adapter $plugin_status_adapter = null; public static function render() { ?> <div class="wrap e-a-apps"> <div class="e-a-page-title"> <h2><?php echo esc_html__( 'Popular Add-ons, New Possibilities.', 'elementor' ); ?></h2> <p><?php echo esc_html__( 'Boost your web-creation process with add-ons, plugins, and more tools specially selected to unleash your creativity, increase productivity, and enhance your Elementor-powered website.', 'elementor' ); ?>*<br> <a href="https://go.elementor.com/wp-dash-apps-about-apps-page/" target="_blank"><?php echo esc_html__( 'Learn more about this page.', 'elementor' ); ?></a> </p> </div> <div class="e-a-list"> <?php self::render_plugins_list(); ?> </div> <div class="e-a-page-footer"> <p>*<?php echo esc_html__( 'Please note that certain tools and services on this page are developed by third-party companies and are not part of Elementor\'s suite of products or support. Before using them, we recommend independently evaluating them. Additionally, when clicking on their action buttons, you may be redirected to an external website.', 'elementor' ); ?></p> </div> </div> <?php } private static function render_plugins_list() { $plugins = self::get_plugins(); foreach ( $plugins as $plugin ) { self::render_plugin_item( $plugin ); } } private static function get_plugins(): array { if ( ! self::$wordpress_adapter ) { self::$wordpress_adapter = new Wordpress_Adapter(); } if ( ! self::$plugin_status_adapter ) { self::$plugin_status_adapter = new Plugin_Status_Adapter( self::$wordpress_adapter ); } $apps = static::get_remote_apps(); return static::filter_apps( $apps ); } private static function get_remote_apps() { $editor_assets_api = new EditorAssetsAPI( [ EditorAssetsAPI::ASSETS_DATA_URL => static::APPS_URL, EditorAssetsAPI::ASSETS_DATA_TRANSIENT_KEY => '_elementor_apps_data', EditorAssetsAPI::ASSETS_DATA_KEY => 'apps', ] ); return $editor_assets_api->get_assets_data(); } private static function filter_apps( $apps ) { $filtered_apps = []; foreach ( $apps as $app ) { if ( static::is_wporg_app( $app ) ) { $app = static::filter_wporg_app( $app ); } if ( static::is_ecom_app( $app ) ) { $app = static::filter_ecom_app( $app ); } if ( empty( $app ) ) { continue; } $filtered_apps[] = $app; } return $filtered_apps; } private static function is_wporg_app( $app ) { return isset( $app['type'] ) && 'wporg' === $app['type']; } private static function filter_wporg_app( $app ) { if ( self::$wordpress_adapter->is_plugin_active( $app['file_path'] ) ) { return null; } if ( self::$plugin_status_adapter->is_plugin_installed( $app['file_path'] ) ) { if ( current_user_can( 'activate_plugins' ) ) { $app['action_label'] = esc_html__( 'Activate', 'elementor' ); $app['action_url'] = self::$plugin_status_adapter->get_activate_plugin_url( $app['file_path'] ); } else { $app['action_label'] = esc_html__( 'Cannot Activate', 'elementor' ); $app['action_url'] = '#'; } } elseif ( current_user_can( 'install_plugins' ) ) { $app['action_label'] = esc_html__( 'Install', 'elementor' ); $app['action_url'] = self::$plugin_status_adapter->get_install_plugin_url( $app['file_path'] ); } else { $app['action_label'] = esc_html__( 'Cannot Install', 'elementor' ); $app['action_url'] = '#'; } return $app; } private static function is_ecom_app( $app ) { return isset( $app['type'] ) && 'ecom' === $app['type']; } private static function filter_ecom_app( $app ) { if ( self::$wordpress_adapter->is_plugin_active( $app['file_path'] ) ) { return null; } if ( ! self::$plugin_status_adapter->is_plugin_installed( $app['file_path'] ) ) { return $app; } if ( current_user_can( 'activate_plugins' ) ) { $app['action_label'] = esc_html__( 'Activate', 'elementor' ); $app['action_url'] = self::$plugin_status_adapter->get_activate_plugin_url( $app['file_path'] ); } else { $app['action_label'] = esc_html__( 'Cannot Activate', 'elementor' ); $app['action_url'] = '#'; } $app['target'] = '_self'; return $app; } private static function get_images_url() { return ELEMENTOR_URL . 'modules/apps/images/'; } private static function is_elementor_pro_installed() { return defined( 'ELEMENTOR_PRO_VERSION' ); } private static function render_plugin_item( $plugin ) { ?> <div class="e-a-item"<?php echo ! empty( $plugin['file_path'] ) ? ' data-plugin="' . esc_attr( $plugin['file_path'] ) . '"' : ''; ?>> <div class="e-a-heading"> <img class="e-a-img" src="<?php echo esc_url( $plugin['image'] ); ?>" alt="<?php echo esc_attr( $plugin['name'] ); ?>"> <?php if ( ! empty( $plugin['badge'] ) ) : ?> <span class="e-a-badge"><?php echo esc_html( $plugin['badge'] ); ?></span> <?php endif; ?> </div> <h3 class="e-a-title"><?php echo esc_html( $plugin['name'] ); ?></h3> <p class="e-a-author"><?php esc_html_e( 'By', 'elementor' ); ?> <a href="<?php echo esc_url( $plugin['author_url'] ); ?>" target="_blank"><?php echo esc_html( $plugin['author'] ); ?></a></p> <div class="e-a-desc"> <p><?php echo esc_html( $plugin['description'] ); ?></p> <?php if ( ! empty( $plugin['offering'] ) ) : ?> <p class="e-a-offering"><?php echo esc_html( $plugin['offering'] ); ?></p> <?php endif; ?> </div> <p class="e-a-actions"> <?php if ( ! empty( $plugin['learn_more_url'] ) ) : ?> <a class="e-a-learn-more" href="<?php echo esc_url( $plugin['learn_more_url'] ); ?>" target="_blank"><?php echo esc_html__( 'Learn More', 'elementor' ); ?></a> <?php endif; ?> <a href="<?php echo esc_url( $plugin['action_url'] ); ?>" class="e-btn e-accent" target="<?php echo isset( $plugin['target'] ) ? esc_attr( $plugin['target'] ) : '_blank'; ?>"><?php echo esc_html( $plugin['action_label'] ); ?></a> </p> </div> <?php } } apps/module.php 0000644 00000002157 15252521347 0007520 0 ustar 00 <?php namespace Elementor\Modules\Apps; use Elementor\Core\Base\Module as BaseModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const PAGE_ID = 'elementor-apps'; public function get_name() { return 'apps'; } public function __construct() { parent::__construct(); Admin_Pointer::add_hooks(); add_filter( 'elementor/finder/categories', function( array $categories ) { $categories['site']['items']['apps'] = [ 'title' => esc_html__( 'Add-ons', 'elementor' ), 'url' => admin_url( 'admin.php?page=' . static::PAGE_ID ), 'icon' => 'apps', 'keywords' => [ 'apps', 'addon', 'plugin', 'extension', 'integration' ], ]; return $categories; } ); } public function enqueue_assets() { add_filter( 'admin_body_class', [ $this, 'body_status_classes' ] ); wp_enqueue_style( 'elementor-apps', $this->get_css_assets_url( 'modules/apps/admin' ), [], ELEMENTOR_VERSION ); } public function body_status_classes( $admin_body_classes ) { $admin_body_classes .= ' elementor-apps-page'; return $admin_body_classes; } } apps/admin-pointer.php 0000644 00000003660 15252521347 0011001 0 ustar 00 <?php namespace Elementor\Modules\Apps; use Elementor\Core\Upgrade\Manager as Upgrade_Manager; use Elementor\User; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin_Pointer { const RELEASE_VERSION = '3.15.0'; const CURRENT_POINTER_SLUG = 'e-apps'; public static function add_hooks() { add_action( 'admin_print_footer_scripts-index.php', [ __CLASS__, 'admin_print_script' ] ); } public static function admin_print_script() { if ( static::is_dismissed() || static::is_new_installation() ) { return; } wp_enqueue_script( 'wp-pointer' ); wp_enqueue_style( 'wp-pointer' ); $pointer_content = '<h3>' . esc_html__( 'New! Popular Add-ons', 'elementor' ) . '</h3>'; $pointer_content .= '<p>' . esc_html__( 'Discover our collection of plugins and add-ons carefully selected to enhance your Elementor website and unleash your creativity.', 'elementor' ) . '</p>'; $pointer_content .= sprintf( '<p><a class="button button-primary" href="%s">%s</a></p>', admin_url( 'admin.php?page=' . Module::PAGE_ID ), esc_html__( 'Explore Add-ons', 'elementor' ) ) ?> <script> jQuery( document ).ready( function( $ ) { $( '#toplevel_page_elementor' ).pointer( { content: '<?php echo $pointer_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>', position: { edge: <?php echo is_rtl() ? "'right'" : "'left'"; ?>, align: 'center' }, close: function() { elementorCommon.ajax.addRequest( 'introduction_viewed', { data: { introductionKey: '<?php echo esc_attr( static::CURRENT_POINTER_SLUG ); ?>', }, } ); } } ).pointer( 'open' ); } ); </script> <?php } private static function is_dismissed() { return User::get_introduction_meta( static::CURRENT_POINTER_SLUG ); } private static function is_new_installation() { return Upgrade_Manager::install_compare( static::RELEASE_VERSION, '>=' ); } } wp-rest/base/query.php 0000644 00000011011 15252521347 0010735 0 ustar 00 <?php namespace Elementor\Modules\WpRest\Base; use Elementor\Core\Utils\Api\Error_Builder; use Elementor\Core\Utils\Collection; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Query { const NAMESPACE = 'elementor/v1'; const NONCE_KEY = 'x_wp_nonce'; const KEYS_CONVERSION_MAP_KEY = 'keys_conversion_map'; const IS_PUBLIC_KEY = 'is_public'; const TAX_QUERY_KEY = 'tax_query'; const META_QUERY_KEY = 'meta_query'; const MAX_RESPONSE_COUNT = 100; const ITEMS_COUNT_KEY = 'items_count'; const INCLUDED_TYPE_KEY = 'included_types'; const EXCLUDED_TYPE_KEY = 'excluded_types'; const HIDE_EMPTY_KEY = 'hide_empty'; const SEARCH_TERM_KEY = 'term'; const SEARCH_FILTER_PRIORITY = 10; /** * @param \WP_REST_Request $request * @return \WP_REST_Response **/ abstract protected function get( \WP_REST_Request $request ); abstract protected static function get_allowed_param_keys(): array; abstract protected static function get_keys_to_encode(): array; abstract protected function get_endpoint_registration_args(): array; abstract protected function permission_check( \WP_REST_Request $request ): bool; public function register( $endpoint, bool $override_existing_endpoints = false ): void { register_rest_route( self::NAMESPACE, $endpoint, [ [ 'methods' => \WP_REST_Server::READABLE, 'permission_callback' => fn ( \WP_REST_Request $request ) => $this->validate_access_permission( $request ), 'args' => $this->get_endpoint_registration_args(), 'callback' => fn ( \WP_REST_Request $request ) => $this->send( fn () => $this->get( $request ) ), ], ], $override_existing_endpoints ); } /** * @param array $item The input array with original keys. * @param array $dictionary An associative array mapping old keys to new keys. * @return array The array with translated keys. */ public function translate_keys( array $item, array $dictionary ): array { if ( empty( $dictionary ) ) { return $item; } $replaced = []; foreach ( $item as $key => $value ) { if ( ! isset( $dictionary[ $key ] ) ) { continue; } $replaced[ $dictionary[ $key ] ] = $value; } return $replaced; } /** * @param array<string>|string $input The input data, expected to be an array or JSON-encoded string. * @return array The sanitized array of strings. */ public static function sanitize_string_array( $input ) { if ( ! is_array( $input ) ) { $raw = sanitize_text_field( $input ); $decoded = json_decode( $raw, true ); if ( is_array( $decoded ) ) { $input = $decoded; } else { $input = false !== strpos( $raw, ',' ) ? explode( ',', $raw ) : ( '' !== $raw ? [ $raw ] : [] ); } } return Collection::make( $input ) ->reduce( function ( $carry, $value, $key ) { if ( $value ) { $carry[ $key ] = is_array( $value ) ? self::sanitize_string_array( $value ) : sanitize_text_field( $value ); } return $carry; }, [] ); } protected function validate_access_permission( \WP_REST_Request $request ): bool { $nonce = $request->get_header( self::NONCE_KEY ); return $this->permission_check( $request ) && wp_verify_nonce( $nonce, 'wp_rest' ); } protected function filter_keys_conversion_map( array $requested_map, array $allowed_map ): array { $sanitized_map = []; foreach ( $requested_map as $source_key => $destination_key ) { if ( ! isset( $allowed_map[ $source_key ] ) ) { continue; } $sanitized_map[ $source_key ] = $destination_key; } return ! empty( $sanitized_map ) ? $sanitized_map : $allowed_map; } /** * @param callable $cb The route callback. * @return \WP_REST_Response | \WP_Error */ private function send( callable $cb ) { try { $response = $cb(); } catch ( \Exception $e ) { return Error_Builder::make( $e->getCode() ) ->set_message( $e->getMessage() ) ->build(); } return $response; } /** * @param $args array{ * excluded_types: array, * included_types: array, * keys_conversion_map: array, * } The query parameters * @return array The query parameters. */ public static function build_query_params( array $args ): array { $allowed_keys = static::get_allowed_param_keys(); $keys_to_encode = static::get_keys_to_encode(); $params = []; foreach ( $args as $key => $value ) { if ( ! in_array( $key, $allowed_keys, true ) || ! isset( $value ) ) { continue; } if ( ! in_array( $key, $keys_to_encode, true ) ) { $params[ $key ] = $value; continue; } $params[ $key ] = wp_json_encode( $value ); } return $params; } } wp-rest/classes/post-query.php 0000644 00000020123 15252521347 0012447 0 ustar 00 <?php namespace Elementor\Modules\WpRest\Classes; use Elementor\Core\Utils\Collection; use Elementor\Modules\WpRest\Base\Query as Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Post_Query extends Base { const ENDPOINT = 'post'; const SEARCH_FILTER_ACCEPTED_ARGS = 2; const DEFAULT_FORBIDDEN_POST_TYPES = [ 'e-floating-buttons', 'e-landing-page', 'elementor_library', 'attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset' ]; const SEARCH_IN_CONTENT_KEY = 'search_in_content'; const ALLOWED_KEYS_CONVERSION_MAP = [ 'ID' => 'id', 'post_title' => 'label', 'post_type' => 'groupLabel', ]; /** * @param string $search_term The original search query. * @param \WP_Query $wp_query The WP_Query instance. * @return string Modified search query. */ public function customize_post_query( string $search_term, \WP_Query $wp_query ) { $term = $wp_query->get( 'search_term' ) ?? ''; $is_custom_search = $wp_query->get( 'custom_search' ) ?? false; if ( $is_custom_search && ! empty( $term ) ) { $escaped = esc_sql( $term ); $search_in_content = $wp_query->get( self::SEARCH_IN_CONTENT_KEY ) ?? false; $search_term .= ' AND ('; $search_term .= "post_title LIKE '%{$escaped}%'"; if ( $search_in_content ) { $search_term .= " OR post_content LIKE '%{$escaped}%'"; $search_term .= " OR post_excerpt LIKE '%{$escaped}%'"; } if ( ctype_digit( $term ) ) { $search_term .= ' OR ID = ' . intval( $term ); } else { $search_term .= " OR ID LIKE '%{$escaped}%'"; } $search_term .= ')'; } return $search_term; } /** * @param \WP_REST_Request $request * @return \WP_REST_Response */ protected function get( \WP_REST_Request $request ) { $params = $request->get_params(); $term = trim( $params[ self::SEARCH_TERM_KEY ] ?? '' ); $keys_format_map = $this->filter_keys_conversion_map( $params[ self::KEYS_CONVERSION_MAP_KEY ] ?? self::ALLOWED_KEYS_CONVERSION_MAP, self::ALLOWED_KEYS_CONVERSION_MAP ); $requested_count = $params[ self::ITEMS_COUNT_KEY ] ?? 0; $validated_count = max( $requested_count, 1 ); $post_count = min( $validated_count, self::MAX_RESPONSE_COUNT ); $is_public_only = $params[ self::IS_PUBLIC_KEY ] ?? true; $post_types = $this->get_post_types_from_params( $request ); $query_args = [ 'post_type' => array_keys( $post_types ), 'numberposts' => $post_count, 'suppress_filters' => false, 'custom_search' => true, 'post_status' => $is_public_only ? 'publish' : 'any', 'orderby' => 'modified', 'order' => 'DESC', ]; if ( ! empty( $term ) ) { $query_args['search_term'] = $term; $query_args[ self::SEARCH_IN_CONTENT_KEY ] = $params[ self::SEARCH_IN_CONTENT_KEY ] ?? false; } if ( ! empty( $params[ self::META_QUERY_KEY ] ) && is_array( $params[ self::META_QUERY_KEY ] ) ) { $query_args['meta_query'] = $params[ self::META_QUERY_KEY ]; } if ( ! empty( $params[ self::TAX_QUERY_KEY ] ) && is_array( $params[ self::TAX_QUERY_KEY ] ) ) { $query_args['tax_query'] = $params[ self::TAX_QUERY_KEY ]; } $this->add_filter_to_customize_query(); $posts = new Collection( get_posts( $query_args ) ); $this->remove_filter_to_customize_query(); $post_type_labels = ( new Collection( $post_types ) ) ->map( function ( $pt ) { return $pt->label; } ) ->all(); return new \WP_REST_Response( [ 'success' => true, 'data' => [ 'value' => $posts ->filter( function ( $post ) { return current_user_can( 'read_post', $post->ID ); } ) ->map( function ( $post ) use ( $keys_format_map, $post_type_labels ) { $post_type_label = $post->post_type; if ( isset( $post_type_labels[ $post->post_type ] ) ) { $post_type_label = $post_type_labels[ $post->post_type ]; } $post_object = [ 'ID' => $post->ID, 'post_title' => $post->post_title, 'post_type' => $post_type_label, ]; return $this->translate_keys( $post_object, $keys_format_map ); } ) ->all(), ], ], 200 ); } /** * @return void */ private function add_filter_to_customize_query() { $priority = self::SEARCH_FILTER_PRIORITY; $accepted_args = self::SEARCH_FILTER_ACCEPTED_ARGS; add_filter( 'posts_search', [ $this, 'customize_post_query' ], $priority, $accepted_args ); } /** * @return void */ private function remove_filter_to_customize_query() { $priority = self::SEARCH_FILTER_PRIORITY; $accepted_args = self::SEARCH_FILTER_ACCEPTED_ARGS; remove_filter( 'posts_search', [ $this, 'customize_post_query' ], $priority, $accepted_args ); } protected function permission_check( \WP_REST_Request $request ): bool { return current_user_can( 'edit_posts' ); } protected function get_endpoint_registration_args(): array { return [ self::INCLUDED_TYPE_KEY => [ 'description' => 'Included post types', 'type' => 'array', 'required' => false, 'default' => null, 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ), ], self::EXCLUDED_TYPE_KEY => [ 'description' => 'Post type to exclude', 'type' => 'array', 'required' => false, 'default' => self::DEFAULT_FORBIDDEN_POST_TYPES, 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ), ], self::SEARCH_TERM_KEY => [ 'description' => 'Posts to search', 'type' => 'string', 'required' => false, 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ], self::KEYS_CONVERSION_MAP_KEY => [ 'description' => 'Specify keys to extract and convert, i.e. ["key_1" => "new_key_1"].', 'type' => 'array', 'required' => false, 'default' => [ 'ID' => 'id', 'post_title' => 'label', 'post_type' => 'groupLabel', ], 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ), ], self::ITEMS_COUNT_KEY => [ 'description' => 'Posts per page', 'type' => 'integer', 'required' => false, 'default' => self::MAX_RESPONSE_COUNT, ], self::IS_PUBLIC_KEY => [ 'description' => 'Whether to include only public post types', 'type' => 'boolean', 'required' => false, 'default' => true, ], self::META_QUERY_KEY => [ 'description' => 'WP_Query meta_query array', 'type' => 'array', 'required' => false, 'default' => null, 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ), ], self::TAX_QUERY_KEY => [ 'description' => 'WP_Query tax_query array', 'type' => 'array', 'required' => false, 'default' => null, 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ), ], self::SEARCH_IN_CONTENT_KEY => [ 'description' => 'Whether to search within post content and excerpt in addition to title', 'type' => 'boolean', 'required' => false, 'default' => false, ], ]; } protected static function get_allowed_param_keys(): array { return [ self::EXCLUDED_TYPE_KEY, self::INCLUDED_TYPE_KEY, self::KEYS_CONVERSION_MAP_KEY, self::META_QUERY_KEY, self::TAX_QUERY_KEY, self::IS_PUBLIC_KEY, self::ITEMS_COUNT_KEY, self::SEARCH_IN_CONTENT_KEY, ]; } protected static function get_keys_to_encode(): array { return [ self::EXCLUDED_TYPE_KEY, self::INCLUDED_TYPE_KEY, self::KEYS_CONVERSION_MAP_KEY, self::META_QUERY_KEY, self::TAX_QUERY_KEY, ]; } private function get_post_types_from_params( \WP_REST_Request $request ) { $included_types = $request->get_param( self::INCLUDED_TYPE_KEY ); $excluded_types = $request->get_param( self::EXCLUDED_TYPE_KEY ); $post_type_query_args = [ 'public' => true, ]; $post_types = get_post_types( $post_type_query_args, 'objects' ); return Collection::make( $post_types ) ->filter( function ( $slug, $post_type ) use ( $included_types, $excluded_types ) { return ( empty( $included_types ) || in_array( $post_type, $included_types ) ) && ( empty( $excluded_types ) || ! in_array( $post_type, $excluded_types ) ); } )->all(); } } wp-rest/classes/design-system-rest-api.php 0000644 00000005662 15252521350 0014641 0 ustar 00 <?php namespace Elementor\Modules\WpRest\Classes; use Elementor\App\Modules\SiteBuilder\Services\Design_System_Service; use Elementor\Core\Utils\Api\Error_Builder; use Elementor\Core\Utils\Api\Response_Builder; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Design_System_REST_API { const API_NAMESPACE = 'elementor/v1'; const API_BASE = 'site-builder/deploy-design-system'; private Design_System_Service $service; public function __construct( ?Design_System_Service $service = null ) { $this->service = $service ?? new Design_System_Service(); } public function register(): void { register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'deploy' ], 'permission_callback' => [ $this, 'check_permissions' ], 'args' => $this->get_endpoint_args(), ], ] ); } public function check_permissions() { return current_user_can( 'manage_options' ); } public function deploy( \WP_REST_Request $request ) { $global_classes = $request->get_param( 'globalClasses' ); $global_variables = $request->get_param( 'globalVariables' ); if ( empty( $global_classes ) && empty( $global_variables ) ) { return Error_Builder::make( 'invalid_payload' ) ->set_status( 400 ) ->set_message( esc_html__( 'Either globalClasses or globalVariables must be provided.', 'elementor' ) ) ->build(); } try { $results = []; if ( ! empty( $global_classes ) ) { $results['globalClasses'] = $this->service->deploy_global_classes( $global_classes ); } if ( ! empty( $global_variables ) ) { $results['globalVariables'] = $this->service->deploy_global_variables( $global_variables ); } return Response_Builder::make( $results )->build(); } catch ( \Exception $e ) { return $this->handle_unexpected_error( $e ); } } private function handle_unexpected_error( \Exception $e ) { Plugin::$instance->logger->get_logger()->error( $e->getMessage(), [ 'meta' => [ 'trace' => $e->getTraceAsString() ], ] ); return Error_Builder::make( 'design_system_deploy_failed' ) ->set_status( 500 ) ->set_message( esc_html__( 'Something went wrong', 'elementor' ) ) ->build(); } private function get_endpoint_args() { return [ 'globalClasses' => [ 'type' => 'object', 'required' => false, 'properties' => [ 'items' => [ 'type' => 'object', 'required' => true, ], 'order' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'string' ], ], ], ], 'globalVariables' => [ 'type' => 'object', 'required' => false, 'properties' => [ 'data' => [ 'type' => 'object', 'required' => true, ], 'watermark' => [ 'type' => 'integer', 'required' => false, ], 'version' => [ 'type' => 'integer', 'required' => false, ], ], ], ]; } } wp-rest/classes/elementor-user-meta.php 0000644 00000002331 15252521350 0014204 0 ustar 00 <?php namespace Elementor\Modules\WpRest\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Elementor_User_Meta { private function get_meta_config(): array { return [ 'elementor_introduction' => [ 'schema' => [ 'description' => 'Elementor user meta data', 'type' => 'object', 'properties' => [ 'ai_get_started' => [ 'type' => 'boolean', ], ], 'additionalProperties' => true, 'context' => [ 'view', 'edit' ], ], ], ]; } public function register(): void { foreach ( $this->get_meta_config() as $key => $config ) { $config['get_callback'] = function( $user, $field_name, $request ) { return get_user_meta( $user['id'], $field_name, true ); }; $config['update_callback'] = function( $meta_value, \WP_User $user, $field_name, $request ) { if ( 'PATCH' === $request->get_method() ) { $existing = get_user_meta( $user->ID, $field_name, true ); if ( is_array( $existing ) && is_array( $meta_value ) ) { $meta_value = array_merge( $existing, $meta_value ); } } return update_user_meta( $user->ID, $field_name, $meta_value ); }; register_rest_field( 'user', $key, $config ); } } } wp-rest/classes/user-query.php 0000644 00000006543 15252521350 0012444 0 ustar 00 <?php namespace Elementor\Modules\WpRest\Classes; use Elementor\Core\Utils\Collection; use Elementor\Modules\WpRest\Base\Query as Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @deprecated Use wp/v2/users instead. This endpoint proxies to wp/v2/users internally * and will be removed in 4.6.0 version. */ class User_Query extends Base { const ENDPOINT = 'user'; protected function get( \WP_REST_Request $request ) { $params = $request->get_params(); $search_term = trim( $params[ self::SEARCH_TERM_KEY ] ?? '' ); if ( empty( $search_term ) ) { return new \WP_REST_Response( [ 'success' => true, 'data' => [ 'value' => [], ], ], 200 ); } $keys_format_map = $params[ self::KEYS_CONVERSION_MAP_KEY ]; $requested_count = $params[ self::ITEMS_COUNT_KEY ] ?? 0; $validated_count = max( $requested_count, 1 ); $count = min( $validated_count, self::MAX_RESPONSE_COUNT ); $wp_request = new \WP_REST_Request( 'GET', '/wp/v2/users' ); $wp_request->set_param( 'search', $search_term ); $wp_request->set_param( 'per_page', $count ); $wp_request->set_param( 'context', 'edit' ); $response = rest_do_request( $wp_request ); if ( $response->is_error() ) { return new \WP_REST_Response( [ 'success' => true, 'data' => [ 'value' => [], ], ], 200 ); } global $wp_roles; $roles = $wp_roles->roles; $users = Collection::make( $response->get_data() ); $result = new \WP_REST_Response( [ 'success' => true, 'data' => [ 'value' => array_values( $users->map( function ( $user ) use ( $keys_format_map, $roles ) { $user_data = [ 'ID' => $user['id'], 'display_name' => $user['name'], ]; if ( ! empty( $user['roles'][0] ) ) { $user_role = $user['roles'][0]; $role_name = $roles[ $user_role ]['name'] ?? ucfirst( $user_role ); $user_data['role'] = $role_name; } return $this->translate_keys( $user_data, $keys_format_map ); } )->all() ), ], ], 200 ); _doing_it_wrong( 'elementor/v1/user', 'Use wp/v2/users instead. This endpoint will be removed in 4.6.0 version.', '4.0.4' ); return $result; } protected function permission_check( \WP_REST_Request $request ): bool { return current_user_can( 'list_users' ); } protected function get_endpoint_registration_args(): array { return [ self::SEARCH_TERM_KEY => [ 'description' => 'Users to search', 'type' => 'string', 'required' => false, 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ], self::KEYS_CONVERSION_MAP_KEY => [ 'description' => 'Specify keys to extract and convert, i.e. ["key_1" => "new_key_1"].', 'type' => [ 'array', 'string' ], 'required' => false, 'default' => [ 'ID' => 'id', 'display_name' => 'label', 'role' => 'groupLabel', ], 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ), ], self::ITEMS_COUNT_KEY => [ 'description' => 'Number of users to return', 'type' => 'integer', 'required' => false, 'default' => self::MAX_RESPONSE_COUNT, ], ]; } protected static function get_allowed_param_keys(): array { return [ self::KEYS_CONVERSION_MAP_KEY, self::ITEMS_COUNT_KEY, ]; } protected static function get_keys_to_encode(): array { return [ self::KEYS_CONVERSION_MAP_KEY ]; } } wp-rest/classes/elementor-settings.php 0000644 00000005040 15252521350 0014142 0 ustar 00 <?php namespace Elementor\Modules\WpRest\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Elementor_Settings { public function register(): void { register_rest_route('elementor/v1', '/settings/(?P<key>[\w_-]+)', [ [ 'methods' => \WP_REST_Server::READABLE, 'permission_callback' => function (): bool { return current_user_can( 'manage_options' ); }, 'sanitize_callback' => function ( string $param ): string { return esc_attr( $param ); }, 'validate_callback' => function ( \WP_REST_Request $request ): bool { $params = $request->get_params(); return 0 === strpos( $params['key'], 'elementor' ); }, 'callback' => function ( $request ): \WP_REST_Response { try { $key = $request->get_param( 'key' ); $current_value = get_option( $key ); return new \WP_REST_Response([ 'success' => true, // Nest in order to allow extending the response with more details. 'data' => [ 'value' => $current_value, ], ], 200); } catch ( \Exception $e ) { return new \WP_REST_Response([ 'success' => false, 'data' => [ 'message' => $e->getMessage(), ], ], 500); } }, ], ]); register_rest_route('elementor/v1', '/settings/(?P<key>[\w_-]+)', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'permission_callback' => function (): bool { return current_user_can( 'manage_options' ); }, 'sanitize_callback' => function ( string $param ): string { return esc_attr( $param ); }, 'validate_callback' => function ( \WP_REST_Request $request ): bool { $params = $request->get_params(); return 0 === strpos( $params['key'], 'elementor' ) && isset( $params['value'] ); }, 'callback' => function ( \WP_REST_Request $request ): \WP_REST_Response { $key = $request->get_param( 'key' ); $new_value = $request->get_param( 'value' ); $current_value = get_option( $key ); if ( $new_value === $current_value ) { return new \WP_REST_Response([ 'success' => true, ], 200); } $success = update_option( $key, $new_value ); if ( $success ) { return new \WP_REST_Response([ 'success' => true, 'data' => [ 'message' => 'Setting updated successfully.', ], ], 200); } else { return new \WP_REST_Response([ 'success' => false, 'data' => [ 'message' => 'Failed to update setting.', ], ], 500); } }, ], ]); } } wp-rest/classes/elementor-post-meta.php 0000644 00000007705 15252521350 0014225 0 ustar 00 <?php namespace Elementor\Modules\WpRest\Classes; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Elementor_Post_Meta { public function register(): void { $post_types = get_post_types_by_support( 'elementor' ); foreach ( $post_types as $post_type ) { $this->register_edit_mode_meta( $post_type ); $this->register_template_type_meta( $post_type ); $this->register_elementor_data_meta( $post_type ); $this->register_page_settings_meta( $post_type ); if ( Utils::has_pro() ) { $this->register_conditions_meta( $post_type ); } } } private function register_edit_mode_meta( string $post_type ): void { register_meta( 'post', '_elementor_edit_mode', [ 'single' => true, 'object_subtype' => $post_type, 'show_in_rest' => [ 'schema' => [ 'title' => 'Elementor edit mode', 'description' => 'Elementor edit mode, `builder` is required for Elementor editing', 'type' => 'string', 'enum' => [ '', 'builder' ], 'default' => '', 'context' => [ 'edit' ], ], ], 'auth_callback' => [ $this, 'check_edit_permission' ], ]); } private function register_template_type_meta( string $post_type ): void { $document_types = Plugin::$instance->documents->get_document_types(); register_meta( 'post', '_elementor_template_type', [ 'single' => true, 'object_subtype' => $post_type, 'show_in_rest' => [ 'schema' => [ 'title' => 'Elementor template type', 'description' => 'Elementor document type', 'type' => 'string', 'enum' => array_merge( array_keys( $document_types ), [ '' ] ), 'default' => '', 'context' => [ 'edit' ], ], ], 'auth_callback' => [ $this, 'check_edit_permission' ], ]); } private function register_elementor_data_meta( string $post_type ): void { register_meta( 'post', '_elementor_data', [ 'single' => true, 'object_subtype' => $post_type, 'show_in_rest' => [ 'schema' => [ 'title' => 'Elementor data', 'description' => 'Elementor JSON as a string', 'type' => 'string', 'default' => '', 'context' => [ 'edit' ], ], ], 'auth_callback' => [ $this, 'check_edit_permission' ], ]); } private function register_page_settings_meta( string $post_type ): void { register_meta( 'post', '_elementor_page_settings', [ 'single' => true, 'object_subtype' => $post_type, 'type' => 'object', 'show_in_rest' => [ 'schema' => [ 'title' => 'Elementor page settings', 'description' => 'Elementor page level settings', 'type' => 'object', 'properties' => [ 'hide_title' => [ 'type' => 'string', 'enum' => [ 'yes', 'no' ], 'default' => '', ], ], 'default' => '{}', 'additionalProperties' => true, 'context' => [ 'edit' ], ], ], 'auth_callback' => [ $this, 'check_edit_permission' ], ]); } private function register_conditions_meta( string $post_type ): void { register_meta( 'post', '_elementor_conditions', [ 'object_subtype' => $post_type, 'type' => 'object', 'title' => 'Elementor conditions', 'description' => 'Elementor conditions', 'single' => true, 'show_in_rest' => [ 'schema' => [ 'description' => 'Elementor conditions', 'type' => 'array', 'additionalProperties' => true, 'default' => [], 'context' => [ 'edit' ], ], ], 'auth_callback' => [ $this, 'check_edit_permission' ], ]); } /** * Check if current user has permission to edit the specific post with elementor * * @param bool $allowed Whether the user can add the post meta. Default false. * @param string $meta_key The meta key. * @param int $post_id Post ID. * @return bool * @since 3.27.0 */ public function check_edit_permission( bool $allowed, string $meta_key, int $post_id ): bool { $document = Plugin::$instance->documents->get( $post_id ); return $document && $document->is_editable_by_current_user(); } } wp-rest/classes/term-query.php 0000644 00000014414 15252521350 0012431 0 ustar 00 <?php namespace Elementor\Modules\WpRest\Classes; use Elementor\Core\Utils\Collection; use Elementor\Modules\WpRest\Base\Query as Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Term_Query extends Base { const ENDPOINT = 'term'; const SEARCH_FILTER_ACCEPTED_ARGS = 3; /** * @param array $clauses Associative array of the clauses for the query. * @param array $taxonomies Array of taxonomy names. * @param array $args The args passed to 'get_terms()'. * @return array Modified clauses. */ public function customize_terms_query( $clauses, $taxonomies, $args ) { if ( ! $args['custom_search'] ) { return $clauses; } if ( is_numeric( $args['name__like'] ) ) { $clauses['where'] = '(' . $clauses['where'] . ' OR t.term_id = ' . $args['name__like'] . ')'; } if ( empty( $args['excluded_taxonomies'] ) ) { return $clauses; } $excluded_taxonomies = $args['excluded_taxonomies']; $escaped = array_map( 'esc_sql', $excluded_taxonomies ); $list = "'" . implode( "','", $escaped ) . "'"; $clauses['where'] .= " AND tt.taxonomy NOT IN ({$list})"; return $clauses; } /** * @param \WP_REST_Request $request * @return \WP_REST_Response */ protected function get( \WP_REST_Request $request ) { $params = $request->get_params(); $term = trim( $params[ self::SEARCH_TERM_KEY ] ?? '' ); if ( empty( $term ) ) { return new \WP_REST_Response( [ 'success' => true, 'data' => [ 'value' => [], ], ], 200 ); } $included_taxonomies = $params[ self::INCLUDED_TYPE_KEY ]; $excluded_taxonomies = $params[ self::EXCLUDED_TYPE_KEY ]; $keys_format_map = $params[ self::KEYS_CONVERSION_MAP_KEY ]; $requested_count = $params[ self::ITEMS_COUNT_KEY ] ?? 0; $validated_count = max( $requested_count, 1 ); $count = min( $validated_count, self::MAX_RESPONSE_COUNT ); $should_hide_empty = $params[ self::HIDE_EMPTY_KEY ] ?? false; $query_args = [ 'number' => $count, 'name__like' => $term, 'hide_empty' => $should_hide_empty, 'taxonomy' => ! empty( $included_taxonomies ) ? $included_taxonomies : null, 'excluded_taxonomies' => $excluded_taxonomies ?? [], 'suppress_filter' => false, 'custom_search' => true, ]; if ( ! empty( $params[ self::META_QUERY_KEY ] ) && is_array( $params[ self::META_QUERY_KEY ] ) ) { $query_args['meta_query'] = $params[ self::META_QUERY_KEY ]; } $this->add_filter_to_customize_query(); $terms = new Collection( get_terms( $query_args ) ); $this->remove_filter_to_customize_query(); $term_group_labels = $terms ->reduce( function ( $term_types, $term ) { if ( ! isset( $term_types[ $term->taxonomy ] ) ) { $taxonomy = get_taxonomy( $term->taxonomy ); $term_types[ $term->taxonomy ] = $taxonomy->labels->name ?? $term->labels; } return $term_types; }, [] ); return new \WP_REST_Response( [ 'success' => true, 'data' => [ 'value' => $terms ->map( function ( $term ) use ( $keys_format_map, $term_group_labels ) { $term_object = (array) $term; if ( isset( $term_object['taxonomy'] ) ) { $group_name = $term_object['taxonomy']; if ( isset( $term_group_labels[ $group_name ] ) ) { $term_object['taxonomy'] = $term_group_labels[ $group_name ]; } } return $this->translate_keys( $term_object, $keys_format_map ); } ) ->all(), ], ], 200 ); } /** * @return void */ private function add_filter_to_customize_query() { $priority = self::SEARCH_FILTER_PRIORITY; $accepted_args = self::SEARCH_FILTER_ACCEPTED_ARGS; add_filter( 'terms_clauses', [ $this, 'customize_terms_query' ], $priority, $accepted_args ); } /** * @return void */ private function remove_filter_to_customize_query() { $priority = self::SEARCH_FILTER_PRIORITY; $accepted_args = self::SEARCH_FILTER_ACCEPTED_ARGS; remove_filter( 'terms_clauses', [ $this, 'customize_terms_query' ], $priority, $accepted_args ); } protected function permission_check( \WP_REST_Request $request ): bool { return current_user_can( 'edit_posts' ); } /** * @return array */ protected function get_endpoint_registration_args(): array { return [ self::INCLUDED_TYPE_KEY => [ 'description' => 'Included taxonomy containing terms (categories, tags, etc...)', 'type' => 'array', 'required' => false, 'default' => null, 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ), ], self::EXCLUDED_TYPE_KEY => [ 'description' => 'Excluded taxonomy containing terms (categories, tags, etc...)', 'type' => 'array', 'required' => false, 'default' => null, 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ), ], self::SEARCH_TERM_KEY => [ 'description' => 'Terms to search', 'type' => 'string', 'required' => false, 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ], self::KEYS_CONVERSION_MAP_KEY => [ 'description' => 'Specify keys to extract and convert, i.e. ["key_1" => "new_key_1"].', 'type' => 'array', 'required' => false, 'default' => [ 'term_id' => 'id', 'name' => 'label', 'taxonomy' => 'groupLabel', ], 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ), ], self::ITEMS_COUNT_KEY => [ 'description' => 'Terms per request', 'type' => 'integer', 'required' => false, 'default' => self::MAX_RESPONSE_COUNT, ], self::HIDE_EMPTY_KEY => [ 'description' => 'Whether to include only public terms', 'type' => 'boolean', 'required' => false, 'default' => false, ], self::META_QUERY_KEY => [ 'description' => 'WP_Query meta_query array', 'type' => 'array', 'required' => false, 'default' => null, 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ), ], ]; } protected static function get_allowed_param_keys(): array { return [ self::EXCLUDED_TYPE_KEY, self::INCLUDED_TYPE_KEY, self::KEYS_CONVERSION_MAP_KEY, self::META_QUERY_KEY, self::TAX_QUERY_KEY, self::ITEMS_COUNT_KEY, ]; } protected static function get_keys_to_encode(): array { return [ self::EXCLUDED_TYPE_KEY, self::INCLUDED_TYPE_KEY, self::KEYS_CONVERSION_MAP_KEY, self::META_QUERY_KEY, self::TAX_QUERY_KEY, ]; } } wp-rest/module.php 0000644 00000002305 15252521350 0010143 0 ustar 00 <?php namespace Elementor\Modules\WpRest; use Elementor\Core\Base\Module as BaseModule; use Elementor\Modules\WpRest\Classes\Design_System_REST_API; use Elementor\Modules\WpRest\Classes\Elementor_Post_Meta; use Elementor\Modules\WpRest\Classes\Elementor_Settings; use Elementor\Modules\WpRest\Classes\Elementor_User_Meta; use Elementor\Modules\WpRest\Classes\Post_Query; use Elementor\Modules\WpRest\Classes\Term_Query; use Elementor\Modules\WpRest\Classes\User_Query; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { public function get_name() { return 'wp-rest'; } public function __construct() { parent::__construct(); add_action( 'rest_api_init', function () { ( new Elementor_Post_Meta() )->register(); ( new Elementor_Settings() )->register(); ( new Elementor_User_Meta() )->register(); ( new Post_Query() )->register( Post_Query::ENDPOINT ); ( new Term_Query() )->register( Term_Query::ENDPOINT ); ( new User_Query() )->register( User_Query::ENDPOINT ); if ( Plugin::instance()->experiments->is_feature_active( 'site-builder' ) ) { ( new Design_System_REST_API() )->register(); } } ); } } nested-tabs/widgets/nested-tabs.php 0000644 00000117607 15252521350 0013361 0 ustar 00 <?php namespace Elementor\Modules\NestedTabs\Widgets; use Elementor\Controls_Manager; use Elementor\Core\Kits\Documents\Tabs\Global_Colors; use Elementor\Core\Kits\Documents\Tabs\Global_Typography; 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_Text_Stroke; use Elementor\Group_Control_Typography; use Elementor\Icons_Manager; use Elementor\Modules\NestedElements\Base\Widget_Nested_Base; use Elementor\Modules\NestedElements\Controls\Control_Nested_Repeater; use Elementor\Plugin; use Elementor\Repeater; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class NestedTabs extends Widget_Nested_Base { private $tab_item_settings = []; private $optimized_markup = null; private $widget_container_selector = ''; public function get_name() { return 'nested-tabs'; } public function get_title() { return esc_html__( 'Tabs', 'elementor' ); } public function get_icon() { return 'eicon-tabs'; } public function get_keywords() { return [ 'nested', 'tabs', 'accordion', 'toggle' ]; } public function get_style_depends(): array { return [ 'widget-nested-tabs' ]; } public function has_widget_inner_wrapper(): bool { return ! Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' ); } public function show_in_panel(): bool { return Plugin::$instance->experiments->is_feature_active( 'nested-elements', true ); } protected function tab_content_container( int $index ) { return [ 'elType' => 'container', 'settings' => [ '_title' => sprintf( /* translators: %d: Tab index. */ __( 'Tab #%d', 'elementor' ), $index ), 'content_width' => 'full', ], ]; } protected function get_default_children_elements() { return [ $this->tab_content_container( 1 ), $this->tab_content_container( 2 ), $this->tab_content_container( 3 ), ]; } protected function get_default_repeater_title_setting_key() { return 'tab_title'; } protected function get_default_children_title() { /* translators: %d: Tab index. */ return esc_html__( 'Tab #%d', 'elementor' ); } protected function get_default_children_placeholder_selector() { return '.e-n-tabs-content'; } protected function get_html_wrapper_class() { return 'elementor-widget-n-tabs'; } protected function register_controls() { if ( null === $this->optimized_markup ) { $this->optimized_markup = Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' ) && ! $this->has_widget_inner_wrapper(); $this->widget_container_selector = $this->optimized_markup ? '' : ' > .elementor-widget-container'; } $start = is_rtl() ? 'right' : 'left'; $end = is_rtl() ? 'left' : 'right'; $start_logical = is_rtl() ? 'end' : 'start'; $end_logical = is_rtl() ? 'start' : 'end'; $heading_selector_non_touch_device = "{{WRAPPER}}.elementor-widget-n-tabs{$this->widget_container_selector} > .e-n-tabs[data-touch-mode='false'] > .e-n-tabs-heading"; $heading_selector_touch_device = "{{WRAPPER}}.elementor-widget-n-tabs{$this->widget_container_selector} > .e-n-tabs[data-touch-mode='true'] > .e-n-tabs-heading"; $heading_selector = "{{WRAPPER}}.elementor-widget-n-tabs{$this->widget_container_selector} > .e-n-tabs > .e-n-tabs-heading"; $content_selector = ":where( {{WRAPPER}}.elementor-widget-n-tabs{$this->widget_container_selector} > .e-n-tabs > .e-n-tabs-content ) > .e-con"; $this->start_controls_section( 'section_tabs', [ 'label' => esc_html__( 'Tabs', 'elementor' ), ] ); $repeater = new Repeater(); $repeater->add_control( 'tab_title', [ 'label' => esc_html__( 'Title', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => esc_html__( 'Tab Title', 'elementor' ), 'placeholder' => esc_html__( 'Tab Title', 'elementor' ), 'label_block' => true, 'dynamic' => [ 'active' => true, ], ] ); $repeater->add_control( 'tab_icon', [ 'label' => esc_html__( 'Icon', 'elementor' ), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon', 'skin' => 'inline', 'label_block' => false, ] ); $repeater->add_control( 'tab_icon_active', [ 'label' => esc_html__( 'Active Icon', 'elementor' ), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon', 'skin' => 'inline', 'label_block' => false, 'condition' => [ 'tab_icon[value]!' => '', ], ] ); $repeater->add_control( 'element_id', [ 'label' => esc_html__( 'CSS ID', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], 'title' => esc_html__( 'Add your custom id WITHOUT the Pound key. e.g: my-id', 'elementor' ), 'style_transfer' => false, 'classes' => 'elementor-control-direction-ltr', ] ); $this->add_control( 'tabs', [ 'label' => esc_html__( 'Tabs Items', 'elementor' ), 'type' => Control_Nested_Repeater::CONTROL_TYPE, 'fields' => $repeater->get_controls(), 'default' => [ [ 'tab_title' => esc_html__( 'Tab #1', 'elementor' ), ], [ 'tab_title' => esc_html__( 'Tab #2', 'elementor' ), ], [ 'tab_title' => esc_html__( 'Tab #3', 'elementor' ), ], ], 'title_field' => '{{{ tab_title }}}', 'button_text' => esc_html__( 'Add Tab', 'elementor' ), ] ); $styling_block_start = '--n-tabs-direction: column; --n-tabs-heading-direction: row; --n-tabs-heading-width: initial; --n-tabs-title-flex-basis: content; --n-tabs-title-flex-shrink: 0;'; $styling_block_end = '--n-tabs-direction: column-reverse; --n-tabs-heading-direction: row; --n-tabs-heading-width: initial; --n-tabs-title-flex-basis: content; --n-tabs-title-flex-shrink: 0'; $styling_inline_end = '--n-tabs-direction: row-reverse; --n-tabs-heading-direction: column; --n-tabs-heading-width: 240px; --n-tabs-title-flex-basis: initial; --n-tabs-title-flex-shrink: initial;'; $styling_inline_start = '--n-tabs-direction: row; --n-tabs-heading-direction: column; --n-tabs-heading-width: 240px; --n-tabs-title-flex-basis: initial; --n-tabs-title-flex-shrink: initial;'; $this->add_responsive_control( 'tabs_direction', [ 'label' => esc_html__( 'Direction', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'block-start' => [ 'title' => esc_html__( 'Above', 'elementor' ), 'icon' => 'eicon-v-align-top', ], 'block-end' => [ 'title' => esc_html__( 'Below', 'elementor' ), 'icon' => 'eicon-v-align-bottom', ], 'inline-end' => [ 'title' => esc_html__( 'After', 'elementor' ), 'icon' => 'eicon-h-align-' . $end, ], 'inline-start' => [ 'title' => esc_html__( 'Before', 'elementor' ), 'icon' => 'eicon-h-align-' . $start, ], ], 'separator' => 'before', 'selectors_dictionary' => [ 'block-start' => $styling_block_start, 'block-end' => $styling_block_end, 'inline-end' => $styling_inline_end, 'inline-start' => $styling_inline_start, // Styling duplication for BC reasons. 'top' => $styling_block_start, 'bottom' => $styling_block_end, 'end' => $styling_inline_end, 'start' => $styling_inline_start, ], 'selectors' => [ '{{WRAPPER}}' => '{{VALUE}}', ], 'control_type' => 'content', ] ); $this->add_responsive_control( 'tabs_justify_horizontal', [ 'label' => esc_html__( 'Justify', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Start', 'elementor' ), 'icon' => "eicon-align-$start_logical-h", ], 'center' => [ 'title' => esc_html__( 'Center', 'elementor' ), 'icon' => 'eicon-align-center-h', ], 'end' => [ 'title' => esc_html__( 'End', 'elementor' ), 'icon' => "eicon-align-$end_logical-h", ], 'stretch' => [ 'title' => esc_html__( 'Stretch', 'elementor' ), 'icon' => 'eicon-align-stretch-h', ], ], 'selectors_dictionary' => [ 'start' => '--n-tabs-heading-justify-content: flex-start; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: center; --n-tabs-title-flex-grow: 0;', 'center' => '--n-tabs-heading-justify-content: center; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: center; --n-tabs-title-flex-grow: 0;', 'end' => '--n-tabs-heading-justify-content: flex-end; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: center; --n-tabs-title-flex-grow: 0;', 'stretch' => '--n-tabs-heading-justify-content: initial; --n-tabs-title-width: 100%; --n-tabs-title-height: initial; --n-tabs-title-align-items: center; --n-tabs-title-flex-grow: 1;', ], 'selectors' => [ '{{WRAPPER}}' => '{{VALUE}}', ], 'condition' => [ 'tabs_direction' => [ '', 'block-start', 'block-end', 'top', 'bottom', ], ], 'frontend_available' => true, ] ); $this->add_responsive_control( 'tabs_justify_vertical', [ 'label' => esc_html__( 'Justify', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Start', 'elementor' ), 'icon' => 'eicon-align-start-v', ], 'center' => [ 'title' => esc_html__( 'Center', 'elementor' ), 'icon' => 'eicon-align-center-v', ], 'end' => [ 'title' => esc_html__( 'End', 'elementor' ), 'icon' => 'eicon-align-end-v', ], 'stretch' => [ 'title' => esc_html__( 'Stretch', 'elementor' ), 'icon' => 'eicon-align-stretch-v', ], ], 'selectors_dictionary' => [ 'start' => '--n-tabs-heading-justify-content: flex-start; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: initial; --n-tabs-heading-wrap: wrap; --n-tabs-title-flex-basis: content', 'center' => '--n-tabs-heading-justify-content: center; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: initial; --n-tabs-heading-wrap: wrap; --n-tabs-title-flex-basis: content', 'end' => '--n-tabs-heading-justify-content: flex-end; --n-tabs-title-width: initial; --n-tabs-title-height: initial; --n-tabs-title-align-items: initial; --n-tabs-heading-wrap: wrap; --n-tabs-title-flex-basis: content', 'stretch' => '--n-tabs-heading-justify-content: flex-start; --n-tabs-title-width: initial; --n-tabs-title-height: 100%; --n-tabs-title-align-items: center; --n-tabs-heading-wrap: nowrap; --n-tabs-title-flex-basis: auto', ], 'selectors' => [ '{{WRAPPER}}' => '{{VALUE}}', ], 'condition' => [ 'tabs_direction' => [ 'inline-start', 'inline-end', 'start', 'end', ], ], ] ); $this->add_responsive_control( 'tabs_width', [ 'label' => esc_html__( 'Width', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'min' => 10, 'max' => 50, ], 'px' => [ 'min' => 20, 'max' => 600, ], ], 'default' => [ 'unit' => '%', ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-heading-width: {{SIZE}}{{UNIT}}', ], 'condition' => [ 'tabs_direction' => [ 'inline-start', 'inline-end', 'start', 'end', ], ], ] ); $this->add_responsive_control( 'title_alignment', [ 'label' => esc_html__( 'Align Title', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Start', 'elementor' ), 'icon' => 'eicon-text-align-left', ], 'center' => [ 'title' => esc_html__( 'Center', 'elementor' ), 'icon' => 'eicon-text-align-center', ], 'end' => [ 'title' => esc_html__( 'End', 'elementor' ), 'icon' => 'eicon-text-align-right', ], ], 'selectors_dictionary' => [ 'start' => '--n-tabs-title-justify-content: flex-start; --n-tabs-title-align-items: flex-start; --n-tabs-title-text-align: start;', 'center' => '--n-tabs-title-justify-content: center; --n-tabs-title-align-items: center; --n-tabs-title-text-align: center;', 'end' => '--n-tabs-title-justify-content: flex-end; --n-tabs-title-align-items: flex-end; --n-tabs-title-text-align: end;', ], 'selectors' => [ '{{WRAPPER}}' => '{{VALUE}}', ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_tabs_responsive', [ 'label' => esc_html__( 'Additional Settings', 'elementor' ), ] ); $this->add_responsive_control( 'horizontal_scroll', [ 'label' => esc_html__( 'Horizontal Scroll', 'elementor' ), 'type' => Controls_Manager::SELECT, 'description' => esc_html__( 'Note: Scroll tabs if they don’t fit into their parent container.', 'elementor' ), 'options' => [ 'disable' => esc_html__( 'Disable', 'elementor' ), 'enable' => esc_html__( 'Enable', 'elementor' ), ], 'default' => 'disable', 'selectors_dictionary' => [ 'disable' => '--n-tabs-heading-wrap: wrap; --n-tabs-heading-overflow-x: initial; --n-tabs-title-white-space: initial;', 'enable' => '--n-tabs-heading-wrap: nowrap; --n-tabs-heading-overflow-x: scroll; --n-tabs-title-white-space: nowrap;', ], 'selectors' => [ '{{WRAPPER}}' => '{{VALUE}}', ], 'frontend_available' => true, 'condition' => [ 'tabs_direction' => [ '', 'block-start', 'block-end', 'top', 'bottom', ], ], ] ); $dropdown_options = [ 'none' => esc_html__( 'None', 'elementor' ), ]; $excluded_breakpoints = [ 'laptop', 'tablet_extra', 'widescreen', ]; foreach ( Plugin::$instance->breakpoints->get_active_breakpoints() as $breakpoint_key => $breakpoint_instance ) { // Exclude the larger breakpoints from the dropdown selector. if ( in_array( $breakpoint_key, $excluded_breakpoints, true ) ) { continue; } $dropdown_options[ $breakpoint_key ] = sprintf( /* translators: 1: Breakpoint label, 2: `>` character, 3: Breakpoint value. */ esc_html__( '%1$s (%2$s %3$dpx)', 'elementor' ), $breakpoint_instance->get_label(), '>', $breakpoint_instance->get_value() ); } $this->add_control( 'breakpoint_selector', [ 'label' => esc_html__( 'Breakpoint', 'elementor' ), 'type' => Controls_Manager::SELECT, 'description' => esc_html__( 'Note: Choose at which breakpoint tabs will automatically switch to a vertical (“accordion”) layout.', 'elementor' ), 'options' => $dropdown_options, 'default' => 'mobile', 'prefix_class' => 'e-n-tabs-', ] ); $this->end_controls_section(); $this->start_controls_section( 'section_tabs_style', [ 'label' => esc_html__( 'Tabs', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_responsive_control( 'tabs_title_space_between', [ 'label' => esc_html__( 'Gap between tabs', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'range' => [ 'px' => [ 'max' => 400, ], 'em' => [ 'max' => 40, ], 'rem' => [ 'max' => 40, ], ], 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-title-gap: {{SIZE}}{{UNIT}}', ], ] ); $this->add_responsive_control( 'tabs_title_spacing', [ 'label' => esc_html__( 'Distance from content', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'range' => [ 'px' => [ 'max' => 400, ], 'em' => [ 'max' => 40, ], 'rem' => [ 'max' => 40, ], ], 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-gap: {{SIZE}}{{UNIT}}', ], ] ); $this->start_controls_tabs( 'tabs_title_style' ); $this->start_controls_tab( 'tabs_title_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'tabs_title_background_color', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], 'selector' => "{{WRAPPER}}{$this->widget_container_selector} > .e-n-tabs > .e-n-tabs-heading > .e-n-tab-title[aria-selected='false']:not( :hover )", 'fields_options' => [ 'color' => [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'selectors' => [ '{{SELECTOR}}' => 'background: {{VALUE}}', ], ], ], ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'tabs_title_border', 'selector' => "{$heading_selector} > .e-n-tab-title[aria-selected=\"false\"]:not( :hover )", 'fields_options' => [ 'color' => [ 'label' => esc_html__( 'Border Color', 'elementor' ), ], 'width' => [ 'label' => esc_html__( 'Border Width', 'elementor' ), ], ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'tabs_title_box_shadow', 'separator' => 'after', 'selector' => "{$heading_selector} > .e-n-tab-title[aria-selected=\"false\"]:not( :hover )", ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tabs_title_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'tabs_title_background_color_hover', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], 'selector' => "{$heading_selector_non_touch_device} > .e-n-tab-title[aria-selected=\"false\"]:hover", 'fields_options' => [ 'background' => [ 'default' => 'classic', ], 'color' => [ 'global' => [ 'default' => Global_Colors::COLOR_ACCENT, ], 'label' => esc_html__( 'Background Color', 'elementor' ), 'selectors' => [ '{{SELECTOR}}' => 'background: {{VALUE}};', ], ], ], ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'tabs_title_border_hover', 'selector' => "{$heading_selector_non_touch_device} > .e-n-tab-title[aria-selected=\"false\"]:hover", 'fields_options' => [ 'color' => [ 'label' => esc_html__( 'Border Color', 'elementor' ), ], 'width' => [ 'label' => esc_html__( 'Border Width', 'elementor' ), ], ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'tabs_title_box_shadow_hover', 'separator' => 'after', 'selector' => "{$heading_selector_non_touch_device} > .e-n-tab-title[aria-selected=\"false\"]:hover", ] ); $this->add_control( 'hover_animation', [ 'label' => esc_html__( 'Hover Animation', 'elementor' ), 'type' => Controls_Manager::HOVER_ANIMATION, ] ); $this->add_control( 'tabs_title_transition_duration', [ 'label' => esc_html__( 'Transition Duration', 'elementor' ) . ' (s)', 'type' => Controls_Manager::SLIDER, 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-title-transition: {{SIZE}}s', ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 3, 'step' => 0.1, ], ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tabs_title_active', [ 'label' => esc_html__( 'Active', 'elementor' ), ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'tabs_title_background_color_active', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], 'selector' => "{$heading_selector} > .e-n-tab-title[aria-selected=\"true\"], {$heading_selector_touch_device} > .e-n-tab-title[aria-selected=\"false\"]:hover", 'fields_options' => [ 'background' => [ 'default' => 'classic', ], 'color' => [ 'global' => [ 'default' => Global_Colors::COLOR_ACCENT, ], 'label' => esc_html__( 'Background Color', 'elementor' ), 'selectors' => [ '{{SELECTOR}}' => 'background: {{VALUE}};', ], ], ], ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'tabs_title_border_active', 'selector' => "{$heading_selector} > .e-n-tab-title[aria-selected=\"true\"], {$heading_selector_touch_device} > .e-n-tab-title[aria-selected=\"false\"]:hover", 'fields_options' => [ 'color' => [ 'label' => esc_html__( 'Border Color', 'elementor' ), ], 'width' => [ 'label' => esc_html__( 'Border Width', 'elementor' ), ], ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'tabs_title_box_shadow_active', 'selector' => "{$heading_selector} > .e-n-tab-title[aria-selected=\"true\"], {$heading_selector_touch_device} > .e-n-tab-title[aria-selected=\"false\"]:hover", ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_responsive_control( 'tabs_title_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'separator' => 'before', 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-title-border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_responsive_control( 'padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-title-padding-top: {{TOP}}{{UNIT}}; --n-tabs-title-padding-right: {{RIGHT}}{{UNIT}}; --n-tabs-title-padding-bottom: {{BOTTOM}}{{UNIT}}; --n-tabs-title-padding-left: {{LEFT}}{{UNIT}};', ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_title_style', [ 'label' => esc_html__( 'Titles', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'title_typography', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_ACCENT, ], 'selector' => "{$heading_selector} > :is( .e-n-tab-title > .e-n-tab-title-text, .e-n-tab-title )", 'fields_options' => [ 'font_size' => [ 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-title-font-size: {{SIZE}}{{UNIT}}', ], ], ], ] ); $this->start_controls_tabs( 'title_style' ); $this->start_controls_tab( 'title_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_control( 'title_text_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-title-color: {{VALUE}}', ], ] ); $this->add_group_control( Group_Control_Text_Shadow::get_type(), [ 'name' => 'title_text_shadow', 'selector' => "{$heading_selector} > .e-n-tab-title[aria-selected=\"false\"]:not( :hover )", ] ); $this->add_group_control( Group_Control_Text_Stroke::get_type(), [ 'name' => 'title_text_stroke', 'selector' => "{$heading_selector} > .e-n-tab-title[aria-selected=\"false\"]:not( :hover ) :is( span, a, i )", ] ); $this->end_controls_tab(); $this->start_controls_tab( 'title_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_control( 'title_text_color_hover', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} [data-touch-mode="false"] .e-n-tab-title[aria-selected="false"]:hover' => '--n-tabs-title-color-hover: {{VALUE}}', ], ] ); $this->add_group_control( Group_Control_Text_Shadow::get_type(), [ 'name' => 'title_text_shadow_hover', 'selector' => "{$heading_selector_non_touch_device} > .e-n-tab-title[aria-selected=\"false\"]:hover", ] ); $this->add_group_control( Group_Control_Text_Stroke::get_type(), [ 'name' => 'title_text_stroke_hover', 'selector' => "{$heading_selector_non_touch_device} > .e-n-tab-title[aria-selected=\"false\"]:hover :is( span, a, i )", ] ); $this->end_controls_tab(); $this->start_controls_tab( 'title_active', [ 'label' => esc_html__( 'Active', 'elementor' ), ] ); $this->add_control( 'title_text_color_active', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-title-color-active: {{VALUE}}', ], ] ); $this->add_group_control( Group_Control_Text_Shadow::get_type(), [ 'name' => 'title_text_shadow_active', 'selector' => "{$heading_selector} > .e-n-tab-title[aria-selected=\"true\"], {$heading_selector_touch_device} > .e-n-tab-title[aria-selected=\"false\"]:hover", ] ); $this->add_group_control( Group_Control_Text_Stroke::get_type(), [ 'name' => 'title_text_stroke_active', 'selector' => "{$heading_selector} > .e-n-tab-title[aria-selected=\"true\"] :is( span, a, i ), {$heading_selector_touch_device} > .e-n-tab-title[aria-selected=\"false\"]:hover :is( span, a, i )", ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->end_controls_section(); $this->start_controls_section( 'icon_section_style', [ 'label' => esc_html__( 'Icon', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $styling_block_start = '--n-tabs-title-direction: column; --n-tabs-icon-order: initial; --n-tabs-title-justify-content-toggle: center; --n-tabs-title-align-items-toggle: initial;'; $styling_block_end = '--n-tabs-title-direction: column; --n-tabs-icon-order: 1; --n-tabs-title-justify-content-toggle: center; --n-tabs-title-align-items-toggle: initial;'; $styling_inline_start = '--n-tabs-title-direction: row; --n-tabs-icon-order: initial; --n-tabs-title-justify-content-toggle: initial; --n-tabs-title-align-items-toggle: center;'; $styling_inline_end = '--n-tabs-title-direction: row; --n-tabs-icon-order: 1; --n-tabs-title-justify-content-toggle: initial; --n-tabs-title-align-items-toggle: center;'; $this->add_responsive_control( 'icon_position', [ 'label' => esc_html__( 'Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'block-start' => [ 'title' => esc_html__( 'Above', 'elementor' ), 'icon' => 'eicon-v-align-top', ], 'inline-end' => [ 'title' => esc_html__( 'After', 'elementor' ), 'icon' => 'eicon-h-align-' . $end, ], 'block-end' => [ 'title' => esc_html__( 'Below', 'elementor' ), 'icon' => 'eicon-v-align-bottom', ], 'inline-start' => [ 'title' => esc_html__( 'Before', 'elementor' ), 'icon' => 'eicon-h-align-' . $start, ], ], 'selectors_dictionary' => [ // The toggle variables for 'align items' and 'justify content' have been added to separate the styling of the two 'flex direction' modes. 'block-start' => $styling_block_start, 'inline-end' => $styling_inline_end, 'block-end' => $styling_block_end, 'inline-start' => $styling_inline_start, // Styling duplication for BC reasons. 'top' => $styling_block_start, 'bottom' => $styling_block_end, 'start' => $styling_inline_start, 'end' => $styling_inline_end, ], 'selectors' => [ '{{WRAPPER}}' => '{{VALUE}}', ], ] ); $this->add_responsive_control( 'icon_size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'max' => 100, ], 'em' => [ 'max' => 10, ], 'rem' => [ 'max' => 10, ], ], 'size_units' => [ 'px', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-icon-size: {{SIZE}}{{UNIT}}', ], ] ); $this->add_responsive_control( 'icon_spacing', [ 'label' => esc_html__( 'Spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'max' => 400, ], 'vw' => [ 'max' => 50, 'step' => 0.1, ], ], 'size_units' => [ 'px', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-icon-gap: {{SIZE}}{{UNIT}}', ], ] ); $this->start_controls_tabs( 'icon_style_states' ); $this->start_controls_tab( 'icon_section_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_control( 'icon_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-icon-color: {{VALUE}};', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'icon_section_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_control( 'icon_color_hover', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} [data-touch-mode="false"] .e-n-tab-title[aria-selected="false"]:hover' => '--n-tabs-icon-color-hover: {{VALUE}};', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'icon_section_active', [ 'label' => esc_html__( 'Active', 'elementor' ), ] ); $this->add_control( 'icon_color_active', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}}' => '--n-tabs-icon-color-active: {{VALUE}};', ], ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->end_controls_section(); $this->start_controls_section( 'section_box_style', [ 'label' => esc_html__( 'Content', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'box_background_color', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], 'selector' => $content_selector, 'fields_options' => [ 'color' => [ 'label' => esc_html__( 'Background Color', 'elementor' ), ], ], ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'box_border', 'selector' => $content_selector, 'fields_options' => [ 'color' => [ 'label' => esc_html__( 'Border Color', 'elementor' ), ], 'width' => [ 'label' => esc_html__( 'Border Width', 'elementor' ), ], ], ] ); $this->add_responsive_control( 'box_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ $content_selector => '--border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'box_shadow_box_shadow', 'selector' => $content_selector, 'condition' => [ 'box_height!' => 'height', ], ] ); $this->add_responsive_control( 'box_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ $content_selector => '--padding-top: {{TOP}}{{UNIT}}; --padding-right: {{RIGHT}}{{UNIT}}; --padding-bottom: {{BOTTOM}}{{UNIT}}; --padding-left: {{LEFT}}{{UNIT}};', ], ] ); $this->end_controls_section(); } protected function render_tab_titles_html( $item_settings ): void { $setting_key = $this->get_repeater_setting_key( 'tab_title', 'tabs', $item_settings['index'] ); $title = $item_settings['item']['tab_title']; $css_classes = [ 'e-n-tab-title' ]; if ( $item_settings['settings']['hover_animation'] ) { $css_classes[] = 'elementor-animation-' . $item_settings['settings']['hover_animation']; } $this->add_render_attribute( $setting_key, [ 'id' => $item_settings['tab_id'], 'data-tab-title-id' => $item_settings['tab_title_id'], 'class' => $css_classes, 'aria-selected' => 1 === $item_settings['tab_count'] ? 'true' : 'false', 'data-tab-index' => $item_settings['tab_count'], 'role' => 'tab', 'tabindex' => 1 === $item_settings['tab_count'] ? '0' : '-1', 'aria-controls' => $item_settings['container_id'], 'style' => '--n-tabs-title-order: ' . $item_settings['tab_count'] . ';', ] ); ?> <button <?php $this->print_render_attribute_string( $setting_key ); ?>> <?php $this->maybe_render_tab_icons_html( $item_settings ); ?> <span <?php $this->print_render_attribute_string( 'tab-title-text' ); ?>> <?php echo wp_kses_post( $title ); ?> </span> </button> <?php } protected function maybe_render_tab_icons_html( $item_settings ): void { $icon_settings = $item_settings['item']['tab_icon']; if ( empty( $icon_settings['value'] ) ) { return; } $active_icon_settings = $this->is_active_icon_exist( $item_settings['item'] ) ? $item_settings['item']['tab_icon_active'] : $icon_settings; ?> <span <?php $this->print_render_attribute_string( 'tab-icon' ); ?>> <?php Icons_Manager::render_icon( $icon_settings, [ 'aria-hidden' => 'true' ] ); ?> <?php Icons_Manager::render_icon( $active_icon_settings, [ 'aria-hidden' => 'true' ] ); ?> </span> <?php } protected function render_tab_containers_html( $settings ): void { foreach ( $settings['tabs'] as $index => $item ) { $item_settings = $this->tab_item_settings[ $index ]; $this->print_child( $item_settings['index'], $item_settings ); } } /** * Print the content area. * * @param int $index * @param array $item_settings */ public function print_child( $index, $item_settings = [] ) { $children = $this->get_children(); $child_ids = []; foreach ( $children as $child ) { $child_ids[] = $child->get_id(); } // Add data-tab-index attribute to the content area. $add_attribute_to_container = function ( $should_render, $container ) use ( $item_settings, $child_ids ) { if ( in_array( $container->get_id(), $child_ids ) ) { $this->add_attributes_to_container( $container, $item_settings ); } return $should_render; }; add_filter( 'elementor/frontend/container/should_render', $add_attribute_to_container, 10, 3 ); if ( isset( $children[ $index ] ) ) { $children[ $index ]->print_element(); } remove_filter( 'elementor/frontend/container/should_render', $add_attribute_to_container ); } protected function add_attributes_to_container( $container, $item_settings ) { $container->add_render_attribute( '_wrapper', [ 'id' => $item_settings['container_id'], 'role' => 'tabpanel', 'aria-labelledby' => $item_settings['tab_id'], 'data-tab-index' => $item_settings['tab_count'], 'style' => '--n-tabs-title-order: ' . $item_settings['tab_count'] . ';', 'class' => 0 === $item_settings['index'] ? 'e-active' : '', ] ); } protected function render() { $settings = $this->get_settings_for_display(); $widget_number = $this->get_id_int(); if ( ! empty( $settings['link'] ) ) { $this->add_link_attributes( 'elementor-tabs', $settings['link'] ); } $this->add_render_attribute( 'elementor-tabs', [ 'class' => 'e-n-tabs', 'data-widget-number' => $widget_number, 'aria-label' => esc_html__( 'Tabs. Open items with Enter or Space, close with Escape and navigate using the Arrow keys.', 'elementor' ), ] ); $this->add_render_attribute( 'tab-title-text', 'class', 'e-n-tab-title-text' ); $this->add_render_attribute( 'tab-icon', 'class', 'e-n-tab-icon' ); $this->add_render_attribute( 'tab-icon-active', 'class', [ 'e-n-tab-icon' ] ); ?> <div <?php $this->print_render_attribute_string( 'elementor-tabs' ); ?>> <div class="e-n-tabs-heading" role="tablist"> <?php foreach ( $settings['tabs'] as $index => $item ) { $tab_count = $index + 1; $tab_title_id = 'e-n-tab-title-' . $widget_number . $tab_count; $tab_id = empty( $item['element_id'] ) ? $tab_title_id : $item['element_id']; $item_settings = [ 'index' => $index, 'tab_count' => $tab_count, 'tab_id' => $tab_id, 'tab_title_id' => $tab_title_id, 'container_id' => 'e-n-tab-content-' . $widget_number . $tab_count, 'widget_number' => $widget_number, 'item' => $item, 'settings' => $settings, ]; $this->tab_item_settings[] = $item_settings; $this->render_tab_titles_html( $item_settings ); } ?> </div> <div class="e-n-tabs-content"> <?php $this->render_tab_containers_html( $settings ); ?> </div> </div> <?php } protected function get_initial_config(): array { return array_merge( parent::get_initial_config(), [ 'support_improved_repeaters' => true, 'target_container' => [ '.e-n-tabs-heading' ], 'node' => 'button', ] ); } protected function content_template_single_repeater_item() { ?> <# const tabIndex = view.collection.length, elementUid = view.getIDInt().toString(), item = data, hoverAnimationSetting = view?.container?.settings?.attributes?.hover_animation; hoverAnimationClass = hoverAnimationSetting ? `elementor-animation-${ hoverAnimationSetting }` : ''; #> <?php $this->content_template_single_item( '{{ tabIndex }}', '{{ item }}', '{{ elementUid }}', '{{ hoverAnimationClass }}' ); } protected function content_template() { ?> <# const elementUid = view.getIDInt().toString(); #> <div class="e-n-tabs" data-widget-number="{{ elementUid }}" aria-label="<?php echo esc_html__( 'Tabs. Open items with Enter or Space, close with Escape and navigate using the Arrow keys.', 'elementor' ); ?>"> <# if ( settings['tabs'] ) { #> <div class="e-n-tabs-heading" role="tablist"> <# _.each( settings['tabs'], function( item, index ) { const tabIndex = index, hoverAnimationSetting = settings['hover_animation'], hoverAnimationClass = hoverAnimationSetting ? `elementor-animation-${ hoverAnimationSetting }` : ''; #> <?php $this->content_template_single_item( '{{ tabIndex }}', '{{ item }}', '{{ elementUid }}', '{{ hoverAnimationClass }}' ); ?> <# } ); #> </div> <div class="e-n-tabs-content"></div> <# } #> </div> <?php } private function content_template_single_item( $tab_index, $item, $element_uid, $hover_animation_class ) { ?> <# const tabCount = tabIndex + 1, tabTitleId = 'e-n-tab-title-' + elementUid + tabCount, tabId = item.element_id ? item.element_id : tabTitleId, tabUid = elementUid + tabCount, tabIcon = elementor.helpers.renderIcon( view, item.tab_icon, { 'aria-hidden': true }, 'i' , 'object' ), activeTabIcon = item.tab_icon_active.value ? elementor.helpers.renderIcon( view, item.tab_icon_active, { 'aria-hidden': true }, 'i' , 'object' ) : tabIcon, escapedHoverAnimationClass = _.escape( hoverAnimationClass ); view.addRenderAttribute( 'tab-title', { 'id': tabId, 'data-tab-title-id': tabTitleId, 'class': [ 'e-n-tab-title',escapedHoverAnimationClass ], 'data-tab-index': tabCount, 'role': 'tab', 'aria-selected': 1 === tabCount ? 'true' : 'false', 'tabindex': 1 === tabCount ? '0' : '-1', 'aria-controls': 'e-n-tab-content-' + tabUid, 'style': '--n-tabs-title-order: ' + tabCount + ';', }, null, true ); view.addRenderAttribute( 'tab-title-text', { 'class': [ 'e-n-tab-title-text' ], 'data-binding-type': 'repeater-item', 'data-binding-repeater-name': 'tabs', 'data-binding-setting': [ 'tab_title', 'element_id' ], 'data-binding-index': tabCount, 'data-binding-config': JSON.stringify({ 'element_id': { attr: 'id', selector: 'button', editType: 'attribute', }, 'tab_title': { editType: 'text', }, }), }, null, true ); view.addRenderAttribute( 'tab-icon', { 'class': [ 'e-n-tab-icon' ], 'data-binding-type': 'repeater-item', 'data-binding-repeater-name': 'tabs', 'data-binding-index': tabCount, }, null, true ); #> <button {{{ view.getRenderAttributeString( 'tab-title' ) }}}> <# if ( !! item.tab_icon.value ) { #> <span {{{ view.getRenderAttributeString( 'tab-icon' ) }}}>{{{ tabIcon.value }}}{{{ activeTabIcon.value }}}</span> <# } #> <span {{{ view.getRenderAttributeString( 'tab-title-text' ) }}}>{{{ item.tab_title }}}</span> </button> <?php } /** * @param $item * @return bool */ private function is_active_icon_exist( $item ) { return array_key_exists( 'tab_icon_active', $item ) && ! empty( $item['tab_icon_active'] ) && ! empty( $item['tab_icon_active']['value'] ); } } nested-tabs/module.php 0000644 00000002654 15252521350 0010762 0 ustar 00 <?php namespace Elementor\Modules\NestedTabs; use Elementor\Plugin; use Elementor\Modules\NestedElements\Module as NestedElementsModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends \Elementor\Core\Base\Module { public static function is_active() { return Plugin::$instance->experiments->is_feature_active( NestedElementsModule::EXPERIMENT_NAME ); } public function get_name() { return 'nested-tabs'; } public function __construct() { parent::__construct(); add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] ); add_action( 'elementor/editor/before_enqueue_scripts', function () { wp_enqueue_script( $this->get_name(), $this->get_js_assets_url( $this->get_name() ), [ 'nested-elements', ], ELEMENTOR_VERSION, true ); } ); } /** * Register styles. * * At build time, Elementor compiles `/modules/nested-tabs/assets/scss/frontend.scss` * to `/assets/css/widget-nested-tabs.min.css`. * * @return void */ public function register_styles() { $direction_suffix = is_rtl() ? '-rtl' : ''; $has_custom_breakpoints = Plugin::$instance->breakpoints->has_custom_breakpoints(); wp_register_style( 'widget-nested-tabs', $this->get_frontend_file_url( "widget-nested-tabs{$direction_suffix}.min.css", $has_custom_breakpoints ), [ 'elementor-frontend' ], $has_custom_breakpoints ? null : ELEMENTOR_VERSION ); } } mcp/module.php 0000644 00000006145 15252521350 0007327 0 ustar 00 <?php namespace Elementor\Modules\Mcp; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; use Elementor\Core\Experiments\Manager as ExperimentsManager; use WP\MCP\Core\McpAdapter; if ( ! defined( 'ABSPATH' ) ) { exit; } class Module extends BaseModule { const EXPERIMENT_NAME = 'e_wp_abilities_api'; public function get_name() { return 'mcp'; } public static function is_active() { return class_exists( McpAdapter::class ) && function_exists( 'wp_register_ability' ) && Plugin::instance()->experiments->is_feature_active( self::EXPERIMENT_NAME ); } public static function get_experimental_data() { return [ 'name' => self::EXPERIMENT_NAME, 'title' => __( 'Elementor MCP WP Abilities API', 'elementor' ), 'description' => __( 'Enable Elementor MCP WP Abilities API. Requirements: 1. WordPress 7.0 or higher. 2. Create an application password for your agent user. 3. Add to your MCP config: {url: "https://<your-site-url>/wp-json/elementor/mcp", headers: {Authorization: "Basic <base64(user:application-password)>"}}', 'elementor' ), 'hidden' => true, 'default' => ExperimentsManager::STATE_INACTIVE, ]; } public function __construct() { parent::__construct(); if ( ! $this->is_active() ) { return; } McpAdapter::instance(); add_action( 'wp_abilities_api_categories_init', [ $this, 'register_ability_category' ] ); add_action( 'wp_abilities_api_init', [ $this, 'register_abilities' ] ); add_action( 'mcp_adapter_init', [ $this, 'register_server' ] ); } public function register_ability_category() { if ( ! function_exists( 'wp_register_ability_category' ) ) { return; } wp_register_ability_category( 'elementor', [ 'label' => __( 'Elementor', 'elementor' ), 'description' => __( 'Elementor page builder data, global classes, and variables.', 'elementor' ), ] ); } public function register_abilities() { if ( ! function_exists( 'wp_register_ability' ) ) { return; } ( new Abilities\List_Pages_Ability() )->register(); ( new Abilities\Get_Structure_Ability() )->register(); ( new Abilities\Update_Settings_Ability() )->register(); ( new Abilities\Create_Page_Ability() )->register(); ( new Abilities\Get_Globals_Ability() )->register(); } public function register_server( $adapter ) { if ( ! $adapter instanceof McpAdapter ) { return; } $result = $adapter->create_server( 'elementor-mcp-server', 'elementor', 'mcp', 'Elementor MCP', 'Read and modify Elementor Editor abilities.', 'v1.0.0', [ \WP\MCP\Transport\HttpTransport::class ], \WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class, \WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class, [ 'elementor/list-pages', 'elementor/get-page-structure', 'elementor/update-page-settings', 'elementor/create-page', 'elementor/get-globals', ], [], [] ); if ( is_wp_error( $result ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( sprintf( '[Elementor MCP] Server registration failed: %s', $result->get_error_message() ) ); return; } } } mcp/abilities/get-globals-ability.php 0000644 00000004204 15252521350 0013634 0 ustar 00 <?php namespace Elementor\Modules\Mcp\Abilities; use Elementor\Modules\GlobalClasses\Global_Classes_Repository; use Elementor\Modules\Variables\Services\Batch_Operations\Batch_Processor; use Elementor\Modules\Variables\Services\Variables_Service; use Elementor\Modules\Variables\Storage\Variables_Repository; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Get_Globals_Ability extends Abstract_Ability { protected function get_ability_id(): string { return 'elementor/get-globals'; } protected function get_definition(): Ability_Definition { return new Ability_Definition( __( 'Get Elementor Globals', 'elementor' ), __( 'Returns site-wide Elementor design data: global classes (shared CSS classes from the kit) and variables (design tokens such as colors and fonts tied to the active kit). Use when you need kit-level styling context, not a single page tree.', 'elementor' ), 'elementor', [ 'type' => 'object', 'properties' => [ 'global_classes' => [ 'type' => 'object', 'description' => 'Global class definitions and order from the active kit.', ], 'variables' => [ 'type' => 'object', 'description' => 'Variables list, total count, and watermark from the active kit.', ], ], ], [ 'annotations' => [ 'readonly' => true, 'idempotent' => true, 'destructive' => false, ], ], function () { return current_user_can( 'edit_posts' ); } // No input_schema — this ability takes no input. ); } public function execute( $input = [] ) { $kit = Plugin::$instance->kits_manager->get_active_kit(); $classes_payload = Global_Classes_Repository::make( $kit )->all()->get(); $variables_service = new Variables_Service( new Variables_Repository( $kit ), new Batch_Processor() ); $variables_payload = $variables_service->load(); return [ 'global_classes' => $classes_payload, 'variables' => [ 'variables' => $variables_payload['data'] ?? [], 'total' => isset( $variables_payload['data'] ) ? count( $variables_payload['data'] ) : 0, 'watermark' => $variables_payload['watermark'] ?? null, ], ]; } } mcp/abilities/create-page-ability.php 0000644 00000006742 15252521350 0013622 0 ustar 00 <?php namespace Elementor\Modules\Mcp\Abilities; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Create_Page_Ability extends Abstract_Ability { protected function get_ability_id(): string { return 'elementor/create-page'; } protected function get_definition(): Ability_Definition { return new Ability_Definition( __( 'Create Elementor Page', 'elementor' ), __( 'Creates a new draft post or page and marks it as built with Elementor (blank canvas in the editor). Use when the user wants a new layout shell to design in Elementor. Returns the new post ID and edit URL.', 'elementor' ), 'elementor', [ 'type' => 'object', 'properties' => [ 'id' => [ 'type' => 'integer' ], 'edit_url' => [ 'type' => 'string' ], 'status' => [ 'type' => 'string' ], 'type' => [ 'type' => 'string' ], ], ], [ 'annotations' => [ 'readonly' => false, 'idempotent' => false, 'destructive' => false, ], ], function () { return current_user_can( 'edit_posts' ); }, [ 'type' => 'object', 'properties' => [ 'title' => [ 'type' => 'string', 'description' => 'Optional title for the new post.', ], 'post_type' => [ 'type' => 'string', 'description' => 'Post type slug; must support Elementor. Defaults to page.', 'default' => 'page', ], ], ] ); } public function execute( $input = [] ) { $input = is_array( $input ) ? $input : []; $post_type = ! empty( $input['post_type'] ) ? sanitize_key( $input['post_type'] ) : 'page'; $type_error = $this->validate_post_type( $post_type ); if ( $type_error ) { return $type_error; } $permission_error = $this->check_create_permission( $post_type ); if ( $permission_error ) { return $permission_error; } $title = isset( $input['title'] ) && is_string( $input['title'] ) ? $input['title'] : ''; return $this->create_post( $post_type, $title ); } private function validate_post_type( string $post_type ): ?\WP_Error { if ( ! post_type_exists( $post_type ) || ! post_type_supports( $post_type, 'elementor' ) ) { return new \WP_Error( 'invalid_post_type', __( 'This post type does not support Elementor.', 'elementor' ), [ 'status' => \WP_Http::BAD_REQUEST ] ); } return null; } private function check_create_permission( string $post_type ): ?\WP_Error { $post_type_object = get_post_type_object( $post_type ); if ( ! $post_type_object || ! current_user_can( $post_type_object->cap->create_posts ) ) { return new \WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create posts of this type.', 'elementor' ), [ 'status' => \WP_Http::FORBIDDEN ] ); } return null; } private function create_post( string $post_type, string $title ) { $post_id = wp_insert_post( [ 'post_title' => $title ? $title : __( 'Elementor Draft', 'elementor' ), 'post_status' => 'draft', 'post_type' => $post_type, ], true ); if ( is_wp_error( $post_id ) ) { return $post_id; } $document = Plugin::$instance->documents->get( $post_id ); if ( ! $document ) { return new \WP_Error( 'document_not_found', __( 'Document could not be loaded.', 'elementor' ), [ 'status' => \WP_Http::INTERNAL_SERVER_ERROR ] ); } $document->set_is_built_with_elementor( true ); return [ 'id' => (int) $post_id, 'edit_url' => $document->get_edit_url(), 'status' => get_post_status( $post_id ), 'type' => $post_type, ]; } } mcp/abilities/update-settings-ability.php 0000644 00000005470 15252521350 0014562 0 ustar 00 <?php namespace Elementor\Modules\Mcp\Abilities; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Update_Settings_Ability extends Abstract_Ability { protected function get_ability_id(): string { return 'elementor/update-page-settings'; } protected function get_definition(): Ability_Definition { return new Ability_Definition( __( 'Update Elementor Page Settings', 'elementor' ), __( 'Updates Elementor document-level settings for a post (for example page layout, title visibility, or custom page settings). Pass only the keys you want to change. Use list-pages to resolve IDs and get-page-structure when you also need the element tree. Requires permission to edit the target post.', 'elementor' ), 'elementor', [ 'type' => 'object', 'properties' => [ 'success' => [ 'type' => 'boolean' ], 'post_id' => [ 'type' => 'integer' ], ], ], [ 'annotations' => [ 'readonly' => false, 'idempotent' => false, 'destructive' => false, ], ], function () { return current_user_can( 'edit_posts' ); }, [ 'type' => 'object', 'required' => [ 'post_id', 'settings' ], 'properties' => [ 'post_id' => [ 'type' => 'integer', 'description' => 'WordPress post ID of the Elementor document.', ], 'settings' => [ 'type' => 'object', 'description' => 'Partial document settings object; merged into existing settings. Schema enforcement is delegated to document->save().', 'additionalProperties' => true, ], ], ] ); } public function execute( $input = [] ) { $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0; $settings = isset( $input['settings'] ) && is_array( $input['settings'] ) ? $input['settings'] : null; if ( ! $post_id ) { return new \WP_Error( 'invalid_post_id', __( 'A valid post_id is required.', 'elementor' ), [ 'status' => \WP_Http::BAD_REQUEST ] ); } if ( null === $settings ) { return new \WP_Error( 'invalid_settings', __( 'The settings object is required.', 'elementor' ), [ 'status' => \WP_Http::BAD_REQUEST ] ); } $document = Plugin::$instance->documents->get( $post_id ); if ( ! $document ) { return new \WP_Error( 'document_not_found', __( 'Document not found.', 'elementor' ), [ 'status' => \WP_Http::NOT_FOUND ] ); } if ( ! $document->is_editable_by_current_user() ) { return new \WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this document.', 'elementor' ), [ 'status' => \WP_Http::FORBIDDEN ] ); } $saved = $document->save( [ 'settings' => $settings ] ); if ( ! $saved ) { return new \WP_Error( 'save_failed', __( 'Could not save document settings.', 'elementor' ), [ 'status' => \WP_Http::INTERNAL_SERVER_ERROR ] ); } return [ 'success' => true, 'post_id' => $post_id, ]; } } mcp/abilities/get-structure-ability.php 0000644 00000005746 15252521350 0014265 0 ustar 00 <?php namespace Elementor\Modules\Mcp\Abilities; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Get_Structure_Ability extends Abstract_Ability { protected function get_ability_id(): string { return 'elementor/get-page-structure'; } protected function get_definition(): Ability_Definition { return new Ability_Definition( __( 'Get Elementor Page Structure', 'elementor' ), __( 'Returns the Elementor element tree (widgets, containers, and nested content) for a single post or page ID. Use after list-pages when you need the live JSON structure to reason about layout, widget types, or to plan edits. Only works for posts that were saved with Elementor.', 'elementor' ), 'elementor', [ 'type' => 'object', 'properties' => [ 'elements' => [ 'type' => 'array', 'description' => 'Root-level Elementor elements for the document.', ], ], ], [ 'annotations' => [ 'readonly' => true, 'idempotent' => true, 'destructive' => false, ], ], function () { return current_user_can( 'edit_posts' ); }, [ 'type' => 'object', 'required' => [ 'post_id' ], 'properties' => [ 'post_id' => [ 'type' => 'integer', 'description' => 'WordPress post ID of the Elementor document.', ], ], ] ); } public function execute( $input = [] ) { $post_id = $this->resolve_post_id( $input ); if ( is_wp_error( $post_id ) ) { return $post_id; } $document = $this->get_editable_document( $post_id ); if ( is_wp_error( $document ) ) { return $document; } $elements = $document->get_elements_data(); return [ 'elements' => is_array( $elements ) ? $elements : [], ]; } private function resolve_post_id( $input ) { $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0; if ( ! $post_id ) { return new \WP_Error( 'invalid_post_id', __( 'A valid post_id is required.', 'elementor' ), [ 'status' => \WP_Http::BAD_REQUEST ] ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new \WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to access this document.', 'elementor' ), [ 'status' => \WP_Http::FORBIDDEN ] ); } return $post_id; } private function get_editable_document( int $post_id ) { $document = Plugin::$instance->documents->get( $post_id ); if ( ! $document ) { return new \WP_Error( 'document_not_found', __( 'Document not found.', 'elementor' ), [ 'status' => \WP_Http::NOT_FOUND ] ); } if ( ! $document->is_built_with_elementor() ) { return new \WP_Error( 'not_elementor', __( 'This post is not built with Elementor.', 'elementor' ), [ 'status' => \WP_Http::BAD_REQUEST ] ); } if ( ! $document->is_editable_by_current_user() ) { return new \WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to edit this document.', 'elementor' ), [ 'status' => \WP_Http::FORBIDDEN ] ); } return $document; } } mcp/abilities/abstract-ability.php 0000644 00000001006 15252521350 0013234 0 ustar 00 <?php namespace Elementor\Modules\Mcp\Abilities; if ( ! defined( 'ABSPATH' ) ) { exit; } abstract class Abstract_Ability { abstract protected function get_ability_id(): string; abstract protected function get_definition(): Ability_Definition; abstract public function execute( $input = [] ); public function register(): void { $definition = $this->get_definition()->to_array(); $definition['execute_callback'] = [ $this, 'execute' ]; wp_register_ability( $this->get_ability_id(), $definition ); } } mcp/abilities/ability-definition.php 0000644 00000002253 15252521350 0013566 0 ustar 00 <?php namespace Elementor\Modules\Mcp\Abilities; if ( ! defined( 'ABSPATH' ) ) { exit; } class Ability_Definition { public string $label; public string $description; public string $category; public array $output_schema; public array $meta; /** @var callable */ public $permission_callback; public array $input_schema; public function __construct( string $label, string $description, string $category, array $output_schema, array $meta, callable $permission_callback, array $input_schema = [] ) { $this->label = $label; $this->description = $description; $this->category = $category; $this->output_schema = $output_schema; $this->meta = $meta; $this->permission_callback = $permission_callback; $this->input_schema = $input_schema; } public function to_array(): array { $definition = [ 'label' => $this->label, 'description' => $this->description, 'category' => $this->category, 'output_schema' => $this->output_schema, 'meta' => $this->meta, 'permission_callback' => $this->permission_callback, ]; if ( ! empty( $this->input_schema ) ) { $definition['input_schema'] = $this->input_schema; } return $definition; } } mcp/abilities/list-pages-ability.php 0000644 00000004715 15252521350 0013513 0 ustar 00 <?php namespace Elementor\Modules\Mcp\Abilities; if ( ! defined( 'ABSPATH' ) ) { exit; } class List_Pages_Ability extends Abstract_Ability { protected function get_ability_id(): string { return 'elementor/list-pages'; } protected function get_definition(): Ability_Definition { return new Ability_Definition( __( 'List Elementor Pages', 'elementor' ), __( 'Returns pages and posts built with Elementor on this WordPress site. Each item includes ID, title, status (publish/draft), URL, and post type. Use this first to discover which pages exist before fetching their structure or modifying settings.', 'elementor' ), 'elementor', [ 'type' => 'array', 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'type' => 'integer' ], 'title' => [ 'type' => 'string' ], 'status' => [ 'type' => 'string' ], 'url' => [ 'type' => 'string' ], 'type' => [ 'type' => 'string' ], ], ], ], [ 'annotations' => [ 'readonly' => true, 'idempotent' => true, 'destructive' => false, ], ], function () { return current_user_can( 'edit_posts' ); }, [ 'type' => 'object', 'properties' => [ 'status' => [ 'type' => 'string', 'enum' => [ 'publish', 'draft', 'any' ], 'default' => 'any', ], 'post_type' => [ 'type' => 'string', 'description' => 'Filter by post type. Omit for all Elementor-supported types.', ], ], ] ); } public function execute( $input = [] ) { $input = is_array( $input ) ? $input : []; $args = [ 'post_type' => get_post_types_by_support( 'elementor' ), 'meta_key' => '_elementor_edit_mode', 'meta_value' => 'builder', 'post_status' => isset( $input['status'] ) ? $input['status'] : 'any', 'fields' => 'ids', 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC', 'suppress_filters' => false, ]; if ( ! empty( $input['post_type'] ) ) { $args['post_type'] = sanitize_key( $input['post_type'] ); } $ids = get_posts( $args ); $ids = array_values( array_filter( array_map( 'absint', $ids ), function ( $id ) { return $id && current_user_can( 'edit_post', $id ); } ) ); return array_map( function ( $id ) { return [ 'id' => $id, 'title' => get_the_title( $id ), 'status' => get_post_status( $id ), 'url' => (string) get_permalink( $id ), 'type' => get_post_type( $id ), ]; }, $ids ); } } nested-accordion/widgets/nested-accordion.php 0000644 00000074500 15252521350 0015373 0 ustar 00 <?php namespace Elementor\Modules\NestedAccordion\Widgets; use Elementor\Controls_Manager; use Elementor\Group_Control_Background; use Elementor\Group_Control_Border; use Elementor\Group_Control_Text_Shadow; use Elementor\Group_Control_Typography; use Elementor\Icons_Manager; use Elementor\Modules\NestedElements\Base\Widget_Nested_Base; use Elementor\Modules\NestedElements\Controls\Control_Nested_Repeater; use Elementor\Plugin; use Elementor\Repeater; use Elementor\Utils; use Elementor\Group_Control_Text_Stroke; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Nested Accordion widget. * * Elementor widget that displays a collapsible display of content in an * accordion style. * * @since 3.15.0 */ class Nested_Accordion extends Widget_Nested_Base { private $optimized_markup = null; private $widget_container_selector = ''; protected function is_dynamic_content(): bool { return true; } public function get_name() { return 'nested-accordion'; } public function get_title() { return esc_html__( 'Accordion', 'elementor' ); } public function get_icon() { return 'eicon-accordion'; } public function get_keywords() { return [ 'nested', 'tabs', 'accordion', 'toggle' ]; } public function get_style_depends(): array { return [ 'widget-nested-accordion' ]; } public function show_in_panel(): bool { return Plugin::$instance->experiments->is_feature_active( 'nested-elements', true ); } public function has_widget_inner_wrapper(): bool { return ! Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' ); } protected function item_content_container( int $index ) { return [ 'elType' => 'container', 'settings' => [ '_title' => sprintf( /* translators: %d: Item index. */ __( 'item #%d', 'elementor' ), $index ), 'content_width' => 'full', ], ]; } protected function get_default_children_elements() { return [ $this->item_content_container( 1 ), $this->item_content_container( 2 ), $this->item_content_container( 3 ), ]; } protected function get_default_repeater_title_setting_key() { return 'item_title'; } protected function get_default_children_title() { /* translators: %d: Item index. */ return esc_html__( 'Item #%d', 'elementor' ); } protected function get_default_children_placeholder_selector() { return '.e-n-accordion'; } protected function get_default_children_container_placeholder_selector() { return '.e-n-accordion-item'; } protected function get_html_wrapper_class() { return 'elementor-widget-n-accordion'; } protected function register_controls() { if ( null === $this->optimized_markup ) { $this->optimized_markup = Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' ) && ! $this->has_widget_inner_wrapper(); $this->widget_container_selector = $this->optimized_markup ? '' : ' > .elementor-widget-container'; } $this->start_controls_section( 'section_items', [ 'label' => esc_html__( 'Layout', 'elementor' ), ] ); $repeater = new Repeater(); $repeater->add_control( 'item_title', [ 'label' => esc_html__( 'Title', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => esc_html__( 'Item Title', 'elementor' ), 'placeholder' => esc_html__( 'Item Title', 'elementor' ), 'label_block' => true, 'dynamic' => [ 'active' => true, ], ] ); $repeater->add_control( 'element_css_id', [ 'label' => esc_html__( 'CSS ID', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'title' => esc_html__( 'Add your custom id WITHOUT the Pound key. e.g: my-id', 'elementor' ), 'style_transfer' => false, ] ); $this->add_control( 'items', [ 'label' => esc_html__( 'Items', 'elementor' ), 'type' => Control_Nested_Repeater::CONTROL_TYPE, 'fields' => $repeater->get_controls(), 'default' => [ [ 'item_title' => esc_html__( 'Item #1', 'elementor' ), ], [ 'item_title' => esc_html__( 'Item #2', 'elementor' ), ], [ 'item_title' => esc_html__( 'Item #3', 'elementor' ), ], ], 'title_field' => '{{{ item_title }}}', 'button_text' => esc_html__( 'Add Item', 'elementor' ), ] ); $this->add_responsive_control( 'accordion_item_title_position_horizontal', [ 'label' => esc_html__( 'Item Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'separator' => 'before', 'options' => [ 'start' => [ 'title' => esc_html__( 'Start', 'elementor' ), 'icon' => 'eicon-flex eicon-align-start-h', ], 'center' => [ 'title' => esc_html__( 'Center', 'elementor' ), 'icon' => 'eicon-h-align-center', ], 'end' => [ 'title' => esc_html__( 'End', 'elementor' ), 'icon' => 'eicon-flex eicon-align-end-h', ], 'stretch' => [ 'title' => esc_html__( 'Stretch', 'elementor' ), 'icon' => 'eicon-h-align-stretch', ], ], 'selectors_dictionary' => [ 'start' => '--n-accordion-title-justify-content: initial; --n-accordion-title-flex-grow: initial;', 'center' => '--n-accordion-title-justify-content: center; --n-accordion-title-flex-grow: initial;', 'end' => '--n-accordion-title-justify-content: flex-end; --n-accordion-title-flex-grow: initial;', 'stretch' => '--n-accordion-title-justify-content: space-between; --n-accordion-title-flex-grow: 1;', ], 'selectors' => [ '{{WRAPPER}}' => '{{VALUE}}', ], ] ); $this->add_control( 'heading_accordion_item_title_icon', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Icon', 'elementor' ), 'separator' => 'before', ] ); $this->add_responsive_control( 'accordion_item_title_icon_position', [ 'label' => esc_html__( 'Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Start', 'elementor' ), 'icon' => 'eicon-h-align-left', ], 'end' => [ 'title' => esc_html__( 'End', 'elementor' ), 'icon' => 'eicon-h-align-right', ], ], 'selectors_dictionary' => [ 'start' => '--n-accordion-title-icon-order: -1;', 'end' => '--n-accordion-title-icon-order: initial;', ], 'selectors' => [ '{{WRAPPER}}' => '{{VALUE}}', ], ] ); $this->add_control( 'accordion_item_title_icon', [ 'label' => esc_html__( 'Expand', 'elementor' ), 'type' => Controls_Manager::ICONS, 'default' => [ 'value' => 'fas fa-plus', 'library' => 'fa-solid', ], 'skin' => 'inline', 'label_block' => false, ] ); $this->add_control( 'accordion_item_title_icon_active', [ 'label' => esc_html__( 'Collapse', 'elementor' ), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon_active', 'default' => [ 'value' => 'fas fa-minus', 'library' => 'fa-solid', ], 'condition' => [ 'accordion_item_title_icon[value]!' => '', ], 'skin' => 'inline', 'label_block' => false, ] ); $this->add_control( 'title_tag', [ 'label' => esc_html__( 'Title 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', ], 'selectors_dictionary' => [ 'h1' => '--n-accordion-title-font-size: 2.5rem;', 'h2' => '--n-accordion-title-font-size: 2rem;', 'h3' => '--n-accordion-title-font-size: 1,75rem;', 'h4' => '--n-accordion-title-font-size: 1.5rem;', 'h5' => '--n-accordion-title-font-size: 1rem;', 'h6' => '--n-accordion-title-font-size: 1rem; ', 'div' => '--n-accordion-title-font-size: 1rem;', 'span' => '--n-accordion-title-font-size: 1rem; ', 'p' => '--n-accordion-title-font-size: 1rem;', ], 'selectors' => [ '{{WRAPPER}}' => '{{VALUE}}', ], 'default' => 'div', 'separator' => 'before', 'render_type' => 'template', ] ); $this->add_control( 'faq_schema', [ 'label' => esc_html__( 'FAQ Schema', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'default' => 'no', ] ); $this->add_control( 'faq_schema_message', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => esc_html__( 'Google no longer supports the FAQ schema; however, it may still hold secondary value for AI and LLMs.', 'elementor' ), 'condition' => [ 'faq_schema[value]' => 'yes', ], ] ); $this->end_controls_section(); $this->start_controls_section( 'section_interactions', [ 'label' => esc_html__( 'Interactions', 'elementor' ), ] ); $this->add_control( 'default_state', [ 'label' => esc_html__( 'Default State', 'elementor' ), 'type' => Controls_Manager::SELECT, 'options' => [ 'expanded' => esc_html__( 'First expanded', 'elementor' ), 'all_collapsed' => esc_html__( 'All collapsed', 'elementor' ), ], 'default' => 'expanded', 'frontend_available' => true, ] ); $this->add_control( 'max_items_expended', [ 'label' => esc_html__( 'Max Items Expanded', 'elementor' ), 'type' => Controls_Manager::SELECT, 'options' => [ 'one' => esc_html__( 'One', 'elementor' ), 'multiple' => esc_html__( 'Multiple', 'elementor' ), ], 'default' => 'one', 'frontend_available' => true, ] ); $this->add_control( 'n_accordion_animation_duration', [ 'label' => esc_html__( 'Animation Duration', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 's', 'ms' ], 'default' => [ 'unit' => 'ms', 'size' => 400, ], 'frontend_available' => true, ] ); $this->end_controls_section(); $this->add_style_tab(); } private function add_style_tab() { $this->add_accordion_style_section(); $this->add_header_style_section(); $this->add_content_style_section(); } private function add_accordion_style_section() { $this->start_controls_section( 'section_accordion_style', [ 'label' => esc_html__( 'Accordion', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_responsive_control( 'accordion_item_title_space_between', [ 'label' => esc_html__( 'Space between Items', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'range' => [ 'px' => [ 'max' => 200, ], 'em' => [ 'max' => 20, ], 'rem' => [ 'max' => 20, ], ], 'default' => [ 'size' => 0, ], 'selectors' => [ '{{WRAPPER}}' => '--n-accordion-item-title-space-between: {{SIZE}}{{UNIT}}', ], ] ); $this->add_responsive_control( 'accordion_item_title_distance_from_content', [ 'label' => esc_html__( 'Distance from content', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'range' => [ 'px' => [ 'max' => 200, ], 'em' => [ 'max' => 20, ], 'rem' => [ 'max' => 20, ], ], 'default' => [ 'size' => 0, ], 'selectors' => [ '{{WRAPPER}}' => '--n-accordion-item-title-distance-from-content: {{SIZE}}{{UNIT}}', ], ] ); $this->start_controls_tabs( 'accordion_border_and_background' ); foreach ( [ 'normal', 'hover', 'active' ] as $state ) { $this->add_border_and_radius_style( $state ); } $this->end_controls_tabs(); $this->add_responsive_control( 'accordion_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}}' => '--n-accordion-border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); $this->add_responsive_control( 'accordion_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ '{{WRAPPER}} ' => '--n-accordion-padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->end_controls_section(); } private function add_content_style_section() { $low_specificity_accordion_item_selector = ":where( {{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item ) > .e-con"; $this->start_controls_section( 'section_content_style', [ 'label' => esc_html__( 'Content', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'content_background', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], 'selector' => $low_specificity_accordion_item_selector, ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'content_border', 'selector' => $low_specificity_accordion_item_selector, 'fields_options' => [ 'color' => [ 'label' => esc_html__( 'Border Color', 'elementor' ), ], 'width' => [ 'label' => esc_html__( 'Border Width', 'elementor' ), ], ], ] ); $this->add_responsive_control( 'content_border_radius', [ 'label' => esc_html__( 'Border Radius', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ $low_specificity_accordion_item_selector => '--border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_responsive_control( 'content_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'selectors' => [ $low_specificity_accordion_item_selector => '--padding-top: {{TOP}}{{UNIT}}; --padding-right: {{RIGHT}}{{UNIT}}; --padding-bottom: {{BOTTOM}}{{UNIT}}; --padding-left: {{LEFT}}{{UNIT}};', ], ] ); $this->end_controls_section(); } private function add_header_style_section() { $this->start_controls_section( 'section_header_style', [ 'label' => esc_html__( 'Header', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'heading_header_style_title', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Title', 'elementor' ), ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'title_typography', 'selector' => ":where( {{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item > .e-n-accordion-item-title > .e-n-accordion-item-title-header ) > .e-n-accordion-item-title-text", 'fields_options' => [ 'font_size' => [ 'selectors' => [ '{{WRAPPER}}' => '--n-accordion-title-font-size: {{SIZE}}{{UNIT}}', ], ], ], ] ); $this->start_controls_tabs( 'header_title_color_style' ); foreach ( [ 'normal', 'hover', 'active' ] as $state ) { $this->add_header_style( $state, 'title' ); } $this->end_controls_tabs(); $this->add_control( 'heading_icon_style_title', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Icon', 'elementor' ), 'separator' => 'before', ] ); $this->add_responsive_control( 'icon_size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'em' => [ 'max' => 10, ], 'rem' => [ 'max' => 10, ], ], 'default' => [ 'unit' => 'px', 'size' => 15, ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}}' => '--n-accordion-icon-size: {{SIZE}}{{UNIT}}', ], ] ); $this->add_responsive_control( 'icon_spacing', [ 'label' => esc_html__( 'Spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'max' => 400, ], 'vw' => [ 'max' => 50, 'step' => 0.1, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}}' => '--n-accordion-icon-gap: {{SIZE}}{{UNIT}}', ], 'condition' => [ 'accordion_item_title_position_horizontal!' => 'stretch', ], ] ); $this->start_controls_tabs( 'header_icon_color_style' ); foreach ( [ 'normal', 'hover', 'active' ] as $state ) { $this->add_header_style( $state, 'icon' ); } $this->end_controls_tabs(); $this->end_controls_section(); } private function add_header_style( $state, $context ) { $variable = '--n-accordion-' . $context . '-' . $state . '-color'; switch ( $state ) { case 'hover': $translated_tab_text = esc_html__( 'Hover', 'elementor' ); $translated_tab_css_selector = ":where( {{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item:not([open]) > .e-n-accordion-item-title:hover > .e-n-accordion-item-title-header ) > .e-n-accordion-item-title-text"; break; case 'active': $translated_tab_text = esc_html__( 'Active', 'elementor' ); $translated_tab_css_selector = ":where( {{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item[open] > .e-n-accordion-item-title > .e-n-accordion-item-title-header ) > .e-n-accordion-item-title-text"; break; default: $translated_tab_text = esc_html__( 'Normal', 'elementor' ); $translated_tab_css_selector = ":where( {{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item:not([open]) > .e-n-accordion-item-title:not(hover) > .e-n-accordion-item-title-header ) > .e-n-accordion-item-title-text"; break; } $this->start_controls_tab( 'header_' . $state . '_' . $context, [ 'label' => $translated_tab_text, ] ); $this->add_control( $state . '_' . $context . '_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}}' => $variable . ': {{VALUE}};', ], ] ); if ( 'title' === $context ) { $this->add_group_control( Group_Control_Text_Shadow::get_type(), [ 'name' => $context . '_' . $state . '_text_shadow', 'selector' => '{{WRAPPER}} ' . $translated_tab_css_selector, ] ); $this->add_group_control( Group_Control_Text_Stroke::get_type(), [ 'name' => $context . '_' . $state . '_stroke', 'selector' => '{{WRAPPER}} ' . $translated_tab_css_selector, ] ); } $this->end_controls_tab(); } /** * @string $state */ private function add_border_and_radius_style( $state ) { $selector = "{{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item > .e-n-accordion-item-title"; $translated_tab_text = esc_html__( 'Normal', 'elementor' ); switch ( $state ) { case 'hover': $selector .= ':hover'; $translated_tab_text = esc_html__( 'Hover', 'elementor' ); break; case 'active': $selector = "{{WRAPPER}}{$this->widget_container_selector} > .e-n-accordion > .e-n-accordion-item[open] > .e-n-accordion-item-title"; $translated_tab_text = esc_html__( 'Active', 'elementor' ); break; } $this->start_controls_tab( 'accordion_' . $state . '_border_and_background', [ 'label' => $translated_tab_text, ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'accordion_background_' . $state, 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], 'fields_options' => [ 'color' => [ 'label' => esc_html__( 'Color', 'elementor' ), ], ], 'selector' => $selector, ] ); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'accordion_border_' . $state, 'selector' => $selector, ] ); $this->end_controls_tab(); } private function is_active_icon_exist( $settings ): bool { return array_key_exists( 'accordion_item_title_icon_active', $settings ) && ! empty( $settings['accordion_item_title_icon_active'] ) && ! empty( $settings['accordion_item_title_icon_active']['value'] ); } private function render_accordion_icons( $settings ) { $icon_html = Icons_Manager::try_get_icon_html( $settings['accordion_item_title_icon'], [ 'aria-hidden' => 'true' ] ); $icon_active_html = $this->is_active_icon_exist( $settings ) ? Icons_Manager::try_get_icon_html( $settings['accordion_item_title_icon_active'], [ 'aria-hidden' => 'true' ] ) : $icon_html; ob_start(); ?> <span class='e-n-accordion-item-title-icon'> <span class='e-opened' ><?php echo $icon_active_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span> <span class='e-closed'><?php echo $icon_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span> </span> <?php return ob_get_clean(); } protected function render() { $settings = $this->get_settings_for_display(); $items = $settings['items']; $id_int = substr( $this->get_id_int(), 0, 3 ); $items_title_html = ''; $icons_content = $this->render_accordion_icons( $settings ); $this->add_render_attribute( 'elementor-accordion', 'class', 'e-n-accordion' ); $this->add_render_attribute( 'elementor-accordion', 'aria-label', 'Accordion. Open links with Enter or Space, close with Escape, and navigate with Arrow Keys' ); $default_state = $settings['default_state']; $title_html_tag = Utils::validate_html_tag( $settings['title_tag'] ); $faq_schema = []; foreach ( $items as $index => $item ) { $accordion_count = $index + 1; $item_setting_key = $this->get_repeater_setting_key( 'item_title', 'items', $index ); $item_summary_key = $this->get_repeater_setting_key( 'item_summary', 'items', $index ); $item_classes = [ 'e-n-accordion-item' ]; $item_id = empty( $item['element_css_id'] ) ? 'e-n-accordion-item-' . $id_int . $index : $item['element_css_id']; $item_title = $item['item_title']; $is_open = 'expanded' === $default_state && 0 === $index ? 'open' : ''; $aria_expanded = 'expanded' === $default_state && 0 === $index; $this->add_render_attribute( $item_setting_key, [ 'id' => $item_id, 'class' => $item_classes, ] ); $this->add_render_attribute( $item_summary_key, [ 'class' => [ 'e-n-accordion-item-title' ], 'data-accordion-index' => $accordion_count, 'tabindex' => 0 === $index ? 0 : -1, 'aria-expanded' => $aria_expanded ? 'true' : 'false', 'aria-controls' => $item_id, ] ); $title_render_attributes = $this->get_render_attribute_string( $item_setting_key ); $title_render_attributes = $title_render_attributes . ' ' . $is_open; $summary_render_attributes = $this->get_render_attribute_string( $item_summary_key ); // items content. ob_start(); $this->print_child( $index, $item_id ); $item_content = ob_get_clean(); $faq_schema[ $item_title ] = $item_content; ob_start(); ?> <details <?php echo wp_kses_post( $title_render_attributes ); ?>> <summary <?php echo wp_kses_post( $summary_render_attributes ); ?> > <span class='e-n-accordion-item-title-header'><?php echo wp_kses_post( "<$title_html_tag class=\"e-n-accordion-item-title-text\"> $item_title </$title_html_tag>" ); ?></span> <?php if ( ! empty( $settings['accordion_item_title_icon']['value'] ) ) { echo $icons_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } ?> </summary> <?php echo $item_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </details> <?php $items_title_html .= ob_get_clean(); } ?> <div <?php $this->print_render_attribute_string( 'elementor-accordion' ); ?>> <?php echo $items_title_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </div> <?php if ( isset( $settings['faq_schema'] ) && 'yes' === $settings['faq_schema'] ) { $json = [ '@context' => 'https://schema.org', '@type' => 'FAQPage', 'mainEntity' => [], ]; foreach ( $faq_schema as $name => $text ) { $json['mainEntity'][] = [ '@type' => 'Question', 'name' => wp_strip_all_tags( $name ), 'acceptedAnswer' => [ '@type' => 'Answer', 'text' => wp_strip_all_tags( $text ), ], ]; } ?> <script type="application/ld+json"><?php echo wp_json_encode( $json ); ?></script> <?php } } public function print_child( $index, $item_id = null ) { $children = $this->get_children(); if ( ! empty( $children[ $index ] ) ) { // Add data-tab-index attribute to the content area. $add_attribute_to_container = function ( $should_render, $container ) use ( $item_id ) { $this->add_attributes_to_container( $container, $item_id ); return $should_render; }; add_filter( 'elementor/frontend/container/should_render', $add_attribute_to_container, 10, 3 ); $children[ $index ]->print_element(); remove_filter( 'elementor/frontend/container/should_render', $add_attribute_to_container ); } } protected function add_attributes_to_container( $container, $item_id ) { $container->add_render_attribute( '_wrapper', [ 'role' => 'region', 'aria-labelledby' => $item_id, ] ); } protected function get_initial_config(): array { return array_merge( parent::get_initial_config(), [ 'support_improved_repeaters' => true, 'target_container' => [ '.e-n-accordion' ], 'node' => 'details', 'is_interlaced' => true, ] ); } protected function content_template_single_repeater_item() { ?> <# const elementUid = view.getIDInt().toString().substring( 0, 3 ) + view.collection.length; const itemWrapperAttributes = { 'id': 'e-n-accordion-item-' + elementUid, 'class': [ 'e-n-accordion-item', 'e-normal' ], }; const itemTitleAttributes = { 'class': [ 'e-n-accordion-item-title' ], 'data-accordion-index': view.collection.length + 1, 'tabindex': -1, 'aria-expanded': 'false', 'aria-controls': 'e-n-accordion-item-' + elementUid, }; const itemTitleTextAttributes = { 'class': [ 'e-n-accordion-item-title-text' ], 'data-binding-index': view.collection.length + 1, 'data-binding-type': 'repeater-item', 'data-binding-repeater-name': 'items', 'data-binding-setting': ['item_title', 'element_css_id'], 'data-binding-config': JSON.stringify({ 'element_css_id': { editType: 'attribute', attr: 'id', selector: 'details' }, 'item_title': { editType: 'text' } }), }; view.addRenderAttribute( 'details-container', itemWrapperAttributes, null, true ); view.addRenderAttribute( 'summary-container', itemTitleAttributes, null, true ); view.addRenderAttribute( 'text-container', itemTitleTextAttributes, null, true ); #> <details {{{ view.getRenderAttributeString( 'details-container' ) }}}> <summary {{{ view.getRenderAttributeString( 'summary-container' ) }}}> <span class="e-n-accordion-item-title-header"> <div {{{ view.getRenderAttributeString( 'text-container' ) }}}>{{{ data.item_title }}}</div> </span> <span class="e-n-accordion-item-title-icon"> <span class="e-opened"><i aria-hidden="true" class="fas fa-minus"></i></span> <span class="e-closed"><i aria-hidden="true" class="fas fa-plus"></i></span> </span> </summary> </details> <?php } protected function content_template() { ?> <div class="e-n-accordion" aria-label="Accordion. Open links with Enter or Space, close with Escape, and navigate with Arrow Keys"> <# if ( settings['items'] ) { const elementUid = view.getIDInt().toString().substring( 0, 3 ), titleHTMLTag = elementor.helpers.validateHTMLTag( settings.title_tag ), defaultState = settings.default_state, itemTitleIcon = elementor.helpers.renderIcon( view, settings['accordion_item_title_icon'], { 'aria-hidden': true }, 'i', 'object' ) ?? '', itemTitleIconActive = '' === settings.accordion_item_title_icon_active.value ? itemTitleIcon : elementor.helpers.renderIcon( view, settings['accordion_item_title_icon_active'], { 'aria-hidden': true }, 'i', 'object' ); #> <# _.each( settings['items'], function( item, index ) { const itemCount = index + 1, itemUid = elementUid + index, itemTitleTextKey = 'item-title-text-' + itemUid, itemWrapperKey = itemUid, itemTitleKey = 'item-' + itemUid, ariaExpanded = 'expanded' === defaultState && 0 === index ? 'true' : 'false'; if ( '' !== item.element_css_id ) { itemId = item.element_css_id; } else { itemId = 'e-n-accordion-item-' + itemUid; } const itemWrapperAttributes = { 'id': itemId, 'class': [ 'e-n-accordion-item', 'e-normal' ], }; if ( defaultState === 'expanded' && index === 0) { itemWrapperAttributes['open'] = true; } view.addRenderAttribute( itemWrapperKey, itemWrapperAttributes ); view.addRenderAttribute( itemTitleKey, { 'class': ['e-n-accordion-item-title'], 'data-accordion-index': itemCount, 'tabindex': 0 === index ? 0 : -1, 'aria-expanded': ariaExpanded, 'aria-controls': itemId, }); view.addRenderAttribute( itemTitleTextKey, { 'class': ['e-n-accordion-item-title-text'], 'data-binding-index': itemCount, 'data-binding-type': 'repeater-item', 'data-binding-repeater-name': 'items', 'data-binding-setting': ['item_title', 'element_css_id'], 'data-binding-config': JSON.stringify({ 'element_css_id': { editType: 'attribute', attr: 'id', selector: 'details' }, 'item_title': { editType: 'text' } }), }); #> <details {{{ view.getRenderAttributeString( itemWrapperKey ) }}}> <summary {{{ view.getRenderAttributeString( itemTitleKey ) }}}> <span class="e-n-accordion-item-title-header"> <{{{ titleHTMLTag }}} {{{ view.getRenderAttributeString( itemTitleTextKey ) }}}> {{{ item.item_title }}} </{{{ titleHTMLTag }}}> </span> <# if (settings.accordion_item_title_icon.value) { #> <span class="e-n-accordion-item-title-icon"> <span class="e-opened">{{{ itemTitleIconActive.value }}}</span> <span class="e-closed">{{{ itemTitleIcon.value }}}</span> </span> <# } #> </summary> </details> <# } ); #> <# } #> </div> <?php } } nested-accordion/module.php 0000644 00000002312 15252521350 0011761 0 ustar 00 <?php namespace Elementor\Modules\NestedAccordion; use Elementor\Plugin; use Elementor\Core\Base\Module as BaseModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { public static function is_active() { return Plugin::$instance->experiments->is_feature_active( 'nested-elements', true ); } public function get_name() { return 'nested-accordion'; } public function __construct() { parent::__construct(); add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] ); add_action( 'elementor/editor/before_enqueue_scripts', function () { wp_enqueue_script( $this->get_name(), $this->get_js_assets_url( $this->get_name() ), [ 'nested-elements', ], ELEMENTOR_VERSION, true ); } ); } /** * Register styles. * * At build time, Elementor compiles `/modules/nested-accordion/assets/scss/frontend.scss` * to `/assets/css/widget-nested-accordion.min.css`. * * @return void */ public function register_styles() { wp_register_style( 'widget-nested-accordion', $this->get_css_assets_url( 'widget-nested-accordion', null, true, true ), [ 'elementor-frontend' ], ELEMENTOR_VERSION ); } } checklist/steps/step-base.php 0000644 00000012620 15252521350 0012250 0 ustar 00 <?php namespace Elementor\Modules\Checklist\Steps; use Elementor\Core\Isolation\Wordpress_Adapter; use Elementor\Core\Isolation\Wordpress_Adapter_Interface; use Elementor\Core\Isolation\Elementor_Adapter; use Elementor\Core\Isolation\Elementor_Adapter_Interface; use Elementor\Modules\Checklist\Module as Checklist_Module; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Step_Base { /** * @var string * This is the key to be set to true if the step can be completed, and still be considered completed even if the user later did something to the should have it marked as not completed */ const IS_COMPLETION_IMMUTABLE = 'is_completion_immutable'; const MARKED_AS_COMPLETED_KEY = 'is_marked_completed'; const IMMUTABLE_COMPLETION_KEY = 'is_immutable_completed'; const ABSOLUTE_COMPLETION_KEY = 'is_absolute_completed'; private array $user_progress; protected Wordpress_Adapter_Interface $wordpress_adapter; protected Elementor_Adapter_Interface $elementor_adapter; protected ?array $promotion_data; protected Checklist_Module $module; /** * Returns a steps current completion status * * @return bool */ abstract protected function is_absolute_completed(): bool; /** * @return string */ abstract public function get_id(): string; /** * @return string */ abstract public function get_title(): string; /** * @return string */ abstract public function get_description(): string; /** * For instance; 'Create 3 pages' * * @return string */ abstract public function get_cta_text(): string; /** * @return string */ abstract public function get_cta_url(): string; /** * @return bool */ abstract public function get_is_completion_immutable(): bool; /** * @return string */ abstract public function get_image_src(): string; /** * Step_Base constructor. * * @param Checklist_Module $module * @param ?Wordpress_Adapter_Interface $wordpress_adapter * @param ?Elementor_Adapter_Interface $elementor_adapter * @return void */ public function __construct( Checklist_Module $module, ?Wordpress_Adapter_Interface $wordpress_adapter = null, ?Elementor_Adapter_Interface $elementor_adapter = null, $promotion_data = null ) { $this->module = $module; $this->wordpress_adapter = $wordpress_adapter ?? new Wordpress_Adapter(); $this->elementor_adapter = $elementor_adapter ?? new Elementor_Adapter(); $this->promotion_data = $promotion_data; $this->user_progress = $module->get_step_progress( $this->get_id() ) ?? $this->get_step_initial_progress(); } /** * Returns step visibility (by-default step is visible) * * @return bool */ public function is_visible(): bool { return true; } public function get_learn_more_text(): string { return esc_html__( 'Learn more', 'elementor' ); } public function get_learn_more_url(): string { return 'https://go.elementor.com/getting-started-with-elementor/'; } public function update_step( array $step_data ): void { $allowed_properties = [ self::MARKED_AS_COMPLETED_KEY => $step_data[ self::MARKED_AS_COMPLETED_KEY ] ?? null, self::IMMUTABLE_COMPLETION_KEY => $step_data[ self::IMMUTABLE_COMPLETION_KEY ] ?? null, self::ABSOLUTE_COMPLETION_KEY => $step_data[ self::ABSOLUTE_COMPLETION_KEY ] ?? null, ]; foreach ( $allowed_properties as $key => $value ) { if ( null !== $value ) { $this->user_progress[ $key ] = $value; } } $this->set_step_progress(); } /** * Marking a step as completed based on user's desire * * @return void */ public function mark_as_completed(): void { $this->update_step( [ self::MARKED_AS_COMPLETED_KEY => true ] ); } /** * Unmarking a step as completed based on user's desire * * @return void */ public function unmark_as_completed(): void { $this->update_step( [ self::MARKED_AS_COMPLETED_KEY => false ] ); } /** * Marking a step as completed if it was completed once, and it's suffice to marketing's requirements * * @return void */ public function maybe_immutably_mark_as_completed(): void { $is_immutable_completed = $this->user_progress[ self::IMMUTABLE_COMPLETION_KEY ] ?? false; if ( ! $is_immutable_completed && $this->get_is_completion_immutable() && $this->is_absolute_completed() ) { $this->update_step( [ self::MARKED_AS_COMPLETED_KEY => false, self::IMMUTABLE_COMPLETION_KEY => true, ] ); } } /** * Returns the step marked as completed value * * @return bool */ public function is_marked_as_completed(): bool { return $this->user_progress[ self::MARKED_AS_COMPLETED_KEY ]; } /** * Returns the step completed value * * @return bool */ public function is_immutable_completed(): bool { return $this->get_is_completion_immutable() && $this->user_progress[ self::IMMUTABLE_COMPLETION_KEY ] ?? false; } /** * Sets and returns the initial progress of the step * * @return array */ public function get_step_initial_progress(): array { $initial_progress = [ self::MARKED_AS_COMPLETED_KEY => false, self::IMMUTABLE_COMPLETION_KEY => false, ]; $this->module->set_step_progress( $this->get_id(), $initial_progress ); return $initial_progress; } /** * @return ?array */ public function get_promotion_data(): ?array { return $this->promotion_data; } /** * Sets the step progress * * @return void */ private function set_step_progress(): void { $this->module->set_step_progress( $this->get_id(), $this->user_progress ); } } checklist/steps/create-pages.php 0000644 00000002467 15252521350 0012735 0 ustar 00 <?php namespace Elementor\Modules\Checklist\Steps; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Create_Pages extends Step_Base { const STEP_ID = 'create_pages'; public function get_id(): string { return self::STEP_ID; } public function is_absolute_completed(): bool { $pages = $this->wordpress_adapter->get_pages( [ 'meta_key' => '_elementor_version', 'number' => 3, ] ) ?? []; return count( $pages ) >= 3; } public function get_title(): string { return esc_html__( 'Create your first 3 pages', 'elementor' ); } public function get_description(): string { return esc_html__( 'Jumpstart your creation with professional designs from the Template Library or start from scratch.', 'elementor' ); } public function get_cta_text(): string { return esc_html__( 'Create a new page', 'elementor' ); } public function get_cta_url(): string { return Plugin::$instance->documents->get_create_new_post_url( 'page' ); } public function get_learn_more_url(): string { return 'http://go.elementor.com/app-website-checklist-pages-article'; } public function get_is_completion_immutable(): bool { return true; } public function get_image_src(): string { return 'https://assets.elementor.com/checklist/v1/images/checklist-step-3.jpg'; } } checklist/steps/add-logo.php 0000644 00000002460 15252521350 0012054 0 ustar 00 <?php namespace Elementor\Modules\Checklist\Steps; use Elementor\Core\DocumentTypes\Page; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Add_Logo extends Step_Base { const STEP_ID = 'add_logo'; const SITE_IDENTITY_TAB = 'settings-site-identity'; public function get_id(): string { return self::STEP_ID; } public function is_absolute_completed(): bool { return $this->wordpress_adapter->has_custom_logo(); } public function get_title(): string { return esc_html__( 'Add your logo', 'elementor' ); } public function get_description(): string { return __( 'Let\'s start by adding your logo and filling in the site identity settings. This will establish your initial presence and also improve SEO.', 'elementor' ); } public function get_cta_text(): string { return esc_html__( 'Go to Site Identity', 'elementor' ); } public function get_cta_url(): string { return Page::get_site_settings_url_config( self::SITE_IDENTITY_TAB )['url']; } public function get_is_completion_immutable(): bool { return false; } public function get_image_src(): string { return 'https://assets.elementor.com/checklist/v1/images/checklist-step-1.jpg'; } public function get_learn_more_url(): string { return 'http://go.elementor.com/app-website-checklist-logo-article'; } } checklist/steps/set-fonts-and-colors.php 0000644 00000003063 15252521350 0014347 0 ustar 00 <?php namespace Elementor\Modules\Checklist\Steps; use Elementor\Core\DocumentTypes\Page; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Set_Fonts_And_Colors extends Step_Base { const STEP_ID = 'set_fonts_and_colors'; public function get_id(): string { return self::STEP_ID; } public function is_absolute_completed(): bool { $settings = $this->elementor_adapter->get_kit_settings(); $custom_color = $settings['custom_colors'] ?? ''; $custom_fonts = $settings['custom_typography'] ?? ''; return ! empty( $custom_color ) && ! empty( $custom_fonts ); } public function get_title(): string { return __( 'Set up your Global Fonts & Colors', 'elementor' ); } public function get_description(): string { return esc_html__( 'Global colors and fonts ensure a cohesive look across your site. Start by defining one color and one font.', 'elementor' ); } public function get_cta_text(): string { return esc_html__( 'Go to Site Identity', 'elementor' ); } public function get_cta_url(): string { $settings = $this->elementor_adapter->get_kit_settings(); $tab = ! $settings['custom_colors'] ? 'global-typography' : 'global-colors'; return Page::get_site_settings_url_config( $tab )['url']; } public function get_is_completion_immutable(): bool { return false; } public function get_image_src(): string { return 'https://assets.elementor.com/checklist/v1/images/checklist-step-2.jpg'; } public function get_learn_more_url(): string { return 'http://go.elementor.com/app-website-checklist-global-article'; } } checklist/steps/setup-header.php 0000644 00000004670 15252521350 0012761 0 ustar 00 <?php namespace Elementor\Modules\Checklist\Steps; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Setup_Header extends Step_Base { const STEP_ID = 'setup_header'; public function __construct( $module, $wordpress_adapter = null, $elementor_adapter = null, $should_promote = true ) { $promotion_data = $should_promote ? $this->render_promotion() : null; parent::__construct( $module, $wordpress_adapter, $elementor_adapter, $promotion_data ); } public function get_id(): string { return self::STEP_ID; } public function is_visible(): bool { if ( Utils::has_pro() ) { return false; } return parent::is_visible(); } public function is_absolute_completed(): bool { $args = [ 'post_type' => 'elementor_library', 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_elementor_template_type', 'value' => 'header', 'compare' => '=', ], [ 'key' => '_elementor_conditions', ], ], 'posts_per_page' => 1, 'fields' => 'ids', 'no_found_rows' => true, 'update_post_term_cache' => false, 'update_post_meta_cache' => false, ]; $query = $this->wordpress_adapter->get_query( $args ); $header_templates = $query->posts ?? []; return count( $header_templates ) >= 1; } public function get_title(): string { return esc_html__( 'Set up a header', 'elementor' ); } public function get_description(): string { return esc_html__( 'This element applies across different pages, so visitors can easily navigate around your site.', 'elementor' ); } public function get_cta_text(): string { return esc_html__( 'Add a header', 'elementor' ); } public function get_cta_url(): string { return ''; } public function get_image_src(): string { return 'https://assets.elementor.com/checklist/v1/images/checklist-step-4.jpg'; } public function get_is_completion_immutable(): bool { return false; } public function get_learn_more_url(): string { return 'https://go.elementor.com/app-website-checklist-header-article'; } private function render_promotion() { return Filtered_Promotions_Manager::get_filtered_promotion_data( [ 'url' => 'https://go.elementor.com/go-pro-website-checklist-header', 'text' => esc_html__( 'Upgrade Now', 'elementor' ), 'icon' => 'default', ], 'elementor/checklist/promotion', 'upgrade_url' ); } } checklist/steps/assign-homepage.php 0000644 00000002357 15252521350 0013442 0 ustar 00 <?php namespace Elementor\Modules\Checklist\Steps; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Assign_Homepage extends Step_Base { const STEP_ID = 'assign_homepage'; public function get_id(): string { return self::STEP_ID; } public function is_absolute_completed(): bool { $front_page_id = (int) ( $this->wordpress_adapter->get_option( 'page_on_front' ) ?? 0 ); return (bool) $front_page_id; } public function get_title(): string { return esc_html__( 'Assign a homepage', 'elementor' ); } public function get_description(): string { return esc_html__( 'Before your launch, make sure to assign a homepage so visitors have a clear entry point into your site.', 'elementor' ); } public function get_cta_text(): string { return esc_html__( 'Assign homepage', 'elementor' ); } public function get_cta_url(): string { return admin_url( 'options-reading.php' ); } public function get_is_completion_immutable(): bool { return false; } public function get_image_src(): string { return 'https://assets.elementor.com/checklist/v1/images/checklist-step-6.jpg'; } public function get_learn_more_url(): string { return 'http://go.elementor.com/app-website-checklist-assign-home-article'; } } checklist/module.php 0000644 00000016321 15252521350 0010516 0 ustar 00 <?php namespace Elementor\Modules\Checklist; use Elementor\Core\Base\Module as BaseModule; use Elementor\Modules\ElementorCounter\Module as Elementor_Counter; use Elementor\Core\Isolation\Wordpress_Adapter; use Elementor\Core\Isolation\Wordpress_Adapter_Interface; use Elementor\Core\Isolation\Elementor_Adapter; use Elementor\Core\Isolation\Elementor_Adapter_Interface; use Elementor\Core\Isolation\Elementor_Counter_Adapter_Interface; use Elementor\Plugin; use Elementor\Utils; use Elementor\Modules\Checklist\Data\Controller; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule implements Checklist_Module_Interface { const DB_OPTION_KEY = 'elementor_checklist'; const VISIBILITY_SWITCH_ID = 'show_launchpad_checklist'; const FIRST_CLOSED_CHECKLIST_IN_EDITOR = 'first_closed_checklist_in_editor'; const LAST_OPENED_TIMESTAMP = 'last_opened_timestamp'; const SHOULD_OPEN_IN_EDITOR = 'should_open_in_editor'; const IS_POPUP_MINIMIZED_KEY = 'is_popup_minimized'; private Steps_Manager $steps_manager; private Wordpress_Adapter_Interface $wordpress_adapter; private Elementor_Adapter_Interface $elementor_adapter; private Elementor_Counter_Adapter_Interface $counter_adapter; private $user_progress = null; /** * @param ?Wordpress_Adapter_Interface $wordpress_adapter * @param ?Elementor_Adapter_Interface $elementor_adapter * * @return void */ public function __construct( ?Wordpress_Adapter_Interface $wordpress_adapter = null, ?Elementor_Adapter_Interface $elementor_adapter = null ) { $this->wordpress_adapter = $wordpress_adapter ?? new Wordpress_Adapter(); $this->elementor_adapter = $elementor_adapter ?? new Elementor_Adapter(); parent::__construct(); $this->init_user_progress(); Plugin::$instance->data_manager_v2->register_controller( new Controller() ); $this->user_progress = $this->user_progress ?? $this->get_user_progress_from_db(); $this->handle_checklist_visibility_with_kit(); $this->steps_manager = new Steps_Manager( $this ); if ( ! current_user_can( 'manage_options' ) ) { return; } $this->enqueue_editor_scripts(); } /** * Get the module name. * * @return string */ public function get_name(): string { return 'e-checklist'; } /** * Gets user's progress from db * * @return array { * @type bool $is_hidden * @type int $last_opened_timestamp * @type array $steps { * @type string $step_id => { * @type bool $is_marked_completed * @type bool $is_absolute_competed * @type bool $is_immutable_completed * } * } * } */ public function get_user_progress_from_db(): array { $db_progress = json_decode( $this->wordpress_adapter->get_option( self::DB_OPTION_KEY ), true ); $db_progress = is_array( $db_progress ) ? $db_progress : []; $progress = array_merge( $this->get_default_user_progress(), $db_progress ); $editor_visit_count = $this->elementor_adapter->get_count( Elementor_Counter::EDITOR_COUNTER_KEY ); $progress[ self::SHOULD_OPEN_IN_EDITOR ] = 2 === $editor_visit_count && ! $progress[ self::LAST_OPENED_TIMESTAMP ]; return $progress; } /** * Using the step's ID, get the progress of the step should it exist * * @param $step_id * * @return null|array { * @type bool $is_marked_completed * @type bool $is_completed * } */ public function get_step_progress( $step_id ): ?array { return $this->user_progress['steps'][ $step_id ] ?? null; } /** * Update the progress of a step * * @param $step_id * @param $step_progress * * @return void */ public function set_step_progress( $step_id, $step_progress ): void { $this->user_progress['steps'][ $step_id ] = $step_progress; $this->update_user_progress_in_db(); } public function update_user_progress( $new_data ): void { $allowed_properties = [ self::FIRST_CLOSED_CHECKLIST_IN_EDITOR => $new_data[ self::FIRST_CLOSED_CHECKLIST_IN_EDITOR ] ?? null, self::LAST_OPENED_TIMESTAMP => $new_data[ self::LAST_OPENED_TIMESTAMP ] ?? null, self::IS_POPUP_MINIMIZED_KEY => $new_data[ self::IS_POPUP_MINIMIZED_KEY ] ?? null, ]; foreach ( $allowed_properties as $key => $value ) { if ( null !== $value ) { $this->user_progress[ $key ] = $this->get_formatted_value( $key, $value ); } } $this->update_user_progress_in_db(); if ( isset( $new_data[ Elementor_Counter::EDITOR_COUNTER_KEY ] ) ) { $this->elementor_adapter->set_count( Elementor_Counter::EDITOR_COUNTER_KEY, $new_data[ Elementor_Counter::EDITOR_COUNTER_KEY ] ); } } /** * @return Steps_Manager */ public function get_steps_manager(): Steps_Manager { return $this->steps_manager; } /** * @return Wordpress_Adapter */ public function get_wordpress_adapter(): Wordpress_Adapter { return $this->wordpress_adapter; } /** * @return Elementor_Adapter */ public function get_elementor_adapter(): Elementor_Adapter { return $this->elementor_adapter; } public function enqueue_editor_scripts(): void { add_action( 'elementor/editor/before_enqueue_scripts', function () { $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_script( $this->get_name(), ELEMENTOR_ASSETS_URL . 'js/checklist' . $min_suffix . '.js', [ 'react', 'react-dom', 'elementor-common', 'elementor-v2-ui', 'elementor-v2-icons', 'elementor-v2-editor-app-bar', 'elementor-web-cli', ], ELEMENTOR_VERSION, true ); wp_set_script_translations( $this->get_name(), 'elementor' ); } ); } public function is_preference_switch_on(): bool { if ( $this->should_switch_preferences_off() ) { return false; } $user_preferences = $this->wordpress_adapter->get_user_preferences( self::VISIBILITY_SWITCH_ID ); return 'yes' === $user_preferences || $this->wordpress_adapter->is_new_installation(); } public function should_switch_preferences_off(): bool { return ! $this->elementor_adapter->is_active_kit_default() && ! $this->user_progress[ self::LAST_OPENED_TIMESTAMP ] && ! $this->elementor_adapter->get_count( Elementor_Counter::EDITOR_COUNTER_KEY ); } private function init_user_progress(): void { $default_settings = $this->get_default_user_progress(); $this->wordpress_adapter->add_option( self::DB_OPTION_KEY, wp_json_encode( $default_settings ) ); } private function get_default_user_progress(): array { return [ self::LAST_OPENED_TIMESTAMP => null, self::FIRST_CLOSED_CHECKLIST_IN_EDITOR => false, self::IS_POPUP_MINIMIZED_KEY => false, 'steps' => [], ]; } private function update_user_progress_in_db(): void { $this->wordpress_adapter->update_option( self::DB_OPTION_KEY, wp_json_encode( $this->user_progress ) ); } private function get_formatted_value( $key, $value ) { if ( self::LAST_OPENED_TIMESTAMP === $key ) { return $value ? time() : null; } return $value; } private function handle_checklist_visibility_with_kit() { if ( ! $this->should_switch_preferences_off() ) { return; } add_action( 'elementor/editor/init', function () { $this->wordpress_adapter->set_user_preferences( self::VISIBILITY_SWITCH_ID, '' ); }, 11 ); } public static function should_display_checklist_toggle_control(): bool { return current_user_can( 'manage_options' ); } } checklist/checklist-module-interface.php 0000644 00000001216 15252521350 0014420 0 ustar 00 <?php namespace Elementor\Modules\Checklist; use Elementor\Core\Isolation\Wordpress_Adapter; use Elementor\Core\Isolation\Elementor_Adapter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Checklist_Module_Interface { public function get_name(): string; public function get_user_progress_from_db(): array; public function get_step_progress( $step_id ): ?array; public function set_step_progress( $step_id, $step_progress ): void; public function get_steps_manager(): Steps_Manager; public function get_wordpress_adapter(): Wordpress_Adapter; public function get_elementor_adapter(): Elementor_Adapter; } checklist/data/controller.php 0000644 00000002022 15252521350 0012316 0 ustar 00 <?php namespace Elementor\Modules\Checklist\Data; use Elementor\Data\V2\Base\Controller as Controller_Base; use Elementor\Modules\Checklist\Data\Endpoints\Steps; use Elementor\Modules\Checklist\Data\Endpoints\User_Progress; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Controller_Base { public function get_name() { return 'checklist'; } public function register_endpoints() { $this->index_endpoint->register_item_route(); $this->register_endpoint( new Steps( $this ) ); $this->register_endpoint( new User_Progress( $this ) ); } public function update_items_permissions_check( $request ) { return current_user_can( 'manage_options' ); } public function update_item_permissions_check( $request ) { return current_user_can( 'manage_options' ); } public function get_item_permissions_check( $request ) { return current_user_can( 'manage_options' ); } public function get_items_permissions_check( $request ) { return current_user_can( 'manage_options' ); } } checklist/data/endpoints/user-progress.php 0000644 00000001775 15252521350 0014774 0 ustar 00 <?php namespace Elementor\Modules\Checklist\Data\Endpoints; use Elementor\Data\V2\Base\Endpoint as Endpoint_Base; use Elementor\Modules\Checklist\Module as Checklist_Module; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class User_Progress extends Endpoint_Base { protected function register() { parent::register(); $this->register_items_route( \WP_REST_Server::EDITABLE ); } public function get_name(): string { return 'user-progress'; } public function get_format(): string { return 'checklist'; } public function get_items( $request ) { return $this->get_checklist_data(); } public function update_items( $request ) { Checklist_Module::instance()->update_user_progress( $request->get_json_params() ); return [ 'data' => 'success', ]; } private function get_checklist_data(): array { $checklist_module = Checklist_Module::instance(); $progress_data = $checklist_module->get_user_progress_from_db(); return [ 'data' => $progress_data, ]; } } checklist/data/endpoints/steps.php 0000644 00000003005 15252521350 0013276 0 ustar 00 <?php namespace Elementor\Modules\Checklist\Data\Endpoints; use Elementor\Data\V2\Base\Endpoint as Endpoint_Base; use Elementor\Modules\Checklist\Steps\Step_Base; use Elementor\Modules\Checklist\Steps_Manager; use Elementor\Modules\Checklist\Module as Checklist_Module; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Steps extends Endpoint_Base { public function get_name(): string { return 'steps'; } public function get_format(): string { return 'checklist'; } public function get_items( $request ) { return $this->get_checklist_data(); } public function update_item( $id, $request ) { $checklist_module = Checklist_Module::instance(); $step = $checklist_module->get_steps_manager()->get_step_by_id( $id ); $step->update_step( $request->get_json_params() ); return [ 'data' => 'success', ]; } private function get_checklist_data(): array { $checklist_module = Checklist_Module::instance(); $steps_data = $checklist_module->get_steps_manager()->get_steps_for_frontend(); return [ 'data' => $steps_data, ]; } protected function register() { parent::register(); $this->register_item_route(); $this->register_item_route( \WP_REST_Server::EDITABLE, [ 'id_arg_name' => 'id', 'id_arg_type_regex' => '[\w\-\_]+', 'id' => [ 'type' => 'string', 'description' => 'The step id.', 'required' => true, 'validate_callback' => function ( $step_id ) { return in_array( $step_id, Steps_Manager::get_step_ids() ); }, ], ] ); } } checklist/steps-manager.php 0000644 00000012270 15252521350 0011776 0 ustar 00 <?php namespace Elementor\Modules\Checklist; use Elementor\Modules\Checklist\Steps\Assign_Homepage; use Elementor\Modules\Checklist\Steps\Create_Pages; use Elementor\Modules\Checklist\Steps\Setup_Header; use Elementor\Modules\Checklist\Steps\Add_Logo; use Elementor\Modules\Checklist\Steps\Step_Base; use Elementor\Modules\Checklist\Steps\Set_Fonts_And_Colors; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Steps_Manager { /** @var Step_Base[] $step_instances */ private array $step_instances = []; private static array $step_ids = [ Add_Logo::STEP_ID, Set_Fonts_And_Colors::STEP_ID, Create_Pages::STEP_ID, Setup_Header::STEP_ID, Assign_Homepage::STEP_ID, ]; private Checklist_Module_Interface $module; public function __construct( Checklist_Module_Interface $module ) { $this->module = $module; $this->register_steps(); add_action( 'elementor/init', function() { $this->filter_steps(); } ); } /** * Gets formatted and ordered array of step ( step data, is_marked_completed and is_completed ) * * @return array */ public function get_steps_for_frontend(): array { $formatted_steps = []; foreach ( self::$step_ids as $step_id ) { $instance = $this->step_instances[ $step_id ]; $instance->maybe_immutably_mark_as_completed(); $step = [ Step_Base::MARKED_AS_COMPLETED_KEY => $instance->is_marked_as_completed(), Step_Base::IMMUTABLE_COMPLETION_KEY => $instance->is_immutable_completed(), Step_Base::ABSOLUTE_COMPLETION_KEY => $instance->is_absolute_completed(), 'config' => $this->get_step_config( $step_id ), ]; $formatted_steps[] = $step; } return $formatted_steps; } public function update_step( string $step_id, array $data ): void { $step = $this->get_step_by_id( $step_id ); if ( ! $step ) { return; } $step->update_step( $data ); } /** * Marks a step as completed, returns true if the step was found and marked or false otherwise * * @param string $step_id * * @return void */ public function mark_step_as_completed( string $step_id ): void { $this->update_step( $step_id, [ Step_Base::MARKED_AS_COMPLETED_KEY => true ] ); } /** * Unmarks a step as completed, returns true if the step was found and unmarked or false otherwise * * @param string $step_id * * @return void */ public function unmark_step_as_completed( string $step_id ): void { $this->update_step( $step_id, [ Step_Base::MARKED_AS_COMPLETED_KEY => false ] ); } /** * Maybe marks a step as completed (depending on if source allows it), returns true if the step was found and marked or false otherwise * * @param $step_id * * @return void */ public function maybe_set_step_as_immutable_completed( string $step_id ): void { $step = $this->get_step_by_id( $step_id ); if ( ! $step ) { return; } $step->maybe_immutably_mark_as_completed(); } public function get_step_by_id( string $step_id ): ?Step_Base { return $this->step_instances[ $step_id ] ?? null; } /** * @return array */ public function get_step_config( $step_id ): array { $step_instance = $this->step_instances[ $step_id ]; return $step_instance ? [ 'id' => $step_instance->get_id(), 'title' => $step_instance->get_title(), 'description' => $step_instance->get_description(), 'learn_more_text' => $step_instance->get_learn_more_text(), 'learn_more_url' => $step_instance->get_learn_more_url(), Step_Base::IS_COMPLETION_IMMUTABLE => $step_instance->get_is_completion_immutable(), 'cta_text' => $step_instance->get_cta_text(), 'cta_url' => $step_instance->get_cta_url(), 'image_src' => $step_instance->get_image_src(), 'promotion_data' => $step_instance->get_promotion_data(), ] : []; } /** * Getting the step instances array based on source's order * * @return void */ private function register_steps(): void { foreach ( self::$step_ids as $step_id ) { $step_instance = $this->get_step_instance( $step_id ); if ( $step_instance && ! isset( $this->step_instances[ $step_id ] ) ) { $this->step_instances[ $step_id ] = $step_instance; } } } /** * Returns the steps config from source * * @return array */ public static function get_step_ids(): array { return self::$step_ids; } /** * Using step data->id, instantiates and returns the step class or null if the class does not exist * * @param $step_data * * @return Step_Base|null */ private function get_step_instance( string $step_id ): ?Step_Base { $class_name = '\\Elementor\\Modules\\Checklist\\Steps\\' . $step_id; if ( ! class_exists( $class_name ) ) { return null; } /** @var Step_Base $step */ return new $class_name( $this->module, $this->module->get_wordpress_adapter(), $this->module->get_elementor_adapter() ); } private function filter_steps() { $step_ids = []; $filtered_steps = apply_filters( 'elementor/checklist/steps', $this->step_instances ); foreach ( $filtered_steps as $step_id => $step_instance ) { if ( ! $step_instance instanceof Step_Base ) { continue; } if ( ! $step_instance->is_visible() ) { continue; } $this->step_instances[ $step_id ] = $step_instance; $step_ids[] = $step_id; } self::$step_ids = $step_ids; } } usage/usage-reporter.php 0000644 00000006056 15252521350 0011334 0 ustar 00 <?php namespace Elementor\Modules\Usage; use Elementor\Modules\System_Info\Reporters\Base; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor usage report. * * Elementor system report handler class responsible for generating a report for * the user. */ class Usage_Reporter extends Base { const RECALC_ACTION = 'elementor_usage_recalc'; public function get_title() { return esc_html__( 'Elements Usage', 'elementor' ); } public function get_fields() { return [ 'usage' => '', ]; } public function print_html_label( $label ) { $title = $this->get_title(); if ( empty( $_GET[ self::RECALC_ACTION ] ) ) { // phpcs:ignore -- nonce validation is not required here. $nonce = wp_create_nonce( self::RECALC_ACTION ); $url = add_query_arg( [ self::RECALC_ACTION => 1, '_wpnonce' => $nonce, ] ); $title .= '<a id="elementor-usage-recalc" href="' . esc_url( $url ) . '#elementor-usage-recalc" class="box-title-tool">' . esc_html__( 'Recalculate', 'elementor' ) . '</a>'; } else { $title .= $this->get_remove_recalc_query_string_script(); } parent::print_html_label( $title ); } public function get_usage() { /** @var Module $module */ $module = Module::instance(); if ( ! empty( $_GET[ self::RECALC_ACTION ] ) ) { // phpcs:ignore $nonce = Utils::get_super_global_value( $_GET, '_wpnonce' ); if ( ! wp_verify_nonce( $nonce, self::RECALC_ACTION ) ) { wp_die( 'Invalid Nonce', 'Invalid Nonce', [ 'back_link' => true, ] ); } $module->recalc_usage(); } $usage = ''; foreach ( $module->get_formatted_usage() as $doc_type => $data ) { $usage .= '<tr><td>' . $data['title'] . ' ( ' . $data['count'] . ' )</td><td>'; foreach ( $data['elements'] as $element => $count ) { $usage .= $element . ': ' . $count . PHP_EOL; } $usage .= '</td></tr>'; } return [ 'value' => $usage, ]; } public function get_raw_usage() { /** @var Module $module */ $module = Module::instance(); $usage = PHP_EOL; foreach ( $module->get_formatted_usage( 'raw' ) as $doc_type => $data ) { $usage .= "\t{$data['title']} : " . $data['count'] . PHP_EOL; foreach ( $data['elements'] as $element => $count ) { $usage .= "\t\t{$element} : {$count}" . PHP_EOL; } } return [ 'value' => $usage, ]; } /** * Removes the "elementor_usage_recalc" param from the query string to avoid recalc every refresh. * When using a redirect header in place of this approach it throws an error because some components have already output some content. * * @return string */ private function get_remove_recalc_query_string_script() { ob_start(); ?> <script> // Origin file: modules/usage/usage-reporter.php - get_remove_recalc_query_string_script() { const url = new URL( window.location ); url.hash = ''; url.searchParams.delete( 'elementor_usage_recalc' ); url.searchParams.delete( '_wpnonce' ); history.replaceState( '', window.title, url.toString() ); } </script> <?php return ob_get_clean(); } } usage/contracts/element-usage-calculator.php 0000644 00000000517 15252521350 0015246 0 ustar 00 <?php namespace Elementor\Modules\Usage\Contracts; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Element_Usage_Calculator { public function can_calculate( array $element, $element_instance ): bool; public function calculate( array $element, $element_instance, array $existing_usage ): array; } usage/module.php 0000644 00000037350 15252521350 0007656 0 ustar 00 <?php namespace Elementor\Modules\Usage; use Elementor\Core\Base\Document; use Elementor\Core\Base\Module as BaseModule; use Elementor\Modules\AtomicWidgets\Logger\Logger; use Elementor\Modules\AtomicWidgets\Module as Atomic_Widgets_Module; use Elementor\Modules\AtomicWidgets\Usage\Atomic_Element_Usage_Calculator; use Elementor\Modules\System_Info\Module as System_Info; use Elementor\Modules\Usage\Calculators\Legacy_Element_Usage_Calculator; use Elementor\Plugin; use Elementor\Settings; use Elementor\Tracker; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor usage module. * * Elementor usage module handler class is responsible for registering and * managing Elementor usage data. */ class Module extends BaseModule { const GENERAL_TAB = 'general'; const META_KEY = '_elementor_controls_usage'; const OPTION_NAME = 'elementor_controls_usage'; /** * @var bool */ private $is_document_saving = false; /** * Get module name. * * Retrieve the usage module name. * * @access public * * @return string Module name. */ public function get_name() { return 'usage'; } /** * Get doc type count. * * Get count of documents based on doc type * * Remove 'wp-' from $doc_type for BC, support doc type change since 2.7.0. * * @param \Elementor\Core\Documents_Manager $doc_class * @param String $doc_type * * @return int */ public function get_doc_type_count( $doc_class, $doc_type ) { static $posts = null; static $library = null; if ( null === $posts ) { $posts = \Elementor\Tracker::get_posts_usage(); } if ( null === $library ) { $library = \Elementor\Tracker::get_library_usage(); } $posts_usage = $posts; if ( $doc_class::get_property( 'show_in_library' ) ) { $posts_usage = $library; } $doc_type_common = str_replace( 'wp-', '', $doc_type ); $doc_usage = isset( $posts_usage[ $doc_type_common ] ) ? $posts_usage[ $doc_type_common ] : 0; return is_array( $doc_usage ) ? $doc_usage['publish'] : $doc_usage; } /** * Get formatted usage. * * Retrieve formatted usage, for frontend. * * @param String $format Optional. Default is 'html'. * * @return array */ public function get_formatted_usage( $format = 'html' ) { $usage = []; foreach ( get_option( self::OPTION_NAME, [] ) as $doc_type => $elements ) { $doc_class = Plugin::$instance->documents->get_document_type( $doc_type ); if ( 'html' === $format && $doc_class ) { $doc_title = $doc_class::get_title(); } else { $doc_title = $doc_type; } $doc_count = $this->get_doc_type_count( $doc_class, $doc_type ); $tab_group = $doc_class::get_property( 'admin_tab_group' ); if ( 'html' === $format && $tab_group ) { $doc_title = ucwords( $tab_group ) . ' - ' . $doc_title; } // Replace element type with element title. foreach ( $elements as $element_type => $data ) { unset( $elements[ $element_type ] ); if ( in_array( $element_type, [ 'section', 'column' ], true ) ) { continue; } $widget_instance = Plugin::$instance->widgets_manager->get_widget_types( $element_type ); if ( 'html' === $format && $widget_instance ) { $widget_title = $widget_instance->get_title(); } else { $widget_title = $element_type; } $widget_title = apply_filters( 'elementor/usage/elements/element_title', $widget_title, $element_type ); $elements[ $widget_title ] = $data['count']; } // Sort elements by key. ksort( $elements ); $usage[ $doc_type ] = [ 'title' => $doc_title, 'elements' => $elements, 'count' => $doc_count, ]; // ' ? 1 : 0;' In sorters is compatibility for PHP8.0. // Sort usage by title. uasort( $usage, function( $a, $b ) { return ( $a['title'] > $b['title'] ) ? 1 : 0; } ); // If title includes '-' will have lower priority. uasort( $usage, function( $a ) { return strpos( $a['title'], '-' ) ? 1 : 0; } ); } return $usage; } /** * Before document Save. * * Called on elementor/document/before_save, remove document from global & set saving flag. * * @param Document $document * @param array $data new settings to save. */ public function before_document_save( $document, $data ) { $current_status = get_post_status( $document->get_post() ); $new_status = isset( $data['settings']['post_status'] ) ? $data['settings']['post_status'] : ''; if ( $current_status === $new_status ) { $this->remove_from_global( $document ); } $this->is_document_saving = true; } /** * After document save. * * Called on elementor/document/after_save, adds document to global & clear saving flag. * * @param Document $document */ public function after_document_save( $document ) { if ( Document::STATUS_PUBLISH === $document->get_post()->post_status || Document::STATUS_PRIVATE === $document->get_post()->post_status ) { $this->save_document_usage( $document ); } $this->is_document_saving = false; } /** * On status change. * * Called on transition_post_status. * * @param string $new_status * @param string $old_status * @param \WP_Post $post */ public function on_status_change( $new_status, $old_status, $post ) { if ( wp_is_post_autosave( $post ) ) { return; } // If it's from elementor editor, the usage should be saved via `before_document_save`/`after_document_save`. if ( $this->is_document_saving ) { return; } $document = Plugin::$instance->documents->get( $post->ID ); if ( ! $document ) { return; } $is_public_unpublish = 'publish' === $old_status && 'publish' !== $new_status; $is_private_unpublish = 'private' === $old_status && 'private' !== $new_status; if ( $is_public_unpublish || $is_private_unpublish ) { $this->remove_from_global( $document ); } $is_public_publish = 'publish' !== $old_status && 'publish' === $new_status; $is_private_publish = 'private' !== $old_status && 'private' === $new_status; if ( $is_public_publish || $is_private_publish ) { $this->save_document_usage( $document ); } } /** * On before delete post. * * Called on on_before_delete_post. * * @param int $post_id */ public function on_before_delete_post( $post_id ) { $document = Plugin::$instance->documents->get( $post_id ); if ( $document->get_id() !== $document->get_main_id() ) { return; } $this->remove_from_global( $document ); } /** * Add's tracking data. * * Called on elementor/tracker/send_tracking_data_params. * * @param array $params * * @return array */ public function add_tracking_data( $params ) { $params['usages']['elements'] = get_option( self::OPTION_NAME ); return $params; } /** * Recalculate usage. * * Recalculate usage for all elementor posts. * * @param int $limit * @param int $offset * * @return int */ public function recalc_usage( $limit = -1, $offset = 0 ) { // While requesting recalc_usage, data should be deleted. // if its in a batch the data should be deleted only on the first batch. if ( 0 === $offset ) { delete_option( self::OPTION_NAME ); } $post_types = get_post_types( [ 'public' => true ] ); $query = new \WP_Query( [ 'no_found_rows' => true, 'meta_key' => '_elementor_data', 'post_type' => $post_types, 'post_status' => [ 'publish', 'private' ], 'posts_per_page' => $limit, 'offset' => $offset, ] ); foreach ( $query->posts as $post ) { $document = Plugin::$instance->documents->get( $post->ID ); if ( ! $document ) { continue; } $this->after_document_save( $document ); } // Clear query memory before leave. wp_cache_flush(); return count( $query->posts ); } /** * Increase controls count. * * Increase controls count, for each element. * * @param array &$element_ref * @param string $tab * @param string $section * @param string $control * @param int $count */ private function increase_controls_count( &$element_ref, $tab, $section, $control, $count ) { if ( ! isset( $element_ref['controls'][ $tab ] ) ) { $element_ref['controls'][ $tab ] = []; } if ( ! isset( $element_ref['controls'][ $tab ][ $section ] ) ) { $element_ref['controls'][ $tab ][ $section ] = []; } if ( ! isset( $element_ref['controls'][ $tab ][ $section ][ $control ] ) ) { $element_ref['controls'][ $tab ][ $section ][ $control ] = 0; } $element_ref['controls'][ $tab ][ $section ][ $control ] += $count; } /** * Add to global. * * Add's usage to global (update database). * * @param string $doc_name * @param array $doc_usage */ private function add_to_global( $doc_name, $doc_usage ) { $global_usage = get_option( self::OPTION_NAME, [] ); foreach ( $doc_usage as $element_type => $element_data ) { if ( ! isset( $global_usage[ $doc_name ] ) ) { $global_usage[ $doc_name ] = []; } if ( ! isset( $global_usage[ $doc_name ][ $element_type ] ) ) { $global_usage[ $doc_name ][ $element_type ] = [ 'count' => 0, 'controls' => [], ]; } $global_element_ref = &$global_usage[ $doc_name ][ $element_type ]; $global_element_ref['count'] += $element_data['count']; if ( empty( $element_data['controls'] ) ) { continue; } foreach ( $element_data['controls'] as $tab => $sections ) { foreach ( $sections as $section => $controls ) { foreach ( $controls as $control => $count ) { $this->increase_controls_count( $global_element_ref, $tab, $section, $control, $count ); } } } } update_option( self::OPTION_NAME, $global_usage, false ); } /** * Remove from global. * * Remove's usage from global (update database). * * @param Document $document */ private function remove_from_global( $document ) { $prev_usage = $document->get_meta( self::META_KEY ); if ( empty( $prev_usage ) ) { return; } $doc_name = $document->get_name(); $global_usage = get_option( self::OPTION_NAME, [] ); foreach ( $prev_usage as $element_type => $doc_value ) { if ( isset( $global_usage[ $doc_name ][ $element_type ]['count'] ) ) { $global_usage[ $doc_name ][ $element_type ]['count'] -= $prev_usage[ $element_type ]['count']; if ( 0 === $global_usage[ $doc_name ][ $element_type ]['count'] ) { unset( $global_usage[ $doc_name ][ $element_type ] ); if ( 0 === count( $global_usage[ $doc_name ] ) ) { unset( $global_usage[ $doc_name ] ); } continue; } foreach ( $prev_usage[ $element_type ]['controls'] as $tab => $sections ) { foreach ( $sections as $section => $controls ) { foreach ( $controls as $control => $count ) { if ( isset( $global_usage[ $doc_name ][ $element_type ]['controls'][ $tab ][ $section ][ $control ] ) ) { $section_ref = &$global_usage[ $doc_name ][ $element_type ]['controls'][ $tab ][ $section ]; $section_ref[ $control ] -= $count; if ( 0 === $section_ref[ $control ] ) { unset( $section_ref[ $control ] ); } } } } } } } update_option( self::OPTION_NAME, $global_usage, false ); $document->delete_meta( self::META_KEY ); } /** * Get elements usage. * * Get's the current elements usage by passed elements array parameter. * * @param array $elements * * @return array */ private function get_elements_usage( $elements ) { $usage = []; $registry = $this->get_calculator_registry(); Plugin::$instance->db->iterate_data( $elements, function ( $element ) use ( &$usage, $registry ) { if ( empty( $element['widgetType'] ) ) { $type = $element['elType']; $element_instance = Plugin::$instance->elements_manager->get_element_types( $type ); } else { $type = $element['widgetType']; $element_instance = Plugin::$instance->widgets_manager->get_widget_types( $type ); } try { $calculator = $registry->get_calculator_for( $element, $element_instance ); if ( $calculator ) { $usage = $calculator->calculate( $element, $element_instance, $usage ); } } catch ( \Throwable $e ) { Logger::warning( 'Usage calculation failed: ' . $e->getMessage(), [ 'element_type' => $type, 'element_id' => $element['id'] ?? 'unknown', ] ); if ( ! isset( $usage[ $type ] ) ) { $usage[ $type ] = [ 'count' => 0, 'control_percent' => 0, 'controls' => [], ]; } $usage[ $type ]['count']++; } return $element; } ); return $usage; } /** * @return Element_Usage_Calculator_Registry */ private function get_calculator_registry(): Element_Usage_Calculator_Registry { static $registry = null; if ( null === $registry ) { $calculators = []; if ( Atomic_Widgets_Module::is_active() ) { $calculators[] = new Atomic_Element_Usage_Calculator(); } $registry = new Element_Usage_Calculator_Registry( $calculators ); $registry->set_fallback( new Legacy_Element_Usage_Calculator() ); } return $registry; } /** * Save document usage. * * Save requested document usage, and update global. * * @param Document $document */ private function save_document_usage( Document $document ) { if ( ! $document::get_property( 'is_editable' ) && ! $document->is_built_with_elementor() ) { return; } // Get data manually to avoid conflict with `\Elementor\Core\Base\Document::get_elements_data... convert_to_elementor`. $data = $document->get_json_meta( '_elementor_data' ); if ( ! empty( $data ) ) { try { $usage = $this->get_elements_usage( $document->get_elements_raw_data( $data ) ); $document->update_meta( self::META_KEY, $usage ); $this->add_to_global( $document->get_name(), $usage ); } catch ( \Exception $exception ) { Logger::warning( $exception->getMessage(), [ 'document_id' => $document->get_id(), 'document_name' => $document->get_name(), ] ); return; } } } public static function get_settings_usage() { $usage = []; $settings_tab = Plugin::$instance->settings->get_tabs(); $settings = array_merge( $settings_tab[ Settings::TAB_GENERAL ]['sections'], $settings_tab[ Settings::TAB_ADVANCED ]['sections'] ); foreach ( $settings as $setting_data ) { foreach ( $setting_data['fields'] as $field_name => $field_data ) { $is_hidden_field = ( empty( $field_data['field_args']['type'] ) || 'hidden' === $field_data['field_args']['type'] ); if ( $is_hidden_field ) { continue; } $setting_value = get_option( 'elementor_' . $field_name ); if ( empty( $setting_value ) ) { continue; } $is_default_value = ( ! empty( $field_data['field_args']['std'] ) && $setting_value === $field_data['field_args']['std'] ); if ( $is_default_value ) { continue; } $usage[ $field_name ] = $setting_value; } } $usage = apply_filters( 'elementor/system-info/usage/settings', $usage ); return $usage; } /** * Add system info report. */ public function add_system_info_report() { System_Info::add_report( 'usage', [ 'file_name' => __DIR__ . '/usage-reporter.php', 'class_name' => __NAMESPACE__ . '\Usage_Reporter', ] ); System_Info::add_report( 'settings', [ 'file_name' => __DIR__ . '/settings-reporter.php', 'class_name' => __NAMESPACE__ . '\Settings_Reporter', ] ); } /** * Usage module constructor. * * Initializing Elementor usage module. * * @access public */ public function __construct() { if ( ! Tracker::is_allow_track() ) { return; } add_action( 'transition_post_status', [ $this, 'on_status_change' ], 10, 3 ); add_action( 'before_delete_post', [ $this, 'on_before_delete_post' ] ); add_action( 'elementor/document/before_save', [ $this, 'before_document_save' ], 10, 2 ); add_action( 'elementor/document/after_save', [ $this, 'after_document_save' ] ); add_filter( 'elementor/tracker/send_tracking_data_params', [ $this, 'add_tracking_data' ] ); add_action( 'admin_init', [ $this, 'add_system_info_report' ], 50 ); } } usage/element-usage-calculator-registry.php 0000644 00000001564 15252521350 0015117 0 ustar 00 <?php namespace Elementor\Modules\Usage; use Elementor\Modules\Usage\Contracts\Element_Usage_Calculator; if ( ! defined( 'ABSPATH' ) ) { exit; } class Element_Usage_Calculator_Registry { /** @var Element_Usage_Calculator[] */ private array $calculators = []; private ?Element_Usage_Calculator $fallback = null; /** * @param Element_Usage_Calculator[] $calculators */ public function __construct( array $calculators = [] ) { $this->calculators = $calculators; } public function set_fallback( Element_Usage_Calculator $calculator ): void { $this->fallback = $calculator; } public function get_calculator_for( array $element, $element_instance ): ?Element_Usage_Calculator { foreach ( $this->calculators as $calculator ) { if ( $calculator->can_calculate( $element, $element_instance ) ) { return $calculator; } } return $this->fallback; } } usage/calculators/legacy-element-usage-calculator.php 0000644 00000006305 15252521350 0017025 0 ustar 00 <?php namespace Elementor\Modules\Usage\Calculators; use Elementor\Core\DynamicTags\Manager; use Elementor\Modules\Usage\Contracts\Element_Usage_Calculator; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Legacy_Element_Usage_Calculator implements Element_Usage_Calculator { const GENERAL_TAB = 'general'; public function can_calculate( array $element, $element_instance ): bool { return true; } public function calculate( array $element, $element_instance, array $existing_usage ): array { $type = $element['widgetType'] ?? $element['elType']; if ( ! isset( $existing_usage[ $type ] ) ) { $existing_usage[ $type ] = [ 'count' => 0, 'control_percent' => 0, 'controls' => [], ]; } $existing_usage[ $type ]['count']++; if ( ! $element_instance ) { return $existing_usage; } $element_controls = $element_instance->get_controls(); if ( isset( $element['settings'] ) ) { $settings_controls = $element['settings']; $element_ref = &$existing_usage[ $type ]; $settings_controls = $this->add_general_controls( $settings_controls, $element_ref ); $changed_controls_count = $this->add_controls( $settings_controls, $element_controls, $element_ref ); $percent = ! empty( $element_controls ) ? $changed_controls_count / ( count( $element_controls ) / 100 ) : 0; $existing_usage[ $type ]['control_percent'] = (int) round( $percent ); } return $existing_usage; } private function increase_controls_count( array &$element_ref, string $tab, string $section, string $control, int $count ): void { if ( ! isset( $element_ref['controls'][ $tab ] ) ) { $element_ref['controls'][ $tab ] = []; } if ( ! isset( $element_ref['controls'][ $tab ][ $section ] ) ) { $element_ref['controls'][ $tab ][ $section ] = []; } if ( ! isset( $element_ref['controls'][ $tab ][ $section ][ $control ] ) ) { $element_ref['controls'][ $tab ][ $section ][ $control ] = 0; } $element_ref['controls'][ $tab ][ $section ][ $control ] += $count; } private function add_controls( array $settings_controls, array $element_controls, array &$element_ref ): int { $changed_controls_count = 0; foreach ( $settings_controls as $control => $value ) { if ( empty( $element_controls[ $control ] ) ) { continue; } $control_config = $element_controls[ $control ]; if ( ! isset( $control_config['section'], $control_config['default'] ) ) { continue; } $tab = $control_config['tab']; $section = $control_config['section']; if ( $value !== $control_config['default'] ) { $this->increase_controls_count( $element_ref, $tab, $section, $control, 1 ); ++$changed_controls_count; } } return $changed_controls_count; } private function add_general_controls( array $settings_controls, array &$element_ref ): array { if ( ! empty( $settings_controls[ Manager::DYNAMIC_SETTING_KEY ] ) ) { $settings_controls = array_merge( $settings_controls, $settings_controls[ Manager::DYNAMIC_SETTING_KEY ] ); $this->increase_controls_count( $element_ref, self::GENERAL_TAB, Manager::DYNAMIC_SETTING_KEY, 'count', count( $settings_controls[ Manager::DYNAMIC_SETTING_KEY ] ) ); } return $settings_controls; } } usage/settings-reporter.php 0000644 00000002356 15252521350 0012067 0 ustar 00 <?php namespace Elementor\Modules\Usage; use Elementor\Modules\System_Info\Reporters\Base as Base_Reporter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Settings_Reporter extends Base_Reporter { public function get_title() { return esc_html__( 'Settings', 'elementor' ); } public function get_fields() { return [ 'settings' => '', ]; } public function get_settings(): array { $usage_settings_text = ''; $settings = Module::get_settings_usage(); foreach ( $settings as $setting_name => $setting_value ) { $setting_value_text = is_array( $setting_value ) ? implode( ', ', $setting_value ) : $setting_value; $usage_settings_text .= '<tr><td>' . $setting_name . '</td><td>' . $setting_value_text . '</td></tr>'; } return [ 'value' => $usage_settings_text, ]; } public function get_raw_settings(): array { $usage_settings = PHP_EOL; $settings = Module::get_settings_usage(); foreach ( $settings as $setting_name => $setting_value ) { $setting_value_text = is_array( $setting_value ) ? implode( ', ', $setting_value ) : $setting_value; $usage_settings .= "\t" . $setting_name . ': ' . $setting_value_text . PHP_EOL; } return [ 'value' => $usage_settings, ]; } } landing-pages/module.php 0000644 00000043657 15252521350 0011272 0 ustar 00 <?php namespace Elementor\Modules\LandingPages; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Documents_Manager; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Modules\LandingPages\Documents\Landing_Page; use Elementor\Modules\LandingPages\AdminMenuItems\Editor_One_Landing_Pages_Menu; use Elementor\Modules\LandingPages\Module as Landing_Pages_Module; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const DOCUMENT_TYPE = 'landing-page'; const CPT = 'e-landing-page'; const ADMIN_PAGE_SLUG = 'edit.php?post_type=' . self::CPT; const ACTIVATION_KEY = 'elementor_landing_pages_activation'; private $has_pages = null; private $trashed_posts; private $new_lp_url; private $permalink_structure; public function get_name() { return 'landing-pages'; } /** * Register Experimental Feature * * Implementation of this method makes the module an experiment. * * @since 3.28.0 */ private function register_experiment() { Plugin::$instance->experiments->add_feature( [ 'name' => 'landing-pages', 'title' => esc_html__( 'Landing Pages', 'elementor' ), 'description' => esc_html__( 'Adds a new Elementor content type that allows creating beautiful landing pages instantly in a streamlined workflow.', 'elementor' ), 'release_status' => Experiments_Manager::RELEASE_STATUS_BETA, 'default' => Experiments_Manager::STATE_ACTIVE, 'new_site' => [ 'default_inactive' => true, 'minimum_installation_version' => '3.22.0', ], 'deprecated' => true, ] ); } /** * Should activate landing pages * * Checks whether the Landing Pages should be activated. * * If the activation key set to `1` in wp_options, the Landing Pages feature should be active. Otherwise not. * This is a backwards compatibility for websites that had Landing Pages, therefore couldn't be deactivated. * When deleting posts in Landing Pages CPT, Elementor checks again whether this feature should be activated. * * @since 3.31.0 */ private function should_activate_landing_pages() { if ( '1' === get_option( self::ACTIVATION_KEY ) ) { return true; } if ( $this->has_landing_pages() ) { update_option( self::ACTIVATION_KEY, '1' ); return true; } update_option( self::ACTIVATION_KEY, '0' ); return false; } /** * Get Trashed Landing Pages Posts * * Returns the posts property of a WP_Query run for Landing Pages with post_status of 'trash'. * * @since 3.1.0 * * @return array trashed posts */ private function get_trashed_landing_page_posts() { if ( $this->trashed_posts ) { return $this->trashed_posts; } // `'posts_per_page' => 1` is because this is only used as an indicator to whether there are any trashed landing pages. $trashed_posts_query = new \WP_Query( [ 'no_found_rows' => true, 'post_type' => self::CPT, 'post_status' => 'trash', 'posts_per_page' => 1, 'meta_key' => '_elementor_template_type', 'meta_value' => self::DOCUMENT_TYPE, ] ); $this->trashed_posts = $trashed_posts_query->posts; return $this->trashed_posts; } private function has_landing_pages() { if ( null !== $this->has_pages ) { return $this->has_pages; } $posts_query = new \WP_Query( [ 'no_found_rows' => true, 'post_type' => self::CPT, 'post_status' => 'any', 'posts_per_page' => 1, 'meta_key' => '_elementor_template_type', 'meta_value' => self::DOCUMENT_TYPE, ] ); $this->has_pages = $posts_query->post_count > 0; return $this->has_pages; } /** * Is Elementor Landing Page. * * Check whether the post is an Elementor Landing Page. * * @since 3.1.0 * @access public * * @param \WP_Post $post Post Object. * * @return bool Whether the post was built with Elementor. */ public function is_elementor_landing_page( $post ) { return self::CPT === $post->post_type; } public function get_menu_args() { if ( $this->has_landing_pages() ) { $menu_slug = self::ADMIN_PAGE_SLUG; $function = null; } else { $menu_slug = self::CPT; $function = [ $this, 'print_empty_landing_pages_page' ]; } return [ 'menu_slug' => $menu_slug, 'function' => $function, ]; } private function register_editor_one_menu( Menu_Data_Provider $menu_data_provider ): void { $menu_data_provider->register_menu( new Editor_One_Landing_Pages_Menu( $this ) ); } /** * Get 'Add New' Landing Page URL * * Retrieves the custom URL for the admin dashboard's 'Add New' button in the Landing Pages admin screen. This URL * creates a new Landing Pages and directly opens the Elementor Editor with the Template Library modal open on the * Landing Pages tab. * * @since 3.1.0 * * @return string */ private function get_add_new_landing_page_url() { if ( ! $this->new_lp_url ) { $this->new_lp_url = Plugin::$instance->documents->get_create_new_post_url( self::CPT, self::DOCUMENT_TYPE ) . '#library'; } return $this->new_lp_url; } /** * Get Empty Landing Pages Page * * Prints the HTML content of the page that is displayed when there are no existing landing pages in the DB. * Added as the callback to add_submenu_page. * * @since 3.1.0 */ public function print_empty_landing_pages_page() { $template_sources = Plugin::$instance->templates_manager->get_registered_sources(); $source_local = $template_sources['local']; $trashed_posts = $this->get_trashed_landing_page_posts(); ?> <div class="e-landing-pages-empty"> <?php /** @var Source_Local $source_local */ $source_local->print_blank_state_template( esc_html__( 'Landing Page', 'elementor' ), $this->get_add_new_landing_page_url(), esc_html__( 'Build Effective Landing Pages for your business\' marketing campaigns.', 'elementor' ) ); if ( ! empty( $trashed_posts ) ) : ?> <div class="e-trashed-items"> <?php printf( /* translators: %1$s Link open tag, %2$s: Link close tag. */ esc_html__( 'Or view %1$sTrashed Items%2$s', 'elementor' ), '<a href="' . esc_url( admin_url( 'edit.php?post_status=trash&post_type=' . self::CPT ) ) . '">', '</a>' ); ?> </div> <?php endif; ?> </div> <?php } /** * Is Current Admin Page Edit LP * * Checks whether the current page is a native WordPress edit page for a landing page. */ private function is_landing_page_admin_edit() { $screen = get_current_screen(); if ( 'post' === $screen->base ) { return $this->is_elementor_landing_page( get_post() ); } return false; } /** * Admin Localize Settings * * Enables adding properties to the globally available elementorAdmin.config JS object in the Admin Dashboard. * Runs on the 'elementor/admin/localize_settings' filter. * * @since 3.1.0 * * @param $settings * @return array|null */ private function admin_localize_settings( $settings ) { $additional_settings = [ 'urls' => [ 'addNewLandingPageUrl' => $this->get_add_new_landing_page_url(), ], 'landingPages' => [ 'landingPagesHasPages' => $this->has_landing_pages(), 'isLandingPageAdminEdit' => $this->is_landing_page_admin_edit(), ], ]; return array_replace_recursive( $settings, $additional_settings ); } /** * Register Landing Pages CPT * * @since 3.1.0 */ private function register_landing_page_cpt() { $labels = [ 'name' => esc_html__( 'Landing Pages', 'elementor' ), 'singular_name' => esc_html__( 'Landing Page', 'elementor' ), 'add_new' => esc_html__( 'Add New', 'elementor' ), 'add_new_item' => esc_html__( 'Add New Landing Page', 'elementor' ), 'edit_item' => esc_html__( 'Edit Landing Page', 'elementor' ), 'new_item' => esc_html__( 'New Landing Page', 'elementor' ), 'all_items' => esc_html__( 'All Landing Pages', 'elementor' ), 'view_item' => esc_html__( 'View Landing Page', 'elementor' ), 'search_items' => esc_html__( 'Search Landing Pages', 'elementor' ), 'not_found' => esc_html__( 'No landing pages found', 'elementor' ), 'not_found_in_trash' => esc_html__( 'No landing pages found in trash', 'elementor' ), 'parent_item_colon' => '', 'menu_name' => esc_html__( 'Landing Pages', 'elementor' ), ]; $args = [ 'labels' => $labels, 'public' => true, 'show_in_menu' => 'edit.php?post_type=elementor_library&tabs_group=library', 'capability_type' => 'page', 'taxonomies' => [ Source_Local::TAXONOMY_TYPE_SLUG ], 'supports' => [ 'title', 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes', 'thumbnail', 'custom-fields', 'post-formats', 'elementor' ], ]; register_post_type( self::CPT, $args ); } /** * Remove Post Type Slug * * Landing Pages are supposed to act exactly like pages. This includes their URLs being directly under the site's * domain name. Since "Landing Pages" is a CPT, WordPress automatically adds the landing page slug as a prefix to * it's posts' permalinks. This method checks if the post's post type is Landing Pages, and if it is, it removes * the CPT slug from the requested post URL. * * Runs on the 'post_type_link' filter. * * @since 3.1.0 * * @param $post_link * @param $post * @param $leavename * @return string|string[] */ private function remove_post_type_slug( $post_link, $post, $leavename ) { // Only try to modify the permalink if the post is a Landing Page. if ( self::CPT !== $post->post_type || 'publish' !== $post->post_status ) { return $post_link; } // Any slug prefixes need to be removed from the post link. return trailingslashit( get_home_url() ) . trailingslashit( $post->post_name ); } /** * Adjust Landing Page Query * * Since Landing Pages are a CPT but should act like pages, the WP_Query that is used to fetch the page from the * database needs to be adjusted. This method adds the Landing Pages CPT to the list of queried post types, to * make sure the database query finds the correct Landing Page to display. * Runs on the 'pre_get_posts' action. * * @since 3.1.0 * * @param \WP_Query $query */ private function adjust_landing_page_query( \WP_Query $query ) { // Only handle actual pages. if ( ! $query->is_main_query() // If the query is not for a page. || ! isset( $query->query['page'] ) // If the query is for a static home/blog page. || is_home() // If the post type comes already set, the main query is probably a custom one made by another plugin. // In this case we do not want to intervene in order to not cause a conflict. || isset( $query->query['post_type'] ) ) { return; } // Create the post types property as an array and include the landing pages CPT in it. $query_post_types = [ 'post', 'page', self::CPT ]; // Since WordPress determined this is supposed to be a page, we'll pre-set the post_type query arg to make sure // it includes the Landing Page CPT, so when the query is parsed, our CPT will be a legitimate match to the // Landing Page's permalink (that is directly under the domain, without a CPT slug prefix). In some cases, // The 'name' property will be set, and in others it is the 'pagename', so we have to cover both cases. if ( ! empty( $query->query['name'] ) ) { $query->set( 'post_type', $query_post_types ); } elseif ( ! empty( $query->query['pagename'] ) && false === strpos( $query->query['pagename'], '/' ) ) { $query->set( 'post_type', $query_post_types ); // We also need to set the name query var since redirect_guess_404_permalink() relies on it. add_filter( 'pre_redirect_guess_404_permalink', function( $value ) use ( $query ) { set_query_var( 'name', $query->query['pagename'] ); return $value; } ); } } /** * Handle 404 * * This method runs after a page is not found in the database, but before a page is returned as a 404. * These cases are handled in this filter callback, that runs on the 'pre_handle_404' filter. * * In some cases (such as when a site uses custom permalink structures), WordPress's WP_Query does not identify a * Landing Page's URL as a post belonging to the Landing Page CPT. Some cases are handled successfully by the * adjust_landing_page_query() method, but some are not and still trigger a 404 process. This method handles such * cases by overriding the $wp_query global to fetch the correct landing page post entry. * * For example, since Landing Pages slugs come directly after the site domain name, WP_Query might parse the post * as a category page. Since there is no category matching the slug, it triggers a 404 process. In this case, we * run a query for a Landing Page post with the passed slug ($query->query['category_name']. If a Landing Page * with the passed slug is found, we override the global $wp_query with the new, correct query. * * @param $current_value * @param $query * @return false */ private function handle_404( $current_value, $query ) { global $wp_query; // If another plugin/theme already used this filter, exit here to avoid conflicts. if ( $current_value ) { return $current_value; } if ( // Make sure we only intervene in the main query. ! $query->is_main_query() // If a post was found, this is not a 404 case, so do not intervene. || ! empty( $query->posts ) // This filter is only meant to deal with wrong queries where the only query var is 'category_name'. // If there is no 'category_name' query var, do not intervene. || empty( $query->query['category_name'] ) // If the query is for a real taxonomy (determined by it including a table to search in, such as the // wp_term_relationships table), do not intervene. || ! empty( $query->tax_query->table_aliases ) ) { return false; } // Search for a Landing Page with the same name passed as the 'category name'. $possible_new_query = new \WP_Query( [ 'no_found_rows' => true, 'post_type' => self::CPT, 'name' => $query->query['category_name'], ] ); // Only if such a Landing Page is found, override the query to fetch the correct page. if ( ! empty( $possible_new_query->posts ) ) { $wp_query = $possible_new_query; //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } return false; } public function __construct() { if ( ! $this->should_activate_landing_pages() ) { return; } $this->register_experiment(); if ( ! Plugin::$instance->experiments->is_feature_active( 'landing-pages' ) ) { return; } $this->permalink_structure = get_option( 'permalink_structure' ); $this->register_landing_page_cpt(); // If there is a permalink structure set to the site, run the hooks that modify the Landing Pages permalinks to // match WordPress' native 'Pages' post type. if ( '' !== $this->permalink_structure ) { // Landing Pages' post link needs to be modified to be identical to the pages permalink structure. This // needs to happen in both the admin and the front end, since post links are also used in the admin pages. add_filter( 'post_type_link', function( $post_link, $post, $leavename ) { return $this->remove_post_type_slug( $post_link, $post, $leavename ); }, 10, 3 ); // The query itself only has to be manipulated when pages are viewed in the front end. if ( ! is_admin() || wp_doing_ajax() ) { add_action( 'pre_get_posts', function ( $query ) { $this->adjust_landing_page_query( $query ); } ); // Handle cases where visiting a Landing Page's URL returns 404. add_filter( 'pre_handle_404', function ( $value, $query ) { return $this->handle_404( $value, $query ); }, 10, 2 ); } } add_action( 'elementor/documents/register', function( Documents_Manager $documents_manager ) { $documents_manager->register_document_type( self::DOCUMENT_TYPE, Landing_Page::get_class_full_name() ); } ); add_action( 'elementor/editor-one/menu/register', function ( Menu_Data_Provider $menu_data_provider ) { $this->register_editor_one_menu( $menu_data_provider ); } ); add_filter( 'elementor/editor-one/menu/elementor_post_types', function ( array $elementor_post_types ): array { $elementor_post_types[ static::CPT ] = [ 'menu_slug' => 'elementor-editor-templates', 'child_slug' => 'edit.php?post_type=' . static::CPT, ]; return $elementor_post_types; } ); // Add the custom 'Add New' link for Landing Pages into Elementor's admin config. add_action( 'elementor/admin/localize_settings', function( array $settings ) { return $this->admin_localize_settings( $settings ); } ); add_filter( 'elementor/template_library/sources/local/register_taxonomy_cpts', function( array $cpts ) { $cpts[] = self::CPT; return $cpts; } ); // When deleting posts in Landing Page CPT, force Elementor to check again whether this feature should be activated. add_action( 'deleted_post_' . self::CPT, function () { delete_option( self::ACTIVATION_KEY ); } ); // In the Landing Pages Admin Table page - Overwrite Template type column header title. add_action( 'manage_' . Landing_Pages_Module::CPT . '_posts_columns', function( $posts_columns ) { /** @var Source_Local $source_local */ $source_local = Plugin::$instance->templates_manager->get_source( 'local' ); return $source_local->admin_columns_headers( $posts_columns ); } ); // In the Landing Pages Admin Table page - Overwrite Template type column row values. add_action( 'manage_' . Landing_Pages_Module::CPT . '_posts_custom_column', function( $column_name, $post_id ) { /** @var Landing_Page $document */ $document = Plugin::$instance->documents->get( $post_id ); $document->admin_columns_content( $column_name ); }, 10, 2 ); // Overwrite the Admin Bar's 'New +' Landing Page URL with the link that creates the new LP in Elementor // with the Template Library modal open. add_action( 'admin_bar_menu', function( $admin_bar ) { // Get the Landing Page menu node. $new_landing_page_node = $admin_bar->get_node( 'new-e-landing-page' ); if ( $new_landing_page_node ) { $new_landing_page_node->href = $this->get_add_new_landing_page_url(); $admin_bar->add_node( $new_landing_page_node ); } }, 100 ); } } landing-pages/admin-menu-items/landing-pages-menu-item.php 0000644 00000001250 15252521350 0017544 0 ustar 00 <?php namespace Elementor\Modules\LandingPages\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Landing_Pages_Menu_Item implements Admin_Menu_Item { public function is_visible() { return true; } public function get_parent_slug() { return Source_Local::ADMIN_MENU_SLUG; } public function get_label() { return esc_html__( 'Landing Pages', 'elementor' ); } public function get_page_title() { return esc_html__( 'Landing Pages', 'elementor' ); } public function get_capability() { return 'manage_options'; } } landing-pages/admin-menu-items/editor-one-landing-pages-menu.php 0000644 00000003432 15252521350 0020657 0 ustar 00 <?php namespace Elementor\Modules\LandingPages\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Modules\LandingPages\Module; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Landing_Pages_Menu implements Menu_Item_Interface, Admin_Menu_Item_With_Page { private $module; private $menu_item; public function __construct( Module $module ) { $this->module = $module; $this->initialize_menu_item(); } private function initialize_menu_item() { $menu_args = $this->module->get_menu_args(); $slug = $menu_args['menu_slug']; $function = $menu_args['function']; if ( is_callable( $function ) ) { $this->menu_item = new Landing_Pages_Empty_View_Menu_Item( $function ); } else { $this->menu_item = new Landing_Pages_Menu_Item(); } } public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'Landing Pages', 'elementor' ); } public function get_position(): int { return 20; } public function get_slug(): string { $menu_args = $this->module->get_menu_args(); return $menu_args['menu_slug']; } public function get_group_id(): string { return Menu_Config::TEMPLATES_GROUP_ID; } public function get_page_title(): string { return $this->get_label(); } public function render() { if ( $this->menu_item instanceof Admin_Menu_Item_With_Page ) { $this->menu_item->render(); } else { wp_safe_redirect( admin_url( Module::ADMIN_PAGE_SLUG ) ); exit; } } } landing-pages/admin-menu-items/landing-pages-empty-view-menu-item.php 0000644 00000001010 15252521350 0021642 0 ustar 00 <?php namespace Elementor\Modules\LandingPages\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Landing_Pages_Empty_View_Menu_Item extends Landing_Pages_Menu_Item implements Admin_Menu_Item_With_Page { private $render_callback; public function __construct( callable $render_callback ) { $this->render_callback = $render_callback; } public function render() { ( $this->render_callback )(); } } landing-pages/documents/landing-page.php 0000644 00000004440 15252521350 0014317 0 ustar 00 <?php namespace Elementor\Modules\LandingPages\Documents; use Elementor\Core\DocumentTypes\PageBase; use Elementor\Modules\LandingPages\Module as Landing_Pages_Module; use Elementor\Modules\Library\Traits\Library; use Elementor\Modules\PageTemplates\Module as Page_Templates_Module; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Landing_Page extends PageBase { // Library Document Trait use Library; public static function get_properties() { $properties = parent::get_properties(); $properties['support_kit'] = true; $properties['show_in_library'] = true; $properties['cpt'] = [ Landing_Pages_Module::CPT ]; return $properties; } public static function get_type() { return Landing_Pages_Module::DOCUMENT_TYPE; } /** * @access public */ public function get_name() { return Landing_Pages_Module::DOCUMENT_TYPE; } /** * @access public * @static */ public static function get_title() { return esc_html__( 'Landing Page', 'elementor' ); } /** * @access public * @static */ public static function get_plural_title() { return esc_html__( 'Landing Pages', 'elementor' ); } public static function get_create_url() { return parent::get_create_url() . '#library'; } /** * Save Document. * * Save an Elementor document. * * @since 3.1.0 * @access public * * @param $data * * @return bool */ public function save( $data ) { // This is for the first time a Landing Page is created. It is done in order to load a new Landing Page with // 'Canvas' as the default page template. if ( empty( $data['settings']['template'] ) ) { $data['settings']['template'] = Page_Templates_Module::TEMPLATE_CANVAS; } return parent::save( $data ); } /** * Admin Columns Content * * @since 3.1.0 * * @param $column_name * @access public */ public function admin_columns_content( $column_name ) { if ( 'elementor_library_type' === $column_name ) { $this->print_admin_column_type(); } } protected function get_remote_library_config() { $config = [ 'type' => 'lp', 'default_route' => 'templates/landing-pages', 'autoImportSettings' => true, ]; return array_replace_recursive( parent::get_remote_library_config(), $config ); } } promotions/conversion-banner.php 0000644 00000020071 15252521350 0013116 0 ustar 00 <?php namespace Elementor\Modules\Promotions; use Elementor\Modules\Promotions\AdminMenuItems\Go_Pro_Promotion_Item; use Elementor\User; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Conversion_Banner { const DEFAULT_SELECTOR = '.wrap h1, .wrap h2'; const SCRIPT_HANDLE = 'e-conversion-banner'; const STYLE_HANDLE = 'e-conversion-banner'; const NONCE_ACTION = 'e_conversion_banner_nonce'; const OBJECT_NAME = 'eConversionBanner'; const DISMISS_KEY = 'conversion_banner_go_pro'; const AJAX_ACTION = 'elementor_dismiss_conversion_banner'; const CONTAINER_ID = 'e-conversion-banner'; const BIRTHDAY_PROMOTION_URL = 'https://go.elementor.com/go-pro-wp-admin-upgrad-notice/'; const HELLO_THEME_CONFIG_FILTER = 'hello-plus-theme/rest/admin-config'; const THEME_SLUGS = [ 'hello-elementor', 'hello-biz', 'hello-commerce' ]; const GO_PRO_TITLE_PREFIX = 'Go Pro'; public function __construct() { add_action( 'wp_ajax_' . self::AJAX_ACTION, [ $this, 'ajax_dismiss_banner' ] ); add_filter( self::HELLO_THEME_CONFIG_FILTER, [ $this, 'suppress_hello_theme_banner' ] ); add_action( 'current_screen', [ $this, 'maybe_register_banner_hooks' ] ); } public function maybe_register_banner_hooks(): void { $placement = $this->get_active_placement(); if ( empty( $placement ) ) { return; } add_action( 'in_admin_header', [ $this, 'render_banner_container' ], 11 ); add_action( 'admin_enqueue_scripts', function () use ( $placement ) { $this->enqueue_assets( $placement ); } ); } public function render_banner_container(): void { ?> <div id="<?php echo esc_attr( self::CONTAINER_ID ); ?>"> <?php $this->print_banner_markup( true ); ?> </div> <?php } public function suppress_hello_theme_banner( $config ) { if ( $this->is_request_from_theme_admin_page() || ! ( is_array( $config ) && isset( $config['welcome'] ) ) || Utils::has_pro() ) { return $config; } $title = $config['welcome']['title'] ?? ''; if ( is_string( $title ) && substr( $title, 0, strlen( self::GO_PRO_TITLE_PREFIX ) ) === self::GO_PRO_TITLE_PREFIX ) { $config['welcome'] = []; } return $config; } private function is_request_from_theme_admin_page(): bool { $referer = wp_get_referer(); if ( ! $referer ) { return false; } parse_str( (string) wp_parse_url( $referer, PHP_URL_QUERY ), $query_args ); return in_array( $query_args['page'] ?? '', self::THEME_SLUGS ); } public function ajax_dismiss_banner(): void { try { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); if ( ! $this->is_user_allowed() ) { wp_send_json_error( 'Permission denied', 403 ); } User::set_introduction_viewed( [ 'introductionKey' => self::DISMISS_KEY ] ); wp_send_json_success(); } catch ( \Exception $e ) { wp_send_json_error( 'Failed to dismiss banner', 500 ); } } private function enqueue_assets( array $placement ): void { $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_script( self::SCRIPT_HANDLE, ELEMENTOR_ASSETS_URL . 'js/' . self::SCRIPT_HANDLE . $min_suffix . '.js', [ 'wp-util', 'elementor-common' ], ELEMENTOR_VERSION, true ); wp_set_script_translations( self::SCRIPT_HANDLE, 'elementor' ); wp_localize_script( self::SCRIPT_HANDLE, self::OBJECT_NAME, [ 'nonce' => wp_create_nonce( self::NONCE_ACTION ), 'action' => self::AJAX_ACTION, 'placement' => $placement, ] ); $this->enqueue_styles(); } private function enqueue_styles(): void { wp_enqueue_style( self::STYLE_HANDLE, ELEMENTOR_ASSETS_URL . 'css/modules/promotions/conversion-banner.css', [], ELEMENTOR_VERSION ); } private function print_banner_markup( bool $dismissable ): void { $banner = $this->get_banner_config(); ?> <div class="e-conversion-banner__paper"> <?php if ( $dismissable ) : ?> <button type="button" class="e-conversion-banner__dismiss notice-dismiss"> <span class="screen-reader-text"><?php echo esc_html__( 'Dismiss this notice.', 'elementor' ); ?></span> </button> <?php endif; ?> <div class="e-conversion-banner__content"> <h2 class="e-conversion-banner__title"><?php echo esc_html( $banner['title'] ); ?></h2> <p class="e-conversion-banner__text"><?php echo esc_html( $banner['text'] ); ?></p> <div class="e-conversion-banner__actions"> <?php foreach ( $banner['buttons'] as $button ) : ?> <a class="e-conversion-banner__button button button-primary" href="<?php echo esc_url( $button['link'] ); ?>" target="<?php echo esc_attr( $button['target'] ?? '_self' ); ?>" ><?php echo esc_html( $button['text'] ); ?></a> <?php endforeach; ?> </div> </div> <?php if ( ! empty( $banner['image']['src'] ) ) : ?> <img class="e-conversion-banner__image" src="<?php echo esc_url( $banner['image']['src'] ); ?>" alt="<?php echo esc_attr( $banner['image']['alt'] ); ?>" /> <?php endif; ?> </div> <?php } private function get_banner_config(): array { if ( Utils::is_sale_time() ) { return $this->get_birthday_banner_config(); } return [ 'title' => esc_html__( 'Go Pro, Go Limitless', 'elementor' ), 'text' => esc_html__( 'Unlock the theme builder, popup builder, 100+ widgets and more advanced tools to take your website to the next level.', 'elementor' ), 'buttons' => [ [ 'text' => esc_html__( 'Upgrade Now', 'elementor' ), 'link' => Go_Pro_Promotion_Item::get_url(), 'target' => '_blank', ], ], 'image' => [ 'src' => '', 'alt' => esc_html__( 'Upgrade to Elementor Pro', 'elementor' ), ], ]; } private function get_birthday_banner_config(): array { return [ 'title' => esc_html__( 'Celebrate 10 years of Elementor', 'elementor' ), 'text' => esc_html__( 'Upgrade your workflow with more capabilities for less. Offer ends June 17.', 'elementor' ), 'buttons' => [ [ 'text' => esc_html__( 'Get Discounts', 'elementor' ), 'link' => self::BIRTHDAY_PROMOTION_URL, 'target' => '_blank', ], ], 'image' => [ 'src' => ELEMENTOR_ASSETS_URL . 'images/decade-birthday.png', 'alt' => esc_html__( 'Celebrate 10 years of Elementor', 'elementor' ), ], ]; } public static function should_display_banner(): bool { return self::is_user_allowed() && self::should_display(); } private function get_active_placement(): array { $current_screen = get_current_screen(); if ( ! $current_screen ) { return []; } $allowed_pages = $this->get_allowed_admin_pages(); return $allowed_pages[ $current_screen->id ] ?? []; } private static function should_display(): bool { return ! Utils::has_pro() && ! self::is_dismissed(); } private static function is_user_allowed(): bool { return current_user_can( 'manage_options' ); } private static function is_dismissed(): bool { return (bool) User::get_introduction_meta( self::DISMISS_KEY ); } private function get_allowed_admin_pages(): array { $default = [ 'selector' => self::DEFAULT_SELECTOR ]; return [ 'dashboard' => [ 'selector' => '#wpbody #wpbody-content .wrap h1' ], 'toplevel_page_elementor' => [ 'selector' => '#e-home-screen', 'before' => true, ], 'elementor_page_elementor-settings' => $default, 'elementor_page_elementor-tools' => $default, 'elementor_page_elementor-role-manager' => $default, 'elementor_page_elementor-element-manager' => [ 'selector' => '.wrap h1, .wrap h3.wp-heading-inline', ], 'elementor_page_elementor-system-info' => [ 'selector' => '#wpbody #wpbody-content #elementor-system-info .elementor-system-info-header', 'before' => true, ], 'elementor_library_page_e-floating-buttons' => [ 'selector' => '#wpbody-content .e-landing-pages-empty, .wrap h2', 'before' => true, ], 'edit-e-floating-buttons' => $default, 'edit-elementor_library' => [ 'selector' => self::DEFAULT_SELECTOR, 'before' => true, ], 'edit-elementor_library_category' => $default, 'themes' => $default, 'nav-menus' => $default, 'theme-editor' => $default, 'plugins' => $default, 'plugin-install' => $default, 'plugin-editor' => $default, ]; } } promotions/widgets/collection-loop-widget-promotion.php 0000644 00000003360 15252521350 0017545 0 ustar 00 <?php namespace Elementor\Modules\Promotions\Widgets; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Collection_Loop_Widget_Promotion { private const LOOP_PROMOTION_IMAGE_URL = 'https://assets.elementor.com/packages/v1/images/Loop_grid_promotion.png'; public function register(): void { add_filter( 'elementor/editor/localize_settings', [ $this, 'add_promotion_data' ] ); } private function is_active(): bool { return Plugin::$instance->experiments->is_feature_active( 'e_atomic_elements' ); } public function add_promotion_data( array $settings ): array { if ( ! current_user_can( 'manage_options' ) || ! $this->is_active() ) { return $settings; } if ( ! isset( $settings['atomicWidgetPromotions'] ) ) { $settings['atomicWidgetPromotions'] = []; } $settings['atomicWidgetPromotions'][] = [ 'type' => 'collection-loop', 'cardType' => 'atomic', 'widgets' => $this->get_widgets(), 'content' => $this->get_promotion_content(), ]; return $settings; } private function get_widgets(): array { return [ [ 'name' => 'e-collection-loop', 'title' => __( 'Loop', 'elementor' ), 'icon' => 'eicon-loop-widget', 'categories' => '["v4-elements"]', ], ]; } private function get_promotion_content(): array { return [ 'title' => __( 'Loop', 'elementor' ), 'content' => __( 'Upgrade to connect custom layouts directly to your site database, seamlessly rendering dynamic content queries that engage your site visitors.', 'elementor' ), 'ctaText' => __( 'Upgrade now', 'elementor' ), 'image' => self::LOOP_PROMOTION_IMAGE_URL, 'widgetCtaUrl' => 'https://go.elementor.com/go-pro-loop-modal/', 'sectionCtaUrl' => 'https://go.elementor.com/go-pro-loop-section/', ]; } } promotions/widgets/pro-widget-promotion.php 0000644 00000005500 15252521350 0015241 0 ustar 00 <?php namespace Elementor\Modules\Promotions\Widgets; use Elementor\Widget_Base; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Pro_Widget_Promotion extends Widget_Base { private $widget_data; public function hide_on_search() { return true; } public function show_in_panel() { return false; } public function get_name() { return $this->widget_data['widget_name']; } public function get_title() { return $this->widget_data['widget_title']; } public function get_categories() { return [ 'general', 'pro-elements' ]; } public function on_import( $element ) { $element['settings']['__should_import'] = true; return $element; } protected function register_controls() {} protected function render() { if ( $this->is_editor_render() ) { $this->render_promotion(); } else { $this->render_empty_content(); } } private function is_editor_render(): bool { return \Elementor\Plugin::$instance->editor->is_edit_mode(); } private function render_promotion() { $promotion = Filtered_Promotions_Manager::get_filtered_promotion_data( [ 'image_url' => esc_url( $this->get_promotion_image_url() ), 'text' => sprintf( /* translators: %s: Widget title. */ esc_html__( 'This result includes the Elementor Pro %s widget. Upgrade now to unlock it and grow your web creation toolkit.', 'elementor' ), esc_html( $this->widget_data['widget_title'] ) ), 'upgrade_url' => esc_url( 'https://go.elementor.com/go-pro-element-pro/' ), ], 'elementor/pro-widget/promotion', 'upgrade_url' ); ?> <div class="e-container"> <span class="e-badge"><i class="eicon-upgrade-crown-full" aria-hidden="true"></i> <?php echo esc_html__( 'Pro', 'elementor' ); ?></span> <p> <img src="<?php echo esc_url( $promotion['image_url'] ); ?>" loading="lazy" alt="Go Pro"> <?php echo esc_html( $promotion['text'] ); ?> </p> <div class="e-actions"> <a href="#" class="e-btn e-btn-txt e-promotion-delete"><?php echo esc_html__( 'Remove', 'elementor' ); ?></a> <a href="<?php echo esc_url( $promotion['upgrade_url'] ); ?>" rel="noreferrer" target="_blank" class="e-btn go-pro elementor-clickable e-promotion-go-pro"><?php echo esc_html__( 'Go Pro', 'elementor' ); ?></a> </div> </div> <?php } private function get_promotion_image_url(): string { return ELEMENTOR_ASSETS_URL . 'images/go-pro.svg'; } private function render_empty_content() { echo ' '; } protected function content_template() {} public function __construct( $data = [], $args = null ) { $this->widget_data = [ 'widget_name' => $args['widget_name'], 'widget_title' => $args['widget_title'], ]; parent::__construct( $data, $args ); } public function render_plain_content( $instance = [] ) {} } promotions/widgets/atomic-form-widget-promotion.php 0000644 00000004606 15252521350 0016664 0 ustar 00 <?php namespace Elementor\Modules\Promotions\Widgets; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Atomic_Form_Widget_Promotion { public function register(): void { add_filter( 'elementor/editor/localize_settings', [ $this, 'add_promotion_data' ] ); } private function is_active(): bool { return Plugin::$instance->experiments->is_feature_active( 'e_atomic_elements' ); } public function add_promotion_data( array $settings ): array { if ( ! current_user_can( 'manage_options' ) || ! $this->is_active() ) { return $settings; } if ( ! isset( $settings['atomicWidgetPromotions'] ) ) { $settings['atomicWidgetPromotions'] = []; } $settings['atomicWidgetPromotions'][] = [ 'type' => 'atomic-form', 'cardType' => 'atomic', 'widgets' => $this->get_widgets(), 'content' => $this->get_promotion_content(), ]; return $settings; } private function get_widgets(): array { return [ [ 'name' => 'e-form', 'title' => __( 'Atomic Form', 'elementor' ), 'icon' => 'eicon-atomic-form', 'categories' => '["atomic-form"]', ], [ 'name' => 'e-form-input', 'title' => __( 'Input', 'elementor' ), 'icon' => 'eicon-atomic-input', 'categories' => '["atomic-form"]', ], [ 'name' => 'e-form-label', 'title' => __( 'Label', 'elementor' ), 'icon' => 'eicon-atomic-label', 'categories' => '["atomic-form"]', ], [ 'name' => 'e-form-textarea', 'title' => __( 'Text area', 'elementor' ), 'icon' => 'eicon-atomic-text-area', 'categories' => '["atomic-form"]', ], [ 'name' => 'e-form-submit-button', 'title' => __( 'Submit button', 'elementor' ), 'icon' => 'eicon-atomic-submit-button', 'categories' => '["atomic-form"]', ], [ 'name' => 'e-form-checkbox', 'title' => __( 'Checkbox', 'elementor' ), 'icon' => 'eicon-atomic-checkbox', 'categories' => '["atomic-form"]', ], ]; } private function get_promotion_content(): array { return [ 'title' => __( 'Atomic form', 'elementor' ), 'content' => __( 'Design fully customized forms to capture leads without compromising on style.', 'elementor' ), 'ctaText' => __( 'Upgrade now', 'elementor' ), 'animation' => 'atomic-form-animation', 'widgetCtaUrl' => 'https://go.elementor.com/go-pro-atomic-form-modal/', 'sectionCtaUrl' => 'https://go.elementor.com/go-pro-atomic-form-section/', ]; } } promotions/prop-types/promotion-prop-type.php 0000644 00000001622 15252521350 0015574 0 ustar 00 <?php namespace Elementor\Modules\Promotions\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Promotion_Prop_Type extends Array_Prop_Type { private string $key; public function __construct( string $key = 'promotion' ) { $this->key = $key; parent::__construct(); } public static function make( string $key = 'promotion' ): self { return new static( $key ); } public static function get_key(): string { return 'promotion'; } public function jsonSerialize(): array { $data = parent::jsonSerialize(); $data['key'] = $this->key; return $data; } protected function define_item_type(): Prop_Type { return String_Prop_Type::make(); } } promotions/module.php 0000644 00000023334 15252521350 0010760 0 ustar 00 <?php namespace Elementor\Modules\Promotions; use Elementor\Api; use Elementor\Controls_Manager; use Elementor\Core\Base\Module as Base_Module; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Modules\Promotions\AdminMenuItems\Editor_One_Custom_Code_Menu; use Elementor\Modules\Promotions\AdminMenuItems\Editor_One_Custom_Elements_Menu; use Elementor\Modules\Promotions\AdminMenuItems\Editor_One_Fonts_Menu; use Elementor\Modules\Promotions\AdminMenuItems\Editor_One_Icons_Menu; use Elementor\Modules\Promotions\AdminMenuItems\Editor_One_Popups_Menu; use Elementor\Modules\Promotions\AdminMenuItems\Editor_One_Submissions_Menu; use Elementor\Modules\Promotions\AdminMenuItems\Go_Pro_Promotion_Item; use Elementor\Modules\Promotions\Controls\Atomic_Promotion_Control; use Elementor\Modules\Promotions\Conversion_Banner; use Elementor\Modules\Promotions\Pointers\Birthday; use Elementor\Modules\Promotions\Pointers\Black_Friday; use Elementor\Modules\Promotions\PropTypes\Promotion_Prop_Type; use Elementor\Modules\Promotions\Widgets\Atomic_Form_Widget_Promotion; use Elementor\Modules\Promotions\Widgets\Collection_Loop_Widget_Promotion; use Elementor\Widgets_Manager; use Elementor\Utils; use Elementor\Includes\EditorAssetsAPI; use Elementor\Plugin; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends Base_Module { const ADMIN_MENU_PRIORITY = 100; const ADMIN_MENU_PROMOTIONS_PRIORITY = 120; public static function is_active() { return ! Utils::has_pro() || ! Utils::is_license_active(); } public function get_name() { return 'promotions'; } public function __construct() { parent::__construct(); add_filter( 'elementor/editor/localize_settings', [ $this, 'add_v4_promotions_data' ] ); if ( Utils::has_pro() ) { add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue_react_data' ] ); $this->register_atomic_promotions(); return; } add_action( 'admin_init', function () { $this->handle_external_redirects(); } ); add_action( 'elementor/editor-one/menu/register', function ( Menu_Data_Provider $menu_data_provider ) { $this->register_editor_one_menu_items( $menu_data_provider ); } ); if ( Utils::is_sale_time() ) { add_filter( 'add_menu_classes', [ $this, 'override_one_menu_upgrade_label_during_sale' ] ); } add_action( 'elementor/widgets/register', function( Widgets_Manager $manager ) { foreach ( Api::get_promotion_widgets() as $widget_data ) { $manager->register( new Widgets\Pro_Widget_Promotion( [], [ 'widget_name' => $widget_data['name'], 'widget_title' => $widget_data['title'], ] ) ); } } ); if ( Birthday::should_display_notice() ) { new Birthday(); } if ( Black_Friday::should_display_notice() ) { new Black_Friday(); } if ( Conversion_Banner::should_display_banner() ) { new Conversion_Banner(); } add_filter( 'elementor/editor/localize_settings', [ $this, 'add_editing_panel_sticky_promotion' ] ); add_action( 'elementor/controls/register', function ( Controls_Manager $controls_manager ) { $controls_manager->register( new Controls\Promotion_Control() ); } ); add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue_react_data' ] ); add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue_editor_v4_alphachip' ] ); $this->register_atomic_promotions(); } private function handle_external_redirects() { $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); if ( empty( $page ) ) { return; } if ( in_array( $page, [ 'go_elementor_pro', 'elementor-one-upgrade' ], true ) ) { wp_redirect( Go_Pro_Promotion_Item::get_url() ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect die; } } public function override_one_menu_upgrade_label_during_sale( $menu ) { global $submenu; $parent_slug = Menu_Config::ELEMENTOR_HOME_MENU_SLUG; $upgrade_slug = 'elementor-one-upgrade'; if ( empty( $submenu[ $parent_slug ] ) ) { return $menu; } foreach ( $submenu[ $parent_slug ] as &$item ) { if ( isset( $item[2] ) && $upgrade_slug === $item[2] ) { $item[0] = esc_html__( 'Sale!', 'elementor' ) . '<br />' . esc_html__( 'Upgrade Now', 'elementor' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited break; } } return $menu; } private function register_editor_one_menu_items( Menu_Data_Provider $menu_data_provider ) { $menu_data_provider->register_menu( new Editor_One_Custom_Elements_Menu() ); $menu_data_provider->register_menu( new Editor_One_Submissions_Menu() ); $menu_data_provider->register_menu( new Editor_One_Fonts_Menu() ); $menu_data_provider->register_menu( new Editor_One_Icons_Menu() ); $menu_data_provider->register_menu( new Editor_One_Custom_Code_Menu() ); $menu_data_provider->register_menu( new Editor_One_Popups_Menu() ); } public function enqueue_react_data(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_script( 'e-react-promotions', ELEMENTOR_ASSETS_URL . 'js/e-react-promotions' . $min_suffix . '.js', [ 'react', 'react-dom', 'backbone-marionette', 'elementor-editor-modules', 'elementor-v2-ui', 'elementor-v2-icons', ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'e-react-promotions', 'elementor' ); wp_localize_script( 'e-react-promotions', 'elementorPromotionsData', $this->get_app_js_config() ); } public function enqueue_editor_v4_alphachip(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_script( 'editor-v4-opt-in-alphachip', ELEMENTOR_ASSETS_URL . 'js/editor-v4-opt-in-alphachip' . $min_suffix . '.js', [ 'react', 'react-dom', 'elementor-common', 'elementor-v2-ui', ], ELEMENTOR_VERSION, true ); } private function get_app_js_config(): array { $editor_assets_api = new EditorAssetsAPI( $this->get_api_config() ); $promotion_data = new PromotionData( $editor_assets_api ); return $promotion_data->get_promotion_data(); } private function get_api_config(): array { return [ EditorAssetsAPI::ASSETS_DATA_URL => 'https://assets.elementor.com/free-to-pro-upsell/v1/free-to-pro-upsell.json', EditorAssetsAPI::ASSETS_DATA_TRANSIENT_KEY => '_elementor_free_to_pro_upsell', EditorAssetsAPI::ASSETS_DATA_KEY => 'free-to-pro-upsell', ]; } public function add_editing_panel_sticky_promotion( array $settings ): array { if ( ! Plugin::$instance->experiments->is_feature_active( 'e_panel_promotions' ) ) { return $settings; } $settings['editingPanelStickyPromotion'] = Filtered_Promotions_Manager::get_editor_panel_sticky_promotion(); return $settings; } public function add_v4_promotions_data( array $settings ): array { if ( ! current_user_can( 'manage_options' ) ) { return $settings; } $editor_assets_api = new EditorAssetsAPI( $this->get_v4_promotions_api_config() ); $promotion_data = new PromotionData( $editor_assets_api ); $settings['v4Promotions'] = $promotion_data->get_v4_promotions_data(); return $settings; } private function get_v4_promotions_api_config(): array { return [ EditorAssetsAPI::ASSETS_DATA_URL => 'https://assets.elementor.com/packages/v1/promotions.json', EditorAssetsAPI::ASSETS_DATA_TRANSIENT_KEY => '_elementor_v4_promotions', EditorAssetsAPI::ASSETS_DATA_KEY => 'promotions', ]; } private function is_atomic_widgets_active(): bool { return Plugin::$instance->experiments->is_feature_active( 'e_atomic_elements' ); } private function get_atomic_promotion_configs(): array { return [ [ 'key' => 'attributes', 'label' => __( 'Attributes', 'elementor' ), 'section' => 'settings', 'priority' => 40, ], [ 'key' => 'display-conditions', 'label' => __( 'Display Conditions', 'elementor' ), 'section' => 'settings', 'priority' => 50, ], ]; } private function register_atomic_promotions(): void { add_action( 'elementor/init', function() { if ( ! $this->is_atomic_widgets_active() ) { return; } add_filter( 'elementor/atomic-widgets/props-schema', [ $this, 'inject_atomic_promotion_props' ] ); foreach ( $this->get_atomic_promotion_configs() as $config ) { add_filter( 'elementor/atomic-widgets/controls', fn( array $controls, $element ) => $this->inject_atomic_promotion_control( $controls, $element, $config ), $config['priority'], 2 ); } } ); ( new Atomic_Form_Widget_Promotion() )->register(); ( new Collection_Loop_Widget_Promotion() )->register(); } public function inject_atomic_promotion_props( array $schema ): array { foreach ( $this->get_atomic_promotion_configs() as $config ) { $key = $config['key']; if ( isset( $schema[ $key ] ) ) { continue; } $schema[ $key ] = Promotion_Prop_Type::make( $key ); } return $schema; } protected function inject_atomic_promotion_control( array $element_controls, $atomic_element, array $config ): array { $key = $config['key']; $schema = $atomic_element::get_props_schema(); if ( ! array_key_exists( $key, $schema ) ) { return $element_controls; } foreach ( $element_controls as $item ) { if ( ! ( $item instanceof \Elementor\Modules\AtomicWidgets\Controls\Section ) ) { continue; } if ( $item->get_id() !== $config['section'] ) { continue; } $control = Atomic_Promotion_Control::make( $key ) ->set_label( $config['label'] ) ->set_meta( [ 'topDivider' => true, ] ); $item->add_item( $control ); break; } return $element_controls; } } promotions/admin-menu-items/base-promotion-template.php 0000644 00000006042 15252521350 0017410 0 ustar 00 <?php namespace Elementor\Modules\Promotions\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Settings; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Promotion_Template implements Admin_Menu_Item_With_Page { abstract protected function get_promotion_title(): string; abstract protected function get_cta_url(): string; abstract protected function get_content_lines(): array; abstract protected function get_video_url(): string; public function is_visible(): bool { return true; } public function get_parent_slug(): string { return Settings::PAGE_ID; } public function get_capability(): string { return 'manage_options'; } protected function get_cta_text() { return esc_html__( 'Upgrade Now', 'elementor' ); } /** * Should the promotion have a side note. * * @return string */ protected function get_side_note(): string { return ''; } private function get_lines() { ob_start(); if ( ! empty( $this->get_content_lines() ) ) { ?> <ul> <?php foreach ( $this->get_content_lines() as $item ) { ?> <li><?php Utils::print_unescaped_internal_string( $item ); ?></li> <?php } ?> </ul> <?php } return ob_get_clean(); } public function render() { $promotion_data = $this->get_promotion_data(); ?> <div class="e-feature-promotion"> <div class="e-feature-promotion_data"> <h3><?php Utils::print_unescaped_internal_string( $promotion_data['promotion_title'] ); ?></h3> <?php Utils::print_unescaped_internal_string( $promotion_data['lines'] ); ?> <a class="elementor-button go-pro" href="<?php echo esc_url( $promotion_data['cta_url'] ); ?>" target="_blank"> <?php Utils::print_unescaped_internal_string( $promotion_data['cta_text'] ); ?> </a> <?php if ( ! empty( $promotion_data['side_note'] ) ) { ?> <div class="side-note"> <p><?php Utils::print_unescaped_internal_string( $promotion_data['side_note'] ); ?></p> </div> <?php } ?> </div> <iframe class="e-feature-promotion_iframe" src="<?php Utils::print_unescaped_internal_string( $promotion_data['video_url'] ); ?>&rel=0" title="Elementor" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> </div> <?php } /** * @return array|null */ private function get_promotion_data(): ?array { return Filtered_Promotions_Manager::get_filtered_promotion_data( $this->build_promotion_data_array(), 'elementor/' . $this->get_name() . '/custom_promotion', 'cta_url' ); } /** * @return array */ private function build_promotion_data_array(): array { return [ 'promotion_title' => $this->get_promotion_title(), 'cta_url' => $this->get_cta_url(), 'cta_text' => $this->get_cta_text(), 'video_url' => $this->get_video_url(), 'lines' => $this->get_lines(), 'side_note' => $this->get_side_note(), ]; } } promotions/admin-menu-items/editor-one-popups-menu.php 0000644 00000003753 15252521350 0017202 0 ustar 00 <?php namespace Elementor\Modules\Promotions\AdminMenuItems; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Popups_Menu extends Base_Promotion_Item implements Menu_Item_Interface { private array $promotion_data; public function __construct() { $this->promotion_data = [ 'title' => esc_html__( 'Get Popup Builder', 'elementor' ), 'content' => esc_html__( 'The Popup Builder lets you take advantage of all the amazing features in Elementor, so you can build beautiful & highly converting popups. Get Elementor Pro and start designing your popups today.', 'elementor' ), 'action_button' => [ 'text' => esc_html__( 'Upgrade Now', 'elementor' ), 'url' => 'https://go.elementor.com/go-pro-popup-builder/', ], ]; $this->promotion_data = Filtered_Promotions_Manager::get_filtered_promotion_data( $this->promotion_data, 'elementor/templates/popup', 'action_button', 'url' ); } public function get_position(): int { return 50; } public function get_slug(): string { return 'popup_templates'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function get_label(): string { return esc_html__( 'Popups', 'elementor' ); } public function get_group_id(): string { return Menu_Config::TEMPLATES_GROUP_ID; } public function get_name(): string { return 'popups'; } public function get_page_title() { return esc_html__( 'Popups', 'elementor' ); } public function get_promotion_title() { return $this->promotion_data['title']; } public function get_promotion_description() { return $this->promotion_data['content']; } public function get_cta_url() { return $this->promotion_data['action_button']['url']; } public function get_cta_text() { return $this->promotion_data['action_button']['text']; } } promotions/admin-menu-items/base-promotion-item.php 0000644 00000003752 15252521350 0016540 0 ustar 00 <?php namespace Elementor\Modules\Promotions\AdminMenuItems; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Modules\Promotions\AdminMenuItems\Interfaces\Promotion_Menu_Item; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Base_Promotion_Item implements Promotion_Menu_Item { public function get_name(): string { return 'base_promotion'; } public function is_visible(): bool { return true; } public function get_parent_slug(): string { return Settings::PAGE_ID; } public function get_capability(): string { return 'manage_options'; } public function get_cta_text() { return esc_html__( 'Upgrade Now', 'elementor' ); } public function get_image_url() { return ELEMENTOR_ASSETS_URL . 'images/go-pro-wp-dashboard.svg'; } public function get_promotion_description() { return ''; } public function render() { $config = [ 'title' => $this->get_promotion_title(), 'description' => $this->get_promotion_description(), 'image' => $this->get_image_url(), 'upgrade_text' => $this->get_cta_text(), 'upgrade_url' => $this->get_cta_url(), ]; $config = Filtered_Promotions_Manager::get_filtered_promotion_data( $config, 'elementor/' . $this->get_name() . '/custom_promotion', 'upgrade_url' ); $description = $config['description'] ?? $this->get_promotion_description() ?? ''; ?> <div class="wrap"> <div class="elementor-blank_state"> <img src="<?php echo esc_url( $config['image'] ?? $this->get_image_url() ); ?>" loading="lazy" /> <h3><?php echo esc_html( $config['title'] ?? $this->get_promotion_title() ); ?></h3> <?php if ( $description ) : ?> <p><?php echo esc_html( $description ); ?></p> <?php endif; ?> <a class="elementor-button go-pro" href="<?php echo esc_url( $config['upgrade_url'] ?? $this->get_cta_url() ); ?>"> <?php echo esc_html( $config['upgrade_text'] ?? $this->get_cta_text() ); ?> </a> </div> </div> <?php } } promotions/admin-menu-items/interfaces/promotion-menu-item.php 0000644 00000000730 15252521350 0020706 0 ustar 00 <?php namespace Elementor\Modules\Promotions\AdminMenuItems\Interfaces; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Promotion_Menu_Item extends Admin_Menu_Item_With_Page { public function get_image_url(); public function get_promotion_title(); public function get_promotion_description(); public function get_cta_text(); public function get_cta_url(); } promotions/admin-menu-items/editor-one-custom-code-menu.php 0000644 00000003433 15252521350 0020071 0 ustar 00 <?php namespace Elementor\Modules\Promotions\AdminMenuItems; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Custom_Code_Menu extends Base_Promotion_Template implements Menu_Item_Interface { public function get_position(): int { return 40; } public function get_slug(): string { return 'elementor_custom_code'; } public function get_parent_slug(): string { return 'elementor_custom_code'; } public function get_label(): string { return esc_html__( 'Code', 'elementor' ); } public function get_group_id(): string { return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID; } public function get_name() { return 'custom_code'; } public function get_page_title() { return esc_html__( 'Custom Code', 'elementor' ); } protected function get_promotion_title(): string { return esc_html__( 'Enjoy Creative Freedom with Custom Code', 'elementor' ); } protected function get_content_lines(): array { return [ esc_html__( 'Add Custom Code snippets anywhere on your website, including the header or footer to measure your page\'s performance*', 'elementor' ), esc_html__( 'Use Custom Code to create sophisticated custom interactions to engage visitors', 'elementor' ), esc_html__( 'Leverage Elementor AI to instantly generate Custom Code for Elementor', 'elementor' ), ]; } protected function get_side_note(): string { return esc_html__( '* Requires an Advanced subscription or higher', 'elementor' ); } protected function get_cta_url(): string { return 'https://go.elementor.com/go-pro-custom-code/'; } protected function get_video_url(): string { return 'https://www.youtube-nocookie.com/embed/IOovQd1hJUg?si=xeBJ_mRZxRH1l5O6'; } } promotions/admin-menu-items/editor-one-icons-menu.php 0000644 00000003172 15252521350 0016762 0 ustar 00 <?php namespace Elementor\Modules\Promotions\AdminMenuItems; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Icons_Menu extends Base_Promotion_Template implements Menu_Item_Interface { public function get_position(): int { return 20; } public function get_slug(): string { return 'elementor_custom_icons'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function get_label(): string { return esc_html__( 'Icons', 'elementor' ); } public function get_group_id(): string { return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID; } public function get_name() { return 'custom_icons'; } public function get_page_title() { return esc_html__( 'Custom Icons', 'elementor' ); } protected function get_promotion_title(): string { return sprintf( /* translators: %s: `<br>` tag. */ esc_html__( 'Enjoy creative freedom %s with Custom Icons', 'elementor' ), '<br />' ); } protected function get_content_lines(): array { return [ sprintf( /* translators: %s: `<br>` tag. */ esc_html__( 'Expand your icon library beyond FontAwesome and add icon %s libraries of your choice', 'elementor' ), '<br />' ), esc_html__( 'Add any icon, anywhere on your website', 'elementor' ), ]; } protected function get_cta_url(): string { return 'https://go.elementor.com/go-pro-custom-icons/'; } protected function get_video_url(): string { return 'https://www.youtube-nocookie.com/embed/PsowinxDWfM?si=SV9Z3TLz3_XEy5C6'; } } promotions/admin-menu-items/editor-one-submissions-menu.php 0000644 00000004057 15252521350 0020230 0 ustar 00 <?php namespace Elementor\Modules\Promotions\AdminMenuItems; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Third_Level_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Submissions_Menu extends Base_Promotion_Template implements Menu_Item_Third_Level_Interface { public function get_position(): int { return 70; } public function get_slug(): string { return 'e-form-submissions'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function get_label(): string { return esc_html__( 'Submissions', 'elementor' ); } public function get_group_id(): string { return Menu_Config::EDITOR_GROUP_ID; } public function get_icon(): string { return 'send'; } public function has_children(): bool { return false; } public function get_name() { return 'submissions'; } public function get_page_title() { return esc_html__( 'Submissions', 'elementor' ); } public function get_promotion_title(): string { return sprintf( /* translators: %s: `<br>` tag. */ esc_html__( 'Create Forms and Collect Leads %s with Elementor Pro', 'elementor' ), '<br>' ); } protected function get_content_lines(): array { return [ esc_html__( 'Create single or multi-step forms to engage and convert visitors', 'elementor' ), esc_html__( 'Use any field to collect the information you need', 'elementor' ), esc_html__( 'Integrate your favorite marketing software*', 'elementor' ), esc_html__( 'Collect lead submissions directly within your WordPress Admin to manage, analyze and perform bulk actions on the submitted lead*', 'elementor' ), ]; } protected function get_cta_url(): string { return 'https://go.elementor.com/go-pro-submissions/'; } protected function get_video_url(): string { return 'https://www.youtube-nocookie.com/embed/LNfnwba9C-8?si=JLHk3UAexnvTfU1a'; } protected function get_side_note(): string { return esc_html__( '* Requires an Advanced subscription or higher', 'elementor' ); } } promotions/admin-menu-items/editor-one-custom-elements-menu.php 0000644 00000001754 15252521350 0020777 0 ustar 00 <?php namespace Elementor\Modules\Promotions\AdminMenuItems; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Third_Level_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Custom_Elements_Menu implements Menu_Item_Third_Level_Interface { public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'Custom Elements', 'elementor' ); } public function get_position(): int { return 80; } public function get_slug(): string { return 'elementor-custom-elements'; } public function get_icon(): string { return 'adjustments'; } public function get_group_id(): string { return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID; } public function has_children(): bool { return true; } } promotions/admin-menu-items/editor-one-fonts-menu.php 0000644 00000003076 15252521350 0017003 0 ustar 00 <?php namespace Elementor\Modules\Promotions\AdminMenuItems; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Fonts_Menu extends Base_Promotion_Template implements Menu_Item_Interface { public function get_position(): int { return 10; } public function get_slug(): string { return 'elementor_custom_fonts'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function get_label(): string { return esc_html__( 'Fonts', 'elementor' ); } public function get_group_id(): string { return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID; } public function get_name() { return 'custom_fonts'; } public function get_page_title() { return esc_html__( 'Custom Fonts', 'elementor' ); } protected function get_promotion_title(): string { return esc_html__( 'Stay on brand with a Custom Font', 'elementor' ); } protected function get_content_lines(): array { return [ esc_html__( 'Upload any font to keep your website true to your brand', 'elementor' ), sprintf( /* translators: %s: br */ esc_html__( 'Remain GDPR compliant with Custom Fonts that let you disable %s Google Fonts from your website', 'elementor' ), '<br />' ), ]; } protected function get_cta_url(): string { return 'https://go.elementor.com/go-pro-custom-fonts/'; } protected function get_video_url(): string { return 'https://www.youtube-nocookie.com/embed/j_guJkm28eY?si=cdd2TInwuGDTtCGD'; } } promotions/admin-menu-items/go-pro-promotion-item.php 0000644 00000003045 15252521350 0017024 0 ustar 00 <?php namespace Elementor\Modules\Promotions\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Settings; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Go_Pro_Promotion_Item implements Admin_Menu_Item_With_Page { const URL = 'https://go.elementor.com/go-pro-upgrade-one-wp-menu/'; public function get_name() { return 'admin_menu_promo'; } public function is_visible() { return true; } public function get_parent_slug() { return Settings::PAGE_ID; } public function get_label() { $upgrade_text = esc_html__( 'Upgrade', 'elementor' ); if ( Utils::is_sale_time() ) { $upgrade_text = esc_html__( 'Upgrade Sale Now', 'elementor' ); } return apply_filters( 'elementor/admin_menu/custom_promotion', [ 'upgrade_text' => $upgrade_text ] )['upgrade_text'] ?? $upgrade_text; } public function get_page_title() { return ''; } public function get_capability() { return 'manage_options'; } public static function get_url() { $url = self::URL; $filtered_url = apply_filters( 'elementor/admin_menu/custom_promotion', [ 'upgrade_url' => $url ] )['upgrade_url'] ?? ''; $promotion_data = Filtered_Promotions_Manager::get_filtered_promotion_data( [ 'upgrade_url' => $filtered_url ], 'elementor/admin_menu/custom_promotion', 'upgrade_url' ); return $promotion_data ['upgrade_url']; } public function render() { // Redirects from the module on `admin_init`. die; } } promotions/controls/promotion-control.php 0000644 00000002354 15252521350 0015041 0 ustar 00 <?php namespace Elementor\Modules\Promotions\Controls; use Elementor\Base_Data_Control; class Promotion_Control extends Base_Data_Control { const TYPE = 'promotion_control'; public function get_type() { return static::TYPE; } public function content_template() { ?> <div data-promotion="{{{ data.name }}}" class="elementor-control-type-switcher elementor-label-inline e-control-promotion__wrapper"> <div class="elementor-control-content"> <div class="elementor-control-field"> <# if ( data.label ) {#> <label for="<?php $this->print_control_uid(); ?>" class="elementor-control-title">{{{ data.label }}}</label> <# } #> <span class="e-control-promotion__lock-wrapper"> <i class="eicon-upgrade-crown-full"></i> </span> <div class="elementor-control-input-wrapper"> <label class="elementor-switch elementor-control-unit-2 e-control-promotion-switch"> <input type="checkbox" class="elementor-switch-input" disabled> <span class="elementor-switch-label" data-off="Off"></span> <span class="elementor-switch-handle"></span> </label> </div> <div class="e-promotion-react-wrapper" data-promotion="{{{ data.name }}}"></div> </div> </div> </div> <?php } } promotions/controls/atomic-promotion-control.php 0000644 00000000731 15252521350 0016310 0 ustar 00 <?php namespace Elementor\Modules\Promotions\Controls; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Promotion_Control extends Atomic_Control_Base { public static function make( string $type ): self { return new self( $type ); } public function get_type(): string { return $this->get_bind(); } public function get_props(): array { return []; } } promotions/promotion-data.php 0000644 00000013005 15252521350 0012422 0 ustar 00 <?php namespace Elementor\Modules\Promotions; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Includes\EditorAssetsAPI; use Elementor\Utils; class PromotionData { protected EditorAssetsAPI $editor_assets_api; public function __construct( EditorAssetsAPI $editor_assets_api ) { $this->editor_assets_api = $editor_assets_api; } public function get_promotion_data( $force_request = false ): array { $assets_data = $this->transform_assets_data( $force_request ); return [ Utils::ANIMATED_HEADLINE => $this->get_animated_headline_data( $assets_data ), Utils::VIDEO_PLAYLIST => $this->get_video_playlist_data( $assets_data ), Utils::CTA => $this->get_cta_button_data( $assets_data ), Utils::IMAGE_CAROUSEL => $this->get_image_carousel_data( $assets_data ), Utils::TESTIMONIAL_WIDGET => $this->get_testimonial_widget_data( $assets_data ), ]; } public function get_v4_promotions_data( $force_request = false ): array { $assets_data = $this->editor_assets_api->get_assets_data( $force_request ); if ( empty( $assets_data ) ) { return []; } $promotions = []; foreach ( $assets_data as $item ) { foreach ( $item as $key => $promotion ) { $promotions[ $key ] = [ 'title' => esc_html( $promotion['title'] ?? '' ), 'content' => esc_html( $promotion['content'] ?? '' ), 'ctaUrl' => esc_url( $promotion['ctaUrl'] ?? '' ), 'image' => esc_url( $promotion['image'] ?? '' ), ]; } } return $promotions; } private function transform_assets_data( $force_request = false ) { $assets_data = $this->editor_assets_api->get_assets_data( $force_request ); $transformed_data = []; foreach ( $assets_data as $asset ) { $transformed_data[ $asset['id'] ] = $asset['imageSrc']; } return $transformed_data; } private function get_animated_headline_data( $assets_data ) { $data = [ 'image' => esc_url( $assets_data[ Utils::ANIMATED_HEADLINE ] ?? '' ), 'image_alt' => esc_attr__( 'Upgrade', 'elementor' ), 'title' => esc_html__( 'Bring Headlines to Life', 'elementor' ), 'description' => [ esc_html__( 'Highlight key messages dynamically.', 'elementor' ), esc_html__( 'Apply rotating effects to text.', 'elementor' ), esc_html__( 'Fully customize your headlines.', 'elementor' ), ], 'upgrade_text' => esc_html__( 'Upgrade Now', 'elementor' ), 'upgrade_url' => 'https://go.elementor.com/go-pro-heading-widget-control/', ]; return $this->filter_data( Utils::ANIMATED_HEADLINE, $data ); } private function get_video_playlist_data( $assets_data ) { $data = [ 'image' => esc_url( $assets_data[ Utils::VIDEO_PLAYLIST ] ?? '' ), 'image_alt' => esc_attr__( 'Upgrade', 'elementor' ), 'title' => esc_html__( 'Showcase Video Playlists', 'elementor' ), 'description' => [ esc_html__( 'Embed videos with full control.', 'elementor' ), esc_html__( 'Adjust layout and playback settings.', 'elementor' ), esc_html__( 'Seamlessly customize video appearance.', 'elementor' ), ], 'upgrade_text' => esc_html__( 'Upgrade Now', 'elementor' ), 'upgrade_url' => 'https://go.elementor.com/go-pro-video-widget-control/', ]; return $this->filter_data( Utils::VIDEO_PLAYLIST, $data ); } private function get_cta_button_data( $assets_data ) { $data = [ 'image' => esc_url( $assets_data[ Utils::CTA ] ?? '' ), 'image_alt' => esc_attr__( 'Upgrade', 'elementor' ), 'title' => esc_html__( 'Boost Conversions with CTAs', 'elementor' ), 'description' => [ esc_html__( 'Combine text, buttons, and images.', 'elementor' ), esc_html__( 'Add hover animations and CSS effects.', 'elementor' ), esc_html__( 'Create unique, interactive designs.', 'elementor' ), ], 'upgrade_text' => esc_html__( 'Upgrade Now', 'elementor' ), 'upgrade_url' => 'https://go.elementor.com/go-pro-button-widget-control/', ]; return $this->filter_data( Utils::CTA, $data ); } private function get_image_carousel_data( $assets_data ) { $data = [ 'image' => esc_url( $assets_data[ Utils::IMAGE_CAROUSEL ] ?? '' ), 'image_alt' => esc_attr__( 'Upgrade', 'elementor' ), 'title' => esc_html__( 'Design Custom Carousels', 'elementor' ), 'description' => [ esc_html__( 'Create flexible custom carousels.', 'elementor' ), esc_html__( 'Adjust transitions and animations.', 'elementor' ), esc_html__( 'Showcase multiple items with style.', 'elementor' ), ], 'upgrade_text' => esc_html__( 'Upgrade Now', 'elementor' ), 'upgrade_url' => 'https://go.elementor.com/go-pro-image-carousel-widget-control/', ]; return $this->filter_data( Utils::IMAGE_CAROUSEL, $data ); } private function get_testimonial_widget_data( $assets_data ) { $data = [ 'image' => esc_url( $assets_data[ Utils::TESTIMONIAL_WIDGET ] ?? '' ), 'image_alt' => esc_attr__( 'Upgrade', 'elementor' ), 'title' => esc_html__( 'Upgrade Your Testimonials', 'elementor' ), 'description' => [ esc_html__( 'Display reviews in a rotating carousel.', 'elementor' ), esc_html__( 'Boost credibility with dynamic testimonials.', 'elementor' ), esc_html__( 'Customize layouts for visual appeal.', 'elementor' ), ], 'upgrade_text' => esc_html__( 'Upgrade Now', 'elementor' ), 'upgrade_url' => 'https://go.elementor.com/go-pro-testimonial-widget-control/', ]; return $this->filter_data( Utils::TESTIMONIAL_WIDGET, $data ); } private function filter_data( $widget_name, $asset_data ): array { return Filtered_Promotions_Manager::get_filtered_promotion_data( $asset_data, "elementor/widgets/{$widget_name}/custom_promotion", 'upgrade_url' ); } } promotions/pointers/black-friday.php 0000644 00000006217 15252521350 0013667 0 ustar 00 <?php namespace Elementor\Modules\Promotions\Pointers; use Elementor\User; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Black_Friday { const PROMOTION_URL = 'https://go.elementor.com/go-pro-wordpress-notice-bf-25/'; const ELEMENTOR_POINTER_ID = 'toplevel_page_elementor'; const SEEN_TODAY_KEY = '_elementor_2025_black_friday'; const DISMISS_ACTION_KEY = 'black_friday_pointer_2025'; public function __construct() { add_action( 'admin_print_footer_scripts-index.php', [ $this, 'enqueue_notice' ] ); } public function enqueue_notice() { if ( ! $this->should_display_notice() ) { return; } $this->set_seen_today(); $this->enqueue_dependencies(); $pointer_content = '<h3>' . esc_html__( 'Black Friday Is On!', 'elementor' ) . '</h3>'; $pointer_content .= '<p>' . esc_html__( 'Save big on Elementor Pro and unlock the tools to design without limits.', 'elementor' ) . '</p>'; $pointer_content .= sprintf( '<p><a class="button button-primary" href="%s" target="_blank">%s</a></p>', self::PROMOTION_URL, esc_html__( 'View Deals', 'elementor' ) ); $allowed_tags = [ 'h3' => [], 'p' => [], 'a' => [ 'class' => [], 'target' => [ '_blank' ], 'href' => [], ], ]; ?> <script> jQuery( document ).ready( function( $ ) { $( "#<?php echo esc_attr( self::ELEMENTOR_POINTER_ID ); ?>" ).pointer( { content: '<?php echo wp_kses( $pointer_content, $allowed_tags ); ?>', position: { edge: <?php echo is_rtl() ? "'right'" : "'left'"; ?>, align: "center" }, close: function() { elementorCommon.ajax.addRequest( "introduction_viewed", { data: { introductionKey: '<?php echo esc_attr( static::DISMISS_ACTION_KEY ); ?>' } } ); } } ).pointer( "open" ); } ); </script> <?php } public static function should_display_notice(): bool { return self::is_user_allowed() && ! self::is_dismissed() && self::is_campaign_time() && ! self::is_already_seen_today() && ! Utils::has_pro(); } private static function is_user_allowed(): bool { return current_user_can( 'manage_options' ) || current_user_can( 'edit_pages' ); } private static function is_campaign_time() { $start = new \DateTime( '2025-11-25 12:00:00', new \DateTimeZone( 'UTC' ) ); $end = new \DateTime( '2025-12-03 03:59:00', new \DateTimeZone( 'UTC' ) ); $now = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) ); return $now >= $start && $now <= $end; } private static function is_already_seen_today() { return get_transient( self::get_user_transient_id() ); } private function set_seen_today() { $now = time(); $midnight = strtotime( 'tomorrow midnight' ); $seconds_until_midnight = $midnight - $now; set_transient( self::get_user_transient_id(), $now, $seconds_until_midnight ); } private static function get_user_transient_id(): string { return self::SEEN_TODAY_KEY . '_' . get_current_user_id(); } private function enqueue_dependencies() { wp_enqueue_script( 'wp-pointer' ); wp_enqueue_style( 'wp-pointer' ); } private static function is_dismissed(): bool { return User::get_introduction_meta( static::DISMISS_ACTION_KEY ); } } promotions/pointers/birthday.php 0000644 00000006261 15252521350 0013144 0 ustar 00 <?php namespace Elementor\Modules\Promotions\Pointers; use Elementor\User; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Birthday { const PROMOTION_URL = 'https://go.elementor.com/go-pro-wordpress-notice-birthday/'; const ELEMENTOR_POINTER_ID = 'toplevel_page_elementor-home'; const SEEN_TODAY_KEY = '_elementor-2026-birthday'; const DISMISS_ACTION_KEY = 'birthday_pointer_2026'; public function __construct() { add_action( 'admin_print_footer_scripts-index.php', [ $this, 'enqueue_notice' ] ); } public function enqueue_notice() { if ( ! $this->should_display_notice() ) { return; } $this->set_seen_today(); $this->enqueue_dependencies(); $pointer_content = '<h3>' . esc_html__( 'Elementor’s 10th Birthday sale!', 'elementor' ) . '</h3>'; $pointer_content .= '<p>' . esc_html__( 'Get more capabilities for less with exclusive discounts. Limited time only.', 'elementor' ); $pointer_content .= sprintf( '<p><a class="button button-primary" href="%s" target="_blank">%s</a></p>', self::PROMOTION_URL, esc_html__( 'View Deals', 'elementor' ) ); $allowed_tags = [ 'h3' => [], 'p' => [], 'a' => [ 'class' => [], 'target' => [ '_blank' ], 'href' => [], ], ]; ?> <script> jQuery( document ).ready( function( $ ) { $( "#<?php echo esc_attr( self::ELEMENTOR_POINTER_ID ); ?>" ).pointer( { content: '<?php echo wp_kses( $pointer_content, $allowed_tags ); ?>', position: { edge: <?php echo is_rtl() ? "'right'" : "'left'"; ?>, align: "center" }, close: function() { elementorCommon.ajax.addRequest( "introduction_viewed", { data: { introductionKey: '<?php echo esc_attr( static::DISMISS_ACTION_KEY ); ?>' } } ); } } ).pointer( "open" ); } ); </script> <?php } public static function should_display_notice(): bool { return self::is_user_allowed() && ! self::is_dismissed() && self::is_campaign_time() && ! self::is_already_seen_today() && ! Utils::has_pro(); } private static function is_user_allowed(): bool { return current_user_can( 'manage_options' ) || current_user_can( 'edit_pages' ); } private static function is_campaign_time() { $start = new \DateTime( '2026-06-15 10:00:00', new \DateTimeZone( 'UTC' ) ); $end = new \DateTime( '2026-06-17 03:59:00', new \DateTimeZone( 'UTC' ) ); $now = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) ); return $now >= $start && $now <= $end; } private static function is_already_seen_today() { return get_transient( self::get_user_transient_id() ); } private function set_seen_today() { $now = time(); $midnight = strtotime( 'tomorrow midnight' ); $seconds_until_midnight = $midnight - $now; set_transient( self::get_user_transient_id(), $now, $seconds_until_midnight ); } private static function get_user_transient_id(): string { return self::SEEN_TODAY_KEY . '_' . get_current_user_id(); } private function enqueue_dependencies() { wp_enqueue_script( 'wp-pointer' ); wp_enqueue_style( 'wp-pointer' ); } private static function is_dismissed(): bool { return User::get_introduction_meta( static::DISMISS_ACTION_KEY ); } } wc-product-editor/module.php 0000644 00000002643 15252521350 0012122 0 ustar 00 <?php namespace Elementor\Modules\WcProductEditor; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { public function __construct() { add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_assets' ] ); } public static function is_active() { return self::is_new_woocommerce_product_editor_page(); } public function enqueue_assets() { $suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_script( 'e-wc-product-editor', ELEMENTOR_ASSETS_URL . 'js/e-wc-product-editor' . $suffix . '.js', [ 'wp-components', 'wp-core-data', 'wc-admin-layout', 'wp-plugins' ], ELEMENTOR_VERSION, true ); $elementor_settings = [ 'editLink' => admin_url( 'post.php' ), ]; Utils::print_js_config( 'e-wc-product-editor', 'ElementorWCProductEditorSettings', $elementor_settings ); } public function get_name() { return 'wc-product-editor'; } public static function is_new_woocommerce_product_editor_page() { $page = Utils::get_super_global_value( $_GET, 'page' ); $path = Utils::get_super_global_value( $_GET, 'path' ); if ( ! isset( $page ) || 'wc-admin' !== $page || ! isset( $path ) ) { return false; } $path_pieces = explode( '/', $path ); $route = $path_pieces[1]; return 'product' === $route || 'add-product' === $route; } } atomic-widgets/base/atomic-control-base.php 0000644 00000000710 15252521350 0014747 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Base; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base as New_Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } // TODO: Remove this class after 3.36 is released. /** * @deprecated 3.34 Use \Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base instead. */ abstract class Atomic_Control_Base extends New_Atomic_Control_Base {} atomic-widgets/styles/style-fonts.php 0000644 00000002247 15252521350 0014014 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } const FONTS_KEY_PREFIX = 'elementor_atomic_styles_fonts-'; class Style_Fonts { private string $style_key; public function __construct( string $style_key ) { $this->style_key = $style_key; } public static function make( string $style_key ) { return new static( $style_key ); } public function add( string $font ) { $style_fonts = $this->get_fonts(); if ( ! in_array( $font, $style_fonts, true ) ) { $style_fonts[] = $font; $this->update_fonts( $style_fonts ); } } public function get(): array { return $this->get_fonts(); } public function clear() { $this->update_fonts( [] ); } private function get_fonts(): array { $style_fonts_key = $this->get_key(); return get_option( $style_fonts_key, [] ); } private function update_fonts( array $fonts ) { $style_fonts_key = $this->get_key(); if ( empty( $fonts ) ) { delete_option( $style_fonts_key ); return; } update_option( $style_fonts_key, $fonts, false ); } private function get_key(): string { return FONTS_KEY_PREFIX . $this->style_key; } } atomic-widgets/styles/style-schema.php 0000644 00000043062 15252521350 0014123 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Types_Mapping; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Box_Shadow_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Border_Radius_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Border_Width_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Font_Family_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Backdrop_Filter_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Filter_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Layout_Direction_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Position_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Grid_Track_Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Stroke_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transition_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Flex_Prop_Type; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Span_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Style_Schema { public static function get() { return apply_filters( 'elementor/atomic-widgets/styles/schema', static::get_style_schema() ); } public static function get_style_schema(): array { return array_merge( self::get_size_props(), self::get_position_props(), self::get_typography_props(), self::get_spacing_props(), self::get_border_props(), self::get_background_props(), self::get_effects_props(), self::get_layout_props(), self::get_alignment_props(), self::get_special_props(), ); } private static function get_size_props() { return [ 'width' => Size_Prop_Type::make()->description( 'The width of the element' ), 'height' => Size_Prop_Type::make()->description( 'The height of the element' ), 'min-width' => Size_Prop_Type::make()->description( 'The minimum width of the element' ), 'min-height' => Size_Prop_Type::make()->description( 'The minimum height of the element' ), 'max-width' => Size_Prop_Type::make()->description( 'The maximum width of the element' ), 'max-height' => Size_Prop_Type::make()->description( 'The maximum height of the element' ), 'overflow' => String_Prop_Type::make()->enum( [ 'visible', 'hidden', 'auto', ] )->description( 'The overflow CSS property. CSS values: visible, hidden, auto' ), 'aspect-ratio' => String_Prop_Type::make()->description( 'Equivalent to CSS aspect-ration property' ), 'object-fit' => String_Prop_Type::make()->enum( [ 'fill', 'cover', 'contain', 'none', 'scale-down', ] )->description( 'The object-fit CSS. CSS values: fill, cover, contain, none, scale-down' ), 'object-position' => Union_Prop_Type::make() ->add_prop_type( String_Prop_Type::make()->enum( Position_Prop_Type::get_position_enum_values() ) ) ->add_prop_type( Position_Prop_Type::make() ) ->set_dependencies( Dependency_Manager::make( Dependency_Manager::RELATION_AND ) ->where( [ 'operator' => 'ne', 'path' => [ 'object-fit' ], 'value' => 'fill', ] ) ->where( [ 'operator' => 'exists', 'path' => [ 'object-fit' ], ] ) ->get() ), ]; } private static function get_position_props() { $non_static_dependency = Dependency_Manager::make( Dependency_Manager::RELATION_AND ) ->where( [ 'operator' => 'exists', 'path' => [ 'position' ], ] ) ->where( [ 'operator' => 'ne', 'path' => [ 'position' ], 'value' => 'static', ] ) ->get(); return [ 'position' => String_Prop_Type::make()->enum( [ 'static', 'relative', 'absolute', 'fixed', 'sticky', ] )->description( 'The CSS position property specifies the type of positioning method used for an element (static, relative, absolute, fixed, or sticky).' ), 'inset-block-start' => Size_Prop_Type::make() ->description( 'Size PropType for the inset-block-start CSS property' ) ->set_dependencies( $non_static_dependency ), 'inset-inline-end' => Size_Prop_Type::make() ->description( 'Size PropType for the inset-inline-end CSS property' ) ->set_dependencies( $non_static_dependency ), 'inset-block-end' => Size_Prop_Type::make() ->description( 'Size PropType for the inset-block-end CSS property' ) ->set_dependencies( $non_static_dependency ), 'inset-inline-start' => Size_Prop_Type::make() ->description( 'Size PropType for the inset-inline-start CSS property' ) ->set_dependencies( $non_static_dependency ), 'z-index' => Number_Prop_Type::make() ->description( 'The z-index CSS property sets the z-order of a positioned element and its descendants or flex items. It specifies the stack order of elements.' ), 'scroll-margin-top' => Size_Prop_Type::make()->units( Size_Constants::anchor_offset() ), ]; } private static function get_typography_props() { return [ 'font-family' => Font_Family_Prop_Type::make() ->description( 'The font family of the text content.' ), 'font-weight' => String_Prop_Type::make()->enum( [ '100', '200', '300', '400', '500', '600', '700', '800', '900', 'normal', 'bold', 'bolder', 'lighter', ] ) ->description( 'The weight (or boldness) of the font. Values should match css font-weight specifications.' ), 'font-size' => Size_Prop_Type::make()->units( Size_Constants::typography() )->description( 'The font size in Size PropType Format' ), 'color' => Color_Prop_Type::make() ->description( 'The text color, specified as a hex code, rgb(a), hsl(a), or a standard css color name.' ), 'letter-spacing' => Size_Prop_Type::make()->units( Size_Constants::typography() )->description( 'The spacing between letters in Size PropType format' ), 'word-spacing' => Size_Prop_Type::make()->units( Size_Constants::typography() )->description( 'The spacing between words in Size PropType format' ), 'column-count' => Number_Prop_Type::make()->description( 'The number of columns the text content should be divided into.' ), 'column-gap' => Size_Prop_Type::make() ->set_dependencies( Dependency_Manager::make() ->where( [ 'operator' => 'gte', 'path' => [ 'column-count' ], 'value' => 1, ] ) ->get() ), 'line-height' => Size_Prop_Type::make()->units( Size_Constants::typography() )->description( 'The line height of the text content in Size PropType format' ), 'text-align' => String_Prop_Type::make()->enum( [ 'start', 'center', 'end', 'justify', ] ) ->description( 'The horizontal alignment of the text content. Allowed values: start, center, end, justify.' ), 'font-style' => String_Prop_Type::make()->enum( [ 'normal', 'italic', 'oblique', ] ) ->description( 'The font style of the text content. CSS values: normal, italic, oblique' ), // TODO: validate text-decoration in more specific way [EDS-524] 'text-decoration' => String_Prop_Type::make() ->description( 'The text decoration style. CSS values like: none, underline, overline, line-through, blink, etc.' ), 'text-transform' => String_Prop_Type::make()->enum( [ 'none', 'capitalize', 'uppercase', 'lowercase', ] ) ->description( 'Controls the capitalization of text. CSS values: none, capitalize, uppercase, lowercase' ), 'direction' => String_Prop_Type::make()->enum( [ 'ltr', 'rtl', ] )->description( 'The text direction. CSS values: ltr (left to right), rtl (right to left)' ), 'stroke' => Stroke_Prop_Type::make(), 'all' => String_Prop_Type::make()->enum( [ 'initial', 'inherit', 'unset', 'revert', 'revert-layer', ] )->description( 'The all CSS property. CSS values: initial, inherit, unset, revert, revert-layer' ), 'cursor' => String_Prop_Type::make()->enum( [ 'pointer', ] ) ->description( 'The type of cursor to be displayed when pointing over the element. E.g., pointer.' ), ]; } private static function get_spacing_props() { return [ 'padding' => Union_Prop_Type::make() ->add_prop_type( Dimensions_Prop_Type::make_with_units( Size_Constants::spacing() ) ) ->add_prop_type( Size_Prop_Type::make() ->units( Size_Constants::spacing() ) ->description( 'Padding css in Size PropType format' ) ), 'margin' => Union_Prop_Type::make() ->add_prop_type( Dimensions_Prop_Type::make_with_units( Size_Constants::spacing_margin() ) ) ->add_prop_type( Size_Prop_Type::make() ->units( Size_Constants::spacing_margin() ) ->description( 'Margin css in Size PropType format' ) ), ]; } private static function get_border_props() { return [ 'border-radius' => Union_Prop_Type::make() ->add_prop_type( Border_Radius_Prop_Type::make() ) ->add_prop_type( Size_Prop_Type::make()->units( Size_Constants::border() ) ), 'border-width' => Union_Prop_Type::make() ->add_prop_type( Border_Width_Prop_Type::make() ) ->add_prop_type( Size_Prop_Type::make()->units( Size_Constants::border() ) ), 'border-color' => Color_Prop_Type::make()->description( 'The border color, specified as a hex code, rgb(a), hsl(a), or a standard css color name.' ), 'border-style' => String_Prop_Type::make()->enum( [ 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset', ] ) ->description( 'The border style in CSS values' ), 'outline-width' => Size_Prop_Type::make() ->units( Size_Constants::border() ) ->description( 'The width of the outline in Size PropType format' ), 'outline-color' => Color_Prop_Type::make()->description( 'The color of the outline, specified as a hex code, rgb(a), hsl(a), or a standard css color name.' ), 'outline-style' => String_Prop_Type::make()->enum( [ 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset', ] )->description( 'The outline style in CSS values' ), 'outline-offset' => Size_Prop_Type::make()->units( Size_Constants::border() )->description( 'The offset of the outline, specified as a length in Size PropType format' ), ]; } private static function get_background_props() { // Background image overlay as an exception $background_prop_type = Background_Prop_Type::make(); $bg_overlay_prop_type = $background_prop_type->get_shape_field( Background_Overlay_Prop_Type::get_key() ); $bg_image_overlay_prop_type = $bg_overlay_prop_type->get_item_type()->get_prop_type( Background_Image_Overlay_Prop_Type::get_key() ); Dynamic_Prop_Types_Mapping::make()->get_extended_schema( $bg_image_overlay_prop_type->get_shape() ); return [ 'background' => $background_prop_type, ]; } private static function get_effects_props() { return [ 'mix-blend-mode' => String_Prop_Type::make()->enum( [ 'normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'saturation', 'color', 'difference', 'exclusion', 'hue', 'luminosity', 'soft-light', 'hard-light', 'color-burn', ] )->description( 'Applied as mix-blend mode css effect.' ), 'box-shadow' => Box_Shadow_Prop_Type::make(), 'opacity' => Size_Prop_Type::make() ->description( 'The opacity of the element, specified as a percentage between 0 (fully transparent) and 100 (fully opaque).' ) ->units( Size_Constants::opacity() ) ->default_unit( Size_Constants::UNIT_PERCENT ), 'filter' => Filter_Prop_Type::make(), 'backdrop-filter' => Backdrop_Filter_Prop_Type::make(), 'transform' => Transform_Prop_Type::make(), 'transition' => Transition_Prop_Type::make(), ]; } private static function get_layout_props() { return [ 'display' => String_Prop_Type::make()->enum( [ 'block', 'inline', 'inline-block', 'flex', 'inline-flex', 'grid', 'inline-grid', 'flow-root', 'none', 'contents', ] )->description( 'The CSS display property defines the display behavior (the type of rendering box) of an element.' ), 'flex-direction' => String_Prop_Type::make() ->description( 'The direction of the contained items.' ) ->enum( [ 'row', 'row-reverse', 'column', 'column-reverse', ] ), 'gap' => Union_Prop_Type::make() ->add_prop_type( Layout_Direction_Prop_Type::make() ) ->add_prop_type( Size_Prop_Type::make()->units( Size_Constants::layout() ) ), 'flex-wrap' => String_Prop_Type::make()->enum( [ 'wrap', 'nowrap', 'wrap-reverse', ] )->description( 'Specifies whether the flex items should wrap or not. CSS values: wrap, nowrap, wrap-reverse' ), 'flex' => Flex_Prop_Type::make(), 'grid-template-columns' => Union_Prop_Type::make() ->add_prop_type( String_Prop_Type::make() ) ->add_prop_type( Grid_Track_Size_Prop_Type::make()->units( Size_Constants::grid_track() ) ), 'grid-template-rows' => Union_Prop_Type::make() ->add_prop_type( String_Prop_Type::make() ) ->add_prop_type( Grid_Track_Size_Prop_Type::make()->units( Size_Constants::grid_track() ) ), 'grid-auto-flow' => String_Prop_Type::make() ->enum( [ 'row', 'column', 'row dense', 'column dense' ] ) ->description( 'Controls how auto-placed items flow in the grid. CSS values: row, column, row dense, column dense.' ), 'grid-auto-rows' => Size_Prop_Type::make() ->units( Size_Constants::grid_auto_track() ) ->default_unit( Size_Constants::UNIT_FR ), 'grid-auto-columns' => Size_Prop_Type::make() ->units( Size_Constants::grid_auto_track() ) ->default_unit( Size_Constants::UNIT_FR ), 'grid-column' => Span_Prop_Type::make() ->regex( '/^(?!.*https?:\/\/)(?!.*;).*$/' ) ->description( 'Defines a grid item column placement. Accepts values like span N or any valid CSS grid-column value. Disallows URLs and semicolons.' ), 'grid-row' => Span_Prop_Type::make() ->regex( '/^(?!.*https?:\/\/)(?!.*;).*$/' ) ->description( 'Defines a grid item row placement. Accepts values like span N or any valid CSS grid-row value. Disallows URLs and semicolons.' ), ]; } private static function get_alignment_props() { return [ 'justify-content' => String_Prop_Type::make()->enum( [ 'center', 'start', 'end', 'flex-start', 'flex-end', 'left', 'right', 'normal', 'space-between', 'space-around', 'space-evenly', 'stretch', ] ) ->description( 'Defines how the browser distributes space between and around content items along the main-axis of a flex container. CSS values: center, start, end, flex-start, flex-end, left, right, normal, space-between, space-around, space-evenly, stretch' ), 'justify-items' => String_Prop_Type::make()->enum( [ 'normal', 'stretch', 'center', 'start', 'end', 'flex-start', 'flex-end', 'left', 'right', 'anchor-center', ] )->description( 'Defines how the browser distributes space between and around content items along the inline axis of a grid container. CSS values: center, start, end, flex-start, flex-end, left, right' ), 'align-content' => String_Prop_Type::make()->enum( [ 'center', 'start', 'end', 'space-between', 'space-around', 'space-evenly', ] ) ->description( 'Aligns a flex container\'s lines within when there is extra space in the cross-axis. CSS values: center, start, end, space-between, space-around, space-evenly' ), 'align-items' => String_Prop_Type::make()->enum( [ 'normal', 'stretch', 'center', 'start', 'end', 'flex-start', 'flex-end', 'self-start', 'self-end', 'anchor-center', ] )->description( 'Defines the default behavior for how flex items are laid out along the cross axis on the current line. CSS values: normal, stretch, center, start, end, flex-start, flex-end, self-start, self-end, anchor-center' ), 'align-self' => String_Prop_Type::make()->enum( [ 'auto', 'normal', 'center', 'start', 'end', 'self-start', 'self-end', 'flex-start', 'flex-end', 'anchor-center', 'baseline', 'first baseline', 'last baseline', 'stretch', ] )->description( 'Allows the default alignment (or the one specified by align-items) to be overridden for individual flex items. CSS values: auto, normal, center, start, end, self-start, self-end, flex-start, flex-end, anchor-center, baseline, first baseline, last baseline, stretch' ), 'order' => Number_Prop_Type::make()->description( 'Specifies the order of the flex items. Items with lower order values are displayed first.' ), ]; } private static function get_special_props() { return [ 'content' => String_Prop_Type::make()->description( 'The string content for pseudo-element content property' ), 'appearance' => String_Prop_Type::make()->enum( [ 'none', 'auto' ] )->description( 'The appearance of the element. CSS values: none, auto' ), 'clip-path' => String_Prop_Type::make()->description( 'The clip-path CSS property defines a shape to be used as clipping region.' ), ]; } } atomic-widgets/styles/atomic-widget-base-styles.php 0000644 00000002330 15252521350 0016504 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; use Elementor\Core\Utils\Collection; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Plugin; class Atomic_Widget_Base_Styles { const STYLES_KEY = 'base'; public function register_hooks() { add_action( 'elementor/atomic-widgets/styles/register', fn( Atomic_Styles_Manager $styles_manager ) => $this->register_styles( $styles_manager ), 10, 1 ); add_action( 'elementor/core/files/clear_cache', fn() => $this->invalidate_cache(), ); } private function register_styles( Atomic_Styles_Manager $styles_manager ) { $styles_manager->register( [ self::STYLES_KEY ], fn () => $this->get_all_base_styles(), ); } private function invalidate_cache() { do_action( 'elementor/atomic-widgets/styles/clear', [ self::STYLES_KEY ] ); } public function get_all_base_styles(): array { $elements = Plugin::$instance->elements_manager->get_element_types(); $widgets = Plugin::$instance->widgets_manager->get_widget_types(); return Collection::make( $elements ) ->merge( $widgets ) ->filter( fn( $element ) => Utils::is_atomic( $element ) ) ->map( fn( $element ) => $element->get_base_styles() ) ->flatten() ->all(); } } atomic-widgets/styles/atomic-styles-manager.php 0000644 00000016266 15252521350 0015740 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; use Elementor\Core\Base\Document; use Elementor\Core\Breakpoints\Breakpoint; use Elementor\Core\Utils\Collection; use Elementor\Modules\AtomicWidgets\Utils\Memo; use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Styles_Manager { private static ?self $instance = null; /** * @var array<string, array{styles: callable, path: array<string>}> */ private array $registered_styles_by_key = []; private Cache_Validity $cache_validity; private array $post_ids = []; private CSS_Files_Manager $css_files_manager; const DEFAULT_BREAKPOINT = 'desktop'; private array $fonts = []; public function __construct() { $this->css_files_manager = new CSS_Files_Manager(); $this->cache_validity = new Cache_Validity(); } public static function instance() { if ( ! self::$instance ) { self::$instance = new self(); } return self::$instance; } public function register_hooks() { add_action( 'elementor/frontend/after_enqueue_post_styles', fn() => $this->enqueue_styles() ); add_action( 'elementor/post/render', function( $post_id ) { $this->post_ids[] = $post_id; } ); add_action( 'elementor/atomic-widgets/styles/clear', fn( array $path ) => $this->clear_styles( $path ) ); } public function register( array $path, callable $get_style_defs ) { $key = $this->convert_path_to_handle( $path ); $this->registered_styles_by_key[ $key ] = [ 'get_styles' => $get_style_defs, 'path' => $path, ]; } private function enqueue_styles() { if ( empty( $this->post_ids ) ) { return; } do_action( 'elementor/atomic-widgets/styles/register', $this, $this->post_ids ); $get_styles_memo = new Memo(); $styles_by_key = Collection::make( $this->registered_styles_by_key ) ->map_with_keys( fn ( $style_params, $style_key ) => [ $style_key => [ 'get_styles' => $get_styles_memo->memoize( $style_key, $style_params['get_styles'] ), 'path' => $style_params['path'], ], ]) ->all(); $this->before_render( $styles_by_key ); $this->render( $styles_by_key ); $this->after_render( $styles_by_key ); } private function before_render( array $styles_by_key ) { $this->fonts = []; foreach ( $styles_by_key as $style_key => $style_params ) { $path = $style_params['path']; // This cache validity check is of the general style, and used to reset dependencies that can only be evaluated // upon the style rendering flow (i.e. when cache is invalid). // (the corresponding css files cache validity includes also the file's breakpoint in the cache keys array) if ( ! $this->cache_validity->is_valid( $path ) ) { Style_Fonts::make( $style_key )->clear(); // We should validate it after this iteration $this->cache_validity->validate( $path, uniqid() ); } } } private function render( array $styles_by_key ) { $group_by_breakpoint_memo = new Memo(); $breakpoints = $this->get_breakpoints(); foreach ( $breakpoints as $breakpoint_key ) { foreach ( $styles_by_key as $style_key => $style_params ) { $path = $style_params['path']; $render_css = fn() => $this->render_css_by_breakpoints( $style_params['get_styles'], $style_key, $breakpoint_key, $group_by_breakpoint_memo ); $version = $this->cache_validity->get_meta( $path ); $breakpoint_media = $this->get_breakpoint_media( $breakpoint_key ); if ( ! $breakpoint_media ) { continue; } $breakpoint_path = array_merge( $path, [ $breakpoint_key ] ); $style_file = $this->css_files_manager->get( $this->convert_path_to_handle( $breakpoint_path ), $breakpoint_media, $render_css, $this->cache_validity->is_valid( $breakpoint_path ) ); $this->cache_validity->validate( $breakpoint_path ); if ( ! $style_file ) { continue; } wp_enqueue_style( $style_file->get_handle(), $style_file->get_url(), [], $version, $style_file->get_media() ); } } } private function render_css( array $styles, string $style_key ) { $style_fonts = Style_Fonts::make( $style_key ); return Styles_Renderer::make( Plugin::$instance->breakpoints->get_breakpoints_config() )->on_font_enqueue( fn( $font ) => $style_fonts->add( $font ) ) ->render( $styles ); } private function get_breakpoint_media( string $breakpoint_key ): ?string { $breakpoint_config = Plugin::$instance->breakpoints->get_breakpoints_config()[ $breakpoint_key ] ?? null; return $breakpoint_config ? Styles_Renderer::get_media_query( $breakpoint_config ) : 'all'; } private function render_css_by_breakpoints( callable $get_styles, string $style_key, string $breakpoint_key, Memo $group_by_breakpoint_memo ) { $memo_key = $style_key . '-' . $breakpoint_key; $get_grouped_styles = $group_by_breakpoint_memo->memoize( $memo_key, fn() => $this->group_by_breakpoint( $get_styles() ) ); $grouped_styles = $get_grouped_styles(); return $this->render_css( $grouped_styles[ $breakpoint_key ] ?? [], $style_key ); } private function group_by_breakpoint( $styles ) { return Collection::make( $styles )->reduce( function( $group, $style ) { Collection::make( $style['variants'] )->each( function( $variant ) use ( &$group, $style ) { $breakpoint = $variant['meta']['breakpoint'] ?? self::DEFAULT_BREAKPOINT; if ( ! isset( $group[ $breakpoint ][ $style['id'] ] ) ) { $group[ $breakpoint ][ $style['id'] ] = [ 'id' => $style['id'], 'type' => $style['type'], 'variants' => [], ]; } $group[ $breakpoint ][ $style['id'] ]['variants'][] = $variant; } ); return $group; }, [] ); } private function get_breakpoints() { return Collection::make( Plugin::$instance->breakpoints->get_breakpoints() ) ->map( fn( Breakpoint $breakpoint ) => $breakpoint->get_name() ) ->reverse() ->prepend( self::DEFAULT_BREAKPOINT ) ->all(); } private function after_render( array $styles_by_key ) { foreach ( $styles_by_key as $style_key => $style_params ) { $this->add_fonts_to_enqueue( $style_key ); } $this->enqueue_fonts(); } private function add_fonts_to_enqueue( string $style_key ) { $style_fonts = Style_Fonts::make( $style_key ); $this->fonts = array_unique( array_merge( $this->fonts, array_values( $style_fonts->get() ) ) ); } private function enqueue_fonts() { foreach ( $this->fonts as $font ) { Plugin::instance()->frontend->enqueue_font( $font ); } } private function clear_styles( array $path ) { $node = $this->cache_validity->get_node( $path ); $this->clear_styles_by_node( $path, $node ); $this->cache_validity->invalidate( $path ); } private function clear_styles_by_node( array $path, $node ) { if ( ! $node ) { return; } if ( true === $node || $node['state'] ) { $this->css_files_manager->delete( $this->convert_path_to_handle( $path ) ); } if ( is_bool( $node ) || empty( $node['children'] ) ) { return; } foreach ( $node['children'] as $child_key => $child_node ) { $this->clear_styles_by_node( array_merge( $path, [ $child_key ] ), $child_node ); } } private function convert_path_to_handle( array $path ) { return implode( '-', $path ); } } atomic-widgets/styles/atomic-widget-styles.php 0000644 00000007422 15252521350 0015603 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; use Elementor\Core\Base\Document; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Modules\GlobalClasses\Utils\Atomic_Elements_Utils; use Elementor\Utils as ElementorUtils; use Elementor\Plugin; class Atomic_Widget_Styles { const STYLES_KEY = 'local'; const CONTEXT_FRONTEND = 'frontend'; const CONTEXT_PREVIEW = 'preview'; public function register_hooks() { add_action( 'elementor/atomic-widgets/styles/register', function( Atomic_Styles_Manager $styles_manager, array $post_ids ) { $this->register_styles( $styles_manager, $post_ids ); }, 30, 2 ); add_action( 'elementor/document/after_save', fn( Document $document ) => $this->invalidate_cache( [ $document->get_main_post()->ID ], $this->get_context( ! Utils::is_post_published( $document ) ) ), 20, 2 ); add_action( 'elementor/core/files/clear_cache', fn() => $this->invalidate_cache(), ); add_action( 'deleted_post', fn( $post_id ) => $this->invalidate_cache( [ $post_id ] ) ); add_action( 'update_option__elementor_pro_license_v2_data', fn() => Plugin::$instance->files_manager->clear_cache() ); add_action( 'delete_option__elementor_pro_license_v2_data', fn() => Plugin::$instance->files_manager->clear_cache() ); } private function register_styles( Atomic_Styles_Manager $styles_manager, array $post_ids ) { $context = $this->get_context( Plugin::$instance->preview->is_editor_or_preview() ); foreach ( $post_ids as $post_id ) { $get_styles = fn() => $this->parse_post_styles( $post_id ); $styles_manager->register( [ self::STYLES_KEY, $post_id, $context ], $get_styles ); } } private function parse_post_styles( $post_id ) { $post_styles = []; Utils::traverse_post_elements( $post_id, function( $element_data ) use ( &$post_styles ) { $post_styles = array_merge( $post_styles, $this->parse_element_style( $element_data ) ); } ); return self::get_license_based_filtered_styles( $post_styles ); } private function parse_element_style( array $element_data ) { $element_type = Atomic_Elements_Utils::get_element_type( $element_data ); $element_instance = Atomic_Elements_Utils::get_element_instance( $element_type ); if ( ! Utils::is_atomic( $element_instance ) ) { return []; } return $element_data['styles'] ?? []; } private function invalidate_cache( ?array $post_ids = null, ?string $context = null ) { if ( empty( $post_ids ) ) { do_action( 'elementor/atomic-widgets/styles/clear', [ self::STYLES_KEY ] ); return; } $is_post_status_publish = self::CONTEXT_FRONTEND === $context; // When a user publishes a post, we should invalidate the styles of the draft too foreach ( $post_ids as $post_id ) { do_action( 'elementor/atomic-widgets/styles/clear', empty( $context ) || $is_post_status_publish ? [ self::STYLES_KEY, $post_id ] : [ self::STYLES_KEY, $post_id, $context ] ); } } private function get_context( bool $is_preview ) { return $is_preview ? self::CONTEXT_PREVIEW : self::CONTEXT_FRONTEND; } public static function get_license_based_filtered_styles( $styles ) { if ( ElementorUtils::has_pro() && version_compare( ELEMENTOR_PRO_VERSION, '3.35', '<' ) ) { return $styles; } return apply_filters( 'elementor/atomic_widgets/editor_data/element_styles', self::remove_custom_css_from_styles( $styles ), $styles ); } public static function remove_custom_css_from_styles( array $styles ) { if ( empty( $styles ) ) { return $styles; } foreach ( $styles as $style_id => $style ) { if ( isset( $style['variants'] ) && is_array( $style['variants'] ) ) { foreach ( $style['variants'] as $variant_index => $variant ) { unset( $styles[ $style_id ]['variants'][ $variant_index ]['custom_css'] ); } } } return $styles; } } atomic-widgets/styles/css-files-manager.php 0000644 00000004637 15252521350 0015032 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; class CSS_Files_Manager { const DEFAULT_CSS_DIR = 'elementor/css/'; const FILE_EXTENSION = '.css'; // Read and write permissions for the owner const PERMISSIONS = 0644; public function get( string $handle, string $media, callable $get_css, bool $is_valid_cache ): ?Style_File { $filesystem = $this->get_filesystem(); $path = $this->get_path( $handle ); if ( $is_valid_cache ) { if ( ! $filesystem->exists( $path ) ) { return null; } // Return the existing file return Style_File::create( $this->sanitize_handle( $handle ), $this->get_filesystem_path( $this->get_path( $handle ) ), $this->get_url( $handle ), $media, ); } $css = $get_css(); if ( empty( $css ) ) { return null; } $filesystem_path = $this->get_filesystem_path( $path ); $is_created = $filesystem->put_contents( $filesystem_path, $css, self::PERMISSIONS ); if ( false === $is_created ) { return null; } return Style_File::create( $this->sanitize_handle( $handle ), $filesystem_path, $this->get_url( $handle ), $media ); } public function delete( string $handle ): void { $filesystem = $this->get_filesystem(); $path = $this->get_path( $handle ); if ( ! $filesystem->exists( $path ) ) { return; } $filesystem->delete( $path ); } private function get_filesystem(): \WP_Filesystem_Base { global $wp_filesystem; if ( empty( $wp_filesystem ) ) { require_once ABSPATH . '/wp-admin/includes/file.php'; WP_Filesystem(); } return $wp_filesystem; } private function get_filesystem_path( $path ): string { $filesystem = $this->get_filesystem(); return str_replace( ABSPATH, $filesystem->abspath(), $path ); } private function get_url( string $handle ): string { $upload_dir = wp_upload_dir(); $sanitized_handle = $this->sanitize_handle( $handle ); $handle = $sanitized_handle . self::FILE_EXTENSION; return trailingslashit( $upload_dir['baseurl'] ) . self::DEFAULT_CSS_DIR . $handle; } private function get_path( string $handle ): string { $upload_dir = wp_upload_dir(); $sanitized_handle = $this->sanitize_handle( $handle ); $handle = $sanitized_handle . self::FILE_EXTENSION; return trailingslashit( $upload_dir['basedir'] ) . self::DEFAULT_CSS_DIR . $handle; } private function sanitize_handle( string $handle ): string { return preg_replace( '/[^a-zA-Z0-9_-]/', '', $handle ); } } atomic-widgets/styles/grid-track-renderer.php 0000644 00000001044 15252521350 0015352 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Grid_Track_Renderer { public const GRID_TRACK_PROPERTIES = [ 'grid-template-columns', 'grid-template-rows' ]; public static function is_grid_track_property( ?string $css_property ): bool { return in_array( $css_property, self::GRID_TRACK_PROPERTIES, true ); } public static function format_repeat( int $count ): ?string { if ( $count < 1 ) { return null; } return "repeat({$count}, 1fr)"; } } atomic-widgets/styles/size-constants.php 0000644 00000014250 15252521350 0014506 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Size_Constants { const UNIT_PX = 'px'; const UNIT_PERCENT = '%'; const UNIT_EM = 'em'; const UNIT_REM = 'rem'; const UNIT_VW = 'vw'; const UNIT_VH = 'vh'; const UNIT_CH = 'ch'; const UNIT_VMIN = 'vmin'; const UNIT_VMAX = 'vmax'; const UNIT_SECOND = 's'; const UNIT_MILLI_SECOND = 'ms'; const UNIT_DEG = 'deg'; const UNIT_RAD = 'rad'; const UNIT_GRAD = 'grad'; const UNIT_TURN = 'turn'; const UNIT_AUTO = 'auto'; const UNIT_CUSTOM = 'custom'; const UNIT_FR = 'fr'; const DEFAULT_UNIT = self::UNIT_PX; private const ORDER = [ self::UNIT_PX, self::UNIT_PERCENT, self::UNIT_EM, self::UNIT_REM, self::UNIT_VW, self::UNIT_VH, self::UNIT_CH, self::UNIT_VMIN, self::UNIT_VMAX, self::UNIT_FR, self::UNIT_DEG, self::UNIT_RAD, self::UNIT_GRAD, self::UNIT_TURN, self::UNIT_SECOND, self::UNIT_MILLI_SECOND, self::UNIT_AUTO, self::UNIT_CUSTOM, ]; private const LENGTH_UNITS = [ self::UNIT_PX, self::UNIT_EM, self::UNIT_REM, self::UNIT_VW, self::UNIT_VH, self::UNIT_CH, ]; private const TIME_UNITS = [ self::UNIT_MILLI_SECOND, self::UNIT_SECOND, ]; private const ANGLE_UNITS = [ self::UNIT_DEG, self::UNIT_RAD, self::UNIT_GRAD, self::UNIT_TURN, ]; private const EXTENDED_UNITS = [ self::UNIT_AUTO, self::UNIT_CUSTOM, ]; private const VIEWPORT_MIN_MAX_UNITS = [ self::UNIT_VMIN, self::UNIT_VMAX, ]; private const NUMERIC_UNITS = [ ...self::LENGTH_UNITS, self::UNIT_PERCENT, self::UNIT_CUSTOM, ]; private static function presets(): array { return [ 'layout' => self::sort_by_preferred_order( self::NUMERIC_UNITS ), 'spacing' => self::sort_by_preferred_order( self::NUMERIC_UNITS ), 'position' => self::NUMERIC_UNITS, 'typography' => self::NUMERIC_UNITS, 'border' => self::NUMERIC_UNITS, 'box_shadow' => self::NUMERIC_UNITS, 'transform' => self::NUMERIC_UNITS, 'spacing_margin' => self::standard_units(), 'anchor_offset' => [ ...self::LENGTH_UNITS, self::UNIT_CUSTOM, ], 'stroke_width' => [ self::UNIT_PX, self::UNIT_EM, self::UNIT_REM, self::UNIT_CUSTOM, ], 'transition' => [ ...self::TIME_UNITS, self::UNIT_CUSTOM, ], 'opacity' => [ self::UNIT_PERCENT, self::UNIT_CUSTOM, ], 'rotate' => [ ...self::ANGLE_UNITS, self::UNIT_CUSTOM, ], 'drop_shadow' => [ ...self::LENGTH_UNITS, self::UNIT_CUSTOM, ], 'blur_filter' => [ ...self::LENGTH_UNITS, self::UNIT_CUSTOM, ], 'intensity_filter' => [ self::UNIT_PERCENT, self::UNIT_CUSTOM, ], 'color_tone_filter' => [ self::UNIT_PERCENT, self::UNIT_CUSTOM, ], 'hue_rotate_filter' => [ ...self::ANGLE_UNITS, self::UNIT_CUSTOM, ], ]; } private static function sort_by_preferred_order( array $units ): array { $index = array_flip( self::ORDER ); usort( $units, fn( $a, $b ) => ( $index[ $a ] ?? PHP_INT_MAX ) <=> ( $index[ $b ] ?? PHP_INT_MAX ) ); return $units; } public static function standard_units(): array { return self::sort_by_preferred_order( [ ...self::LENGTH_UNITS, self::UNIT_PERCENT, self::UNIT_AUTO, self::UNIT_CUSTOM, ] ); } public static function all_supported_units(): array { return [ ...self::LENGTH_UNITS, ...self::TIME_UNITS, ...self::ANGLE_UNITS, ...self::EXTENDED_UNITS, ...self::VIEWPORT_MIN_MAX_UNITS, self::UNIT_PERCENT, self::UNIT_FR, ]; } public static function grid_track(): array { return [ self::UNIT_FR, self::UNIT_CUSTOM, ]; } public static function grid_auto_track(): array { return self::sort_by_preferred_order( [ self::UNIT_PX, self::UNIT_PERCENT, self::UNIT_FR, self::UNIT_AUTO, self::UNIT_CUSTOM, ] ); } public static function grouped_units(): array { return [ 'length' => self::LENGTH_UNITS, 'angle' => self::ANGLE_UNITS, 'time' => self::TIME_UNITS, 'extended_units' => self::EXTENDED_UNITS, ]; } private static function by_group( string $group ): array { $groups = self::grouped_units(); return $groups[ $group ] ?? []; } public static function get_preset( string $name ): array { $presets = self::presets(); return $presets[ $name ] ?? []; } public static function length(): array { return self::by_group( 'length' ); } public static function time(): array { return self::by_group( 'time' ); } public static function angle(): array { return self::by_group( 'angle' ); } public static function layout(): array { return self::get_preset( 'layout' ); } public static function spacing_margin(): array { return self::get_preset( 'spacing_margin' ); } public static function spacing(): array { return self::get_preset( 'spacing' ); } public static function position(): array { return self::get_preset( 'position' ); } public static function anchor_offset(): array { return self::get_preset( 'anchor_offset' ); } public static function typography(): array { return self::get_preset( 'typography' ); } public static function stroke_width(): array { return self::get_preset( 'stroke_width' ); } public static function transition(): array { return self::get_preset( 'transition' ); } public static function border(): array { return self::get_preset( 'border' ); } public static function opacity(): array { return self::get_preset( 'opacity' ); } public static function box_shadow(): array { return self::get_preset( 'box_shadow' ); } public static function rotate(): array { return self::get_preset( 'rotate' ); } public static function transform(): array { return self::get_preset( 'transform' ); } public static function drop_shadow(): array { return self::get_preset( 'drop_shadow' ); } public static function blur_filter(): array { return self::get_preset( 'blur_filter' ); } public static function intensity_filter(): array { return self::get_preset( 'intensity_filter' ); } public static function color_tone_filter(): array { return self::get_preset( 'color_tone_filter' ); } public static function hue_rotate_filter(): array { return self::get_preset( 'hue_rotate_filter' ); } } atomic-widgets/styles/style-file.php 0000644 00000001662 15252521350 0013602 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Style_File { private string $handle; private string $path; private string $url; private string $media; private function __construct( string $handle, string $path, string $url, string $media ) { $this->handle = $handle; $this->path = $path; $this->url = $url; $this->media = $media; } public static function create( string $handle, string $path, string $url, string $media ): self { return new self( $handle, $path, $url, $media ); } public function get_handle(): string { return $this->handle; } public function get_path(): string { return $this->path; } public function get_url(): string { return $this->url; } public function get_media(): string { if ( str_starts_with( $this->media, '@media' ) ) { return substr( $this->media, 6 ); } return $this->media; } } atomic-widgets/styles/style-definition.php 0000644 00000001367 15252521350 0015015 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; class Style_Definition { private string $type = 'class'; private string $label = ''; /** @var Style_Variant[] */ private array $variants = []; public static function make(): self { return new self(); } public function set_type( string $type ): self { $this->type = $type; return $this; } public function set_label( string $label ): self { $this->label = $label; return $this; } public function add_variant( Style_Variant $variant ): self { $this->variants[] = $variant->build(); return $this; } public function build( string $id ): array { return [ 'id' => $id, 'type' => $this->type, 'label' => $this->label, 'variants' => $this->variants, ]; } } atomic-widgets/styles/styles-renderer.php 0000644 00000014030 15252521350 0014645 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; use Elementor\Core\Utils\Collection; use Elementor\Modules\AtomicWidgets\Module; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Font_Enqueueable; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver; use Elementor\Plugin; use Elementor\Utils; class Styles_Renderer { const DEFAULT_SELECTOR_PREFIX = '.elementor'; /** * @var array<string, array{direction: 'min' | 'max', value: int, is_enabled: boolean}> */ private array $breakpoints; private $on_font_enqueue; private string $selector_prefix; /** * @param array<string, array{direction: 'min' | 'max', value: int, is_enabled: boolean}> $breakpoints * @param string $selector_prefix */ private function __construct( array $breakpoints, string $selector_prefix = self::DEFAULT_SELECTOR_PREFIX ) { $this->breakpoints = $breakpoints; $this->selector_prefix = $selector_prefix; } public static function make( array $breakpoints, string $selector_prefix = self::DEFAULT_SELECTOR_PREFIX ): self { return new self( $breakpoints, $selector_prefix ); } /** * Render the styles to a CSS string. * * Styles format: * array<int, array{ * id: string, * type: string, * cssName: string | null, * variants: array<int, array{ * props: array<string, mixed>, * meta: array<string, mixed> * }> * }> * * @param array $styles Array of style definitions. * * @return string Rendered CSS string. */ public function render( array $styles ): string { $css_style = []; foreach ( $styles as $style_def ) { $style = $this->style_definition_to_css_string( $style_def ); $css_style[] = $style; } return implode( '', $css_style ); } public function on_font_enqueue( callable $callback ): self { $this->on_font_enqueue = $callback; return $this; } private function style_definition_to_css_string( array $style ): string { $base_selector = $this->get_base_selector( $style ); if ( ! $base_selector ) { return ''; } $stylesheet = []; foreach ( $style['variants'] as $variant ) { $style_declaration = $this->variant_to_css_string( $base_selector, $variant ); if ( $style_declaration ) { $stylesheet[] = $style_declaration; } } return implode( '', $stylesheet ); } private function get_base_selector( array $style_def ): ?string { $map = [ 'class' => '.', ]; if ( isset( $style_def['type'] ) && isset( $style_def['id'] ) && isset( $map[ $style_def['type'] ] ) && $style_def['id'] ) { $type = $map[ $style_def['type'] ]; $name = $style_def['cssName'] ?? $style_def['id']; $selector_parts = array_filter( [ $this->selector_prefix, "{$type}{$name}", ] ); return implode( ' ', $selector_parts ); } return null; } private function variant_to_css_string( string $base_selector, array $variant ): string { $css = $this->props_to_css_string( $variant['props'] ) ?? ''; $custom_css = $this->custom_css_to_css_string( $variant['custom_css'] ?? null ); if ( ! $css && ! $custom_css ) { return ''; } if ( isset( $variant['meta']['state'] ) ) { $selector = Style_States::get_selector_with_state( $base_selector, $variant['meta']['state'] ); } else { $selector = $base_selector; } $style_declaration = $selector . '{' . $css . $custom_css . '}'; if ( isset( $variant['meta']['breakpoint'] ) ) { $style_declaration = $this->wrap_with_media_query( $variant['meta']['breakpoint'], $style_declaration ); } return $style_declaration; } private function props_to_css_string( array $props ): string { $schema = Style_Schema::get(); return Collection::make( Render_Props_Resolver::for_styles()->resolve( $schema, $props ) ) ->filter() ->map( function ( $value, $prop ) use ( $props, $schema ) { $this->maybe_enqueue_font( $schema, $prop, $props[ $prop ] ?? null ); return $prop . ':' . $value . ';'; } ) ->implode( '' ); } private function maybe_enqueue_font( array $schema, string $prop_key, $prop_value ): void { if ( ! $this->on_font_enqueue || ! is_array( $prop_value ) || empty( $prop_value['value'] ) ) { return; } $enqueueable = $this->resolve_font_enqueueable( $schema[ $prop_key ] ?? null, $prop_value ); if ( ! $enqueueable ) { return; } $font = $enqueueable->get_enqueue_font_family( $prop_value['value'] ); if ( $font ) { call_user_func( $this->on_font_enqueue, $font ); } } private function resolve_font_enqueueable( ?Prop_Type $prop_type, array $prop_value ): ?Font_Enqueueable { if ( $prop_type instanceof Union_Prop_Type ) { $prop_type = $prop_type->get_prop_type( $prop_value['$$type'] ?? '' ); } if ( $prop_type instanceof Font_Enqueueable ) { return $prop_type; } return null; } private function custom_css_to_css_string( ?array $custom_css ): string { return ! empty( $custom_css['raw'] ) ? Utils::decode_string( $custom_css['raw'], '' ) . '\n' : ''; } private function wrap_with_media_query( string $breakpoint_id, string $css ): string { if ( ! isset( $this->breakpoints[ $breakpoint_id ] ) ) { return $css; } $breakpoint = $this->breakpoints[ $breakpoint_id ]; if ( isset( $breakpoint['is_enabled'] ) && ! $breakpoint['is_enabled'] ) { return ''; } $query = $this->get_media_query( $this->breakpoints[ $breakpoint_id ] ); return $query ? $query . '{' . $css . '}' : $css; } public static function get_media_query( $breakpoint ): ?string { if ( isset( $breakpoint['is_enabled'] ) && ! $breakpoint['is_enabled'] ) { return null; } $size = self::get_breakpoint_size( $breakpoint ); return $size ? '@media(' . $size . ')' : null; } private static function get_breakpoint_size( array $breakpoint ): ?string { $bound = 'min' === $breakpoint['direction'] ? 'min-width' : 'max-width'; $width = $breakpoint['value'] . 'px'; return "{$bound}:{$width}"; } } atomic-widgets/styles/style-variant.php 0000644 00000001602 15252521350 0014321 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; class Style_Variant { private ?string $breakpoint = null; private ?string $state = null; /** @var array<string, array> */ private array $props = []; public static function make(): self { return new self(); } public function set_breakpoint( string $breakpoint ): self { $this->breakpoint = $breakpoint; return $this; } public function set_state( string $state ): self { $this->state = $state; return $this; } public function add_prop( string $key, $value ): self { $this->props[ $key ] = $value; return $this; } public function add_props( array $props ): self { $this->props = array_merge( $this->props, $props ); return $this; } public function build(): array { return [ 'meta' => [ 'breakpoint' => $this->breakpoint, 'state' => $this->state, ], 'props' => $this->props, ]; } } atomic-widgets/styles/cache-validity/cache-validity-item.php 0000644 00000017576 15252521350 0020250 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles\CacheValidity; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Cache_Validity_Item { const CACHE_KEY_PREFIX = 'elementor_atomic_cache_validity__'; private string $root; public function __construct( string $root ) { $this->root = $root; } public function get( array $keys ): ?array { return $this->wrap_exception( function() use ( $keys ) { $data = $this->get_stored_data(); $node = $this->get_node( $data, $keys ); if ( null === $node ) { return null; } return is_bool( $node ) ? [ 'state' => $node ] : $node; } ); } public function validate( array $keys, $meta = null ) { return $this->wrap_exception( function() use ( $keys, $meta ) { $data = $this->get_stored_data(); if ( empty( $keys ) ) { $data['state'] = true; $data['meta'] = $meta; $this->update_stored_data( $data ); return; } $this->validate_nested_node( $data, $keys, $meta ); } ); } public function invalidate( array $keys ) { return $this->wrap_exception( function() use ( $keys ) { if ( empty( $keys ) ) { $this->delete_stored_data(); return; } $data = $this->get_stored_data(); $this->invalidate_nested_node( $data, $keys ); } ); } /** * @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data * @param array<string> $keys * @param mixed | null $meta */ private function validate_nested_node( array $data, array $keys, $meta = null ) { $data = $this->ensure_path( $data, $keys ); $last_key = array_pop( $keys ); // parent is guaranteed to be an array as we send the full $keys array to ensure_path $parent = &$this->get_node( $data, $keys ); $old_node = &$parent['children'][ $last_key ]; $has_children = is_array( $old_node ) && ! empty( $old_node['children'] ); if ( null === $meta && ! $has_children ) { $parent['children'][ $last_key ] = true; $this->update_stored_data( $data ); return; } $new_node = [ 'state' => true, ]; if ( $has_children ) { $new_node['children'] = $old_node['children']; } if ( null !== $meta ) { $new_node['meta'] = $meta; } $parent['children'][ $last_key ] = $new_node; $this->update_stored_data( $data ); } /** * @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data * @param array<string> $keys */ private function invalidate_nested_node( array $data, array $keys ) { $last_key = array_pop( $keys ); $parent = &$this->get_node( $data, $keys ); if ( ! is_array( $parent ) || ! isset( $parent['children'][ $last_key ] ) ) { // node doesn't exist - no need to do anything return; } if ( count( $parent['children'] ) === 1 ) { // if the invalidated node is the parent's only child - normalize the data $data = $this->get_normalized_data( $data, $keys, $last_key ); $this->update_stored_data( $data ); return; } unset( $parent['children'][ $last_key ] ); $this->update_stored_data( $data ); } /** * @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} $data * @param array<string> $keys * @param string $last_key * @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} */ private function get_normalized_data( array $data, array $keys, string $last_key ) { $obsolete_root_params = &$this->find_empty_parents_path_root( $data, $keys, $last_key ); $parent = &$this->get_node( $data, $keys ); if ( $obsolete_root_params['node'] && $obsolete_root_params['key'] ) { unset( $obsolete_root_params['node']['children'][ $obsolete_root_params['key'] ] ); return $data; } if ( $obsolete_root_params['node'] ) { unset( $data['children'] ); return $data; } if ( $parent ) { unset( $parent['children'][ $last_key ] ); } return $data; } /** * @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data * @param array<string> $keys * @return array{key: string | null, node: array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | null} */ private function &find_empty_parents_path_root( array &$data, array $keys ) { $root_node = [ 'key' => null, 'node' => null, ]; $current = &$data; $parent = &$current; while ( ! empty( $keys ) ) { $key = array_shift( $keys ); $parent = &$current; $current = &$current['children'][ $key ]; if ( $this->is_empty_parent( $current ) && empty( $root_node['node'] ) ) { $root_node = [ 'key' => $key, 'node' => &$parent, ]; } elseif ( is_array( $current ) && ! $this->is_empty_parent( $current ) ) { $root_node = [ 'key' => null, 'node' => null, ]; } } return $root_node; } /** * Retrieves the stored tree, guaranteed to have a path representation based on $keys * * @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data * @param array<string> $keys * @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} */ private function ensure_path( array $data, array $keys ): ?array { $current = &$data; while ( ! empty( $keys ) ) { $key = array_shift( $keys ); if ( is_bool( $current ) ) { $current = [ 'state' => $current ]; } if ( ! isset( $current['children'] ) ) { $current['children'] = []; } if ( ! isset( $current['children'][ $key ] ) ) { $current['children'][ $key ] = [ 'state' => false ]; } $current = &$current['children'][ $key ]; } return $data; } /** * @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data * @param array<string> $keys * @return array<array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean | null> | null | boolean */ private function &get_node( array &$data, array $keys ) { $current = &$data; while ( ! empty( $keys ) ) { $key = array_shift( $keys ); if ( isset( $current['children'][ $key ] ) ) { $current = &$current['children'][ $key ]; } else { $current = null; } } return $current; } private function is_empty_parent( $data ): bool { if ( ! is_array( $data ) ) { return false; } return ( ! isset( $data['children'] ) || 1 === count( $data['children'] ) ) && ! $data['state']; } /** * @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} */ private function get_stored_data() { return get_option( self::CACHE_KEY_PREFIX . $this->root, [ 'state' => false ] ); } /** * @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} $data */ private function update_stored_data( $data ) { // setting autoload with false to avoid unnecessary memory usage update_option( self::CACHE_KEY_PREFIX . $this->root, $data, false ); } private function delete_stored_data() { delete_option( self::CACHE_KEY_PREFIX . $this->root ); } private function wrap_exception( callable $callback ) { try { return $callback(); } catch ( \Exception $e ) { $this->delete_stored_data(); } } } atomic-widgets/styles/cache-validity/cache-validity.php 0000644 00000003247 15252521350 0017302 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles\CacheValidity; use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity_Item; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Cache_Validity { /** * @param array<string> $keys * @return bool */ public function is_valid( $keys ): bool { $root = array_shift( $keys ); $cache_item = new Cache_Validity_Item( $root ); $item = $cache_item->get( $keys ); if ( ! $item ) { return false; } return $item['state'] ?? false; } /** * @param array<string> $keys * @return mixed | null */ public function get_meta( array $keys ) { $root = array_shift( $keys ); $cache_item = new Cache_Validity_Item( $root ); $item = $cache_item->get( $keys ); if ( ! $item || is_bool( $item ) ) { return null; } return $item['meta'] ?? null; } /** * @param array<string> $keys * @param mixed | null $meta * @return void */ public function validate( $keys, $meta = null ): void { $root = array_shift( $keys ); $cache_item = new Cache_Validity_Item( $root ); $cache_item->validate( $keys, $meta ); } /** * @param array<string> $keys * @return void */ public function invalidate( array $keys ): void { $root = array_shift( $keys ); $cache_item = new Cache_Validity_Item( $root ); $cache_item->invalidate( $keys ); } /** * @param array<string> $keys * @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | null */ public function get_node( array $keys ): ?array { $root = array_shift( $keys ); $cache_item = new Cache_Validity_Item( $root ); return $cache_item->get( $keys ); } } atomic-widgets/styles/style-states.php 0000644 00000005034 15252521350 0014163 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Styles; class Style_States { const HOVER = 'hover'; const ACTIVE = 'active'; const FOCUS = 'focus'; const FOCUS_VISIBLE = 'focus-visible'; const CHECKED = 'checked'; const SELECTED = 'e--selected'; const DISABLED = 'e--disabled'; private static function get_pseudo_states(): array { return [ self::HOVER, self::ACTIVE, self::FOCUS, self::FOCUS_VISIBLE, self::CHECKED, ]; } private static function get_class_states(): array { return [ self::SELECTED, self::DISABLED, ]; } private static function get_additional_states_map(): array { return [ self::HOVER => [ self::FOCUS_VISIBLE ], ]; } public static function get_selector_with_state( string $base_selector, string $state ): string { $additional_states = self::get_additional_states( $state ); $all_states = [ $state, ...$additional_states ]; foreach ( $all_states as $current_state ) { $selector_strings[] = $base_selector . self::get_state_selector( $current_state ); } return implode( ',', $selector_strings ); } public static function get_additional_states( string $state ): array { return self::get_additional_states_map()[ $state ] ?? []; } public static function get_state_selector( string $state ): string { if ( self::is_class_state( $state ) ) { return '.' . $state; } if ( self::is_pseudo_state( $state ) ) { return ':' . $state; } return $state; } public static function get_valid_states(): array { return [ ...array_filter( self::get_pseudo_states(), function ( $state ) { return ! in_array( $state, self::get_additional_states_map()[ $state ] ?? [], true ); } ), ...self::get_class_states(), null, ]; } public static function is_pseudo_state( string $state ): bool { return in_array( $state, self::get_pseudo_states(), true ); } public static function is_class_state( string $state ): bool { return in_array( $state, self::get_class_states(), true ); } public static function is_valid_state( $state ): bool { if ( null === $state ) { return true; } return is_string( $state ) && in_array( $state, self::get_valid_states(), true ); } public static function get_class_states_map(): array { return [ 'selected' => [ 'name' => 'selected', 'value' => self::SELECTED, ], 'disabled' => [ 'name' => 'disabled', 'value' => self::DISABLED, ], ]; } public static function get_pseudo_states_map(): array { return [ 'checked' => [ 'name' => 'checked', 'value' => self::CHECKED, ], ]; } } atomic-widgets/library/atomic-widgets-library.php 0000644 00000001142 15252521350 0016221 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Library; use Elementor\Plugin; class Atomic_Widgets_Library { public function register_hooks() { add_action( 'elementor/documents/register', fn() => $this->register_documents() ); } public function register_documents() { Plugin::$instance->documents ->register_document_type( 'e-div-block', Div_Block::get_class_full_name() ) ->register_document_type( 'e-flexbox', Flexbox::get_class_full_name() ) ->register_document_type( 'e-grid', Grid::get_class_full_name() ) ->register_document_type( 'e-form', Atomic_Form::get_class_full_name() ); } } atomic-widgets/library/div-block.php 0000644 00000002242 15252521350 0013513 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Library; use Elementor\Modules\Library\Documents\Library_Document; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Div_Block library document. * * Elementor div block library document handler class is responsible for * handling a document of a div block type. * * @since 3.29.0 */ class Div_Block extends Library_Document { public static function get_properties() { $properties = parent::get_properties(); $properties['support_kit'] = true; return $properties; } /** * Get document name. * * Retrieve the document name. * * @since 2.0.0 * @access public * * @return string Document name. */ public function get_name() { return 'e-div-block'; } /** * Get document title. * * Retrieve the document title. * * @since 2.0.0 * @access public * @static * * @return string Document title. */ public static function get_title() { return esc_html__( 'Div Block', 'elementor' ); } /** * Get Type * * Return the div block document type. * * @return string */ public static function get_type() { return 'e-div-block'; } } atomic-widgets/library/atomic-form.php 0000644 00000002245 15252521350 0014061 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Library; use Elementor\Modules\Library\Documents\Library_Document; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Atomic Form library document. * * Elementor atomic form library document handler class is responsible for * handling a document of an atomic form type. * * @since 3.29.0 */ class Atomic_Form extends Library_Document { public static function get_properties() { $properties = parent::get_properties(); $properties['support_kit'] = true; return $properties; } /** * Get document name. * * Retrieve the document name. * * @since 2.0.0 * @access public * * @return string Document name. */ public function get_name() { return 'e-form'; } /** * Get document title. * * Retrieve the document title. * * @since 2.0.0 * @access public * @static * * @return string Document title. */ public static function get_title() { return esc_html__( 'Atomic Form', 'elementor' ); } /** * Get Type * * Return the atomic form document type. * * @return string */ public static function get_type() { return 'e-form'; } } atomic-widgets/library/flexbox.php 0000644 00000002222 15252521350 0013306 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Library; use Elementor\Modules\Library\Documents\Library_Document; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Flexbox library document. * * Elementor flexbox library document handler class is responsible for * handling a document of a flexbox type. * * @since 3.29.0 */ class Flexbox extends Library_Document { public static function get_properties() { $properties = parent::get_properties(); $properties['support_kit'] = true; return $properties; } /** * Get document name. * * Retrieve the document name. * * @since 2.0.0 * @access public * * @return string Document name. */ public function get_name() { return 'e-flexbox'; } /** * Get document title. * * Retrieve the document title. * * @since 2.0.0 * @access public * @static * * @return string Document title. */ public static function get_title() { return esc_html__( 'Flexbox', 'elementor' ); } /** * Get Type * * Return the flexbox document type. * * @return string */ public static function get_type() { return 'e-flexbox'; } } atomic-widgets/library/grid.php 0000644 00000002172 15252521350 0012570 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Library; use Elementor\Modules\Library\Documents\Library_Document; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Grid library document. * * Elementor grid library document handler class is responsible for * handling a document of a grid type. * * @since 3.29.0 */ class Grid extends Library_Document { public static function get_properties() { $properties = parent::get_properties(); $properties['support_kit'] = true; return $properties; } /** * Get document name. * * Retrieve the document name. * * @since 2.0.0 * @access public * * @return string Document name. */ public function get_name() { return 'e-grid'; } /** * Get document title. * * Retrieve the document title. * * @since 2.0.0 * @access public * @static * * @return string Document title. */ public static function get_title() { return esc_html__( 'Grid', 'elementor' ); } /** * Get Type * * Return the grid document type. * * @return string */ public static function get_type() { return 'e-grid'; } } atomic-widgets/utils/utils.php 0000644 00000003024 15252521350 0012474 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Utils; use Elementor\Core\Base\Document; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Utils { public static function is_atomic( $element_instance ): bool { return $element_instance instanceof Atomic_Element_Base || $element_instance instanceof Atomic_Widget_Base; } public static function generate_id( string $prefix = '', $existing_ids = [] ): string { do { $generated = substr( bin2hex( random_bytes( 4 ) ), 0, 7 ); $id = "$prefix{$generated}"; } while ( in_array( $id, $existing_ids, true ) ); return $id; } public static function is_post_published( Document $document ): bool { return $document->get_post()->post_status === Document::STATUS_PUBLISH; } public static function traverse_post_elements( string $post_id, callable $callback ): void { $documents = Plugin::$instance->documents; $document = Plugin::$instance->preview->is_editor_or_preview() ? $documents->get_doc_or_auto_save( $post_id, get_current_user_id() ) : $documents->get( $post_id ); if ( ! $document ) { return; } $elements_data = $document->get_elements_data(); if ( empty( $elements_data ) ) { return; } Plugin::$instance->db->iterate_data( $elements_data, function( $element_data ) use ( $callback ) { call_user_func( $callback, $element_data ); } ); } } atomic-widgets/utils/image/image-sizes.php 0000644 00000003067 15252521350 0014642 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Utils\Image; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Sizes { const DEFAULT_SIZE = 'large'; public static function get_keys() { return array_map( fn( $size ) => $size['value'], static::get_all() ); } public static function get_all(): array { $wp_image_sizes = static::get_wp_image_sizes(); $image_sizes = []; foreach ( $wp_image_sizes as $size_key => $size_attributes ) { $control_title = ucwords( str_replace( '_', ' ', $size_key ) ); if ( is_array( $size_attributes ) ) { $control_title .= sprintf( ' - %d*%d', $size_attributes['width'], $size_attributes['height'] ); } $image_sizes[] = [ 'label' => $control_title, 'value' => $size_key, ]; } $image_sizes[] = [ 'label' => esc_html__( 'Full', 'elementor' ), 'value' => 'full', ]; return $image_sizes; } private static function get_wp_image_sizes() { $default_image_sizes = get_intermediate_image_sizes(); $additional_sizes = wp_get_additional_image_sizes(); $image_sizes = []; foreach ( $default_image_sizes as $size ) { $image_sizes[ $size ] = [ 'width' => (int) get_option( $size . '_size_w' ), 'height' => (int) get_option( $size . '_size_h' ), 'crop' => (bool) get_option( $size . '_crop' ), ]; } if ( $additional_sizes ) { $image_sizes = array_merge( $image_sizes, $additional_sizes ); } // /** This filter is documented in wp-admin/includes/media.php */ return apply_filters( 'image_size_names_choose', $image_sizes ); } } atomic-widgets/utils/image/placeholder-image.php 0000644 00000000634 15252521350 0015764 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Utils\Image; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Placeholder_Image { public static function get_placeholder_image() { return ELEMENTOR_ASSETS_URL . 'images/placeholder-v4.svg'; } public static function get_background_placeholder_image() { return ELEMENTOR_ASSETS_URL . 'images/background-placeholder.svg'; } } atomic-widgets/utils/memo.php 0000644 00000000722 15252521350 0012273 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Memo { private array $cache = []; public function memoize( string $key, callable $callback ) { return function() use ( $key, $callback ) { if ( array_key_exists( $key, $this->cache ) ) { return $this->cache[ $key ]; } $this->cache[ $key ] = call_user_func( $callback ); return $this->cache[ $key ]; }; } } atomic-widgets/prop-types/flex-prop-type.php 0000644 00000001210 15252521350 0015204 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Flex_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'flex'; } protected function define_shape(): array { return [ 'flexGrow' => Number_Prop_Type::make(), 'flexShrink' => Number_Prop_Type::make(), 'flexBasis' => Size_Prop_Type::make(), ]; } } atomic-widgets/prop-types/base/plain-prop-type.php 0000644 00000004460 15252521350 0016275 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Base; use Elementor\Modules\AtomicWidgets\PropTypes\Concerns; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Plain_Prop_Type implements Transformable_Prop_Type { // Backward compatibility, do not change to "const". Keep name in uppercase. // phpcs:ignore static $KIND = 'plain'; use Concerns\Has_Default; use Concerns\Has_Generate; use Concerns\Has_Meta; use Concerns\Has_Required_Setting; use Concerns\Has_Settings; use Concerns\Has_Transformable_Validation; use Concerns\Has_Initial_Value; /** * @return array<Plain_Prop_Type> */ public static function get_subclasses(): array { $children = []; foreach ( get_declared_classes() as $class ) { if ( is_subclass_of( $class, self::class ) ) { $children[] = $class; } } return $children; } private ?array $dependencies = null; /** * @return static */ public static function make() { return new static(); } public function get_type(): string { // phpcs:ignore return static::$KIND; } public function validate( $value ): bool { if ( is_null( $value ) || ( $this->is_transformable( $value ) && empty( $value['value'] ) ) ) { return ! $this->is_required(); } return ( $this->is_transformable( $value ) && $this->validate_value( $value['value'] ) ); } public function sanitize( $value ) { $value['value'] = $this->sanitize_value( $value['value'] ); return $value; } public function jsonSerialize(): array { return [ // phpcs:ignore 'kind' => static::$KIND, 'key' => static::get_key(), 'default' => $this->get_default(), 'meta' => (object) $this->get_meta(), 'settings' => (object) $this->get_settings(), 'dependencies' => $this->get_dependencies(), 'initial_value' => $this->get_initial_value(), ]; } abstract public static function get_key(): string; abstract protected function validate_value( $value ): bool; abstract protected function sanitize_value( $value ); public function set_dependencies( ?array $dependencies ): self { $this->dependencies = empty( $dependencies ) ? null : $dependencies; return $this; } public function get_dependencies(): ?array { return $this->dependencies; } } atomic-widgets/prop-types/base/object-prop-type.php 0000644 00000007456 15252521350 0016450 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Base; use Elementor\Modules\AtomicWidgets\PropTypes\Concerns; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Object_Prop_Type implements Transformable_Prop_Type { // Backward compatibility, do not change to "const". Keep name in uppercase. // phpcs:ignore static $KIND = 'object'; use Concerns\Has_Default; use Concerns\Has_Generate; use Concerns\Has_Meta; use Concerns\Has_Required_Setting; use Concerns\Has_Settings; use Concerns\Has_Transformable_Validation; use Concerns\Has_Initial_Value; /** * @var array<Prop_Type> */ protected array $shape; protected ?array $dependencies = null; public function __construct() { $this->shape = $this->define_shape(); } public function get_type(): string { return 'object'; } public function get_default() { if ( null !== $this->default ) { return $this->default; } foreach ( $this->get_shape() as $item ) { // If the object has at least one property with default, return an empty object so // it'll be iterable for processes like validation / transformation. if ( $item->get_default() !== null ) { return static::generate( [] ); } } return null; } /** * @return static */ public static function make() { return new static(); } /** * @param array $shape * * @return $this */ public function set_shape( array $shape ) { $this->shape = $shape; return $this; } public function get_shape(): array { return $this->shape; } public function get_shape_field( $key ): ?Prop_Type { return $this->shape[ $key ] ?? null; } public function validate( $value ): bool { if ( is_null( $value ) ) { return ! $this->is_required(); } return ( $this->is_transformable( $value ) && $this->validate_value( $value['value'] ) ); } protected function validate_value( $value ): bool { if ( ! is_array( $value ) ) { return false; } foreach ( $this->get_shape() as $key => $prop_type ) { if ( ! ( $prop_type instanceof Prop_Type ) ) { Utils::safe_throw( "Object prop type must have a prop type for key: $key" ); } if ( ! $prop_type->validate( $value[ $key ] ?? $prop_type->get_default() ) ) { return false; } } return true; } public function sanitize( $value ) { $value['value'] = $this->sanitize_value( $value['value'] ); return $value; } public function sanitize_value( $value ) { foreach ( $this->get_shape() as $key => $prop_type ) { if ( ! isset( $value[ $key ] ) ) { continue; } $sanitized_value = $prop_type->sanitize( $value[ $key ] ); $value[ $key ] = $sanitized_value; } return $value; } public function jsonSerialize(): array { $default = $this->get_default(); return [ // phpcs:ignore 'kind' => static::$KIND, 'key' => static::get_key(), 'default' => is_array( $default ) ? (object) $default : $default, 'meta' => (object) $this->get_meta(), 'settings' => (object) $this->get_settings(), 'shape' => (object) $this->get_shape(), 'dependencies' => $this->get_dependencies(), 'initial_value' => $this->get_initial_value(), ]; } /** * @return array<Prop_Type> */ abstract protected function define_shape(): array; public function set_dependencies( ?array $dependencies ): self { $this->dependencies = empty( $dependencies ) ? null : $dependencies; return $this; } public function get_dependencies(): ?array { return $this->dependencies; } public function set_shape_meta( string $shape_key, array $meta ): self { foreach ( $meta as $key => $value ) { $this->get_shape_field( $shape_key )->meta( $key, $value ); } return $this; } } atomic-widgets/prop-types/base/unknown-prop-type.php 0000644 00000002437 15252521350 0016673 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Base; use Elementor\Modules\AtomicWidgets\PropTypes\Concerns\Has_Meta; use Elementor\Modules\AtomicWidgets\PropTypes\Concerns\Has_Required_Setting; use Elementor\Modules\AtomicWidgets\PropTypes\Concerns\Has_Settings; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Unknown_Prop_Type implements Prop_Type { use Has_Meta; use Has_Settings; use Has_Required_Setting; public static function get_key(): string { return 'unknown'; } public function get_type(): string { return 'unknown'; } public function get_default() { return null; } public function validate( $value ): bool { return true; } public function sanitize( $value ) { return $value; } public function set_dependencies( ?array $dependencies ): self { return $this; } public function get_dependencies(): ?array { return null; } public function get_initial_value() { return null; } public function jsonSerialize(): array { return [ 'kind' => 'unknown', 'key' => static::get_key(), 'settings' => (object) $this->get_settings(), 'meta' => (object) $this->get_meta(), ]; } public static function make(): self { return new self(); } } atomic-widgets/prop-types/base/array-prop-type.php 0000644 00000005443 15252521350 0016312 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Base; use Elementor\Modules\AtomicWidgets\PropTypes\Concerns; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Array_Prop_Type implements Transformable_Prop_Type { // Backward compatibility, do not change to "const". Keep name in uppercase. // phpcs:ignore static $KIND = 'array'; use Concerns\Has_Default; use Concerns\Has_Generate; use Concerns\Has_Meta; use Concerns\Has_Required_Setting; use Concerns\Has_Settings; use Concerns\Has_Transformable_Validation; use Concerns\Has_Initial_Value; protected Prop_Type $item_type; private ?array $dependencies = null; public function __construct() { $this->item_type = $this->define_item_type(); } /** * @return static */ public static function make() { return new static(); } public function get_type(): string { return 'array'; } /** * @param Prop_Type $item_type * * @return $this */ public function set_item_type( Prop_Type $item_type ) { $this->item_type = $item_type; return $this; } public function get_item_type(): Prop_Type { return $this->item_type; } public function validate( $value ): bool { if ( is_null( $value ) ) { return ! $this->is_required(); } return ( $this->is_transformable( $value ) && $this->validate_value( $value['value'] ) ); } protected function validate_value( $value ): bool { if ( ! is_array( $value ) ) { return false; } $prop_type = $this->get_item_type(); foreach ( $value as $item ) { if ( $prop_type && ! $prop_type->validate( $item ) ) { return false; } } return true; } public function sanitize( $value ) { $value['value'] = $this->sanitize_value( $value['value'] ); return $value; } public function sanitize_value( $value ) { $prop_type = $this->get_item_type(); return array_map( function ( $item ) use ( $prop_type ) { return $prop_type->sanitize( $item ); }, $value ); } public function jsonSerialize(): array { return [ // phpcs:ignore 'kind' => static::$KIND, 'key' => static::get_key(), 'default' => $this->get_default(), 'meta' => (object) $this->get_meta(), 'settings' => (object) $this->get_settings(), 'item_prop_type' => $this->get_item_type(), 'dependencies' => $this->get_dependencies(), 'initial_value' => $this->get_initial_value(), ]; } abstract protected function define_item_type(): Prop_Type; public function set_dependencies( ?array $dependencies ): self { $this->dependencies = empty( $dependencies ) ? null : $dependencies; return $this; } public function get_dependencies(): ?array { return $this->dependencies; } } atomic-widgets/prop-types/time-range-prop-type.php 0000644 00000001044 15252521350 0016303 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Time_String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Time_Range_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'time-range'; } protected function define_shape(): array { return [ 'min' => Time_String_Prop_Type::make(), 'max' => Time_String_Prop_Type::make(), ]; } } atomic-widgets/prop-types/html-prop-type.php 0000644 00000002273 15252521350 0015224 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Html_Prop_Type extends String_Prop_Type { public static function get_key(): string { return 'html'; } protected function validate_value( $value ): bool { return is_string( $value ); } protected function sanitize_value( $value ) { return preg_replace_callback( '/^(\s*)(.*?)(\s*)$/', function ( $matches ) { [, $leading, $value, $trailing ] = $matches; $sanitized = wp_kses( $value, static::get_base_allowed_tags() ); return $leading . $sanitized . $trailing; }, $value ); } public static function get_base_allowed_tags(): array { return [ 'b' => [], 'i' => [], 'em' => [], 'u' => [], 'ul' => [], 'ol' => [], 'li' => [], 'blockquote' => [], 'a' => [ 'href' => true, 'target' => true, ], 'del' => [], 'span' => [], 'br' => [], 'strong' => [], 'sup' => [], 'sub' => [], 's' => [], ]; } } atomic-widgets/prop-types/dimensions-prop-type.php 0000644 00000002630 15252521350 0016425 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Dimensions_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'dimensions'; } protected function define_shape(): array { return static::create_shape_with_units(); } public static function make_with_units( $units = null ) { return static::make()->set_shape( static::create_shape_with_units( $units ) ); } private static function create_shape_with_units( $units = null ) { $size_prop_type = Size_Prop_Type::make(); if ( null !== $units ) { $size_prop_type->units( $units ); } return [ 'block-start' => ( clone $size_prop_type )->description( 'The size for the block-start (top in LTR languages) side in Size PropType format.' ), 'inline-end' => ( clone $size_prop_type )->description( 'The size for the inline-end (right in LTR languages) side in Size PropType format.' ), 'block-end' => ( clone $size_prop_type )->description( 'The size for the block-end (bottom in LTR languages) side in Size PropType format.' ), 'inline-start' => ( clone $size_prop_type )->description( 'The size for the inline-start (left in LTR languages) side in Size PropType format.' ), ]; } } atomic-widgets/prop-types/svg-src-prop-type.php 0000644 00000001432 15252521350 0015640 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Svg_Src_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'svg-src'; } protected function define_shape(): array { return [ 'id' => Image_Attachment_Id_Prop_Type::make(), 'url' => Url_Prop_Type::make(), ]; } public function default_url( string $url ): self { $this->default( [ 'id' => null, 'url' => Url_Prop_Type::generate( $url ), ] ); return $this; } protected function validate_value( $value ): bool { $has_at_least_one_key = count( array_filter( $value ) ) >= 1; return $has_at_least_one_key && parent::validate_value( $value ); } } atomic-widgets/prop-types/image-src-prop-type.php 0000644 00000002137 15252521350 0016126 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Src_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'image-src'; } protected function define_shape(): array { return [ 'id' => Image_Attachment_Id_Prop_Type::make()->description( 'The ID of the image attachment in the WordPress media library, applicable for internal images only' ), 'url' => Url_Prop_Type::make(), 'alt' => String_Prop_Type::make()->description( 'The alt text of the image' ), ]; } public function default_url( string $url ): self { $this->default( [ 'id' => null, 'url' => Url_Prop_Type::generate( $url ), ] ); return $this; } protected function validate_value( $value ): bool { $has_id = ! empty( $value['id'] ); $has_url = ! empty( $value['url'] ); return ( $has_id xor $has_url ) && parent::validate_value( $value ); } } atomic-widgets/prop-types/concerns/has-settings.php 0000644 00000001237 15252521350 0016545 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Concerns; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } trait Has_Settings { protected array $settings = []; /** * @param $key * @param $value * * @return $this */ public function setting( $key, $value ) { $this->settings[ $key ] = $value; return $this; } public function get_settings(): array { return $this->settings; } public function get_setting( string $key, $default_value = null ) { return array_key_exists( $key, $this->settings ) ? $this->settings[ $key ] : $default_value; } } atomic-widgets/prop-types/concerns/has-initial-value.php 0000644 00000001032 15252521350 0017441 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Concerns; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } trait Has_Initial_Value { protected $initial_value = null; /** * @param $value * * @return $this */ public function initial_value( $value ): self { $this->initial_value = static::generate( $value ); return $this; } public function get_initial_value(): ?array { return $this->initial_value; } abstract public static function generate( $value, $disable = false ): array; } atomic-widgets/prop-types/concerns/has-required-setting.php 0000644 00000001367 15252521350 0020204 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Concerns; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } trait Has_Required_Setting { protected function is_required(): bool { return $this->get_setting( 'required', false ); } public function required() { $this->setting( 'required', true ); return $this; } public function optional() { $this->setting( 'required', false ); return $this; } public function set_required( bool $required ) { $this->setting( 'required', $required ); return $this; } abstract public function get_setting( string $key, $default_value = null ); abstract public function setting( $key, $value ); } atomic-widgets/prop-types/concerns/has-default.php 0000644 00000001072 15252521350 0016326 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Concerns; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } trait Has_Default { protected $default = null; /** * @param $value * * @return $this */ public function default( $value ) { $this->default = static::generate( $value ); return $this; } public function get_default() { return $this->default; } abstract public static function generate( $value, $disable = false ): array; } atomic-widgets/prop-types/concerns/has-meta.php 0000644 00000002176 15252521350 0015636 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Concerns; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } trait Has_Meta { protected array $meta = []; /** * @param $key * @param $value * * @return $this */ public function meta( $key, $value = null ) { $is_tuple = is_array( $key ) && 2 === count( $key ); if ( $is_tuple ) { [ $key, $value ] = $key; } $this->meta[ $key ] = $value; return $this; } public function get_meta(): array { return $this->meta; } public function description( string $description ): self { $this->meta['description'] = $description; return $this; } public function alias( string ...$aliases ): self { $existing = $this->meta['aliases'] ?? []; if ( ! is_array( $existing ) ) { $existing = []; } $this->meta['aliases'] = array_values( array_unique( array_merge( $existing, $aliases ) ) ); return $this; } public function get_meta_item( $key, $default_value = null ) { return array_key_exists( $key, $this->meta ) ? $this->meta[ $key ] : $default_value; } } atomic-widgets/prop-types/concerns/has-transformable-validation.php 0000644 00000001210 15252521350 0021663 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Concerns; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } trait Has_Transformable_Validation { protected function is_transformable( $value ): bool { $satisfies_basic_shape = ( is_array( $value ) && array_key_exists( '$$type', $value ) && array_key_exists( 'value', $value ) && static::get_key() === $value['$$type'] ); $supports_disabling = ( ! isset( $value['disabled'] ) || is_bool( $value['disabled'] ) ); return ( $satisfies_basic_shape && $supports_disabling ); } abstract public static function get_key(): string; } atomic-widgets/prop-types/concerns/has-generate.php 0000644 00000001010 15252521350 0016464 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Concerns; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } trait Has_Generate { public static function generate( $value, $disable = false ): array { $value = [ '$$type' => static::get_key(), 'value' => $value, ]; if ( $disable ) { $value['disabled'] = true; } return $value; } abstract public static function get_key(): string; } atomic-widgets/prop-types/link-prop-type.php 0000644 00000002706 15252521350 0015216 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Link_Prop_Type extends Object_Prop_Type { public const DEFAULT_TAG = 'a'; public static function get_key(): string { return 'link'; } protected function define_shape(): array { $target_blank_dependencies = Dependency_Manager::make() ->where( [ 'operator' => 'exists', 'path' => [ 'link', 'destination' ], ] ) ->get(); $tag_dependencies = Dependency_Manager::make() ->where( [ 'operator' => 'ne', 'path' => [ 'link', 'destination' ], 'nestedPath' => [ 'group' ], 'value' => 'action', 'newValue' => String_Prop_Type::generate( 'button' ), ] )->get(); return [ 'destination' => Union_Prop_Type::make() ->add_prop_type( Url_Prop_Type::make()->skip_validation() ) ->add_prop_type( Query_Prop_Type::make() ), 'isTargetBlank' => Boolean_Prop_Type::make() ->set_dependencies( $target_blank_dependencies ), 'tag' => String_Prop_Type::make() ->enum( [ 'a', 'button' ] ) ->default( self::DEFAULT_TAG ) ->set_dependencies( $tag_dependencies ), ]; } } atomic-widgets/prop-types/key-value-prop-type.php 0000644 00000001762 15252521350 0016164 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Key_Value_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'key-value'; } protected function define_shape(): array { return [ 'key' => String_Prop_Type::make(), 'value' => String_Prop_Type::make(), ]; } public function sanitize_value( $value ) { $prop_type = String_Prop_Type::make(); if ( isset( $value['key'] ) ) { $clean_key = esc_attr( $prop_type->sanitize( $value['key'] )['value'] ); $value['key'] = String_Prop_Type::generate( $clean_key ); } if ( isset( $value['value'] ) ) { $clean_value = esc_attr( $prop_type->sanitize( $value['value'] )['value'] ); $value['value'] = String_Prop_Type::generate( $clean_value ); } return $value; } } atomic-widgets/prop-types/query-filter-array-prop-type.php 0000644 00000000772 15252521350 0020026 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Query_Filter_Array_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'query-filter-array'; } protected function define_item_type(): Prop_Type { return Query_Filter_Prop_Type::make(); } } atomic-widgets/prop-types/utils/prop-types-schema-extender.php 0000644 00000007330 15252521350 0020656 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Utils; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } abstract class Prop_Types_Schema_Extender { public function get_extended_schema( array $schema ): array { $result = []; foreach ( $schema as $key => $prop_type ) { if ( ! ( $prop_type instanceof Prop_Type ) ) { $result[ $key ] = $prop_type; continue; } $result[ $key ] = $this->get_extended_prop_type( $prop_type ); } return $result; } public function get_extended_style_schema( array $schema ): array { $result = []; foreach ( $schema as $key => $prop_type ) { if ( ! ( $prop_type instanceof Prop_Type ) ) { $result[ $key ] = $prop_type; continue; } if ( ! $this->contains_prop_type_instance( $prop_type, Color_Prop_Type::class ) ) { $result[ $key ] = $prop_type; continue; } $result[ $key ] = $this->get_extended_prop_type( $prop_type ); } return $result; } private function contains_prop_type_instance( Prop_Type $prop_type, string $prop_type_class ): bool { if ( $prop_type instanceof $prop_type_class ) { return true; } if ( $prop_type instanceof Union_Prop_Type ) { foreach ( $prop_type->get_prop_types() as $inner_prop_type ) { if ( $this->contains_prop_type_instance( $inner_prop_type, $prop_type_class ) ) { return true; } } return false; } if ( $prop_type instanceof Object_Prop_Type ) { foreach ( $prop_type->get_shape() as $shape_prop_type ) { if ( $shape_prop_type instanceof Prop_Type && $this->contains_prop_type_instance( $shape_prop_type, $prop_type_class ) ) { return true; } } return false; } if ( $prop_type instanceof Array_Prop_Type ) { return $this->contains_prop_type_instance( $prop_type->get_item_type(), $prop_type_class ); } return false; } protected function get_extended_prop_type( Prop_Type $prop_type ): Prop_Type { if ( ! ( $prop_type instanceof Transformable_Prop_Type || $prop_type instanceof Union_Prop_Type ) ) { return $prop_type; } $transformable_prop_types = $prop_type instanceof Union_Prop_Type ? $prop_type->get_prop_types() : [ $prop_type ]; foreach ( $transformable_prop_types as $transformable_prop_type ) { if ( $transformable_prop_type instanceof Object_Prop_Type ) { $transformable_prop_type->set_shape( $this->get_extended_schema( $transformable_prop_type->get_shape() ) ); } if ( $transformable_prop_type instanceof Array_Prop_Type ) { $transformable_prop_type->set_item_type( $this->get_extended_prop_type( $transformable_prop_type->get_item_type() ) ); } } $prop_types_to_add = $this->get_prop_types_to_add( $prop_type ); if ( empty( $prop_types_to_add ) ) { return $prop_type; } $union_prop_type = $prop_type; if ( $prop_type instanceof Union_Prop_Type ) { $union_prop_type = clone $prop_type; } elseif ( $prop_type instanceof Transformable_Prop_Type ) { $union_prop_type = Union_Prop_Type::create_from( $prop_type ); } foreach ( $prop_types_to_add as $added_prop_type ) { $union_prop_type->add_prop_type( $added_prop_type ); } return $union_prop_type; } /** * Get the prop types to add to the prop type we extend * * @param Prop_Type $prop_type the prop type we extend */ abstract protected function get_prop_types_to_add( Prop_Type $prop_type ): array; } atomic-widgets/prop-types/contracts/transformable-prop-type.php 0000644 00000000467 15252521350 0021122 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Contracts; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Transformable_Prop_Type extends Prop_Type { public static function get_key(): string; public static function generate( $value, $disable = false ): array; } atomic-widgets/prop-types/contracts/prop-type.php 0000644 00000001500 15252521350 0016252 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Contracts; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Prop_Type extends \JsonSerializable { public static function get_key(): string; public function get_type(): string; public function get_default(); public function validate( $value ): bool; public function sanitize( $value ); public function get_meta(): array; public function get_meta_item( string $key, $default_value = null ); public function get_settings(): array; public function get_setting( string $key, $default_value = null ); public function set_dependencies( ?array $dependencies ): self; public function get_dependencies(): ?array; public function get_initial_value(); } atomic-widgets/prop-types/contracts/font-enqueueable.php 0000644 00000000362 15252521350 0017557 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Contracts; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Font_Enqueueable { public function get_enqueue_font_family( $stored_value ): ?string; } atomic-widgets/prop-types/stroke-prop-type.php 0000644 00000001062 15252521350 0015562 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Stroke_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'stroke'; } protected function define_shape(): array { return [ 'color' => Color_Prop_Type::make(), 'width' => Size_Prop_Type::make()->units( Size_Constants::stroke_width() ), ]; } } atomic-widgets/prop-types/email-prop-type.php 0000644 00000001765 15252521350 0015354 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Email_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'email'; } protected function define_shape(): array { return [ 'to' => String_Prop_Type::make(), 'subject' => String_Prop_Type::make(), 'message' => String_Prop_Type::make() ->default( '[all-fields]' ), 'from' => String_Prop_Type::make(), 'meta-data' => String_Array_Prop_Type::make(), 'send-as' => String_Prop_Type::make() ->enum( [ 'html', 'plain' ] ) ->default( 'html' ), 'from-name' => String_Prop_Type::make(), 'reply-to' => String_Prop_Type::make(), 'cc' => String_Prop_Type::make(), 'bcc' => String_Prop_Type::make(), ]; } } atomic-widgets/prop-types/attributes-prop-type.php 0000644 00000000747 15252521350 0016452 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Attributes_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'attributes'; } protected function define_item_type(): Prop_Type { return Key_Value_Prop_Type::make(); } } atomic-widgets/prop-types/video-attachment-id-prop-type.php 0000644 00000000752 15252521350 0020106 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Video_Attachment_Id_Prop_Type extends Number_Prop_Type { public static function get_key(): string { return 'video-attachment-id'; } protected function validate_value( $value ): bool { return is_numeric( $value ); } protected function sanitize_value( $value ): int { return (int) $value; } } atomic-widgets/prop-types/traits/dimensional-prop-type.php 0000644 00000003603 15252521350 0020066 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Traits; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use Elementor\Modules\AtomicWidgets\PropTypes\Concerns\Has_Default; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; trait Dimensional_Prop_Type { use Has_Default; /** * Defines the shape of the 3D prop type. * * @return array */ protected function define_shape(): array { $shape = []; foreach ( $this->get_dimensions() as $dimension ) { $shape[ $dimension ] = $this->get_prop_type( $dimension ); } return $shape; } protected function get_prop_type( $bind ): Prop_Type { $prop_type = Size_Prop_Type::make(); $units = $this->units( $bind ); // TODO discuss if we need this with the peacock team as its usage its only display the value on the UI $default_value = $this->get_default_value_by_bind( $bind ); $initial_value = $this->get_bind_initial_value(); if ( $units ) { $prop_type->units( $units ); } if ( $default_value ) { $prop_type->default_unit( $default_value['unit'] ); $prop_type->default( $default_value ); } if ( $initial_value ) { $prop_type->initial_value( $initial_value ); } return $prop_type; } protected function get_default_value_unit(): string { return Size_Constants::UNIT_PX; } protected function get_default_value_size(): int { return 0; } protected function units(): ?array { return null; } protected function get_dimensions(): array { return [ 'x', 'y', 'z' ]; } protected function get_default_value_by_bind(): ?array { return [ 'size' => $this->get_default_value_size(), 'unit' => $this->get_default_value_unit(), ]; } protected function get_bind_initial_value() { return $this->get_default_value_by_bind(); } } atomic-widgets/prop-types/date-string-prop-type.php 0000644 00000001037 15252521350 0016476 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Date_String_Prop_Type extends String_Prop_Type { public static function get_key(): string { return 'date-string'; } protected function validate_value( $value ): bool { if ( ! parent::validate_value( $value ) ) { return false; } $date = date_create_from_format( 'Y-m-d', $value ); return false !== $date; } } atomic-widgets/prop-types/border-radius-prop-type.php 0000644 00000001334 15252521350 0017017 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Border_Radius_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'border-radius'; } protected function define_shape(): array { $units = Size_Constants::border(); return [ 'start-start' => Size_Prop_Type::make()->units( $units ), 'start-end' => Size_Prop_Type::make()->units( $units ), 'end-start' => Size_Prop_Type::make()->units( $units ), 'end-end' => Size_Prop_Type::make()->units( $units ), ]; } } atomic-widgets/prop-types/background-image-overlay-prop-type.php 0000644 00000002260 15252521350 0021132 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Position_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Image_Overlay_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'background-image-overlay'; } protected function define_shape(): array { return [ 'image' => Image_Prop_Type::make(), 'repeat' => String_Prop_Type::make()->enum( [ 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' ] ), 'size' => Union_Prop_Type::make() ->add_prop_type( String_Prop_Type::make()->enum( [ 'auto', 'cover', 'contain' ] ) ) ->add_prop_type( Background_Image_Overlay_Size_Scale_Prop_Type::make() ), 'position' => Union_Prop_Type::make() ->add_prop_type( String_Prop_Type::make()->enum( Position_Prop_Type::get_position_enum_values() ) ) ->add_prop_type( Background_Image_Position_Offset_Prop_Type::make() ), 'attachment' => String_Prop_Type::make()->enum( [ 'fixed', 'scroll' ] ), ]; } } atomic-widgets/prop-types/grid-track-size-prop-type.php 0000644 00000000413 15252521350 0017251 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Grid_Track_Size_Prop_Type extends Size_Prop_Type { public static function get_key(): string { return 'grid-track-size'; } } atomic-widgets/prop-types/union-prop-type.php 0000644 00000007746 15252521350 0015422 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Union_Prop_Type implements Prop_Type { // Backward compatibility, do not change to "const". Keep name in uppercase. // phpcs:ignore static $KIND = 'union'; use Concerns\Has_Meta; use Concerns\Has_Settings; use Concerns\Has_Required_Setting; protected $default = null; protected $initial_value = null; private ?array $dependencies = null; /** @var Array<string, Transformable_Prop_Type> */ protected array $prop_types = []; public static function get_key(): string { return 'union'; } public static function make(): self { return new static(); } public static function create_from( Transformable_Prop_Type $prop_type ): self { $dependencies = $prop_type->get_dependencies(); $prop_type->set_dependencies( [] ); $prop_meta = $prop_type->get_meta() ?? []; $result = static::make() ->add_prop_type( $prop_type ) ->default( $prop_type->get_default() ) ->set_dependencies( $dependencies ); foreach ( $prop_meta as $key => $value ) { $result->meta( $key, $value ); } return $result ->initial_value( $prop_type->get_initial_value() ) ->set_dependencies( $dependencies ) ->set_required_settings( $prop_type ); } public function get_type(): string { return 'union'; } public function add_prop_type( Transformable_Prop_Type $prop_type ): self { $this->prop_types[ $prop_type::get_key() ] = $prop_type; return $this; } public function get_prop_types(): array { return $this->prop_types; } public function get_prop_type( $type ): ?Transformable_Prop_Type { return $this->prop_types[ $type ] ?? null; } public function get_prop_type_from_value( $value ): ?Prop_Type { if ( isset( $value['$$type'] ) ) { return $this->get_prop_type( $value['$$type'] ); } if ( is_numeric( $value ) ) { return $this->get_prop_type( 'number' ); } if ( is_bool( $value ) ) { return $this->get_prop_type( 'boolean' ); } if ( is_string( $value ) ) { return $this->get_prop_type( 'string' ); } return null; } public function default( $value, ?string $type = null ): self { $this->default = ! $type ? $value : [ '$$type' => $type, 'value' => $value, ]; return $this; } public function initial_value( $value, ?string $type = null ): self { $this->initial_value = ! $type ? $value : [ '$$type' => $type, 'value' => $value, ]; return $this; } public function get_default() { return $this->default; } public function get_initial_value() { return $this->initial_value; } public function validate( $value ): bool { if ( is_null( $value ) ) { return ! $this->is_required(); } $prop_type = $this->get_prop_type_from_value( $value ); return $prop_type && $prop_type->validate( $value ); } public function sanitize( $value ) { $prop_type = $this->get_prop_type_from_value( $value ); return $prop_type ? $prop_type->sanitize( $value ) : null; } public function jsonSerialize(): array { return [ // phpcs:ignore 'kind' => static::$KIND, 'default' => $this->get_default(), 'meta' => $this->get_meta(), 'settings' => $this->get_settings(), 'prop_types' => $this->get_prop_types(), 'dependencies' => $this->get_dependencies(), 'initial_value' => $this->get_initial_value(), ]; } public function set_dependencies( ?array $dependencies ): self { $this->dependencies = empty( $dependencies ) ? null : $dependencies; return $this; } public function get_dependencies(): ?array { return $this->dependencies; } private function set_required_settings( Transformable_Prop_Type $prop_type ): self { if ( $prop_type->get_setting( 'required', false ) ) { $this->required(); } return $this; } } atomic-widgets/prop-types/shadow-prop-type.php 0000644 00000002310 15252521350 0015535 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Shadow_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'shadow'; } protected function define_shape(): array { $units = Size_Constants::box_shadow(); $size = [ 'size' => 0, 'unit' => Size_Constants::UNIT_PX, ]; $blur = [ 'size' => 10, 'unit' => Size_Constants::UNIT_PX, ]; return [ 'hOffset' => Size_Prop_Type::make()->required()->units( $units )->initial_value( $size ), 'vOffset' => Size_Prop_Type::make()->required()->units( $units )->initial_value( $size ), 'blur' => Size_Prop_Type::make()->required()->units( $units )->initial_value( $blur ), 'spread' => Size_Prop_Type::make()->required()->units( $units )->initial_value( $size ), 'color' => Color_Prop_Type::make()->required()->initial_value( 'rgba(0, 0, 0, 1)' ), 'position' => String_Prop_Type::make()->enum( [ 'inset' ] ), ]; } } atomic-widgets/prop-types/background-image-position-offset-prop-type.php 0000644 00000001176 15252521350 0022606 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Image_Position_Offset_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'background-image-position-offset'; } protected function define_shape(): array { $units = Size_Constants::position(); return [ 'x' => Size_Prop_Type::make()->units( $units ), 'y' => Size_Prop_Type::make()->units( $units ), ]; } } atomic-widgets/prop-types/color-prop-type.php 0000644 00000000505 15252521350 0015372 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Color_Prop_Type extends String_Prop_Type { public static function get_key(): string { return 'color'; } } atomic-widgets/prop-types/span-prop-type.php 0000644 00000000503 15252521350 0015213 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Span_Prop_Type extends String_Prop_Type { public static function get_key(): string { return 'span'; } } atomic-widgets/prop-types/transition-prop-type.php 0000644 00000000755 15252521350 0016455 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transition_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'transition'; } protected function define_item_type(): Prop_Type { return Selection_Size_Prop_Type::make(); } } atomic-widgets/prop-types/font-family-prop-type.php 0000644 00000001553 15252521350 0016505 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Font_Enqueueable; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Font_Family_Prop_Type extends String_Prop_Type implements Font_Enqueueable { public static function get_key(): string { return 'font-family'; } public function get_enqueue_font_family( $stored_value ): ?string { if ( ! is_string( $stored_value ) ) { return null; } $trimmed = trim( $stored_value ); $is_quoted = ( ( str_starts_with( $trimmed, '"' ) && str_ends_with( $trimmed, '"' ) ) || ( str_starts_with( $trimmed, "'" ) && str_ends_with( $trimmed, "'" ) ) ); if ( $is_quoted ) { return trim( substr( $trimmed, 1, -1 ) ); } return $trimmed; } } atomic-widgets/prop-types/date-range-prop-type.php 0000644 00000002233 15252521350 0016263 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Date_String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Date_Range_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'date-range'; } protected function define_shape(): array { return [ 'min' => Date_String_Prop_Type::make(), 'max' => Date_String_Prop_Type::make(), ]; } protected function validate_value( $value ): bool { if ( ! parent::validate_value( $value ) ) { return false; } $min = $this->extract_iso_date( $value['min'] ?? null ); $max = $this->extract_iso_date( $value['max'] ?? null ); if ( null === $min || null === $max ) { return true; } return $max >= $min; } private function extract_iso_date( $field ): ?int { $raw = is_array( $field ) ? ( $field['value'] ?? null ) : null; if ( ! is_string( $raw ) || '' === $raw ) { return null; } $date = date_create_from_format( 'Y-m-d', $raw ); return false === $date ? null : $date->getTimestamp(); } } atomic-widgets/prop-types/emails-prop-type.php 0000644 00000001404 15252521350 0015525 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Array_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Emails_Prop_Type extends Email_Prop_Type { public static function get_key(): string { return 'emails'; } protected function define_shape(): array { $shape = parent::define_shape(); $shape['to'] = String_Array_Prop_Type::make()->required(); $shape['cc'] = String_Array_Prop_Type::make(); $shape['bcc'] = String_Array_Prop_Type::make(); return $shape; } protected function validate_value( $value ): bool { if ( ! parent::validate_value( $value ) ) { return false; } $to = $value['to'] ?? null; return is_array( $to ) && ! empty( $to['value'] ); } } atomic-widgets/prop-types/time-string-prop-type.php 0000644 00000001114 15252521350 0016513 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Time_String_Prop_Type extends String_Prop_Type { const ISO_TIME_REGEX = '/^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$/'; public static function get_key(): string { return 'time-string'; } protected function validate_value( $value ): bool { if ( ! parent::validate_value( $value ) ) { return false; } return 1 === preg_match( self::ISO_TIME_REGEX, $value ); } } atomic-widgets/prop-types/box-shadow-prop-type.php 0000644 00000000745 15252521350 0016335 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Box_Shadow_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'box-shadow'; } protected function define_item_type(): Prop_Type { return Shadow_Prop_Type::make(); } } atomic-widgets/prop-types/selection-size-prop-type.php 0000644 00000002052 15252521350 0017210 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Selection_Size_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'selection-size'; } protected function define_shape(): array { $initial_value = [ 'key' => String_Prop_Type::generate( 'All properties' ), 'value' => String_Prop_Type::generate( 'all' ), ]; return [ 'selection' => Key_Value_Prop_Type::make() ->required() ->initial_value( $initial_value ) ->setting( 'hide_reset', true ), 'size' => Size_Prop_Type::make() ->units( Size_Constants::transition() ) ->default_unit( Size_Constants::UNIT_MILLI_SECOND ) ->initial_value( [ 'size' => 200, 'unit' => Size_Constants::UNIT_MILLI_SECOND, ] ) ->required(), ]; } } atomic-widgets/prop-types/color-stop-prop-type.php 0000644 00000001045 15252521350 0016355 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Color_Stop_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'color-stop'; } protected function define_shape(): array { return [ 'color' => Color_Prop_Type::make(), 'offset' => Number_Prop_Type::make(), ]; } } atomic-widgets/prop-types/image-attachment-id-prop-type.php 0000644 00000001036 15252521350 0020056 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Attachment_Id_Prop_Type extends Number_Prop_Type { public static function get_key(): string { return 'image-attachment-id'; } protected function validate_value( $value ): bool { return is_numeric( $value ); } protected function sanitize_value( $value ): int { return (int) $value; } } atomic-widgets/prop-types/size-prop-type.php 0000644 00000005556 15252521350 0015241 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Size_Prop_Type extends Object_Prop_Type { public function units( $units = 'all' ): self { if ( 'all' === $units ) { $units = Size_Constants::standard_units(); } if ( is_array( $units ) ) { foreach ( $units as $unit ) { if ( ! is_string( $unit ) ) { Utils::safe_throw( 'All units must be strings.' ); } } } $this->settings['available_units'] = $units; return $this; } public function default_unit( $unit ) { $this->settings['default_unit'] = $unit; return $this; } public function get_settings(): array { if ( ! array_key_exists( 'available_units', $this->settings ) ) { $this->units(); } return parent::get_settings(); } public static function get_key(): string { return 'size'; } protected function validate_value( $value ): bool { if ( ! is_array( $value ) || ! array_key_exists( 'size', $value ) || ! array_key_exists( 'unit', $value ) || empty( $value['unit'] ) || ! in_array( $value['unit'], Size_Constants::all_supported_units(), true ) ) { return false; } switch ( $value['unit'] ) { case Size_Constants::UNIT_CUSTOM: return null !== $value['size'] || 'auto' === $value['size']; case Size_Constants::UNIT_AUTO: // for "auto" - the propType schema does not enforce size to be null, so we manually set it to null here $value['size'] = null; return ! $value['size']; default: return ( ! in_array( $value['unit'], [ Size_Constants::UNIT_AUTO, Size_Constants::UNIT_CUSTOM ], true ) && ( ! empty( $value['size'] ) || 0 === $value['size'] ) && is_numeric( $value['size'] ) ); } } public function sanitize_value( $value ) { $unit = sanitize_text_field( $value['unit'] ); if ( ! in_array( $value['unit'], [ Size_Constants::UNIT_AUTO, Size_Constants::UNIT_CUSTOM ] ) ) { return [ // The + operator cast the $value['size'] to numeric (either int or float - depends on the value) 'size' => +$value['size'], 'unit' => $unit, ]; } return [ 'size' => Size_Constants::UNIT_AUTO === $value['unit'] ? '' : sanitize_text_field( $value['size'] ), 'unit' => $unit, ]; } protected function define_shape(): array { return [ // TODO do we send all units for every size 'unit' => String_Prop_Type::make()->enum( Size_Constants::all_supported_units() ) ->required(), 'size' => Union_Prop_Type::make() ->add_prop_type( String_Prop_Type::make() ) ->add_prop_type( Number_Prop_Type::make() ), ]; } } atomic-widgets/prop-types/number-range-prop-type.php 0000644 00000001044 15252521350 0016635 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Number_Range_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'number-range'; } protected function define_shape(): array { return [ 'min' => Number_Prop_Type::make(), 'max' => Number_Prop_Type::make(), ]; } } atomic-widgets/prop-types/html-v2-prop-type.php 0000644 00000005361 15252521350 0015552 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Html_V2_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'html-v2'; } protected function define_shape(): array { return [ 'content' => String_Prop_Type::make(), ]; } protected function validate_value( $value ): bool { if ( ! is_array( $value ) ) { return false; } if ( ! array_key_exists( 'content', $value ) ) { return false; } if ( ! is_null( $value['content'] ) && ! is_string( $value['content'] ) ) { return false; } if ( ! array_key_exists( 'children', $value ) ) { return false; } if ( ! is_array( $value['children'] ) ) { return false; } return true; } public function sanitize_value( $value ) { if ( is_string( $value['content'] ) ) { $value['content'] = $this->sanitize_html_content( $value['content'] ); } $value['children'] = $this->sanitize_children( $value['children'] ); return $value; } private function sanitize_html_content( string $content ): string { return preg_replace_callback( '/^(\s*)(.*?)(\s*)$/', function ( $matches ) { [, $leading, $value, $trailing ] = $matches; $sanitized = wp_kses( $value, self::get_allowed_tags() ); return $leading . $sanitized . $trailing; }, $content ); } private static function get_allowed_tags(): array { $base_tags = Html_Prop_Type::get_base_allowed_tags(); $inline_tags = [ 'b', 'i', 'em', 'u', 'a', 'del', 'span', 'strong', 'sup', 'sub', 's' ]; foreach ( $inline_tags as $tag ) { if ( isset( $base_tags[ $tag ] ) ) { $base_tags[ $tag ]['id'] = true; } } return $base_tags; } private function sanitize_children( array $children ): array { $sanitized = []; foreach ( $children as $child ) { if ( ! is_array( $child ) ) { continue; } $sanitized_child = []; if ( isset( $child['id'] ) && is_string( $child['id'] ) ) { $sanitized_child['id'] = sanitize_text_field( $child['id'] ); } if ( isset( $child['type'] ) && is_string( $child['type'] ) ) { $sanitized_child['type'] = sanitize_text_field( $child['type'] ); } if ( isset( $child['content'] ) && is_string( $child['content'] ) ) { $sanitized_child['content'] = sanitize_text_field( $child['content'] ); } if ( isset( $child['children'] ) && is_array( $child['children'] ) ) { $sanitized_child['children'] = $this->sanitize_children( $child['children'] ); } $sanitized[] = $sanitized_child; } return $sanitized; } } atomic-widgets/prop-types/image-prop-type.php 0000644 00000002303 15252521350 0015334 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\Utils\Image\Image_Sizes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'image'; } protected function define_shape(): array { return [ 'src' => Image_Src_Prop_Type::make()->required(), 'size' => String_Prop_Type::make() ->initial_value( Image_Sizes::DEFAULT_SIZE ) ->enum( Image_Sizes::get_keys() ) ->required() ->description( 'The image file size to use, affecting quality only!. This DOES NOT affect dimensions on the page. For affecting dimensions, use the element\'s style schema instead' ), ]; } public function default_url( string $url ): self { $this->get_shape_field( 'src' )->default( [ 'id' => null, 'url' => Url_Prop_Type::generate( $url ), ] ); return $this; } public function default_size( string $size ): self { $this->get_shape_field( 'size' )->default( $size ); return $this; } } atomic-widgets/prop-types/options-prop-type.php 0000644 00000000741 15252521350 0015751 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Options_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'options'; } protected function define_item_type(): Prop_Type { return Key_Value_Prop_Type::make(); } } atomic-widgets/prop-types/background-prop-type.php 0000644 00000001250 15252521350 0016371 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'background'; } protected function define_shape(): array { return [ 'background-overlay' => Background_Overlay_Prop_Type::make(), 'color' => Color_Prop_Type::make(), 'clip' => String_Prop_Type::make() ->enum( [ 'border-box', 'padding-box', 'content-box', 'text' ] ), ]; } } atomic-widgets/prop-types/transform/functions/transform-scale-prop-type.php 0000644 00000001315 15252521350 0023377 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Traits\Dimensional_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Scale_Prop_Type extends Object_Prop_Type { use Dimensional_Prop_Type; public static function get_key(): string { return 'transform-scale'; } protected function get_prop_type(): Prop_Type { return Number_Prop_Type::make()->float()->default( 1 ); } } atomic-widgets/prop-types/transform/functions/transform-rotate-prop-type.php 0000644 00000001270 15252521350 0023606 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Traits\Dimensional_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Rotate_Prop_Type extends Object_Prop_Type { use Dimensional_Prop_Type; public static function get_key(): string { return 'transform-rotate'; } protected function units(): ?array { return Size_Constants::rotate(); } protected function get_default_value_unit(): string { return Size_Constants::UNIT_DEG; } } atomic-widgets/prop-types/transform/functions/transform-move-prop-type.php 0000644 00000001131 15252521350 0023252 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Traits\Dimensional_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Move_Prop_Type extends Object_Prop_Type { use Dimensional_Prop_Type; public static function get_key(): string { return 'transform-move'; } protected function units(): ?array { return Size_Constants::transform(); } } atomic-widgets/prop-types/transform/functions/transform-skew-prop-type.php 0000644 00000001375 15252521350 0023267 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Traits\Dimensional_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Skew_Prop_Type extends Object_Prop_Type { use Dimensional_Prop_Type; public static function get_key(): string { return 'transform-skew'; } protected function units(): ?array { return Size_Constants::rotate(); } protected function get_default_value_unit(): string { return Size_Constants::UNIT_DEG; } protected function get_dimensions(): array { return [ 'x', 'y' ]; } } atomic-widgets/prop-types/transform/transform-functions-prop-type.php 0000644 00000002221 15252521350 0022305 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Transform; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Move_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Scale_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Rotate_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Skew_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Functions_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'transform-functions'; } protected function define_item_type(): Prop_Type { return Union_Prop_Type::make() ->add_prop_type( Transform_Move_Prop_Type::make() ) ->add_prop_type( Transform_Scale_Prop_Type::make() ) ->add_prop_type( Transform_Rotate_Prop_Type::make() ) ->add_prop_type( Transform_Skew_Prop_Type::make() ); } } atomic-widgets/prop-types/transform/perspective-origin-prop-type.php 0000644 00000001731 15252521350 0022107 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Transform; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Traits\Dimensional_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Perspective_Origin_Prop_Type extends Object_Prop_Type { use Dimensional_Prop_Type; public static function get_key(): string { return 'perspective-origin'; } protected function units(): array { return [ Size_Constants::UNIT_PX, Size_Constants::UNIT_PERCENT, Size_Constants::UNIT_EM, Size_Constants::UNIT_REM ]; } protected function get_dimensions(): array { return [ 'x', 'y' ]; } protected function get_default_value_unit(): string { return Size_Constants::UNIT_PERCENT; } protected function get_default_value_size(): int { return 50; } protected function get_bind_initial_value() { return null; } } atomic-widgets/prop-types/transform/transform-origin-prop-type.php 0000644 00000002004 15252521350 0021563 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Transform; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Traits\Dimensional_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Origin_Prop_Type extends Object_Prop_Type { use Dimensional_Prop_Type; public static function get_key(): string { return 'transform-origin'; } protected function get_default_value_by_bind( $bind ): ?array { return 'z' === $bind ? null : [ 'size' => 50, 'unit' => Size_Constants::UNIT_PERCENT, ]; } protected function units( $bind ): array { return 'z' === $bind ? [ Size_Constants::UNIT_PX, Size_Constants::UNIT_EM, Size_Constants::UNIT_REM ] : [ Size_Constants::UNIT_PX, Size_Constants::UNIT_PERCENT, Size_Constants::UNIT_EM, Size_Constants::UNIT_REM ]; } protected function get_bind_initial_value() { return null; } } atomic-widgets/prop-types/transform/transform-prop-type.php 0000644 00000001427 15252521350 0020306 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Transform; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'transform'; } public function define_shape(): array { return [ 'transform-functions' => Transform_Functions_Prop_Type::make(), 'transform-origin' => Transform_Origin_Prop_Type::make(), 'perspective' => Size_Prop_Type::make()->units( Size_Constants::length() ), 'perspective-origin' => Perspective_Origin_Prop_Type::make(), ]; } } atomic-widgets/prop-types/background-color-overlay-prop-type.php 0000644 00000000752 15252521350 0021172 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Color_Overlay_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'background-color-overlay'; } protected function define_shape(): array { return [ 'color' => Color_Prop_Type::make()->initial_value( '#00000033' ), ]; } } atomic-widgets/prop-types/query-array-prop-type.php 0000644 00000000745 15252521350 0016543 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Query_Array_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'query-array'; } protected function define_item_type(): Prop_Type { return Query_Prop_Type::make(); } } atomic-widgets/prop-types/layout-direction-prop-type.php 0000644 00000001143 15252521350 0017546 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Layout_Direction_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'layout-direction'; } protected function define_shape(): array { $units = Size_Constants::layout(); return [ 'column' => Size_Prop_Type::make()->units( $units ), 'row' => Size_Prop_Type::make()->units( $units ), ]; } } atomic-widgets/prop-types/gradient-color-stop-prop-type.php 0000644 00000001326 15252521350 0020152 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Gradient_Color_Stop_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'gradient-color-stop'; } protected function define_item_type(): Prop_Type { return Color_Stop_Prop_Type::make(); } } atomic-widgets/prop-types/video-src-prop-type.php 0000644 00000001537 15252521350 0016155 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Video_Src_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'video-src'; } protected function define_shape(): array { return [ 'id' => Video_Attachment_Id_Prop_Type::make()->description( 'The ID of the video attachment in the WordPress media library' ), 'url' => Url_Prop_Type::make(), ]; } public function default_url( string $url ): self { $this->default( [ 'id' => null, 'url' => Url_Prop_Type::generate( $url ), ] ); return $this; } protected function validate_value( $value ): bool { $only_one_key = count( array_filter( $value ) ) === 1; return $only_one_key && parent::validate_value( $value ); } } atomic-widgets/prop-types/date-time-prop-type.php 0000644 00000001124 15252521350 0016123 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Date_Time_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'date-time'; } protected function define_shape(): array { return [ 'date' => String_Prop_Type::make(), 'time' => String_Prop_Type::make(), ]; } public function get_default() { return null; } } atomic-widgets/prop-types/primitives/string-array-prop-type.php 0000644 00000000726 15252521350 0021076 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Primitives; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class String_Array_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'string-array'; } protected function define_item_type(): Prop_Type { return String_Prop_Type::make(); } } atomic-widgets/prop-types/primitives/boolean-prop-type.php 0000644 00000001147 15252521350 0020071 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Primitives; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Boolean_Prop_Type extends Plain_Prop_Type { // Backward compatibility, do not change to "const". Keep name in uppercase. // phpcs:ignore static $KIND = 'boolean'; public static function get_key(): string { return 'boolean'; } protected function validate_value( $value ): bool { return is_bool( $value ); } protected function sanitize_value( $value ) { return (bool) $value; } } atomic-widgets/prop-types/primitives/number-prop-type.php 0000644 00000001373 15252521350 0017743 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Primitives; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Number_Prop_Type extends Plain_Prop_Type { // Backward compatibility, do not change to "const". Keep name in uppercase. // phpcs:ignore static $KIND = 'number'; private bool $is_float = false; public static function get_key(): string { return 'number'; } public function float(): self { $this->is_float = true; return $this; } protected function validate_value( $value ): bool { return is_numeric( $value ); } protected function sanitize_value( $value ) { return $this->is_float ? (float) $value : (int) $value; } } atomic-widgets/prop-types/primitives/string-prop-type.php 0000644 00000003553 15252521350 0017763 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Primitives; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class String_Prop_Type extends Plain_Prop_Type { // Backward compatibility, do not change to "const". Keep name in uppercase. // phpcs:ignore static $KIND = 'string'; public static function get_key(): string { return 'string'; } public function enum( array $allowed_values ): self { $all_are_strings = array_reduce( $allowed_values, fn ( $carry, $item ) => $carry && is_string( $item ), true ); if ( ! $all_are_strings ) { Utils::safe_throw( 'All values in an enum must be strings.' ); } $this->settings['enum'] = $allowed_values; return $this; } public function get_enum() { return $this->settings['enum'] ?? null; } public function regex( $pattern ) { if ( ! is_string( $pattern ) ) { Utils::safe_throw( 'Pattern must be a string, and valid regex pattern' ); } $this->settings['regex'] = $pattern; return $this; } public function get_regex() { return $this->settings['regex'] ?? null; } protected function validate_value( $value ): bool { return ( is_string( $value ) && ( ! $this->get_enum() || $this->validate_enum( $value ) ) && ( ! $this->get_regex() || $this->validate_regex( $value ) ) ); } private function validate_enum( $value ): bool { return in_array( $value, $this->settings['enum'], true ); } private function validate_regex( $value ): bool { return preg_match( $this->settings['regex'], $value ); } protected function sanitize_value( $value ) { return preg_replace_callback( '/^(\s*)(.*?)(\s*)$/', function ( $matches ) { [, $leading, $value, $trailing ] = $matches; return $leading . sanitize_text_field( $value ) . $trailing; }, $value ); } } atomic-widgets/prop-types/border-width-prop-type.php 0000644 00000001340 15252521350 0016644 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Border_Width_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'border-width'; } protected function define_shape(): array { $units = Size_Constants::border(); return [ 'block-start' => Size_Prop_Type::make()->units( $units ), 'block-end' => Size_Prop_Type::make()->units( $units ), 'inline-start' => Size_Prop_Type::make()->units( $units ), 'inline-end' => Size_Prop_Type::make()->units( $units ), ]; } } atomic-widgets/prop-types/filters/backdrop-filter-prop-type.php 0000644 00000000426 15252521350 0020776 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Filters; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Backdrop_Filter_Prop_Type extends Filter_Prop_Type { public static function get_key(): string { return 'backdrop-filter'; } } atomic-widgets/prop-types/filters/filter-prop-type.php 0000644 00000000757 15252521350 0017222 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Filters; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Filter_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'filter'; } protected function define_item_type(): Prop_Type { return Css_Filter_Func_Prop_Type::make(); } } atomic-widgets/prop-types/filters/css-filter-func-prop-type.php 0000644 00000003373 15252521350 0020736 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Filters; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions\Blur_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions\Color_Tone_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions\Drop_Shadow_Filter_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions\Hue_Rotate_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions\Intensity_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Css_Filter_Func_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'css-filter-func'; } protected function validate_value( $value ): bool { return true; } protected function define_shape(): array { return [ 'func' => String_Prop_Type::make() ->enum( [ 'blur', 'brightness', 'contrast', 'grayscale', 'invert', 'saturate', 'sepia', 'hue-rotate', 'drop-shadow' ] ) ->default( 'blur' ) ->initial_value( 'blur' ) ->required() ->setting( 'hide_reset', true ), 'args' => Union_Prop_Type::make() ->add_prop_type( Blur_Prop_Type::make() ) ->add_prop_type( Intensity_Prop_Type::make() ) ->add_prop_type( Hue_Rotate_Prop_Type::make() ) ->add_prop_type( Color_Tone_Prop_Type::make() ) ->add_prop_type( Drop_Shadow_Filter_Prop_Type::make() ) ->initial_value( [ 'size' => 0, 'unit' => Size_Constants::UNIT_PX, ] ) ->required(), ]; } } atomic-widgets/prop-types/filters/functions/drop-shadow-filter-prop-type.php 0000644 00000002477 15252521350 0023460 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Drop_Shadow_Filter_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'drop-shadow'; } protected function define_shape(): array { $units = Size_Constants::drop_shadow(); $axis_size = [ 'size' => 0, 'unit' => Size_Constants::UNIT_PX, ]; $blur = [ 'size' => 10, 'unit' => Size_Constants::UNIT_PX, ]; $color = 'rgba(0, 0, 0, 1)'; return [ 'xAxis' => Size_Prop_Type::make() ->default( $axis_size ) ->initial_value( $axis_size ) ->required() ->units( $units ), 'yAxis' => Size_Prop_Type::make() ->default( $axis_size ) ->initial_value( $axis_size ) ->required() ->units( $units ), 'blur' => Size_Prop_Type::make() ->default( $blur ) ->initial_value( $blur ) ->required() ->units( $units ), 'color' => Color_Prop_Type::make() ->default( $color ) ->initial_value( $color ) ->required(), ]; } } atomic-widgets/prop-types/filters/functions/color-tone-prop-type.php 0000644 00000001367 15252521350 0022024 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Color_Tone_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'color-tone'; } protected function define_shape(): array { return [ 'size' => Size_Prop_Type::make() ->units( Size_Constants::color_tone_filter() ) ->default_unit( Size_Constants::UNIT_PERCENT ) ->default( [ 'size' => 0, 'unit' => Size_Constants::UNIT_PERCENT, ] ), ]; } } atomic-widgets/prop-types/filters/functions/blur-prop-type.php 0000644 00000001333 15252521350 0020700 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Blur_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'blur'; } protected function define_shape(): array { return [ 'size' => Size_Prop_Type::make() ->units( Size_Constants::blur_filter() ) ->default_unit( Size_Constants::UNIT_PX ) ->default( [ 'size' => 0, 'unit' => Size_Constants::UNIT_PX, ] ), ]; } } atomic-widgets/prop-types/filters/functions/hue-rotate-prop-type.php 0000644 00000001357 15252521350 0022017 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Hue_Rotate_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'hue-rotate'; } protected function define_shape(): array { return [ 'size' => Size_Prop_Type::make() ->units( Size_Constants::hue_rotate_filter() ) ->default_unit( Size_Constants::UNIT_DEG ) ->default( [ 'size' => 0, 'unit' => Size_Constants::UNIT_DEG, ] ), ]; } } atomic-widgets/prop-types/filters/functions/intensity-prop-type.php 0000644 00000001366 15252521350 0021770 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Intensity_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'intensity'; } protected function define_shape(): array { return [ 'size' => Size_Prop_Type::make() ->units( Size_Constants::intensity_filter() ) ->default_unit( Size_Constants::UNIT_PERCENT ) ->default( [ 'size' => 100, 'unit' => Size_Constants::UNIT_PERCENT, ] ), ]; } } atomic-widgets/prop-types/url-prop-type.php 0000644 00000001270 15252521350 0015056 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Url_Prop_Type extends String_Prop_Type { public static function get_key(): string { return 'url'; } public function skip_validation(): self { $this->settings['skip_validation'] = true; return $this; } protected function validate_value( $value ): bool { if ( ! empty( $this->settings['skip_validation'] ) ) { return true; } return (bool) wp_http_validate_url( $value ); } protected function sanitize_value( $value ) { return esc_url_raw( $value ); } } atomic-widgets/prop-types/background-overlay-prop-type.php 0000644 00000001272 15252521350 0020054 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Overlay_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'background-overlay'; } protected function define_item_type(): Prop_Type { return Union_Prop_Type::make() ->add_prop_type( Background_Color_Overlay_Prop_Type::make() ) ->add_prop_type( Background_Image_Overlay_Prop_Type::make() ) ->add_prop_type( Background_Gradient_Overlay_Prop_Type::make() ); } } atomic-widgets/prop-types/query-filter-prop-type.php 0000644 00000001307 15252521350 0016705 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Query_Filter_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'query-filter'; } protected function define_shape(): array { return [ 'key' => String_Prop_Type::make()->required(), 'values' => Query_Array_Prop_Type::make(), 'taxonomies' => String_Array_Prop_Type::make(), ]; } } atomic-widgets/prop-types/position-prop-type.php 0000644 00000002553 15252521350 0016125 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Position_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'object-position'; } protected function define_shape(): array { $units = Size_Constants::position(); return [ 'x' => Size_Prop_Type::make()->units( $units ), 'y' => Size_Prop_Type::make()->units( $units ), ]; } public static function get_position_enum_values(): array { return [ 'center center', 'center left', 'center right', 'top center', 'top left', 'top right', 'bottom center', 'bottom left', 'bottom right', ]; } public static function get_radial_position_regex(): string { $token_pattern = '(?:(?:top|bottom|left|right|center)|(?:-?\d+(?:\.\d+)?(?:%|px|em|rem|vw|vh)))'; return '/^' . $token_pattern . '(?:\s+' . $token_pattern . ')?$/i'; } public static function is_valid_radial_position( string $pos ): bool { if ( in_array( $pos, self::get_position_enum_values(), true ) ) { return true; } return (bool) preg_match( self::get_radial_position_regex(), $pos ); } } atomic-widgets/prop-types/background-gradient-overlay-prop-type.php 0000644 00000001615 15252521350 0021650 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Position_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Gradient_Overlay_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'background-gradient-overlay'; } protected function define_shape(): array { return [ 'type' => String_Prop_Type::make()->enum( [ 'linear', 'radial' ] ), 'angle' => Number_Prop_Type::make(), 'stops' => Gradient_Color_Stop_Prop_Type::make(), 'positions' => String_Prop_Type::make()->regex( Position_Prop_Type::get_radial_position_regex() ), ]; } } atomic-widgets/prop-types/query-prop-type.php 0000644 00000001247 15252521350 0015425 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Query_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'query'; } protected function define_shape(): array { return [ 'id' => Number_Prop_Type::make() ->required(), 'label' => String_Prop_Type::make(), ]; } public function get_default() { return null; } } atomic-widgets/prop-types/classes-prop-type.php 0000644 00000001733 15252521350 0015715 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Classes_Prop_Type extends Plain_Prop_Type { public static function get_key(): string { return 'classes'; } protected function validate_value( $value ): bool { if ( ! is_array( $value ) ) { return false; } foreach ( $value as $class_name ) { if ( ! is_string( $class_name ) || ! preg_match( '/^[a-z][a-z-_0-9]*$/i', $class_name ) ) { return false; } } return true; } protected function sanitize_value( $value ) { if ( ! is_array( $value ) ) { return null; } $sanitized = array_map(function ( $class_name ) { if ( ! is_string( $class_name ) ) { return null; } return sanitize_text_field( $class_name ); }, $value); return array_filter($sanitized, function ( $class_name ) { return ! empty( $class_name ); }); } } atomic-widgets/prop-types/html-v3-prop-type.php 0000644 00000006740 15252521350 0015555 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Unknown_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Html_V3_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'html-v3'; } protected function define_shape(): array { return [ 'content' => String_Prop_Type::make(), 'children' => Unknown_Prop_Type::make() ->optional() ->description( 'Plain array of child element objects (no prop type wrapping). Example: [{"id": "abc1", "type": "span", "content": "hello", "children": []}]' ), ]; } protected function validate_value( $value ): bool { if ( ! is_array( $value ) ) { return false; } if ( ! array_key_exists( 'content', $value ) ) { return false; } $content = $value['content']; if ( ! is_null( $content ) && ! $this->is_valid_string_prop( $content ) ) { return false; } if ( array_key_exists( 'children', $value ) && ! is_array( $value['children'] ) ) { return false; } return true; } private function is_valid_string_prop( $content ): bool { if ( ! is_array( $content ) ) { return false; } if ( ( $content['$$type'] ?? null ) !== 'string' ) { return false; } if ( ! array_key_exists( 'value', $content ) ) { return false; } return is_string( $content['value'] ); } public function sanitize_value( $value ) { if ( is_array( $value['content'] ) && is_string( $value['content']['value'] ?? null ) ) { $value['content']['value'] = $this->sanitize_html_content( $value['content']['value'] ); } if ( isset( $value['children'] ) && is_array( $value['children'] ) ) { $value['children'] = $this->sanitize_children( $value['children'] ); } return $value; } private function sanitize_html_content( string $content ): string { return preg_replace_callback( '/^(\s*)(.*?)(\s*)$/', function ( $matches ) { [, $leading, $value, $trailing ] = $matches; $sanitized = wp_kses( $value, self::get_allowed_tags() ); return $leading . $sanitized . $trailing; }, $content ); } private static function get_allowed_tags(): array { $base_tags = Html_Prop_Type::get_base_allowed_tags(); $inline_tags = [ 'b', 'i', 'em', 'u', 'a', 'del', 'span', 'strong', 'sup', 'sub', 's' ]; foreach ( $inline_tags as $tag ) { if ( isset( $base_tags[ $tag ] ) ) { $base_tags[ $tag ]['id'] = true; } } return $base_tags; } private function sanitize_children( array $children ): array { $sanitized = []; foreach ( $children as $child ) { if ( ! is_array( $child ) ) { continue; } $sanitized_child = []; if ( isset( $child['id'] ) && is_string( $child['id'] ) ) { $sanitized_child['id'] = sanitize_text_field( $child['id'] ); } if ( isset( $child['type'] ) && is_string( $child['type'] ) ) { $sanitized_child['type'] = sanitize_text_field( $child['type'] ); } if ( isset( $child['content'] ) && is_string( $child['content'] ) ) { $sanitized_child['content'] = sanitize_text_field( $child['content'] ); } if ( isset( $child['children'] ) && is_array( $child['children'] ) ) { $sanitized_child['children'] = $this->sanitize_children( $child['children'] ); } $sanitized[] = $sanitized_child; } return $sanitized; } } atomic-widgets/prop-types/background-image-overlay-size-scale-prop-type.php 0000644 00000001000 15252521350 0023156 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Image_Overlay_Size_Scale_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'background-image-size-scale'; } protected function define_shape(): array { return [ 'width' => Size_Prop_Type::make(), 'height' => Size_Prop_Type::make(), ]; } } atomic-widgets/module.php 0000644 00000067422 15252521350 0011475 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Elements_Manager; use Elementor\Modules\AtomicWidgets\Ajax\Render_Element_Action; use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Tags_Module; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Youtube\Atomic_Youtube; use Elementor\Modules\AtomicWidgets\Elements\Div_Block\Div_Block; use Elementor\Modules\AtomicWidgets\Elements\Flexbox\Flexbox; use Elementor\Modules\AtomicWidgets\Elements\Grid\Grid; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Heading\Atomic_Heading; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Image\Atomic_Image; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph\Atomic_Paragraph; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Button\Atomic_Button; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Divider\Atomic_Divider; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Svg\Atomic_Svg; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs\Atomic_Tabs; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Menu\Atomic_Tabs_Menu; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab\Atomic_Tab; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Content_Area\Atomic_Tabs_Content_Area; use Elementor\Modules\AtomicWidgets\ImportExport\Atomic_Import_Export; use Elementor\Modules\AtomicWidgets\Elements\Promotions\Pro_Promotion_Data_Preservation; use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Combine_Array_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Export\Image_Src_Export_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Export\Svg_Src_Export_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Image_Src_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Image_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Import\Image_Src_Import_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Import\Svg_Src_Import_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Svg_Src_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Import_Export_Plain_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Classes_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Date_Time_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Html_V2_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Html_V3_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Link_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Plain_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Color_Overlay_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Gradient_Overlay_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Color_Stop_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Multi_Props_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Position_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Shadow_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Grid_Track_Size_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Size_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Stroke_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Image_Overlay_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Image_Overlay_Size_Scale_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Overlay_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Filter_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Font_Family_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Origin_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transition_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Rotate_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Skew_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Functions_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Move_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Flex_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Scale_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Attributes_Transformer; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers_Registry; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Color_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Gradient_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Overlay_Size_Scale_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Position_Offset_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Box_Shadow_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Border_Radius_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Border_Width_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Stop_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Date_Time_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Date_Range_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Time_Range_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Backdrop_Filter_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Filter_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Gradient_Color_Stop_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_V2_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Layout_Direction_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Flex_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Src_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Svg_Src_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Position_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Shadow_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Grid_Track_Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Stroke_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Move_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Functions_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Origin_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Scale_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Rotate_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Skew_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transition_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Atomic_Styles_Manager; use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Base_Styles; use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Styles; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; use Elementor\Modules\AtomicWidgets\CssConverter\Css_Converter_REST_API; use Elementor\Modules\AtomicWidgets\Database\Atomic_Widgets_Database_Updater; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab_Content\Atomic_Tab_Content; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Collection_Loop\Collection_Loop_Promotion; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Atomic_Form; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Atomic_Form_Promotion; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Success_Message\Form_Success_Message; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Error_Message\Form_Error_Message; use Elementor\Modules\AtomicWidgets\PropTypeMigrations\Migrations_Orchestrator; use Elementor\Plugin; use Elementor\Widgets_Manager; use Elementor\Modules\AtomicWidgets\Library\Atomic_Widgets_Library; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Query_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Date_Range_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Time_Range_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Perspective_Origin_Transformer; use Elementor\Modules\AtomicWidgets\PropTypes\Query_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Perspective_Origin_Prop_Type; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Self_Hosted_Video\Atomic_Self_Hosted_Video; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Span_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Video_Src_Transformer; use Elementor\Modules\AtomicWidgets\PropTypes\Font_Family_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Span_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Video_Src_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const EXPERIMENT_NAME = 'e_atomic_elements'; const ENFORCE_CAPABILITIES_EXPERIMENT = 'atomic_widgets_should_enforce_capabilities'; const EXPERIMENT_EDITOR_MCP = 'editor_mcp'; const PACKAGES = [ 'editor-canvas', 'editor-controls', // TODO: Need to be registered and not enqueued. 'editor-editing-panel', 'editor-elements', // TODO: Need to be registered and not enqueued. 'editor-props', // TODO: Need to be registered and not enqueued. 'editor-styles', // TODO: Need to be registered and not enqueued. 'editor-styles-repository', 'editor-interactions', 'editor-templates', 'editor-design-system', ]; public function get_name() { return 'atomic-widgets'; } public function __construct() { parent::__construct(); if ( ! self::is_active() ) { return; } $this->register_experimental_features(); $this->register_hooks(); add_filter( 'elementor/editor/v2/packages', fn ( $packages ) => $this->add_packages( $packages ) ); add_filter( 'elementor/editor/localize_settings', fn ( $settings ) => $this->add_styles_schema( $settings ) ); add_filter( 'elementor/editor/localize_settings', fn ( $settings ) => $this->add_supported_units( $settings ) ); add_filter( 'elementor/widgets/register', fn ( Widgets_Manager $widgets_manager ) => $this->register_widgets( $widgets_manager ) ); add_filter( 'elementor/usage/elements/element_title', fn ( $title, $type ) => $this->get_element_usage_name( $title, $type ), 10, 2 ); add_action( 'elementor/elements/elements_registered', fn ( $elements_manager ) => $this->register_elements( $elements_manager ) ); add_action( 'elementor/editor/after_enqueue_scripts', fn () => $this->enqueue_scripts() ); add_action( 'elementor/editor/after_enqueue_styles', fn () => $this->enqueue_promotion_styles() ); add_action( 'elementor/preview/enqueue_styles', fn () => $this->enqueue_promotion_styles() ); add_action( 'elementor/frontend/before_register_scripts', fn () => $this->register_frontend_scripts() ); add_action( 'elementor/frontend/after_enqueue_styles', fn () => $this->add_inline_styles() ); add_action( 'elementor/ajax/register_actions', fn ( Ajax $ajax ) => ( new Render_Element_Action() )->register( $ajax ) ); add_action( 'elementor/atomic-widgets/settings/transformers/register', fn ( $transformers ) => $this->register_settings_transformers( $transformers ) ); add_action( 'elementor/atomic-widgets/styles/transformers/register', fn ( $transformers ) => $this->register_styles_transformers( $transformers ) ); add_action( 'elementor/atomic-widgets/import/transformers/register', fn ( $transformers ) => $this->register_import_transformers( $transformers ) ); add_action( 'elementor/atomic-widgets/export/transformers/register', fn ( $transformers ) => $this->register_export_transformers( $transformers ) ); add_action( 'elementor/editor/templates/panel/category', fn () => $this->render_panel_category_chip() ); } public static function get_experimental_data() { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Atomic Widgets', 'elementor' ), 'description' => esc_html__( 'Enable atomic widgets.', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_INACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_BETA, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '4.0.0', ], ]; } private function register_experimental_features() { Plugin::$instance->experiments->add_feature( [ 'name' => 'e_indications_popover', 'title' => esc_html__( 'V4 Indications Popover', 'elementor' ), 'description' => esc_html__( 'Enable V4 Indication Popovers', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_INACTIVE, ] ); Plugin::$instance->experiments->add_feature( [ 'name' => self::ENFORCE_CAPABILITIES_EXPERIMENT, 'title' => esc_html__( 'Enforce atomic widgets capabilities', 'elementor' ), 'description' => esc_html__( 'Enforce atomic widgets capabilities.', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_ACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_DEV, ] ); Plugin::$instance->experiments->add_feature([ 'name' => self::EXPERIMENT_EDITOR_MCP, 'title' => esc_html__( 'Editor MCP for atomic widgets', 'elementor' ), 'description' => esc_html__( 'Editor MCP for atomic widgets.', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_ACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_DEV, ]); Plugin::$instance->experiments->add_feature([ 'name' => Migrations_Orchestrator::EXPERIMENT_BC_MIGRATIONS, 'title' => esc_html__( 'Backward compatibility migrations', 'elementor' ), 'description' => esc_html__( 'Enable automatic prop type migrations for atomic widgets', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_ACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_DEV, ]); // When a new feature affects settings or style schema, global class, interactions, variable, etc // anything in need of addressing migration for BC purposes, add it here. $migrations_affecting_features = []; Migrations_Orchestrator::register_affecting_feature_flag_hooks( $migrations_affecting_features ); } private function register_hooks() { Dynamic_Tags_Module::instance()->register_hooks(); Atomic_Styles_Manager::instance()->register_hooks(); Migrations_Orchestrator::make()->register_hooks(); ( new Atomic_Widget_Styles() )->register_hooks(); ( new Atomic_Widget_Base_Styles() )->register_hooks(); ( new Atomic_Widgets_Library() )->register_hooks(); ( new Atomic_Import_Export() )->register_hooks(); ( new Atomic_Widgets_Database_Updater() )->register(); ( new Css_Converter_REST_API() )->register_hooks(); ( new Pro_Promotion_Data_Preservation() )->register_hooks(); } private function add_packages( $packages ) { return array_merge( $packages, self::PACKAGES ); } private function add_styles_schema( $settings ) { if ( ! isset( $settings['atomic'] ) ) { $settings['atomic'] = []; } $settings['atomic']['styles_schema'] = Style_Schema::get(); return $settings; } private function add_supported_units( $settings ) { $settings['supported_size_units'] = Size_Constants::all_supported_units(); $settings['size_units'] = Size_Constants::grouped_units(); return $settings; } private function register_widgets( Widgets_Manager $widgets_manager ) { $widgets_manager->register( new Atomic_Heading() ); $widgets_manager->register( new Atomic_Image() ); $widgets_manager->register( new Atomic_Paragraph() ); $widgets_manager->register( new Atomic_Svg() ); $widgets_manager->register( new Atomic_Button() ); $widgets_manager->register( new Atomic_Youtube() ); $widgets_manager->register( new Atomic_Divider() ); $widgets_manager->register( new Atomic_Self_Hosted_Video() ); } private function register_elements( Elements_Manager $elements_manager ) { $elements_manager->register_element_type( new Div_Block() ); $elements_manager->register_element_type( new Flexbox() ); $elements_manager->register_element_type( new Grid() ); $elements_manager->register_element_type( new Atomic_Tabs() ); $elements_manager->register_element_type( new Atomic_Tabs_Menu() ); $elements_manager->register_element_type( new Atomic_Tab() ); $elements_manager->register_element_type( new Atomic_Tabs_Content_Area() ); $elements_manager->register_element_type( new Atomic_Tab_Content() ); if ( \Elementor\Utils::has_pro() && Plugin::$instance->experiments->is_feature_active( 'e_pro_atomic_form' ) ) { $elements_manager->register_element_type( new Atomic_Form() ); $elements_manager->register_element_type( new Form_Success_Message() ); $elements_manager->register_element_type( new Form_Error_Message() ); } elseif ( ! \Elementor\Utils::has_pro() ) { $elements_manager->register_element_type( new Atomic_Form_Promotion() ); } if ( ! \Elementor\Utils::has_pro() ) { $elements_manager->register_element_type( new Collection_Loop_Promotion() ); } } private function register_settings_transformers( Transformers_Registry $transformers ) { $transformers->register_fallback( new Plain_Transformer() ); $transformers->register( Classes_Prop_Type::get_key(), new Classes_Transformer() ); $transformers->register( Image_Prop_Type::get_key(), new Image_Transformer() ); $transformers->register( Image_Src_Prop_Type::get_key(), new Image_Src_Transformer() ); $transformers->register( Svg_Src_Prop_Type::get_key(), new Svg_Src_Transformer() ); $transformers->register( Video_Src_Prop_Type::get_key(), new Video_Src_Transformer() ); $transformers->register( Link_Prop_Type::get_key(), new Link_Transformer() ); $transformers->register( Query_Prop_Type::get_key(), new Query_Transformer() ); $transformers->register( Attributes_Prop_Type::get_key(), new Attributes_Transformer() ); $transformers->register( Date_Time_Prop_Type::get_key(), new Date_Time_Transformer() ); $transformers->register( Date_Range_Prop_Type::get_key(), new Date_Range_Transformer() ); $transformers->register( Time_Range_Prop_Type::get_key(), new Time_Range_Transformer() ); $transformers->register( Html_V2_Prop_Type::get_key(), new Html_V2_Transformer() ); $transformers->register( Html_V3_Prop_Type::get_key(), new Html_V3_Transformer() ); } private function register_styles_transformers( Transformers_Registry $transformers ) { $transformers->register_fallback( new Plain_Transformer() ); $this->register_basic_styles_transformers( $transformers ); $this->register_background_styles_transformers( $transformers ); $this->register_filter_styles_transformers( $transformers ); $this->register_transform_styles_transformers( $transformers ); $this->register_layout_styles_transformers( $transformers ); } private function register_basic_styles_transformers( Transformers_Registry $transformers ): void { $transformers->register( Font_Family_Prop_Type::get_key(), new Font_Family_Transformer() ); $transformers->register( Size_Prop_Type::get_key(), new Size_Transformer() ); $transformers->register( Grid_Track_Size_Prop_Type::get_key(), new Grid_Track_Size_Transformer() ); $transformers->register( Box_Shadow_Prop_Type::get_key(), new Combine_Array_Transformer( ',' ) ); $transformers->register( Shadow_Prop_Type::get_key(), new Shadow_Transformer() ); $transformers->register( Flex_Prop_Type::get_key(), new Flex_Transformer() ); $transformers->register( Stroke_Prop_Type::get_key(), new Stroke_Transformer() ); $transformers->register( Image_Prop_Type::get_key(), new Image_Transformer() ); $transformers->register( Image_Src_Prop_Type::get_key(), new Image_Src_Transformer() ); $transformers->register( Span_Prop_Type::get_key(), new Span_Transformer() ); } private function register_background_styles_transformers( Transformers_Registry $transformers ): void { $transformers->register( Background_Image_Overlay_Prop_Type::get_key(), new Background_Image_Overlay_Transformer() ); $transformers->register( Background_Image_Overlay_Size_Scale_Prop_Type::get_key(), new Background_Image_Overlay_Size_Scale_Transformer() ); $transformers->register( Background_Image_Position_Offset_Prop_Type::get_key(), new Position_Transformer() ); $transformers->register( Background_Color_Overlay_Prop_Type::get_key(), new Background_Color_Overlay_Transformer() ); $transformers->register( Background_Overlay_Prop_Type::get_key(), new Background_Overlay_Transformer() ); $transformers->register( Background_Prop_Type::get_key(), new Background_Transformer() ); $transformers->register( Background_Gradient_Overlay_Prop_Type::get_key(), new Background_Gradient_Overlay_Transformer() ); } private function register_filter_styles_transformers( Transformers_Registry $transformers ): void { $transformers->register( Filter_Prop_Type::get_key(), new Filter_Transformer() ); $transformers->register( Backdrop_Filter_Prop_Type::get_key(), new Filter_Transformer() ); $transformers->register( Transition_Prop_Type::get_key(), new Transition_Transformer() ); $transformers->register( Color_Stop_Prop_Type::get_key(), new Color_Stop_Transformer() ); $transformers->register( Gradient_Color_Stop_Prop_Type::get_key(), new Combine_Array_Transformer( ',' ) ); $transformers->register( Position_Prop_Type::get_key(), new Position_Transformer() ); } private function register_transform_styles_transformers( Transformers_Registry $transformers ): void { $transformers->register( Transform_Move_Prop_Type::get_key(), new Transform_Move_Transformer() ); $transformers->register( Transform_Scale_Prop_Type::get_key(), new Transform_Scale_Transformer() ); $transformers->register( Transform_Rotate_Prop_Type::get_key(), new Transform_Rotate_Transformer() ); $transformers->register( Transform_Skew_Prop_Type::get_key(), new Transform_Skew_Transformer() ); $transformers->register( Transform_Functions_Prop_Type::get_key(), new Transform_Functions_Transformer() ); $transformers->register( Transform_Origin_Prop_Type::get_key(), new Transform_Origin_Transformer() ); $transformers->register( Perspective_Origin_Prop_Type::get_key(), new Perspective_Origin_Transformer() ); $transformers->register( Transform_Prop_Type::get_key(), new Multi_Props_Transformer( [ 'transform-functions', 'transform-origin', 'perspective', 'perspective-origin' ], fn( $_, $key ) => 'transform-functions' === $key ? 'transform' : $key ) ); } private function register_layout_styles_transformers( Transformers_Registry $transformers ): void { $transformers->register( Border_Radius_Prop_Type::get_key(), new Multi_Props_Transformer( [ 'start-start', 'start-end', 'end-start', 'end-end' ], fn ( $_, $key ) => "border-{$key}-radius" ) ); $transformers->register( Border_Width_Prop_Type::get_key(), new Multi_Props_Transformer( [ 'block-start', 'block-end', 'inline-start', 'inline-end' ], fn ( $_, $key ) => "border-{$key}-width" ) ); $transformers->register( Layout_Direction_Prop_Type::get_key(), new Multi_Props_Transformer( [ 'column', 'row' ], fn ( $prop_key, $key ) => "{$key}-{$prop_key}" ) ); $transformers->register( Dimensions_Prop_Type::get_key(), new Multi_Props_Transformer( [ 'block-start', 'block-end', 'inline-start', 'inline-end' ], fn ( $prop_key, $key ) => "{$prop_key}-{$key}" ) ); } public function register_import_transformers( Transformers_Registry $transformers ) { $transformers->register_fallback( new Import_Export_Plain_Transformer() ); $transformers->register( Image_Src_Prop_Type::get_key(), new Image_Src_Import_Transformer() ); $transformers->register( Svg_Src_Prop_Type::get_key(), new Svg_Src_Import_Transformer() ); } public function register_export_transformers( Transformers_Registry $transformers ) { $transformers->register_fallback( new Import_Export_Plain_Transformer() ); $transformers->register( Image_Src_Prop_Type::get_key(), new Image_Src_Export_Transformer() ); $transformers->register( Svg_Src_Prop_Type::get_key(), new Svg_Src_Export_Transformer() ); } public static function is_active(): bool { return Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ); } private function get_element_usage_name( $title, $type ) { $element_instance = Plugin::$instance->elements_manager->get_element_types( $type ); $widget_instance = Plugin::$instance->widgets_manager->get_widget_types( $type ); if ( Utils::is_atomic( $element_instance ) || Utils::is_atomic( $widget_instance ) ) { return $type; } return $title; } /** * Enqueue the module scripts. * * @return void */ private function enqueue_scripts() { wp_enqueue_script( 'elementor-atomic-widgets-editor', $this->get_js_assets_url( 'atomic-widgets-editor' ), [ 'elementor-editor' ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-atomic-widgets-editor', 'elementor' ); } private function render_panel_category_chip() { ?><# if ( 'v4-elements' === name ) { #> <span class="elementor-panel-heading-category-chip"> <?php echo esc_html__( 'New', 'elementor' ); ?><i class="eicon-info"></i> <span class="e-promotion-react-wrapper" data-promotion="v4_chip"></span> </span> <# } #><?php } private function register_frontend_scripts() { $loader = new Frontend_Assets_Loader(); $loader->register_scripts(); } private function add_inline_styles() { $inline_css = implode( '', [ '.e-heading-base a, .e-paragraph-base a { all: unset; cursor: pointer; }', 'form[data-element_type="e-form"].form-state-success [data-element_type="e-form-success-message"],', 'form[data-element_type="e-form"].form-state-error [data-element_type="e-form-error-message"]', '{ display: block; }', ] ); wp_add_inline_style( 'elementor-frontend', $inline_css ); wp_add_inline_style( 'elementor-editor', $inline_css ); } private function enqueue_promotion_styles() { if ( \Elementor\Utils::has_pro() ) { return; } wp_enqueue_style( 'elementor-atomic-widgets-promotion-fonts', 'https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap', [], ELEMENTOR_VERSION ); wp_enqueue_style( 'elementor-atomic-widgets-promotion', $this->get_css_assets_url( 'modules/atomic-widgets/editor' ), [ 'elementor-atomic-widgets-promotion-fonts' ], ELEMENTOR_VERSION ); } } atomic-widgets/prop-dependencies/manager.php 0000644 00000016374 15252521350 0015226 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropDependencies; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Manager { const RELATION_OR = 'or'; const RELATION_AND = 'and'; const OPERATORS = [ 'lt', 'lte', 'eq', 'ne', 'gte', 'gt', 'exists', 'not_exist', 'in', 'nin', 'contains', 'ncontains', ]; /** * @var ?array{ * relation: self::RELATION_OR|self::RELATION_AND, * terms: array{ * operator: string, * path: array<string>, * value?: mixed, * newValue?: array, * effect?: 'hide'|'disable' * } * } */ private ?array $dependencies; public function __construct( string $relation = self::RELATION_OR ) { $this->new( $relation ); return $this; } public static function make( string $relation = self::RELATION_OR ): self { return new self( $relation ); } /** * @param array<string, Prop_Type> $props_schema * @return array<string, array<string>> Returns source prop path => array of dependent prop paths */ public static function get_source_to_dependents( array $props_schema ): array { $dependency_graph = self::build_dependency_graph( $props_schema ); if ( self::has_circular_dependencies( $dependency_graph ) ) { Utils::safe_throw( 'Circular prop dependencies detected' ); } return $dependency_graph; } /** * @param $config array{ * operator: string, * path: array<string>, * value?: mixed, * newValue?: array, * effect?: 'hide'|'disable' * } * @return self */ public function where( array $config, $new_value = null ): self { if ( isset( $config['terms'] ) ) { if ( empty( $this->dependencies ) ) { $this->new(); } $term = [ 'terms' => $config['terms'], 'relation' => $config['relation'] ?? self::RELATION_OR, 'newValue' => $new_value ?? null, 'effect' => $config['effect'] ?? 'disable', ]; $this->dependencies['terms'][] = $term; return $this; } if ( ! isset( $config['operator'] ) || ! isset( $config['path'] ) ) { Utils::safe_throw( 'Term missing mandatory configurations' ); } if ( ! in_array( $config['operator'], self::OPERATORS, true ) ) { Utils::safe_throw( "Invalid operator: {$config['operator']}." ); } $term = [ 'operator' => $config['operator'], 'path' => $config['path'], 'nestedPath' => $config['nestedPath'] ?? null, 'value' => $config['value'] ?? null, 'newValue' => $config['newValue'] ?? null, 'effect' => $config['effect'] ?? 'disable', ]; if ( empty( $this->dependencies ) ) { $this->new(); } $this->dependencies['terms'][] = $term; return $this; } private function new( string $relation = self::RELATION_OR ): self { if ( ! in_array( $relation, [ self::RELATION_OR, self::RELATION_AND ], true ) ) { Utils::safe_throw( "Invalid relation: $relation. Must be one of: " . implode( ', ', [ self::RELATION_OR, self::RELATION_AND ] ) ); } $this->dependencies = [ 'relation' => $relation, 'terms' => [], ]; return $this; } public function get(): ?array { return empty( $this->dependencies['terms'] ?? [] ) ? null : $this->dependencies; } /** * @param array<string, Prop_Type> $props_schema The props schema to analyze, where keys are prop names * @param ?array<string> $current_path The current property path being processed * @param ?array<string, array<string>> $dependency_graph The dependency graph to build */ private static function build_dependency_graph( array $props_schema, ?array $current_path = [], ?array $dependency_graph = [] ): array { foreach ( $props_schema as $prop_name => $prop_type ) { $dependency_graph = self::build_nested_prop_dependency_graph( $prop_name, $prop_type, $current_path, $dependency_graph ); $dependencies = $prop_type->get_dependencies(); if ( ! $dependencies ) { continue; } foreach ( $dependencies['terms'] as $term ) { $dependency_graph = self::process_dependency_term( $term, $current_path, $prop_name, $dependency_graph ); } } return $dependency_graph; } private static function build_nested_prop_dependency_graph( string $prop_name, Prop_Type $prop_type, array $current_path, array $dependency_graph ): array { $nested_prop_path = array_merge( $current_path, [ $prop_name ] ); switch ( $prop_type->get_type() ) { case 'object': foreach ( $prop_type->get_shape() as $nested_prop_name => $nested_prop_type ) { $dependency_graph = self::build_dependency_graph( [ $nested_prop_name => $nested_prop_type ], $nested_prop_path, $dependency_graph ); } break; case 'array': $item_prop_type = $prop_type->get_item_type(); $dependency_graph = self::build_dependency_graph( [ $prop_name => $item_prop_type ], $current_path, $dependency_graph ); break; case 'union': foreach ( $prop_type->get_prop_types() as $nested_prop_type ) { $dependency_graph = self::build_dependency_graph( [ $prop_name => $nested_prop_type ], $current_path, $dependency_graph ); } break; } return $dependency_graph; } private static function process_dependency_term( array $term, array $current_path, string $prop_name, array $dependency_graph ): array { if ( self::is_term_nested( $term ) ) { foreach ( $term['terms'] as $nested_term ) { $dependency_graph = self::process_dependency_term( $nested_term, $current_path, $prop_name, $dependency_graph ); } return $dependency_graph; } if ( ! isset( $term['path'] ) || empty( $term['path'] ) ) { Utils::safe_throw( 'Invalid term path in dependency.' ); } $target_path = implode( '.', $term['path'] ); $source = array_merge( $current_path, [ $prop_name ] ); $source_path = implode( '.', $source ); if ( ! isset( $dependency_graph[ $target_path ] ) ) { $dependency_graph[ $target_path ] = []; } if ( ! in_array( $source_path, $dependency_graph[ $target_path ] ) ) { $dependency_graph[ $target_path ][] = $source_path; } return $dependency_graph; } private static function has_circular_dependencies( array $dependency_graph ): bool { $visited_nodes = []; $current_path_stack = []; foreach ( array_keys( $dependency_graph ) as $node ) { if ( isset( $visited_nodes[ $node ] ) ) { continue; } if ( self::detect_cycle_from_node( $dependency_graph, $node, $visited_nodes, $current_path_stack ) ) { return true; } } return false; } private static function detect_cycle_from_node( array $dependency_graph, string $current_node, array &$visited_nodes, array &$current_path_stack ): bool { if ( isset( $current_path_stack[ $current_node ] ) ) { return true; } if ( isset( $visited_nodes[ $current_node ] ) ) { return false; } $visited_nodes[ $current_node ] = true; $current_path_stack[ $current_node ] = true; foreach ( $dependency_graph[ $current_node ] ?? [] as $dependent_node ) { $is_circular = self::detect_cycle_from_node( $dependency_graph, $dependent_node, $visited_nodes, $current_path_stack ); if ( $is_circular ) { return true; } } unset( $current_path_stack[ $current_node ] ); return false; } private static function is_term_nested( $term ): bool { return isset( $term['terms'] ) && is_array( $term['terms'] ); } } atomic-widgets/props-resolver/import-export-props-resolver.php 0000644 00000002207 15252521350 0021031 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Import_Export_Props_Resolver extends Props_Resolver { const CONTEXT_IMPORT = 'import'; const CONTEXT_EXPORT = 'export'; public static function for_import() { return static::instance( self::CONTEXT_IMPORT ); } public static function for_export() { return static::instance( self::CONTEXT_EXPORT ); } public function resolve( array $schema, array $props ): array { $resolved = []; foreach ( $schema as $key => $prop_type ) { if ( ! ( $prop_type instanceof Prop_Type ) ) { continue; } $value = $this->resolve_item( $props[ $key ] ?? null, $key, $prop_type ); if ( null === $value ) { continue; } $resolved[ $key ] = $value; } return $resolved; } protected function resolve_item( $value, $key, Prop_Type $prop_type ) { if ( null === $value ) { return null; } if ( ! $this->is_transformable( $value ) ) { return $value; } return $this->transform( $value, $key, $prop_type ); } } atomic-widgets/props-resolver/render-props-resolver.php 0000644 00000005215 15252521350 0017461 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver; use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Elementor\Plugin; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Render_Props_Resolver extends Props_Resolver { /** * Each transformer can return a value that is also a transformable value, * which means that it can be transformed again by another transformer. * This constant defines the maximum depth of transformations to avoid infinite loops. */ const TRANSFORM_DEPTH_LIMIT = 3; const CONTEXT_SETTINGS = 'settings'; const CONTEXT_STYLES = 'styles'; public static function for_styles(): self { return static::instance( self::CONTEXT_STYLES ); } public static function for_settings(): self { return static::instance( self::CONTEXT_SETTINGS ); } public function resolve( array $schema, array $props ): array { $resolved = []; foreach ( $schema as $key => $prop_type ) { if ( ! ( $prop_type instanceof Prop_Type ) ) { continue; } $prop_value = $props[ $key ] ?? null; $actual_value = $this->get_validated_value( $prop_type, $prop_value ); $transformed = $this->resolve_item( $actual_value, $key, $prop_type ); if ( Multi_Props::is( $transformed ) ) { $resolved = array_merge( $resolved, Multi_Props::get_value( $transformed ) ); continue; } $resolved[ $key ] = $transformed; } return $resolved; } protected function resolve_item( $value, $key, Prop_Type $prop_type, int $depth = 0 ) { if ( null === $value ) { return null; } if ( ! $this->is_transformable( $value ) ) { return $value; } if ( $depth >= self::TRANSFORM_DEPTH_LIMIT ) { return null; } if ( isset( $value['disabled'] ) && true === $value['disabled'] ) { return null; } $transformed = $this->transform( $value, $key, $prop_type ); return $this->resolve_item( $transformed, $key, $prop_type, $depth + 1 ); } private function get_validated_value( Prop_Type $prop_type, $prop_value ) { $default = $prop_type->get_default() ?? null; if ( null === $prop_value ) { return $default; } if ( ! Dynamic_Prop_Type::is_dynamic_prop_value( $prop_value ) ) { return $prop_value; } $tag_name = $prop_value['value']['name'] ?? null; $tag = Plugin::$instance->dynamic_tags->get_tag_info( $tag_name ); return ! $tag ? $default : $prop_value; } } atomic-widgets/props-resolver/transformer-base.php 0000644 00000000374 15252521350 0016455 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Transformer_Base { abstract public function transform( $value, Props_Resolver_Context $context ); } atomic-widgets/props-resolver/transformers/styles/transform-skew-transformer.php 0000644 00000001103 15252521350 0024564 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Skew_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): string { $default_skew = '0deg'; return sprintf( 'skew(%s, %s)', $value['x'] ?? $default_skew, $value['y'] ?? $default_skew ); } } atomic-widgets/props-resolver/transformers/styles/transform-origin-transformer.php 0000644 00000001574 15252521350 0025116 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Origin_Transformer extends Transformer_Base { private string $default_origin = '0px'; private string $default_xy = '50%'; private function get_val( ?string $val ): string { return $val ?? $this->default_origin; } public function transform( $value, Props_Resolver_Context $context ) { $x = $this->get_val( $value['x'] ); $y = $this->get_val( $value['y'] ); $z = $this->get_val( $value['z'] ); if ( $x === $this->default_xy && $y === $this->default_xy && $z === $this->default_origin ) { return null; } return sprintf( '%s %s %s', $x, $y, $z ); } } atomic-widgets/props-resolver/transformers/styles/transform-scale-transformer.php 0000644 00000001037 15252521350 0024710 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Scale_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): string { return sprintf( 'scale3d(%s, %s, %s)', $value['x'] ?? 1, $value['y'] ?? 1, $value['z'] ?? 1 ); } } atomic-widgets/props-resolver/transformers/styles/transition-transformer.php 0000644 00000003153 15252521350 0024003 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transition_Transformer extends Transformer_Base { const EMPTY_STRING = ''; private function get_allowed_properties(): array { $core_properties = [ 'all' ]; return apply_filters( 'elementor/atomic-widgets/styles/transitions/allowed-properties', $core_properties ); } public function transform( $transitions, Props_Resolver_Context $context ) { if ( ! is_array( $transitions ) ) { return self::EMPTY_STRING; } $allowed_properties = $this->get_allowed_properties(); $transition_strings = array_map( function( $transition ) use ( $allowed_properties ) { return $this->map_to_transition_string( $transition, $allowed_properties ); }, $transitions ); $valid_transitions = array_filter( $transition_strings ); return implode( ', ', $valid_transitions ); } private function map_to_transition_string( $transition, array $allowed_properties ): string { if ( empty( $transition['selection'] ) || empty( $transition['size'] ) ) { return self::EMPTY_STRING; } $selection = $transition['selection']; $size = $transition['size']; if ( empty( $selection['value'] ) ) { return self::EMPTY_STRING; } $property = $selection['value']; if ( ! in_array( $property, $allowed_properties, true ) ) { return self::EMPTY_STRING; } return trim( "{$property} {$size}" ); } } atomic-widgets/props-resolver/transformers/styles/flex-transformer.php 0000644 00000003421 15252521350 0022545 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Flex_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { $grow = $value['flexGrow'] ?? null; $shrink = $value['flexShrink'] ?? null; $basis = $value['flexBasis'] ?? null; $has_grow = null !== $grow && '' !== $grow; $has_shrink = null !== $shrink && '' !== $shrink; $has_basis = null !== $basis && '' !== $basis; if ( ! $has_grow && ! $has_shrink && ! $has_basis ) { return null; } $basis_value = $this->transform_basis_value( $basis ); if ( $has_grow && $has_shrink && $has_basis ) { return "{$grow} {$shrink} {$basis_value}"; } if ( $has_grow && $has_shrink && ! $has_basis ) { return "{$grow} {$shrink}"; } if ( $has_grow && ! $has_shrink && $has_basis ) { return "{$grow} 1 {$basis_value}"; } if ( ! $has_grow && $has_shrink && $has_basis ) { return "0 {$shrink} {$basis_value}"; } if ( $has_grow && ! $has_shrink && ! $has_basis ) { return "{$grow}"; } if ( ! $has_grow && $has_shrink && ! $has_basis ) { return "0 {$shrink}"; } if ( ! $has_grow && ! $has_shrink && $has_basis ) { return "0 1 {$basis_value}"; } return null; } /** * Transform basis value to string format * * @param mixed $basis The basis value * @return string */ private function transform_basis_value( $basis ) { if ( is_array( $basis ) && isset( $basis['size'] ) ) { $unit = $basis['unit'] ?? ''; return $basis['size'] . $unit; } return (string) $basis; } } atomic-widgets/props-resolver/transformers/styles/color-stop-transformer.php 0000644 00000001033 15252521350 0023705 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Color_Stop_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): string { $color = $value['color']; $offset = $value['offset'] . '%'; return $color . ' ' . $offset; } } props-resolver/transformers/styles/background-image-overlay-size-scale-transformer.php 0000644 00000001177 15252521350 0030451 0 ustar 00 atomic-widgets <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Image_Overlay_Size_Scale_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): string { $default_custom_size = 'auto'; $width = $value['width'] ?? $default_custom_size; $height = $value['height'] ?? $default_custom_size; return $width . ' ' . $height; } } atomic-widgets/props-resolver/transformers/styles/background-image-overlay-transformer.php 0000644 00000001655 15252521350 0026474 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Image_Overlay_Transformer extends Transformer_Base { const DEFAULT_IMAGE = 'none'; const DEFAULT_REPEAT = 'repeat'; const DEFAULT_ATTACHMENT = 'scroll'; const DEFAULT_SIZE = 'auto auto'; const DEFAULT_POSITION = '0% 0%'; public function transform( $value, Props_Resolver_Context $context ) { if ( ! isset( $value['image'] ) ) { return ''; } $image_url = $value['image']['src']; return [ 'src' => "url(\"$image_url\")", 'repeat' => $value['repeat'] ?? null, 'attachment' => $value['attachment'] ?? null, 'size' => $value['size'] ?? null, 'position' => $value['position'] ?? null, ]; } } atomic-widgets/props-resolver/transformers/styles/shadow-transformer.php 0000644 00000001162 15252521350 0023074 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Shadow_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { $val = array_filter( [ $value['hOffset'], $value['vOffset'], $value['blur'], $value['spread'], $value['color'], $value['position'] ?? '', ] ); return implode( ' ', $val ); } } atomic-widgets/props-resolver/transformers/styles/background-overlay-transformer.php 0000644 00000005102 15252521350 0025403 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Multi_Props; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Overlay_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { $normalized_values = $this->normalize_overlay_values( $value ); if ( empty( $normalized_values ) ) { return null; } return [ 'background-image' => $this->get_values_string( $normalized_values, 'src', Background_Image_Overlay_Transformer::DEFAULT_IMAGE, true ), 'background-repeat' => $this->get_values_string( $normalized_values, 'repeat', Background_Image_Overlay_Transformer::DEFAULT_REPEAT ), 'background-attachment' => $this->get_values_string( $normalized_values, 'attachment', Background_Image_Overlay_Transformer::DEFAULT_ATTACHMENT ), 'background-size' => $this->get_values_string( $normalized_values, 'size', Background_Image_Overlay_Transformer::DEFAULT_SIZE ), 'background-position' => $this->get_values_string( $normalized_values, 'position', Background_Image_Overlay_Transformer::DEFAULT_POSITION ), ]; } private function normalize_overlay_values( $overlays ): array { $mapped_values = array_map( function( $value ) { if ( is_string( $value ) ) { return [ 'src' => $value, 'repeat' => null, 'attachment' => null, 'size' => null, 'position' => null, ]; } return $value; }, $overlays ); return array_filter( $mapped_values, function( $value ) { return is_array( $value ) && ! empty( $value['src'] ); } ); } private function get_values_string( $value, string $prop, string $default_value, bool $prevent_unification = false ) { $is_empty = empty( array_filter( $value, function ( array $item ) use ( $prop ) { return isset( $item[ $prop ] ) && ! is_null( $item[ $prop ] ); } ) ); if ( $is_empty ) { return $default_value; } $formatted_values = array_map( function ( $item ) use ( $prop, $default_value ) { if ( is_string( $item ) ) { return $default_value; } if ( ! is_array( $item ) ) { return $default_value; } return $item[ $prop ] ?? $default_value; }, $value ); if ( ! $prevent_unification ) { $all_same = count( array_unique( $formatted_values ) ) === 1; if ( $all_same ) { return $formatted_values[0]; } } return implode( ',', $formatted_values ); } } atomic-widgets/props-resolver/transformers/styles/span-transformer.php 0000644 00000000737 15252521350 0022557 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Span_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { return is_null( $value ) ? null : trim( $value ); } } atomic-widgets/props-resolver/transformers/styles/transform-move-transformer.php 0000644 00000001140 15252521350 0024562 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Move_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): string { $default_move = '0px'; return sprintf( 'translate3d(%s, %s, %s)', $value['x'] ?? $default_move, $value['y'] ?? $default_move, $value['z'] ?? $default_move ); } } atomic-widgets/props-resolver/transformers/styles/size-transformer.php 0000644 00000001140 15252521350 0022555 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Size_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { $size = $value['size']; $unit = $value['unit']; if ( 'custom' === $unit ) { return $size; } if ( 'auto' === $unit ) { return 'auto'; } return +$size . $unit; } } atomic-widgets/props-resolver/transformers/styles/stroke-transformer.php 0000644 00000001240 15252521350 0023113 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Multi_Props; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Stroke_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { return Multi_Props::generate( [ '-webkit-text-stroke' => $value['width'] . ' ' . $value['color'], 'stroke' => $value['color'], 'stroke-width' => $value['width'], ] ); } } atomic-widgets/props-resolver/transformers/styles/background-color-overlay-transformer.php 0000644 00000001067 15252521350 0026525 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Color_Overlay_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { $color = $value['color'] ?? ''; if ( ! $color ) { return null; } return "linear-gradient($color, $color)"; } } atomic-widgets/props-resolver/transformers/styles/background-gradient-overlay-transformer.php 0000644 00000001364 15252521350 0027204 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Gradient_Overlay_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): string { $type = $value['type']; $angle = $value['angle']; $positions = $value['positions']; $stops = $value['stops']; if ( 'radial' === $type ) { return sprintf( 'radial-gradient(circle at %s, %s)', $positions, $stops ); } return sprintf( 'linear-gradient(%ddeg, %s)', $angle, $stops ); } } atomic-widgets/props-resolver/transformers/styles/grid-track-size-transformer.php 0000644 00000001324 15252521350 0024606 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; use Elementor\Modules\AtomicWidgets\Styles\Grid_Track_Renderer; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Grid_Track_Size_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { $size = $value['size']; $unit = $value['unit']; if ( 'custom' === $unit ) { return $size; } if ( 'fr' === $unit ) { return Grid_Track_Renderer::format_repeat( (int) $size ); } return +$size . $unit; } } atomic-widgets/props-resolver/transformers/styles/background-transformer.php 0000644 00000001352 15252521350 0023727 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Multi_Props; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Background_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { $overlay = $value['background-overlay'] ?? []; $color = $value['color'] ?? null; $clip = $value['clip'] ?? null; return Multi_Props::generate( array_merge( $overlay, [ 'background-color' => $color, 'background-clip' => $clip, ] ) ); } } atomic-widgets/props-resolver/transformers/styles/transform-functions-transformer.php 0000644 00000000743 15252521350 0025634 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Functions_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): string { return implode( ' ', $value ); } } atomic-widgets/props-resolver/transformers/styles/position-transformer.php 0000644 00000000772 15252521350 0023461 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Position_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): string { return ( $value['x'] ?? '0px' ) . ' ' . ( $value['y'] ?? '0px' ); } } atomic-widgets/props-resolver/transformers/styles/multi-props-transformer.php 0000644 00000002174 15252521350 0024106 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Core\Utils\Collection; use Elementor\Modules\AtomicWidgets\PropsResolver\Multi_Props; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; use phpDocumentor\Reflection\Types\Callable_; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Multi_Props_Transformer extends Transformer_Base { private $key_generator; private array $keys; public function __construct( array $keys, callable $key_generator ) { $this->keys = $keys; $this->key_generator = $key_generator; } public function transform( $value, Props_Resolver_Context $context ) { $values = Collection::make( $this->keys ) ->filter( fn ( $key ) => isset( $value[ $key ] ) ) ->map_with_keys( function( $key ) use ( $value, $context ) { $new_key = call_user_func( $this->key_generator, $context->get_key(), $key ); $new_value = $value[ $key ]; return [ $new_key => $new_value ]; } ) ->all(); return Multi_Props::generate( $values ); } } atomic-widgets/props-resolver/transformers/styles/perspective-origin-transformer.php 0000644 00000001067 15252521350 0025431 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Perspective_Origin_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): string { $default_move = '0px'; $x = $value['x'] ?? $default_move; $y = $value['y'] ?? $default_move; return "$x $y"; } } atomic-widgets/props-resolver/transformers/styles/transform-rotate-transformer.php 0000644 00000001205 15252521350 0025114 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transform_Rotate_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): string { $default_rotate = '0deg'; return sprintf( 'rotateX(%s) rotateY(%s) rotateZ(%s)', $value['x'] ?? $default_rotate, $value['y'] ?? $default_rotate, $value['z'] ?? $default_rotate ); } } atomic-widgets/props-resolver/transformers/styles/font-family-transformer.php 0000644 00000001402 15252521350 0024031 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Font_Family_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { if ( ! is_string( $value ) ) { return null; } $trimmed = trim( $value ); $is_quoted = ( ( str_starts_with( $trimmed, '"' ) && str_ends_with( $trimmed, '"' ) ) || ( str_starts_with( $trimmed, "'" ) && str_ends_with( $trimmed, "'" ) ) ); if ( $is_quoted ) { return $trimmed; } return '"' . $trimmed . '"'; } } atomic-widgets/props-resolver/transformers/styles/filter-transformer.php 0000644 00000001721 15252521350 0023075 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Filter_Transformer extends Transformer_Base { public function transform( $filters, Props_Resolver_Context $context ) { $filter_strings = array_map( [ $this, 'map_to_filter_string' ], $filters ); return implode( ' ', $filter_strings ); } private function map_to_filter_string( $filter ): string { $func = $filter['func']; $args = $filter['args']; if ( 'drop-shadow' === $func ) { $x_axis = $args['xAxis'] ?? '0px'; $y_axis = $args['yAxis'] ?? '0px'; $blur = $args['blur'] ?? '10px'; $color = $args['color'] ?? 'transparent'; return "drop-shadow({$x_axis} {$y_axis} {$blur} {$color})"; } return $func . '(' . $args['size'] . ')'; } } atomic-widgets/props-resolver/transformers/plain-transformer.php 0000644 00000000666 15252521350 0021377 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Plain_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { return $value; } } atomic-widgets/props-resolver/transformers/svg-src-transformer.php 0000644 00000004645 15252521350 0021661 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers; use Elementor\Core\Utils\Svg\Svg_Sanitizer; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Svg_Src_Transformer extends Transformer_Base { const SVG_INLINE_STYLES = 'width: 100%; height: 100%; overflow: unset;'; public function transform( $value, Props_Resolver_Context $context ) { $id = isset( $value['id'] ) ? (int) $value['id'] : null; $url = $value['url'] ?? null; if ( $id ) { $resolved_url = wp_get_attachment_url( $id ); if ( $resolved_url ) { $url = $resolved_url; } } $svg_content = $this->fetch_svg_content( $id, $url ); $html = $svg_content ? $this->process_svg( $svg_content ) : ''; return [ 'html' => $html, 'url' => $url, ]; } private function fetch_svg_content( ?int $id, ?string $url ): ?string { if ( $id ) { $path = get_attached_file( $id ); $content = $path ? Utils::file_get_contents( $path ) : null; if ( $content ) { return $content; } } if ( ! $url ) { return null; } $local_path = $this->resolve_local_path( $url ); if ( $local_path ) { $content = Utils::file_get_contents( $local_path ); if ( $content ) { return $content; } } $response = wp_safe_remote_get( $url ); if ( ! is_wp_error( $response ) ) { return $response['body']; } return null; } private function resolve_local_path( string $url ): ?string { $site_url = site_url(); if ( 0 !== strpos( $url, $site_url ) ) { return null; } $relative = substr( $url, strlen( $site_url ) ); $path = ABSPATH . ltrim( $relative, '/' ); return file_exists( $path ) ? $path : null; } private function process_svg( string $content ): string { $svg = new \WP_HTML_Tag_Processor( $content ); if ( ! $svg->next_tag( 'svg' ) ) { return ''; } $svg->set_attribute( 'fill', 'currentColor' ); $this->merge_inline_styles( $svg ); return ( new Svg_Sanitizer() )->sanitize( $svg->get_updated_html() ); } private function merge_inline_styles( \WP_HTML_Tag_Processor $svg ): void { $existing = trim( (string) $svg->get_attribute( 'style' ) ); $merged = empty( $existing ) ? self::SVG_INLINE_STYLES : rtrim( $existing, ';' ) . '; ' . self::SVG_INLINE_STYLES; $svg->set_attribute( 'style', $merged ); } } atomic-widgets/props-resolver/transformers/import-export-plain-transformer.php 0000644 00000001040 15252521350 0024211 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Import_Export_Plain_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { $prop_type = $context->get_prop_type(); return $prop_type::generate( $value, $context->is_disabled() ); } } atomic-widgets/props-resolver/transformers/export/image-src-export-transformer.php 0000644 00000002141 15252521350 0024771 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Export; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Src_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Url_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Src_Export_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): ?array { if ( ! empty( $value['url'] ) ) { return Image_Src_Prop_Type::generate( [ 'id' => null, 'url' => $value['url'], ], $context->is_disabled() ); } if ( ! empty( $value['id'] ) && ! empty( $value['id']['value'] ) ) { $image = wp_get_attachment_image_src( $value['id']['value'], 'full' ); if ( ! $image ) { return null; } [ $src ] = $image; return Image_Src_Prop_Type::generate( [ 'id' => $value['id'], 'url' => Url_Prop_Type::generate( $src ), ], $context->is_disabled() ); } return null; } } atomic-widgets/props-resolver/transformers/export/svg-src-export-transformer.php 0000644 00000002073 15252521350 0024512 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Export; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; use Elementor\Modules\AtomicWidgets\PropTypes\Svg_Src_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Url_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Svg_Src_Export_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): ?array { if ( ! empty( $value['url'] ) ) { return Svg_Src_Prop_Type::generate( [ 'id' => null, 'url' => $value['url'], ], $context->is_disabled() ); } if ( ! empty( $value['id'] ) && ! empty( $value['id']['value'] ) ) { $image = wp_get_attachment_image_src( $value['id']['value'], 'full' ); if ( ! $image ) { return null; } [ $src ] = $image; return Svg_Src_Prop_Type::generate( [ 'id' => $value['id'], 'url' => Url_Prop_Type::generate( $src ), ], $context->is_disabled() ); } return null; } } atomic-widgets/props-resolver/transformers/combine-array-transformer.php 0000644 00000001226 15252521350 0023015 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Combine_Array_Transformer extends Transformer_Base { private string $separator; public function __construct( string $separator ) { $this->separator = $separator; } public function transform( $value, Props_Resolver_Context $context ) { if ( ! is_array( $value ) ) { return null; } return implode( $this->separator, array_filter( $value ) ); } } atomic-widgets/props-resolver/transformers/import/svg-src-import-transformer.php 0000644 00000001742 15252521350 0024476 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Import; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Attachment_Id_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Svg_Src_Prop_Type; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Svg_Src_Import_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { if ( empty( $value['url']['value'] ) ) { return null; } $uploaded = Plugin::$instance->templates_manager->get_import_images_instance()->import( [ 'id' => $value['id']['value'] ?? null, 'url' => $value['url']['value'], ] ); if ( ! $uploaded ) { return null; } return Svg_Src_Prop_Type::generate( [ 'id' => Image_Attachment_Id_Prop_Type::generate( $uploaded['id'] ), 'url' => null, ] ); } } atomic-widgets/props-resolver/transformers/import/image-src-import-transformer.php 0000644 00000002006 15252521350 0024753 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Import; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Attachment_Id_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Src_Prop_Type; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Src_Import_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { if ( empty( $value['url']['value'] ) ) { return null; } $uploaded = Plugin::$instance->templates_manager->get_import_images_instance()->import( [ 'id' => $value['id']['value'] ?? null, 'url' => $value['url']['value'], ] ); if ( ! $uploaded ) { return null; } return Image_Src_Prop_Type::generate( [ 'id' => Image_Attachment_Id_Prop_Type::generate( $uploaded['id'] ), 'url' => null, ] ); } } atomic-widgets/props-resolver/transformers/video-src-transformer.php 0000644 00000001125 15252521350 0022156 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; } class Video_Src_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { $id = isset( $value['id'] ) ? (int) $value['id'] : null; $url = $value['url'] ?? null; if ( $id ) { $url = wp_get_attachment_url( $id ); } return [ 'id' => $id, 'url' => $url, ]; } } atomic-widgets/props-resolver/transformers/image-src-transformer.php 0000644 00000001462 15252521350 0022136 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Src_Transformer extends Transformer_Base { /** * This transformer (or rather this prop type) exists only to support dynamic images. * Currently, the dynamic tags that return images return it with id & url no matter * what, so we need to keep the same structure in the props. */ public function transform( $value, Props_Resolver_Context $context ) { return [ 'id' => isset( $value['id'] ) ? (int) $value['id'] : null, 'url' => $value['url'] ?? null, 'alt' => $value['alt'] ?? null, ]; } } atomic-widgets/props-resolver/transformers/settings/link-transformer.php 0000644 00000001614 15252521350 0023063 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Link_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): ?array { $url = $this->extract_url( $value ); $link_attrs = [ 'href' => $url, 'target' => $value['isTargetBlank'] ? '_blank' : '_self', 'tag' => $url && 'button' === $value['tag'] ? 'button' : 'a', ]; return array_filter( $link_attrs ); } private function extract_url( $value ): ?string { $destination = $value['destination']; $post = is_numeric( $destination ) ? get_post( $destination ) : null; return $post ? get_permalink( $post ) : $destination; } } atomic-widgets/props-resolver/transformers/settings/time-range-transformer.php 0000644 00000001151 15252521350 0024152 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Time_Range_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { if ( empty( $value ) ) { return null; } return [ 'min' => empty( $value['min'] ) ? null : $value['min'], 'max' => empty( $value['max'] ) ? null : $value['max'], ]; } } atomic-widgets/props-resolver/transformers/settings/classes-transformer.php 0000644 00000001157 15252521350 0023565 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Classes_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { if ( ! is_array( $value ) ) { return null; } $value = apply_filters( 'elementor/atomic-widgets/settings/transformers/classes', $value, $context ); return array_filter( $value ); } } atomic-widgets/props-resolver/transformers/settings/date-range-transformer.php 0000644 00000001151 15252521350 0024131 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Date_Range_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { if ( empty( $value ) ) { return null; } return [ 'min' => empty( $value['min'] ) ? null : $value['min'], 'max' => empty( $value['max'] ) ? null : $value['max'], ]; } } atomic-widgets/props-resolver/transformers/settings/attributes-transformer.php 0000644 00000000702 15252521350 0024311 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Attributes_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { return null; } } atomic-widgets/props-resolver/transformers/settings/date-time-transformer.php 0000644 00000001355 15252521350 0024001 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Date_Time_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { if ( ! is_array( $value ) ) { return null; } $date = isset( $value['date'] ) ? trim( $value['date'] ) : ''; $time = isset( $value['time'] ) ? trim( $value['time'] ) : ''; if ( '' === $date && '' === $time ) { return ''; } $result = trim( $date . ' ' . $time ); return esc_attr( $result ); } } atomic-widgets/props-resolver/transformers/settings/html-v3-transformer.php 0000644 00000000722 15252521350 0023417 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Html_V3_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { return $value['content'] ?? ''; } } atomic-widgets/props-resolver/transformers/settings/query-transformer.php 0000644 00000000715 15252521350 0023274 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Query_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { return $value['id'] ?? null; } } atomic-widgets/props-resolver/transformers/settings/html-v2-transformer.php 0000644 00000000722 15252521350 0023416 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Html_V2_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { return $value['content'] ?? ''; } } atomic-widgets/props-resolver/transformers/image-transformer.php 0000644 00000002253 15252521350 0021350 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { if ( ! empty( $value['src']['id'] ) ) { $image_src = wp_get_attachment_image_src( (int) $value['src']['id'], $value['size'] ?? 'full' ); if ( ! $image_src ) { throw new \Exception( 'Cannot get image src.' ); } [ $src, $width, $height ] = $image_src; return [ 'id' => $value['src']['id'], 'src' => $src, 'width' => (int) $width, 'height' => (int) $height, 'srcset' => wp_get_attachment_image_srcset( $value['src']['id'], $value['size'] ), 'alt' => get_post_meta( $value['src']['id'], '_wp_attachment_image_alt', true ), ]; } if ( empty( $value['src']['url'] ) ) { throw new \Exception( 'Invalid image URL.' ); } return [ 'src' => $value['src']['url'], 'alt' => $value['src']['alt'] ?? '', ]; } } atomic-widgets/props-resolver/transformers/array-transformer.php 0000644 00000000773 15252521350 0021411 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver\Transformers; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Array_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { if ( ! is_array( $value ) ) { return null; } return array_filter( $value ); } } atomic-widgets/props-resolver/props-resolver-context.php 0000644 00000001727 15252521350 0017672 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Props_Resolver_Context { private ?string $key = null; private ?Transformable_Prop_Type $prop_type; private bool $disabled = false; public static function make(): self { return new static(); } public function set_key( ?string $key ): self { $this->key = $key; return $this; } public function set_disabled( bool $disabled ): self { $this->disabled = $disabled; return $this; } public function set_prop_type( Transformable_Prop_Type $prop_type ): self { $this->prop_type = $prop_type; return $this; } public function get_key(): ?string { return $this->key; } public function is_disabled(): bool { return $this->disabled; } public function get_prop_type(): ?Transformable_Prop_Type { return $this->prop_type; } } atomic-widgets/props-resolver/transformers-registry.php 0000644 00000001244 15252521350 0017573 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver; use Elementor\Core\Utils\Collection; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transformers_Registry extends Collection { private ?Transformer_Base $fallback = null; public function register( string $key, Transformer_Base $transformer ): self { $this->items[ $key ] = $transformer; return $this; } public function register_fallback( Transformer_Base $transformer ): self { $this->fallback = $transformer; return $this; } public function get( $key, $fallback = null ) { return parent::get( $key, $fallback ?? $this->fallback ); } } atomic-widgets/props-resolver/multi-props.php 0000644 00000001027 15252521350 0015472 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Multi_Props { public static function is( $value ) { return ( ! empty( $value['$$multi-props'] ) && true === $value['$$multi-props'] && array_key_exists( 'value', $value ) ); } public static function generate( $value ) { return [ '$$multi-props' => true, 'value' => $value, ]; } public static function get_value( $value ) { return $value['value'] ?? null; } } atomic-widgets/props-resolver/props-resolver.php 0000644 00000005576 15252521350 0016216 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropsResolver; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Props_Resolver { protected static array $instances = []; protected Transformers_Registry $transformers_registry; protected function __construct( Transformers_Registry $transformers_registry ) { $this->transformers_registry = $transformers_registry; } protected static function instance( string $context ) { if ( ! isset( static::$instances[ $context ] ) ) { $instance = new static( new Transformers_Registry() ); static::$instances[ $context ] = $instance; do_action( "elementor/atomic-widgets/$context/transformers/register", $instance->get_transformers_registry(), $instance ); } return static::$instances[ $context ]; } public static function reset(): void { static::$instances = []; } public function get_transformers_registry(): Transformers_Registry { return $this->transformers_registry; } protected function transform( $value, $key, Prop_Type $prop_type ) { if ( $prop_type instanceof Union_Prop_Type ) { $prop_type = $prop_type->get_prop_type( $value['$$type'] ); if ( ! $prop_type ) { return null; } } if ( $value['$$type'] !== $prop_type::get_key() ) { return null; } if ( $prop_type instanceof Object_Prop_Type ) { if ( ! is_array( $value['value'] ) ) { return null; } $value['value'] = $this->resolve( $prop_type->get_shape(), $value['value'] ); } if ( $prop_type instanceof Array_Prop_Type ) { if ( ! is_array( $value['value'] ) ) { return null; } $resolved_items = []; foreach ( $value['value'] as $item ) { $resolved = $this->resolve_item( $item, null, $prop_type->get_item_type() ); if ( null !== $resolved ) { $resolved_items[] = $resolved; } } $value['value'] = $resolved_items; } $transformer = $this->transformers_registry->get( $value['$$type'] ); if ( ! ( $transformer instanceof Transformer_Base ) ) { return null; } try { $context = Props_Resolver_Context::make() ->set_key( $key ) ->set_disabled( (bool) ( $value['disabled'] ?? false ) ) ->set_prop_type( $prop_type ); return $transformer->transform( $value['value'], $context ); } catch ( Exception $e ) { return null; } } protected function is_transformable( $value ): bool { return ( ! empty( $value['$$type'] ) && array_key_exists( 'value', $value ) ); } abstract public function resolve( array $schema, array $props ): array; abstract protected function resolve_item( $value, $key, Prop_Type $prop_type ); } atomic-widgets/database/atomic-widgets-database-updater.php 0000644 00000001134 15252521350 0020064 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Database; use Elementor\Core\Database\Base_Database_Updater; use Elementor\Modules\AtomicWidgets\Database\Migrations\Add_Capabilities; class Atomic_Widgets_Database_Updater extends Base_Database_Updater { const DB_VERSION = 1; const OPTION_NAME = 'elementor_atomic_widgets_db_version'; protected function get_migrations(): array { return [ 1 => new Add_Capabilities(), ]; } protected function get_db_version() { return static::DB_VERSION; } protected function get_db_version_option_name(): string { return static::OPTION_NAME; } } atomic-widgets/database/migrations/add-capabilities.php 0000644 00000001400 15252521350 0017267 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Database\Migrations; use Elementor\Core\Database\Base_Migration; class Add_Capabilities extends Base_Migration { const ACCESS_STYLES_TAB = 'elementor_atomic_widgets_access_styles_tab'; const EDIT_LOCAL_CSS_CLASS = 'elementor_atomic_widgets_edit_local_css_class'; public function up() { $capabilities = [ self::ACCESS_STYLES_TAB => [ 'administrator', 'editor', 'author', 'contributor', 'shop_manager' ], self::EDIT_LOCAL_CSS_CLASS => [ 'administrator', 'editor', 'author', 'contributor', 'shop_manager' ], ]; foreach ( $capabilities as $cap => $roles ) { foreach ( $roles as $role_name ) { $role = get_role( $role_name ); if ( $role ) { $role->add_cap( $cap ); } } } } } atomic-widgets/usage/atomic-element-usage-calculator.php 0000644 00000016111 15252521350 0017435 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Usage; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; use Elementor\Modules\AtomicWidgets\Utils\Utils as Atomic_Utils; use Elementor\Modules\Usage\Contracts\Element_Usage_Calculator; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Element_Usage_Calculator implements Element_Usage_Calculator { const TAB_GENERAL = 'General'; const TAB_STYLE = 'Style'; const DEFAULT_SECTION = 'Styles'; private array $style_props_schema; public function __construct() { $this->style_props_schema = Style_Schema::get_style_schema() ?? []; } public function can_calculate( array $element, $element_instance ): bool { if ( null === $element_instance ) { return false; } return Atomic_Utils::is_atomic( $element_instance ); } public function calculate( array $element, $element_instance, array $usage ): array { $type = $this->get_element_type( $element ); $usage = $this->ensure_usage_entry( $usage, $type ); $usage[ $type ]['count']++; if ( ! $element_instance ) { return $usage; } $settings = $element['settings'] ?? []; $styles = $element['styles'] ?? []; $control_sections = $this->build_control_section_map( $element_instance->get_atomic_controls() ); $changed_props = $this->count_props_usage( $settings, $control_sections, $usage, $type ); $changed_styles = $this->count_styles_usage( $styles, $usage, $type ); $usage[ $type ]['control_percent'] = $this->calculate_control_percent( $changed_props + $changed_styles, $element_instance ); return $usage; } private function get_element_type( array $element ): string { return $element['widgetType'] ?? $element['elType']; } private function ensure_usage_entry( array $usage, string $type ): array { if ( ! isset( $usage[ $type ] ) ) { $usage[ $type ] = [ 'count' => 0, 'control_percent' => 0, 'controls' => [], ]; } return $usage; } private function build_control_section_map( array $atomic_controls ): array { $map = []; foreach ( $atomic_controls as $item ) { if ( ! ( $item instanceof Section ) ) { continue; } foreach ( $item->get_items() as $control ) { if ( $control instanceof Atomic_Control_Base ) { $map[ $control->get_bind() ] = $item->get_label(); } } } return $map; } private function count_props_usage( array $settings, array $control_sections, array &$usage, string $type ): int { $count = 0; foreach ( $settings as $prop_name => $value ) { if ( '_cssid' === $prop_name ) { continue; } if ( 'classes' === $prop_name ) { $this->increment_control( $usage, $type, self::TAB_STYLE, $prop_name ); $count++; continue; } $section = $control_sections[ $prop_name ] ?? 'unknown'; $this->increment_control( $usage, $type, self::TAB_GENERAL, $prop_name, $section ); $count++; } return $count; } private function count_styles_usage( array $styles, array &$usage, string $type ): int { $count = 0; $style_props = $this->collect_style_props( $styles ); $has_custom_css = $this->has_custom_css( $styles ); if ( $has_custom_css ) { $this->increment_control( $usage, $type, self::TAB_STYLE, 'custom_css' ); $count++; } foreach ( $style_props as $prop_name => $value ) { $prop_type = $this->style_props_schema[ $prop_name ] ?? null; $count++; if ( $prop_type ) { $decomposed = $this->decompose_style_props( $prop_name, $value, $prop_type ); foreach ( $decomposed as $control_name ) { $this->increment_control( $usage, $type, self::TAB_STYLE, $control_name ); } } else { $this->increment_control( $usage, $type, self::TAB_STYLE, $prop_name ); } } return $count; } private function collect_style_props( array $styles ): array { $props = []; foreach ( $styles as $style_data ) { if ( empty( $style_data['variants'] ) ) { continue; } foreach ( $style_data['variants'] as $variant ) { if ( ! empty( $variant['props'] ) ) { $props = array_merge( $props, $variant['props'] ); } } } return $props; } private function has_custom_css( array $styles ): bool { foreach ( $styles as $style_data ) { if ( empty( $style_data['variants'] ) ) { continue; } foreach ( $style_data['variants'] as $variant ) { if ( ! empty( $variant['custom_css'] ) ) { return true; } } } return false; } private function calculate_control_percent( int $changed_count, $instance ): int { $props_schema = $instance::get_props_schema(); $style_props = Style_Schema::get(); $total = count( $props_schema ) + count( $style_props ); if ( 0 === $total ) { return 0; } return (int) round( ( $changed_count / $total ) * 100 ); } private function increment_control( array &$usage, string $type, string $tab, string $control, string $section = self::DEFAULT_SECTION ): void { if ( ! isset( $usage[ $type ]['controls'][ $tab ] ) ) { $usage[ $type ]['controls'][ $tab ] = []; } if ( ! isset( $usage[ $type ]['controls'][ $tab ][ $section ] ) ) { $usage[ $type ]['controls'][ $tab ][ $section ] = []; } if ( ! isset( $usage[ $type ]['controls'][ $tab ][ $section ][ $control ] ) ) { $usage[ $type ]['controls'][ $tab ][ $section ][ $control ] = 0; } $usage[ $type ]['controls'][ $tab ][ $section ][ $control ]++; } private function decompose_style_props( string $prop_name, $value, $prop_type, string $prefix = '' ): array { $control_names = []; $control_name = $prefix ? "{$prefix}-{$prop_name}" : $prop_name; // phpcs:ignore $kind = $prop_type::$KIND; if ( ! $value ) { return $control_names; } switch ( $kind ) { case 'object': $prop_shape = $prop_type->get_shape(); if ( isset( $value['value'] ) && is_array( $value['value'] ) ) { foreach ( $value['value'] as $key => $nested_value ) { if ( isset( $prop_shape[ $key ] ) ) { $nested = $this->decompose_style_props( $key, $nested_value, $prop_shape[ $key ], $control_name ); $control_names = array_merge( $control_names, $nested ); } } } break; case 'array': $item_type = $prop_type->get_item_type(); if ( isset( $value['value'] ) && is_array( $value['value'] ) ) { foreach ( $value['value'] as $item ) { $item_name = $item['$$type'] ?? 'item'; // phpcs:ignore $item_prop_type = 'union' === $item_type::$KIND ? $item_type->get_prop_type( $item_name ) : $item_type; if ( $item_prop_type ) { $nested = $this->decompose_style_props( $item_name, $item, $item_prop_type, $control_name ); $control_names = array_merge( $control_names, $nested ); } } } break; case 'union': $union_prop_type = $prop_type->get_prop_type_from_value( $value ); if ( $union_prop_type ) { $nested = $this->decompose_style_props( $prop_name, $value, $union_prop_type, $prefix ); $control_names = array_merge( $control_names, $nested ); } break; default: $control_names[] = $control_name; break; } return $control_names; } } atomic-widgets/elements/base/_macros.html.twig 0000644 00000002653 15252521350 0015502 0 ustar 00 {%- macro render_base_classes(id, base_styles, settings, extra_classes) -%} {{- ['elementor-element', 'elementor-element-' ~ id, 'e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | merge(extra_classes | default([])) | join(' ') -}} {%- endmacro -%} {%- macro render_data_attributes(id, type, interaction_id) -%} data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" {%- endmacro -%} {%- macro render_custom_attributes(settings, editor_attributes) -%} {%- if settings._cssid is defined and settings._cssid is not empty %} id="{{ settings._cssid | e }}"{% endif -%} {%- if settings.attributes is defined and settings.attributes is not empty %} {{ settings.attributes | raw }}{% endif -%} {%- if editor_attributes is defined and editor_attributes is not empty %} {{ editor_attributes | raw }}{% endif -%} {%- endmacro -%} {%- macro render_link_attributes(link) -%} {%- if link is defined and link.href is defined and link.href is not empty -%} {%- if link.tag | default('a') == 'button' %} data-action-link="{{ link.href | e }}"{% else %} href="{{ link.href | e }}"{% endif -%} {%- if link.target is defined and link.target is not empty %} target="{{ link.target | e }}"{% endif -%} {%- endif -%} {%- endmacro -%} {%- macro render_interactions(interactions) -%} data-interactions="{{ interactions | json_encode | e('html_attr') }}" {%- endmacro -%} atomic-widgets/elements/base/has-element-template.php 0000644 00000007055 15252521350 0016745 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Base; use Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer\Template_Renderer; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Trait for nested elements that render using Twig templates. * Provides Twig-based rendering with children support for nested elements. * * @mixin Has_Atomic_Base * @mixin Atomic_Element_Base */ trait Has_Element_Template { private static function get_macros_template_key(): string { return 'elementor/macros'; } protected function transform_link_for_render( array $parsed ): array { return $parsed; } public function get_initial_config() { $config = parent::get_initial_config(); $config['support_nesting'] = true; $config['twig_main_template'] = $this->get_main_template(); $config['twig_templates'] = $this->get_templates_contents(); $config['base_styles_dictionary'] = $this->get_base_styles_dictionary(); return $config; } protected function get_shared_templates(): array { return [ self::get_macros_template_key() => __DIR__ . '/_macros.html.twig', ]; } protected function get_templates_contents() { return array_map( fn ( $path ) => Utils::file_get_contents( $path ), array_merge( $this->get_shared_templates(), $this->get_templates() ) ); } protected function render() { try { $renderer = Template_Renderer::instance(); $all_templates = array_merge( $this->get_shared_templates(), $this->get_templates() ); foreach ( $all_templates as $name => $path ) { if ( $renderer->is_registered( $name ) ) { continue; } $renderer->register( $name, $path ); } $context = $this->build_template_context(); $template_html = $renderer->render( $this->get_main_template(), $context ); $children_html = $this->render_children_to_html(); $output = str_replace( $this->get_children_placeholder(), $children_html, $template_html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $output; } catch ( \Exception $e ) { if ( Utils::is_elementor_debug() ) { throw $e; } } } protected function render_children_to_html(): string { $html = ''; foreach ( $this->get_children() as $child ) { ob_start(); $child->print_element(); $html .= ob_get_clean(); } return $html; } protected function get_children_placeholder(): string { return '<!-- elementor-children-placeholder -->'; } protected function build_base_template_context(): array { return [ 'id' => $this->get_id(), 'interaction_id' => $this->get_interaction_id(), 'type' => $this->get_name(), 'settings' => $this->get_atomic_settings(), 'base_styles' => $this->get_base_styles_dictionary(), 'children_placeholder' => $this->get_children_placeholder(), ]; } public function before_render() { // Intentionally empty - Twig template handles full rendering } public function after_render() { // Intentionally empty - Twig template handles full rendering } public function print_content() { $defined_context = $this->define_render_context(); if ( empty( $defined_context ) ) { return $this->render(); } $this->set_render_context( $defined_context ); $this->render(); $this->clear_render_context( $defined_context ); } protected function get_main_template(): string { $templates = $this->get_templates(); foreach ( $templates as $key => $path ) { return $key; } return ''; } abstract protected function get_templates(): array; protected function build_template_context(): array { return $this->build_base_template_context(); } } atomic-widgets/elements/base/atomic-element-base.php 0000644 00000014667 15252521350 0016554 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Base; use Elementor\Element_Base; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Concerns\Has_Meta; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Atomic_Element_Base extends Element_Base { use Has_Atomic_Base; use Has_Meta; protected $version = '0.0'; protected $styles = []; protected $interactions = []; protected $editor_settings = []; protected $origin_id = null; public static $widget_description = null; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->version = $data['version'] ?? '0.0'; $this->styles = $data['styles'] ?? []; $this->interactions = $this->parse_atomic_interactions( $data['interactions'] ?? [] ); $this->editor_settings = $data['editor_settings'] ?? []; if ( static::$widget_description ) { $this->description( static::$widget_description ); } $this->origin_id = $data['origin_id'] ?? null; } private function parse_atomic_interactions( $interactions ) { if ( empty( $interactions ) ) { return []; } if ( is_string( $interactions ) ) { $decoded = json_decode( $interactions, true ); if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) { $interactions = $decoded; } } if ( ! is_array( $interactions ) ) { return []; } return $interactions; } abstract protected function define_atomic_controls(): array; protected function define_atomic_style_states(): array { return []; } protected function define_atomic_pseudo_states(): array { return []; } public function get_global_scripts() { return []; } protected function get_initial_config() { $config = parent::get_initial_config(); $props_schema = static::get_props_schema(); $config['atomic'] = true; $config['atomic_controls'] = $this->get_atomic_controls(); $config['atomic_props_schema'] = $props_schema; $config['atomic_style_states'] = $this->define_atomic_style_states(); $config['atomic_pseudo_states'] = $this->define_atomic_pseudo_states(); $config['dependencies_per_target_mapping'] = Dependency_Manager::get_source_to_dependents( $props_schema ); $config['base_styles'] = $this->get_base_styles(); $config['base_settings'] = $this->get_base_settings(); $config['version'] = $this->version; $config['show_in_panel'] = $this->should_show_in_panel(); $config['categories'] = $this->define_panel_categories(); $config['hide_on_search'] = false; $config['controls'] = []; $config['keywords'] = $this->get_keywords(); $config['default_children'] = $this->define_default_children(); $config['initial_attributes'] = $this->define_initial_attributes(); $config['include_in_widgets_config'] = true; $config['default_html_tag'] = $this->define_default_html_tag(); $config['meta'] = $this->get_meta(); $config['allowed_child_types'] = $this->define_allowed_child_types(); return $config; } protected function should_show_in_panel() { return true; } protected function define_panel_categories(): array { return [ 'v4-elements' ]; } protected function define_default_children() { return []; } protected function define_default_html_tag() { return 'div'; } protected function define_initial_attributes() { return []; } protected function define_allowed_child_types() { return []; } protected function get_interaction_id() { return $this->origin_id ?? $this->get_id(); } protected function add_render_attributes() { parent::add_render_attributes(); $this->add_render_attribute( '_wrapper', 'data-interaction-id', $this->get_interaction_id() ); } /** * Get Element keywords. * * Retrieve the element keywords. * * @since 3.29 * @access public * * @return array Element keywords. */ public function get_keywords() { return []; } /** * @return array<string, Prop_Type> */ abstract protected static function define_props_schema(): array; /** * Get the HTML tag for rendering. * * @return string */ protected function get_html_tag(): string { $settings = $this->get_atomic_settings(); $default_html_tag = $this->define_default_html_tag(); if ( ! empty( $settings['link']['tag'] ) ) { return $settings['link']['tag']; } return $settings['tag'] ?? $default_html_tag; } /** * Print safe HTML tag for the element based on the element settings. * * @return void */ protected function print_html_tag() { $html_tag = $this->get_html_tag(); Utils::print_validated_html_tag( $html_tag ); } /** * Print custom attributes if they exist. * * @return void */ protected function print_custom_attributes() { $settings = $this->get_atomic_settings(); $attributes = $settings['attributes'] ?? ''; if ( isset( $settings['link']['attributes'] ) ) { $attributes .= ' ' . ( $settings['link']['attributes'] ?? '' ); } if ( ! empty( $attributes ) && is_string( $attributes ) ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo ' ' . $attributes; } } /** * Get default child type for container elements. * * @param array $element_data * @return mixed */ protected function _get_default_child_type( array $element_data ) { $el_types = array_keys( Plugin::$instance->elements_manager->get_element_types() ); if ( in_array( $element_data['elType'], $el_types, true ) ) { return Plugin::$instance->elements_manager->get_element_types( $element_data['elType'] ); } if ( ! isset( $element_data['widgetType'] ) ) { return null; } return Plugin::$instance->widgets_manager->get_widget_types( $element_data['widgetType'] ); } /** * Default before render for container elements. * * @return void */ public function before_render() { ?> <<?php $this->print_html_tag(); ?> <?php $this->print_render_attribute_string( '_wrapper' ); $this->print_custom_attributes(); ?>> <?php } /** * Default after render for container elements. * * @return void */ public function after_render() { ?> </<?php $this->print_html_tag(); ?>> <?php } /** * Default content template - can be overridden by elements that need custom templates. * * @return void */ protected function content_template() { ?> <?php } public static function generate() { return Element_Builder::make( static::get_type() ); } } atomic-widgets/elements/base/widget-builder.php 0000644 00000002207 15252521350 0015633 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Base; class Widget_Builder { protected $widget_type; protected $settings = []; protected $is_locked = false; protected $editor_settings = []; protected $meta = []; public static function make( string $widget_type ) { return new self( $widget_type ); } private function __construct( string $widget_type ) { $this->widget_type = $widget_type; } public function settings( array $settings ) { $this->settings = $settings; return $this; } public function is_locked( $is_locked ) { $this->is_locked = $is_locked; return $this; } public function editor_settings( array $editor_settings ) { $this->editor_settings = $editor_settings; return $this; } public function meta( array $meta ) { $this->meta = $meta; return $this; } public function build() { $widget_data = [ 'elType' => 'widget', 'widgetType' => $this->widget_type, 'settings' => $this->settings, 'isLocked' => $this->is_locked, 'editor_settings' => $this->editor_settings, ]; if ( ! empty( $this->meta ) ) { $widget_data['meta'] = $this->meta; } return $widget_data; } } atomic-widgets/elements/base/has-atomic-base.php 0000644 00000031222 15252521350 0015660 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Base; use Elementor\Element_Base; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Atomic_Form; use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader; use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; use Elementor\Modules\AtomicWidgets\Parsers\Props_Parser; use Elementor\Modules\AtomicWidgets\Parsers\Style_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Key_Value_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Utils; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Styles; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @mixin Element_Base */ trait Has_Atomic_Base { use Has_Base_Styles; use Has_Base_Settings; public function has_widget_inner_wrapper(): bool { return false; } abstract public static function get_element_type(): string; final public function get_name() { return static::get_element_type(); } private function get_valid_controls( array $schema, array $controls ): array { $valid_controls = []; foreach ( $controls as $control ) { if ( $control instanceof Section ) { $cloned_section = clone $control; $cloned_section->set_items( $this->get_valid_controls( $schema, $control->get_items() ) ); $valid_controls[] = $cloned_section; continue; } if ( ( $control instanceof Atomic_Control_Base ) ) { $prop_name = $control->get_bind(); if ( ! $prop_name ) { Utils::safe_throw( 'Control is missing a bound prop from the schema.' ); continue; } if ( ! array_key_exists( $prop_name, $schema ) ) { Utils::safe_throw( "Prop `{$prop_name}` is not defined in the schema of `{$this->get_name()}`." ); continue; } } $valid_controls[] = $control; } return $valid_controls; } private static function validate_schema( array $schema ) { $widget_name = static::class; foreach ( $schema as $key => $prop ) { if ( ! ( $prop instanceof Prop_Type ) ) { Utils::safe_throw( "Prop `$key` must be an instance of `Prop_Type` in `{$widget_name}`." ); } } } private function parse_atomic_styles( array $data ): array { $styles = $data['styles'] ?? []; $style_parser = Style_Parser::make( Style_Schema::get() ); foreach ( $styles as $style_id => $style ) { $result = $style_parser->parse( $style ); if ( ! $result->is_valid() ) { throw new \Exception( esc_html( $this->format_styles_validation_error_message( $style_id, $data, $style, $result->errors()->to_string() ) ) ); } $styles[ $style_id ] = $result->unwrap(); } return $styles; } private function format_styles_validation_error_message( string $style_id, array $data, array $style, string $validation_errors ): string { $widget_id = $data['id'] ?? 'unknown'; $structure_label = $this->get_editor_structure_label( $data ); $style_label = isset( $style['label'] ) && is_string( $style['label'] ) ? $style['label'] : null; $message_parts = [ "Styles validation failed for style `$style_id` (widget `$widget_id`)", ]; if ( $structure_label ) { $message_parts[] = "Structure label: `$structure_label`"; } else { $element_name = $this->get_title(); if ( '' === $element_name ) { $element_name = $this->get_name(); } $message_parts[] = "Element: `$element_name`"; } if ( $style_label ) { $message_parts[] = "Style label: `$style_label`"; } return implode( '. ', $message_parts ) . '. ' . $validation_errors; } private function get_editor_structure_label( array $data ): ?string { $title = $data['editor_settings']['title'] ?? $this->editor_settings['title'] ?? null; if ( ! is_string( $title ) || '' === $title ) { return null; } return $title; } private function parse_atomic_settings( array $settings ): array { $schema = static::get_props_schema(); $props_parser = Props_Parser::make( $schema ); $result = $props_parser->parse( $settings ); if ( ! $result->is_valid() ) { throw new \Exception( esc_html( 'Settings validation failed. ' . $result->errors()->to_string() ) ); } return $result->unwrap(); } private function parse_atomic_interactions( $interactions ) { if ( empty( $interactions ) ) { return []; } if ( is_string( $interactions ) ) { $decoded = json_decode( $interactions, true ); if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) { return $decoded; } } if ( is_array( $interactions ) ) { return $interactions; } return []; } private function extract_prop_value( $data, $key, $default = '' ) { if ( ! is_array( $data ) || ! isset( $data[ $key ] ) ) { return $default; } $value = $data[ $key ]; if ( is_array( $value ) && isset( $value['$$type'] ) && isset( $value['value'] ) ) { return $value['value']; } return null !== $value ? $value : $default; } public function get_atomic_controls() { $controls = apply_filters( 'elementor/atomic-widgets/controls', $this->define_atomic_controls(), $this ); $schema = static::get_props_schema(); // Validate the schema only in the Editor. static::validate_schema( $schema ); return $this->get_valid_controls( $schema, $controls ); } protected function get_css_id_control_meta(): array { return [ 'layout' => 'two-columns', 'topDivider' => true, ]; } final public function get_controls( $control_id = null ) { if ( ! empty( $control_id ) ) { return null; } return []; } final public function get_data_for_save() { $data = parent::get_data_for_save(); $data['version'] = $this->version; $data['settings'] = $this->parse_atomic_settings( $data['settings'] ); $data['styles'] = $this->parse_atomic_styles( $data ); $data['editor_settings'] = $this->parse_editor_settings( $data['editor_settings'] ); if ( isset( $data['interactions'] ) && ! empty( $data['interactions'] ) ) { $data['interactions'] = $this->transform_interactions_for_save( $data['interactions'] ); } else { $data['interactions'] = []; } return $data; } private function transform_interactions_for_save( $interactions ) { $decoded = $this->decode_interactions_data( $interactions ); if ( empty( $decoded['items'] ) ) { return []; } return $decoded; } private function decode_interactions_data( $interactions ) { if ( is_array( $interactions ) ) { return $interactions; } if ( is_string( $interactions ) ) { $decoded = json_decode( $interactions, true ); if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) { return $decoded; } } return [ 'items' => [], 'version' => 1, ]; } final public function get_raw_data( $with_html_content = false ) { $raw_data = parent::get_raw_data( $with_html_content ); $raw_data['styles'] = Atomic_Widget_Styles::get_license_based_filtered_styles( $this->styles ?? [] ); $raw_data['interactions'] = $this->interactions ?? []; $raw_data['editor_settings'] = $this->editor_settings; return $raw_data; } final public function get_stack( $with_common_controls = true ) { return [ 'controls' => [], 'tabs' => [], ]; } public function get_atomic_settings(): array { $schema = static::get_props_schema(); $props = $this->get_settings(); $merged_attribute_values = array_merge( $this->get_initial_attributes()['value'] ?? [], $props['attributes']['value'] ?? [] ); $props['attributes'] = Attributes_Prop_Type::generate( $merged_attribute_values ); $parsed = Render_Props_Resolver::for_settings()->resolve( $schema, $props ); return $this->transform_link_for_render( $parsed ); } protected function transform_link_for_render( array $parsed ): array { $link_attributes = isset( $parsed['link'] ) ? $this->get_link_attributes_string( $parsed['link'] ) : ''; $parsed['link'] = ! empty( $link_attributes ) ? [ 'tag' => $parsed['link']['tag'], 'attributes' => $link_attributes, ] : null; return $parsed; } protected function get_initial_attributes() { return Attributes_Prop_Type::generate( [ Key_Value_Prop_Type::generate( [ 'key' => String_Prop_Type::generate( 'data-e-type' ), 'value' => $this->get_type(), ] ), Key_Value_Prop_Type::generate( [ 'key' => String_Prop_Type::generate( 'data-id' ), 'value' => $this->get_id(), ] ), ] ); } public function get_atomic_setting( string $key ) { $schema = static::get_props_schema(); if ( ! isset( $schema[ $key ] ) ) { return null; } $props = $this->get_settings(); $prop_value = $props[ $key ] ?? null; $single_schema = [ $key => $schema[ $key ] ]; $single_props = [ $key => $prop_value ]; $resolved = Render_Props_Resolver::for_settings()->resolve( $single_schema, $single_props ); return $resolved[ $key ] ?? null; } protected function parse_editor_settings( array $data ): array { $editor_data = []; if ( isset( $data['title'] ) && is_string( $data['title'] ) ) { $editor_data['title'] = sanitize_text_field( $data['title'] ); } if ( isset( $data['grid_outline'] ) && is_bool( $data['grid_outline'] ) ) { $editor_data['grid_outline'] = $data['grid_outline']; } return $editor_data; } public static function get_props_schema(): array { $schema = static::define_props_schema(); if ( ! isset( $schema['_cssid'] ) ) { $schema['_cssid'] = String_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ); } return apply_filters( 'elementor/atomic-widgets/props-schema', $schema ); } protected function set_render_context( array $context_pairs ): void { foreach ( $context_pairs as $context_pair ) { $context_key = $context_pair['context_key'] ?? static::class; $context = $context_pair['context']; Render_Context::push( $context_key, $context ); } } protected function clear_render_context( array $context_pairs ): void { foreach ( $context_pairs as $context_pair ) { $context_key = $context_pair['context_key'] ?? static::class; Render_Context::pop( $context_key ); } } public function print_content() { $defined_context = $this->define_render_context(); if ( empty( $defined_context ) ) { return parent::print_content(); } $this->set_render_context( $defined_context ); parent::print_content(); $this->clear_render_context( $defined_context ); } /** * Define the context for element's Render_Context. * * @return array Array of context pairs. Each pair is an associative array with: * - 'context_key' (optional): The context key. Defaults to static::class if not provided. * - 'context' (required): The context value (can be any type). * * @example * [ * [ * 'context_key' => 'custom-key', * 'context' => ['some' => 'data'], * ], * [ * 'context' => ['instance_id' => $this->get_id()], * ], * ] */ protected function define_render_context(): array { return []; } protected function get_link_attributes( $link_settings ) { if ( empty( $link_settings['href'] ) ) { return []; } $tag = $link_settings['tag'] ?? Link_Prop_Type::DEFAULT_TAG; $url = $link_settings['href']; $target = $link_settings['target'] ?? '_self'; $is_action_link = 'button' === $tag; $url_attr_key = $is_action_link ? 'data-action-link' : 'href'; return [ $url_attr_key => $url, 'target' => $target, ]; } private function get_link_attributes_string( $link_settings ) { $link_attributes = $this->get_link_attributes( $link_settings ); if ( empty( $link_attributes ) ) { return ''; } $parts = []; foreach ( $link_attributes as $key => $value ) { if ( 'tag' === $key ) { continue; } $parts[] = sprintf( '%s="%s"', $key, esc_attr( $value ) ); } return implode( ' ', $parts ); } public function has_action_link() { if ( ! $this->get_id() ) { return true; } $link_settings = $this->get_atomic_setting( 'link' ) ?? null; $attributes = $this->get_link_attributes( $link_settings ); return isset( $attributes['data-action-link'] ); } public function get_script_depends() { $depends = parent::get_script_depends(); if ( $this->has_action_link() ) { $depends[] = Frontend_Assets_Loader::ACTION_LINK_HANDLERS_HANDLE; } if ( Atomic_Form::is_instance_form( $this ) ) { $depends[] = Frontend_Assets_Loader::FORM_HANDLERS_HANDLE; } return $depends; } } atomic-widgets/elements/base/element-builder.php 0000644 00000002424 15252521350 0016002 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Base; class Element_Builder { protected $element_type; protected $settings = []; protected $is_locked = false; protected $children = []; protected $editor_settings = []; protected $meta = []; public static function make( string $element_type ) { return new self( $element_type ); } private function __construct( string $element_type ) { $this->element_type = $element_type; } public function settings( array $settings ) { $this->settings = $settings; return $this; } public function is_locked( $is_locked ) { $this->is_locked = $is_locked; return $this; } public function editor_settings( array $editor_settings ) { $this->editor_settings = $editor_settings; return $this; } public function children( array $children ) { $this->children = $children; return $this; } public function meta( array $meta ) { $this->meta = $meta; return $this; } public function build() { $element_data = [ 'elType' => $this->element_type, 'settings' => $this->settings, 'isLocked' => $this->is_locked, 'editor_settings' => $this->editor_settings, 'elements' => $this->children, ]; if ( ! empty( $this->meta ) ) { $element_data['meta'] = $this->meta; } return $element_data; } } atomic-widgets/elements/base/atomic-widget-base.php 0000644 00000005364 15252521350 0016400 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Base; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Concerns\Has_Meta; use Elementor\Widget_Base; if ( ! defined( 'ABSPATH' ) ) { exit; } abstract class Atomic_Widget_Base extends Widget_Base { use Has_Atomic_Base; use Has_Meta; public static $widget_description = null; protected $version = '0.0'; protected $styles = []; protected $interactions = []; protected $editor_settings = []; protected $origin_id = null; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->version = $data['version'] ?? '0.0'; $this->styles = $data['styles'] ?? []; $this->interactions = $this->parse_atomic_interactions( $data['interactions'] ?? [] ); $this->editor_settings = $data['editor_settings'] ?? []; if ( static::$widget_description ) { $this->description( static::$widget_description ); } $this->origin_id = $data['origin_id'] ?? null; } private function parse_atomic_interactions( $interactions ) { if ( empty( $interactions ) ) { return []; } if ( is_string( $interactions ) ) { $decoded = json_decode( $interactions, true ); if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) { $interactions = $decoded; } } if ( ! is_array( $interactions ) ) { return []; } return $interactions; } abstract protected function define_atomic_controls(): array; protected function define_atomic_pseudo_states(): array { return []; } public function get_global_scripts() { return []; } public function get_initial_config() { $config = parent::get_initial_config(); $props_schema = static::get_props_schema(); $config['atomic'] = true; $config['atomic_controls'] = $this->get_atomic_controls(); $config['base_styles'] = $this->get_base_styles(); $config['base_styles_dictionary'] = $this->get_base_styles_dictionary(); $config['base_settings'] = $this->get_base_settings(); $config['atomic_props_schema'] = $props_schema; $config['atomic_pseudo_states'] = $this->define_atomic_pseudo_states(); $config['dependencies_per_target_mapping'] = Dependency_Manager::get_source_to_dependents( $props_schema ); $config['version'] = $this->version; $config['meta'] = $this->get_meta(); return $config; } public function get_categories(): array { return [ 'v4-elements' ]; } public function before_render() {} public function after_render() {} abstract protected static function define_props_schema(): array; public static function generate() { return Widget_Builder::make( static::get_element_type() ); } public function get_interaction_id() { return $this->origin_id ?? $this->get_id(); } } atomic-widgets/elements/base/has-template.php 0000644 00000004415 15252521350 0015313 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Base; use Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer\Template_Renderer; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @mixin Has_Atomic_Base */ trait Has_Template { private static function get_macros_template_key(): string { return 'elementor/macros'; } public function get_initial_config() { $config = parent::get_initial_config(); $config['twig_main_template'] = $this->get_main_template(); $config['twig_templates'] = $this->get_templates_contents(); return $config; } protected function transform_link_for_render( array $parsed ): array { return $parsed; } protected function get_shared_templates(): array { return [ self::get_macros_template_key() => __DIR__ . '/_macros.html.twig', ]; } protected function render() { try { $renderer = Template_Renderer::instance(); $all_templates = array_merge( $this->get_shared_templates(), $this->get_templates() ); foreach ( $all_templates as $name => $path ) { if ( $renderer->is_registered( $name ) ) { continue; } $renderer->register( $name, $path ); } $context = [ 'id' => $this->get_id(), 'interaction_id' => $this->get_interaction_id(), 'type' => $this->get_name(), 'settings' => $this->get_atomic_settings(), 'base_styles' => $this->get_base_styles_dictionary(), ]; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $renderer->render( $this->get_main_template(), $context ); } catch ( \Exception $e ) { if ( Utils::is_elementor_debug() ) { throw $e; } } } protected function get_templates_contents() { return array_map( fn ( $path ) => Utils::file_get_contents( $path ), array_merge( $this->get_shared_templates(), $this->get_templates() ) ); } protected function get_main_template() { $templates = $this->get_templates(); if ( count( $templates ) > 1 ) { Utils::safe_throw( 'When having more than one template, you should override this method to return the main template.' ); return null; } foreach ( $templates as $key => $path ) { // Returns first key in the array. return $key; } return null; } abstract protected function get_templates(): array; } atomic-widgets/elements/base/render-context.php 0000644 00000001655 15252521350 0015673 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Render_Context { private static $context_stack = []; public static function push( string $key, array $context ): void { if ( ! self::get( $key ) ) { self::$context_stack[ $key ] = []; } self::$context_stack[ $key ][] = $context; } public static function pop( string $key ): void { if ( isset( self::$context_stack[ $key ] ) && ! empty( self::$context_stack[ $key ] ) ) { array_pop( self::$context_stack[ $key ] ); } } public static function get( string $key ): array { if ( ! isset( self::$context_stack[ $key ] ) || empty( self::$context_stack[ $key ] ) ) { return []; } $last_key = array_key_last( self::$context_stack[ $key ] ); return self::$context_stack[ $key ][ $last_key ]; } public static function clear(): void { self::$context_stack = []; } } atomic-widgets/elements/base/has-base-settings.php 0000644 00000000545 15252521350 0016250 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @mixin Has_Atomic_Base */ trait Has_Base_Settings { public function get_base_settings(): array { return $this->define_base_settings(); } protected function define_base_settings(): array { return []; } } atomic-widgets/elements/base/has-base-styles.php 0000644 00000002074 15252521350 0015732 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Base; use Elementor\Core\Utils\Collection; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @mixin Has_Atomic_Base */ trait Has_Base_Styles { public function get_base_styles() { $base_styles = $this->define_base_styles(); $style_definitions = []; foreach ( $base_styles as $key => $style ) { $id = $this->generate_base_style_id( $key ); $style_definitions[ $id ] = $style->build( $id ); } return $style_definitions; } public function get_base_styles_dictionary() { $result = []; $base_styles = array_keys( $this->define_base_styles() ); foreach ( $base_styles as $key ) { $result[ $key ] = $this->generate_base_style_id( $key ); } return $result; } private function generate_base_style_id( string $key ): string { return static::get_element_type() . '-' . $key; } /** * @return array<string, Style_Definition> */ protected function define_base_styles(): array { return []; } } atomic-widgets/elements/atomic-collection-loop/collection-loop-promotion.php 0000644 00000003774 15252521350 0023526 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Collection_Loop; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\Elements\Promotions\Preserves_Children_Subtree; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; if ( ! defined( 'ABSPATH' ) ) { exit; } class Collection_Loop_Promotion extends Atomic_Element_Base { use Has_Element_Template; use Preserves_Children_Subtree; const BASE_STYLE_KEY = 'base'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'is_container', true ); $this->meta( 'is_pro_promotion', true ); } public static function get_type() { return 'e-collection-loop'; } public static function get_element_type(): string { return self::get_type(); } public function get_title() { return esc_html__( 'Loop', 'elementor' ); } public function get_icon() { return 'eicon-loop-widget'; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), ]; } protected function define_atomic_controls(): array { return []; } protected function define_base_styles(): array { return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'display', String_Prop_Type::generate( 'block' ) ) ), ]; } protected function should_show_in_panel() { return false; } protected function should_print_empty() { return false; } public function print_content() { } protected function get_templates(): array { return [ 'elementor/elements/collection-loop-promotion' => __DIR__ . '/collection-loop-promotion.html.twig', ]; } } atomic-widgets/elements/atomic-collection-loop/collection-loop-promotion.html.twig 0000644 00000002130 15252521350 0024635 0 ustar 00 {% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %} <div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" {{ editor_attributes | default('') | raw }}> <div class="e-pro-promotion-placeholder"> <i class="eicon-upgrade-crown-full e-pro-promotion-placeholder__icon"></i> <div class="e-pro-promotion-placeholder__title">Loop is a Pro feature</div> <div class="e-pro-promotion-placeholder__description">Upgrade now to display dynamic content in a repeating layout with full query control.</div> <div class="e-pro-promotion-placeholder__actions"> <button type="button" class="e-pro-promotion-placeholder__remove-btn">Remove</button> <a href="https://go.elementor.com/go-pro-loop-canvas-upgrade/" target="_blank" rel="noopener noreferrer" class="e-pro-promotion-placeholder__unlock-btn">Unlock with Pro</a> </div> </div> </div> atomic-widgets/elements/atomic-self-hosted-video/atomic-self-hosted-video.php 0000644 00000016356 15252521350 0023412 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Self_Hosted_Video; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Image_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Number_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Video_Control; use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Type; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Video_Src_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Utils\Image\Placeholder_Image; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Atomic_Self_Hosted_Video extends Atomic_Widget_Base { use Has_Template; protected static function get_preload_options() { return [ 'auto' => esc_html__( 'Auto', 'elementor' ), 'metadata' => esc_html__( 'Metadata', 'elementor' ), 'none' => esc_html__( 'None (Lazy Load)', 'elementor' ), ]; } protected function get_css_id_control_meta(): array { return [ 'layout' => 'two-columns', 'topDivider' => false, ]; } public static function get_element_type(): string { return 'e-self-hosted-video'; } public function get_title() { return esc_html__( 'Video', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic', 'video', 'player', 'media', 'hosted' ]; } public function get_icon() { return 'eicon-video'; } protected static function define_props_schema(): array { $playsinline_dependencies = Dependency_Manager::make() ->where([ 'operator' => 'eq', 'path' => [ 'autoplay' ], 'value' => true, 'effect' => 'hide', ]) ->get(); // NOTE: restore the dependency when dependencies works in overridables $poster_dependencies = Dependency_Manager::make() ->where([ 'operator' => 'eq', 'path' => [ 'poster_enabled' ], 'value' => true, 'effect' => 'hide', ]) ->get(); $allow_download_dependencies = Dependency_Manager::make() ->where([ 'operator' => 'eq', 'path' => [ 'controls' ], 'value' => true, 'effect' => 'hide', ]) ->get(); return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'source' => Video_Src_Prop_Type::make() ->alias( 'video', 'src' ), 'autoplay' => Boolean_Prop_Type::make()->default( false ), 'playsinline' => Boolean_Prop_Type::make() ->default( false ) ->set_dependencies( $playsinline_dependencies ) ->meta( Overridable_Prop_Type::ignore() ), 'mute' => Boolean_Prop_Type::make()->default( false ), 'loop' => Boolean_Prop_Type::make()->default( false ), 'controls' => Boolean_Prop_Type::make()->default( true ), 'preload' => String_Prop_Type::make() ->default( 'metadata' ) ->enum( array_keys( self::get_preload_options() ) ), 'download' => Boolean_Prop_Type::make()->default( false ) ->set_dependencies( $allow_download_dependencies ), 'start_time' => Number_Prop_Type::make() ->default( null ) ->meta( Dynamic_Prop_Type::ignore() ) ->meta( 'suffix', 'SEC' ), 'end_time' => Number_Prop_Type::make() ->default( null ) ->meta( 'suffix', 'SEC' ) ->meta( Dynamic_Prop_Type::ignore() ), 'poster_enabled' => Boolean_Prop_Type::make()->default( false ), 'poster' => Image_Prop_Type::make() ->default_size( 'medium_large' ) ->default_url( Placeholder_Image::get_placeholder_image() ), // TODO: restore the dependency when dependencies works in overridables // ->set_dependencies( $poster_dependencies ), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Content', 'elementor' ) ) ->set_id( 'content' ) ->set_items([ Video_Control::bind_to( 'source' ) ->set_label( esc_html__( 'Video', 'elementor' ) ), Number_Control::bind_to( 'start_time' ) ->set_label( esc_html__( 'Start Time', 'elementor' ) ) ->set_min( 0 ) ->set_max( 10000 ), Number_Control::bind_to( 'end_time' ) ->set_label( esc_html__( 'End Time', 'elementor' ) ) ->set_min( 0 ) ->set_max( 10000 ), Switch_Control::bind_to( 'autoplay' )->set_label( esc_html__( 'Autoplay', 'elementor' ) ), Switch_Control::bind_to( 'playsinline' ) ->set_label( esc_html__( 'Play on mobile', 'elementor' ) ), Switch_Control::bind_to( 'mute' )->set_label( esc_html__( 'Mute', 'elementor' ) ), Switch_Control::bind_to( 'loop' )->set_label( esc_html__( 'Loop', 'elementor' ) ), Switch_Control::bind_to( 'controls' )->set_label( esc_html__( 'Player Controls', 'elementor' ) ), Switch_Control::bind_to( 'download' )->set_label( esc_html__( 'Allow Download', 'elementor' ) ), Select_Control::bind_to( 'preload' ) ->set_label( esc_html__( 'Preload', 'elementor' ) ) ->set_options( self::format_options( self::get_preload_options() ) ), Switch_Control::bind_to( 'poster_enabled' ) ->set_label( esc_html__( 'Poster Image', 'elementor' ) ), Image_Control::bind_to( 'poster' ) ->set_label( esc_html__( 'Image', 'elementor' ) ), ]), Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( $this->get_settings_controls() ), ]; } protected function get_settings_controls(): array { return [ Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ]; } protected function define_base_styles(): array { $max_width = Size_Prop_Type::generate([ 'unit' => 'vw', 'size' => 100, ]); return [ 'base' => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'max-width', $max_width ) ->add_prop( 'display', String_Prop_Type::generate( 'inline-block' ) ) ->add_prop( 'aspect-ratio', String_Prop_Type::generate( '16/9' ) ) ), ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-self-hosted-video' => __DIR__ . '/atomic-self-hosted-video.html.twig', ]; } private static function format_options( array $options ): array { return array_map( fn( $value, $key ) => [ 'value' => $key, 'label' => $value, ], $options, array_keys( $options ) ); } } atomic-widgets/elements/atomic-self-hosted-video/atomic-self-hosted-video.html.twig 0000644 00000004736 15252521350 0024537 0 ustar 00 {% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %} {% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %} {% set video_classes = base_styles.base %} {% set video_start_time = settings.start_time %} {% set video_end_time = settings.end_time %} {% if video_start_time is not empty %} {% set video_timings = '#t=' ~ video_start_time %} {% else %} {% set video_timings = '#t=0' %} {% endif %} {% if video_end_time is not empty and video_end_time > video_start_time %} {% set video_timings = video_timings ~ ',' ~ video_end_time %} {% endif %} {% if settings.source.url is not empty %} {% set video_url = settings.source.url | e('full_url') %} <video data-id="{{ id }}" data-interaction-id="{{ interaction_id }}" data-e-type="{{ type }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" {{ id_attribute }} class="{{ classes }}" {{ settings.attributes | raw }} class="{{ video_classes }}" {% if settings.poster_enabled %}poster="{{ settings.poster.src | e('full_url') }}"{% endif %} {% if settings.autoplay %}autoplay{% endif %} {% if settings.mute %}muted{% endif %} {% if settings.loop %}loop{% endif %} {% if settings.controls %}controls{% endif %} {% if settings.playsinline %}playsinline{% endif %} {% if not settings.download %}controlslist="nodownload"{% endif %} preload="{{ settings.preload }}" > <source src="{{ video_url ~ video_timings }}"> </video> {% else %} {% if settings.poster_enabled %} <div style="width:100%; aspect-ratio: 16/9; background-image: url({{ settings.poster.src | e('full_url') }}); background-size: contain; background-position: center center; background-repeat: no-repeat; min-height: 100px;"></div> {% else %} <div style="width:100%; background-color: rgb(245, 245, 245); aspect-ratio: 16/9; display: flex; align-items: center; justify-content: center;"> <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M17.6902 8.92189C18.3227 8.56844 19.0991 8.58381 19.7162 8.96355L54.3829 30.2969C54.9738 30.661 55.3334 31.3059 55.3334 32C55.3334 32.6941 54.9738 33.339 54.3829 33.7031L19.7162 55.0365C19.0991 55.4162 18.3227 55.4316 17.6902 55.0781C17.0584 54.7245 16.6667 54.0574 16.6667 53.3333V10.6667L16.685 10.3984C16.7685 9.7807 17.1373 9.23132 17.6902 8.92189ZM20.6667 49.7526L49.5157 32L20.6667 14.2448V49.7526Z" fill="black" fill-opacity="0.54"/> </svg> </div> {% endif %} {% endif %} atomic-widgets/elements/atomic-form/atomic-form.html.twig 0000644 00000002402 15252521350 0017567 0 ustar 00 {%- set form_state = form_state | default(settings['form-state']) | default('default') -%} {%- set classes = ['e-con', 'e-atomic-element', base_styles.base, 'form-state-' ~ form_state] | merge(settings.classes | default([])) | join(' ') -%} <form class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" x-data="eForm{{ id }}" x-on:submit="submit" {%- if settings['form-name'] is not empty %} aria-label="{{ settings['form-name'] | e('html_attr') }}" data-form-name="{{ settings['form-name'] | e('html_attr') }}" {% endif -%} {%- if webmcp_tool_name is not empty %} toolname="{{ webmcp_tool_name | e('html_attr') }}" tooldescription="{{ webmcp_tool_description | e('html_attr') }}" {%- if settings['webmcp-autosubmit'] %} toolautosubmit {% endif -%} {% endif -%} {%- if settings['_cssid'] is not empty %} id="{{ settings['_cssid'] | e('html_attr') }}" {% endif -%} {{ editor_attributes | default('') | raw }}> <!-- elementor-children-placeholder --> </form> atomic-widgets/elements/atomic-form/form-success-message/form-success-message.php 0000644 00000002173 15252521350 0024317 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Success_Message; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Message\Form_Message; if ( ! defined( 'ABSPATH' ) ) { exit; } class Form_Success_Message extends Form_Message { public static $widget_description = 'Shown when the form is submitted successfully. Hidden by default, displayed automatically when the form submission result is success.'; public static function get_type() { return 'e-form-success-message'; } public static function get_element_type(): string { return 'e-form-success-message'; } public function get_title() { return esc_html__( 'Success message', 'elementor' ); } protected static function get_background_color(): string { return '#D4E9D6'; } protected static function get_text_color(): string { return '#2F532E'; } protected static function get_default_status_paragraph_text(): string { return __( 'Great! We’ve received your information.', 'elementor' ); } protected function get_css_id_control_meta(): array { return [ 'layout' => 'two-columns', 'topDivider' => false, ]; } } atomic-widgets/elements/atomic-form/atomic-form-promotion.html.twig 0000644 00000002125 15252521350 0021615 0 ustar 00 {% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %} <div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" {{ editor_attributes | default('') | raw }}> <div class="e-pro-promotion-placeholder"> <i class="eicon-upgrade-crown-full e-pro-promotion-placeholder__icon"></i> <div class="e-pro-promotion-placeholder__title">Atomic Form is a Pro feature</div> <div class="e-pro-promotion-placeholder__description">Upgrade now to access advanced styling and build fully custom forms.</div> <div class="e-pro-promotion-placeholder__actions"> <button type="button" class="e-pro-promotion-placeholder__remove-btn">Remove</button> <a href="https://go.elementor.com/go-pro-atomic-form-canvas-upgrade/" target="_blank" rel="noopener noreferrer" class="e-pro-promotion-placeholder__unlock-btn">Unlock with Pro</a> </div> </div> </div> atomic-widgets/elements/atomic-form/atomic-form-promotion.php 0000644 00000003577 15252521350 0020503 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\Elements\Promotions\Preserves_Children_Subtree; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; if ( ! defined( 'ABSPATH' ) ) { exit; } class Atomic_Form_Promotion extends Atomic_Element_Base { use Has_Element_Template; use Preserves_Children_Subtree; const BASE_STYLE_KEY = 'base'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'is_container', true ); $this->meta( 'is_pro_promotion', true ); } public static function get_type() { return 'e-form'; } public static function get_element_type(): string { return self::get_type(); } public function get_title() { return esc_html__( 'Atomic Form', 'elementor' ); } public function get_icon() { return 'eicon-atomic-form'; } protected static function define_props_schema(): array { return Atomic_Form::get_base_props_schema(); } protected function define_atomic_controls(): array { return []; } protected function define_base_styles(): array { return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'display', String_Prop_Type::generate( 'block' ) ) ), ]; } protected function should_show_in_panel() { return false; } protected function should_print_empty() { return false; } public function print_content() { } protected function get_templates(): array { return [ 'elementor/elements/atomic-form-promotion' => __DIR__ . '/atomic-form-promotion.html.twig', ]; } } atomic-widgets/elements/atomic-form/form-error-message/form-error-message.php 0000644 00000002163 15252521350 0023460 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Error_Message; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Message\Form_Message; if ( ! defined( 'ABSPATH' ) ) { exit; } class Form_Error_Message extends Form_Message { public static $widget_description = 'Shown when the form submission fails. Hidden by default, displayed automatically when the form submission result is an error.'; public static function get_type() { return 'e-form-error-message'; } public static function get_element_type(): string { return 'e-form-error-message'; } public function get_title() { return esc_html__( 'Error message', 'elementor' ); } protected static function get_background_color(): string { return '#ffdede'; } protected static function get_text_color(): string { return '#870000'; } protected static function get_default_status_paragraph_text(): string { return __( 'We couldn’t process your submission. Please retry', 'elementor' ); } protected function get_css_id_control_meta(): array { return [ 'layout' => 'two-columns', 'topDivider' => false, ]; } } atomic-widgets/elements/atomic-form/webmcp-utils.php 0000644 00000002114 15252521350 0016637 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form; if ( ! defined( 'ABSPATH' ) ) { exit; } class Webmcp_Utils { public static function build_tool_name( string $form_name, string $element_id = '' ): string { $slug = sanitize_title( $form_name ); $slug = str_replace( '-', '_', $slug ); if ( '' === $slug ) { $slug = 'submit_form'; } $id_suffix = self::build_element_id_suffix( $element_id ); if ( '' === $id_suffix ) { return $slug; } return $slug . '_' . $id_suffix; } private static function build_element_id_suffix( string $element_id ): string { $suffix = sanitize_title( $element_id ); $suffix = str_replace( '-', '_', $suffix ); return $suffix; } public static function build_tool_description( string $form_name ): string { $label = trim( $form_name ); if ( '' === $label ) { return esc_html__( 'Submit this form with the provided field values.', 'elementor' ); } return sprintf( /* translators: %s: form name */ esc_html__( 'Submit the %s form with the provided field values.', 'elementor' ), $label ); } } atomic-widgets/elements/atomic-form/atomic-form.php 0000644 00000041621 15252521350 0016447 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Chips_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Email_Form_Action_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Toggle_Control; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph\Atomic_Paragraph; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Success_Message\Form_Success_Message; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Error_Message\Form_Error_Message; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Element_Builder; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\Elements\Base\Widget_Builder; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Emails_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Key_Value_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Form extends Atomic_Element_Base { use Has_Element_Template; const BASE_STYLE_KEY = 'base'; public static $widget_description = 'A form container that holds form field widgets (labels, inputs, textareas, checkboxes, submit button) and status messages.'; public const ACTION_EMAIL = 'email'; public const ACTION_COLLECT_SUBMISSIONS = 'collect-submissions'; public const ACTION_WEBHOOK = 'webhook'; public const METADATA_REMOTE_IP = 'remote_ip'; public const METADATA_USER_AGENT = 'user_agent'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'is_container', true ); } public static function get_default_recipient_email(): string { return sanitize_email( (string) get_option( 'admin_email', '' ) ); } public static function get_default_sender_email(): string { return sanitize_email( (string) 'email@' . wp_parse_url( home_url(), PHP_URL_HOST ) ); } public static function get_type() { return 'e-form'; } public static function get_element_type(): string { return self::get_type(); } public function get_title() { return esc_html__( 'Atomic form', 'elementor' ); } public function get_keywords() { return [ 'atomic', 'form' ]; } public function get_icon() { return 'eicon-atomic-form'; } public static function get_base_props_schema(): array { return self::define_props_schema(); } protected static function define_props_schema(): array { $submissions_metadata_dependencies = Dependency_Manager::make() ->where( [ 'operator' => 'contains', 'path' => [ 'actions-after-submit' ], 'value' => self::ACTION_COLLECT_SUBMISSIONS, 'effect' => 'hide', ] ) ->get(); $webhook_dependencies = Dependency_Manager::make() ->where( [ 'operator' => 'contains', 'path' => [ 'actions-after-submit' ], 'value' => self::ACTION_WEBHOOK, 'effect' => 'hide', ] ) ->get(); $props = [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'form-name' => String_Prop_Type::make() ->default( __( 'Form', 'elementor' ) ), 'webmcp-autosubmit' => Boolean_Prop_Type::make() ->default( false ), 'form-state' => String_Prop_Type::make() ->enum( [ 'default', 'success', 'error' ] ) ->default( 'default' ) ->meta( 'generates_class', 'form-state-{value}' ), 'actions-after-submit' => String_Array_Prop_Type::make() ->initial_value( [ String_Prop_Type::generate( self::ACTION_EMAIL ) ] ) ->default( [ String_Prop_Type::generate( self::ACTION_EMAIL ) ] ), 'submissions_metadata' => String_Array_Prop_Type::make() ->set_dependencies( $submissions_metadata_dependencies ) ->default( [ String_Prop_Type::generate( self::METADATA_REMOTE_IP ), String_Prop_Type::generate( self::METADATA_USER_AGENT ), ] ), ]; $props = array_merge( $props, self::get_emails_prop_settings(), [ 'webhook_url' => String_Prop_Type::make() ->set_dependencies( $webhook_dependencies ) ->meta( Overridable_Prop_Type::ignore() ) ->default( '' ), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ] ); return $props; } protected function define_atomic_controls(): array { $state_control = Toggle_Control::bind_to( 'form-state' ) ->add_options( [ 'default' => [ 'title' => __( 'Normal', 'elementor' ), ], 'success' => [ 'title' => __( 'Success', 'elementor' ), ], 'error' => [ 'title' => __( 'Error', 'elementor' ), ], ] ) ->set_exclusive( true ) ->set_convert_options( true ) ->set_size( 'tiny' ) ->set_full_width( true ) ->set_label( __( 'States', 'elementor' ) ) ->set_meta( [ 'topDivider' => true ] ); $email_control_settings = $this->get_emails_control_settings(); $form_action_chips = $email_control_settings['form-action-chips']; $email_controls = $email_control_settings['email-controls']; if ( class_exists( '\ElementorPro\License\API' ) ) { $has_form_submissions_feature = \ElementorPro\License\API::is_licence_has_feature( 'form-submissions' ); if ( $has_form_submissions_feature ) { $form_action_chips = array_merge( $form_action_chips, [ [ 'label' => __( 'Collect submissions', 'elementor' ), 'value' => self::ACTION_COLLECT_SUBMISSIONS, ], ] ); } } $content_controls = [ Text_Control::bind_to( 'form-name' ) ->set_label( __( 'Form name', 'elementor' ) ), $state_control, Chips_Control::bind_to( 'actions-after-submit' ) ->set_options( array_merge( $form_action_chips, [ [ 'label' => __( 'Webhook', 'elementor' ), 'value' => self::ACTION_WEBHOOK, ], ] ) ) ->set_label( __( 'Actions after submit', 'elementor' ) ) ->set_meta( [ 'topDivider' => true ] ), ]; return [ Section::make() ->set_label( __( 'Content', 'elementor' ) ) ->set_id( 'content' ) ->set_items( $content_controls ), ...$email_controls, Section::make() ->set_label( __( 'Collect submissions', 'elementor' ) ) ->set_items( [ Chips_Control::bind_to( 'submissions_metadata' ) ->set_options( [ [ 'label' => __( 'User IP', 'elementor' ), 'value' => self::METADATA_REMOTE_IP, ], [ 'label' => __( 'User Agent', 'elementor' ), 'value' => self::METADATA_USER_AGENT, ], ] ) ->set_label( __( 'Include metadata', 'elementor' ) ), ] ), Section::make() ->set_label( __( 'Webhook', 'elementor' ) ) ->set_items( [ Text_Control::bind_to( 'webhook_url' ) ->set_placeholder( __( 'https://your-webhook-url.com', 'elementor' ) ) ->set_label( __( 'Webhook URL', 'elementor' ) ), ] ), Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( [ Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), Switch_Control::bind_to( 'webmcp-autosubmit' ) ->set_label( __( 'Agent auto-submit', 'elementor' ) ), ] ), ]; } protected function define_base_settings(): array { $settings = []; foreach ( self::build_email_action_defaults() as $key => $default_email ) { $settings[ $key ] = Emails_Prop_Type::generate( $default_email ); } return $settings; } protected function define_base_styles(): array { return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->set_breakpoint( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP ) ->add_prop( 'display', String_Prop_Type::generate( 'flex' ) ) ->add_prop( 'flex', String_Prop_Type::generate( '1' ) ) ->add_prop( 'flex-direction', String_Prop_Type::generate( 'row' ) ) ->add_prop( 'flex-wrap', String_Prop_Type::generate( 'wrap' ) ) ->add_prop( 'align-items', String_Prop_Type::generate( 'flex-start' ) ) ->add_prop( 'align-content', String_Prop_Type::generate( 'start' ) ) ->add_prop( 'gap', Size_Prop_Type::generate( [ 'size' => 10, 'unit' => 'px', ] ) ) ->add_prop( 'padding', Size_Prop_Type::generate( [ 'size' => 20, 'unit' => 'px', ] ) ) ), static::BASE_STYLE_KEY . ' .e-form-checkbox-row' => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'align-items', String_Prop_Type::generate( 'center' ) ) ->add_prop( 'gap', Size_Prop_Type::generate( [ 'size' => 8, 'unit' => 'px', ] ) ) ->add_prop( 'padding', Size_Prop_Type::generate( [ 'size' => 0, 'unit' => 'px', ] ) ) ), ]; } protected function define_panel_categories(): array { return [ 'atomic-form' ]; } protected function define_default_html_tag() { return 'form'; } protected function define_default_children() { $prefix = 'e-form-'; $children = [ $this->build_label( __( 'First name', 'elementor' ), $prefix . 'first-name' ), $this->build_input( __( 'First name', 'elementor' ), 'text', $prefix . 'first-name' ), $this->build_label( __( 'Last name', 'elementor' ), $prefix . 'last-name' ), $this->build_input( __( 'Last name', 'elementor' ), 'text', $prefix . 'last-name' ), $this->build_label( __( 'Email', 'elementor' ), $prefix . 'email' ), $this->build_input( __( 'your@mail.com', 'elementor' ), 'email', $prefix . 'email' ), $this->build_label( __( 'Message', 'elementor' ), $prefix . 'message' ), $this->build_input( __( 'Your message', 'elementor' ), 'textarea', $prefix . 'message' ), ]; $children[] = $this->build_checkbox_row( __( 'Checkbox', 'elementor' ), $prefix . 'checkbox' ); $children[] = Widget_Builder::make( 'e-form-submit-button' ) ->settings( [ 'text' => Html_V3_Prop_Type::generate( [ 'content' => String_Prop_Type::generate( __( 'Submit', 'elementor' ) ), 'children' => [], ] ), ] ) ->build(); $children[] = $this->build_status_message( __( 'Great! We’ve received your information.', 'elementor' ), 'success', __( 'Success message', 'elementor' ) ); $children[] = $this->build_status_message( __( 'We couldn’t process your submission. Please retry', 'elementor' ), 'error', __( 'Error message', 'elementor' ) ); return $children; } private function build_checkbox_row( string $label_text, string $checkbox_id ): array { $checkbox = Widget_Builder::make( 'e-form-checkbox' ) ->settings( [ '_cssid' => String_Prop_Type::generate( $checkbox_id ), ] ) ->build(); $label = $this->build_label( $label_text, $checkbox_id ); return Element_Builder::make( 'e-flexbox' ) ->children( [ $checkbox, $label ] ) ->settings( [ 'classes' => Classes_Prop_Type::generate( [ 'e-form-checkbox-row' ] ), ] ) ->build(); } private function build_label( string $text, string $input_id ): array { return Widget_Builder::make( 'e-form-label' ) ->settings( [ 'text' => Html_V3_Prop_Type::generate( [ 'content' => String_Prop_Type::generate( $text ), 'children' => [], ] ), 'input-id' => String_Prop_Type::generate( $input_id ), ] ) ->build(); } private function build_input( string $placeholder, string $type = 'text', $input_id = '' ): array { if ( 'textarea' === $type ) { return Widget_Builder::make( 'e-form-textarea' ) ->settings( [ 'placeholder' => String_Prop_Type::generate( $placeholder ), 'rows' => Number_Prop_Type::generate( 4 ), '_cssid' => String_Prop_Type::generate( $input_id ), ] ) ->build(); } return Widget_Builder::make( 'e-form-input' ) ->settings( [ 'placeholder' => String_Prop_Type::generate( $placeholder ), 'type' => String_Prop_Type::generate( $type ), '_cssid' => String_Prop_Type::generate( $input_id ), ] ) ->build(); } private function build_status_message( string $message, string $state, string $title ): array { $paragraph_value = Html_V3_Prop_Type::generate( [ 'content' => String_Prop_Type::generate( $message ), 'children' => [], ] ); $element_type = 'success' === $state ? Form_Success_Message::get_element_type() : Form_Error_Message::get_element_type(); return Element_Builder::make( $element_type ) ->meta( [ 'required' => true ] ) ->settings( [ 'attributes' => Attributes_Prop_Type::generate( [ Key_Value_Prop_Type::generate( [] ), ] ), ] ) ->editor_settings( [ 'title' => $title, ] ) ->children( [ Widget_Builder::make( Atomic_Paragraph::get_element_type() ) ->settings( [ 'paragraph' => $paragraph_value, ] ) ->build(), ] ) ->build(); } protected function get_templates(): array { return [ 'elementor/elements/atomic-form' => __DIR__ . '/atomic-form.html.twig', ]; } protected function build_template_context(): array { $context = $this->build_base_template_context(); $context['form_state'] = 'default'; if ( ! $this->is_webmcp_enabled() ) { return $context; } $form_name = (string) ( $this->get_atomic_settings()['form-name'] ?? '' ); $context['webmcp_tool_name'] = Webmcp_Utils::build_tool_name( $form_name, (string) $this->get_id() ); $context['webmcp_tool_description'] = Webmcp_Utils::build_tool_description( $form_name ); return $context; } private function is_webmcp_enabled(): bool { return ! Plugin::$instance->editor->is_edit_mode(); } public static function is_instance_form( $instance ): bool { return $instance instanceof Atomic_Form; } public function render_markdown(): string { return ''; } private static function get_emails_prop_settings(): array { $props = []; foreach ( self::build_email_action_defaults() as $key => $default_value ) { $props[ $key ] = Emails_Prop_Type::make() ->set_dependencies( self::make_action_dependency( $key ) ) ->meta( Overridable_Prop_Type::ignore() ) ->initial_value( $default_value ) ->default( $default_value ); } return $props; } private function get_emails_control_settings(): array { $form_action_chips = []; $email_controls = []; for ( $i = 0; $i < self::get_email_action_count(); $i++ ) { $key = self::get_email_action_key( $i ); $label = self::get_email_action_label( $i ); $form_action_chips[] = [ 'label' => $label, 'value' => $key, ]; $email_controls[] = Section::make() ->set_label( $label ) ->set_items( [ Email_Form_Action_Control::bind_to( $key ) ->set_free_chips( true ) ->set_label( $label ), ] ); } return [ 'form-action-chips' => $form_action_chips, 'email-controls' => $email_controls, ]; } private static function get_email_action_key( int $index ): string { return 0 === $index ? self::ACTION_EMAIL : self::ACTION_EMAIL . '_' . ( $index + 1 ); } private static function get_email_action_label( int $index ): string { if ( 0 === $index ) { return __( 'Email', 'elementor' ); } // translators: %d is the index of the email action. return sprintf( __( 'Email %d', 'elementor' ), $index + 1 ); } private static function build_email_action_defaults(): array { $defaults = []; $default_email = self::get_default_email_value(); for ( $i = 0; $i < self::get_email_action_count(); $i++ ) { $key = self::get_email_action_key( $i ); $defaults[ $key ] = $default_email; } return $defaults; } private static function get_default_email_value(): array { return [ 'to' => String_Array_Prop_Type::generate( [ String_Prop_Type::generate( self::get_default_recipient_email() ), ] ), 'from' => String_Prop_Type::generate( self::get_default_sender_email() ), 'message' => String_Prop_Type::generate( '[all-fields]' ), ]; } private static function make_action_dependency( string $action_key ): ?array { return Dependency_Manager::make() ->where( [ 'operator' => 'contains', 'path' => [ 'actions-after-submit' ], 'value' => $action_key, 'effect' => 'hide', ] ) ->get(); } private static function get_email_action_count(): int { return apply_filters( 'elementor/atomic/form/email_action_count', 1 ); } } atomic-widgets/elements/atomic-form/form-message/form-message.html.twig 0000644 00000001251 15252521350 0022325 0 ustar 00 {%- set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %} {%- set message_type = type == 'e-form-success-message' ? 'message-success' : 'message-error' %} {%- set aria_attr = type == 'e-form-success-message' ? 'aria-live="polite"' : 'role="alert"' %} <div class="{{ classes }} {{ message_type }} {{ editor_classes | default('') }}" tabindex="-1" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" {{ aria_attr | raw }} {{ editor_attributes | default('') | raw }}> <!-- elementor-children-placeholder --> </div> atomic-widgets/elements/atomic-form/form-message/form-message.php 0000644 00000007523 15252521350 0021207 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Message; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph\Atomic_Paragraph; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } abstract class Form_Message extends Atomic_Element_Base { use Has_Element_Template; const BASE_STYLE_KEY = 'base'; public static $widget_description = 'A container for form status messages (success or error). Hidden by default, shown based on form submission state.'; abstract protected static function get_background_color(): string; abstract protected static function get_text_color(): string; abstract protected static function get_default_status_paragraph_text(): string; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'is_container', true ); $this->meta( 'permanently_locked', true ); } public function get_icon() { return 'eicon-div-block'; } public function should_show_in_panel() { return false; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_allowed_child_types() { return [ Atomic_Paragraph::get_element_type() ]; } protected function define_default_children() { return [ Atomic_Paragraph::generate() ->settings( [ 'paragraph' => Html_V3_Prop_Type::generate( [ 'content' => String_Prop_Type::generate( static::get_default_status_paragraph_text() ), 'children' => [], ] ), ] ) ->build(), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( [ Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ] ), ]; } protected function define_base_styles(): array { return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_props( [ 'display' => String_Prop_Type::generate( 'none' ), 'background' => Background_Prop_Type::generate( [ 'color' => Color_Prop_Type::generate( static::get_background_color() ), ] ), 'color' => Color_Prop_Type::generate( static::get_text_color() ), 'padding' => Size_Prop_Type::generate( [ 'size' => 12, 'unit' => 'px', ] ), 'text-align' => String_Prop_Type::generate( 'center' ), 'font-size' => Size_Prop_Type::generate( [ 'size' => 12, 'unit' => 'px', ] ), ] ) ), ]; } protected function get_templates(): array { return [ 'elementor/elements/form-message' => __DIR__ . '/form-message.html.twig', ]; } protected function build_template_context(): array { return $this->build_base_template_context(); } } atomic-widgets/elements/grid/grid.html.twig 0000644 00000001170 15252521350 0015010 0 ustar 00 {% import 'elementor/macros' as m %} {%- set tag = settings.tag | default('div') -%} {%- if settings.link is defined and settings.link.href is defined and settings.link.href is not empty -%} {%- set tag = settings.link.tag | default('a') -%} {%- endif -%} <{{ tag }} class="{{ m.render_base_classes(id, base_styles, settings) }} {{ editor_classes | default('') }}" {{- ' ' }}{{ m.render_data_attributes(id, type, interaction_id) }} {{- m.render_link_attributes(settings.link | default(null)) }} {{- m.render_custom_attributes(settings, editor_attributes) }}> <!-- elementor-children-placeholder --> </{{ tag }}> atomic-widgets/elements/grid/grid.php 0000644 00000014654 15252521350 0013675 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Grid; use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Grid_Track_Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Layout_Direction_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Html_Tag_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Grid extends Atomic_Element_Base { use Has_Element_Template; const BASE_STYLE_KEY = 'base'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'is_container', true ); } public static function get_type() { return 'e-grid'; } public static function get_element_type(): string { return 'e-grid'; } public function get_title() { return esc_html__( 'Grid', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic', 'grid', 'layout' ]; } public function get_icon() { return 'eicon-library-grid'; } protected static function define_props_schema(): array { $tag_dependencies = Dependency_Manager::make( Dependency_Manager::RELATION_AND ) ->where( [ 'operator' => 'ne', 'path' => [ 'link', 'destination' ], 'nestedPath' => [ 'group' ], 'value' => 'action', 'newValue' => [ '$$type' => 'string', 'value' => 'button', ], ] )->where( [ 'operator' => 'not_exist', 'path' => [ 'link', 'destination' ], 'newValue' => [ '$$type' => 'string', 'value' => 'a', ], ] )->get(); return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'tag' => String_Prop_Type::make() ->enum( [ 'div', 'header', 'section', 'article', 'aside', 'footer', 'a', 'button' ] ) ->default( 'div' ) ->description( 'The HTML tag for the grid container. Could be div, header, section, article, aside, footer, or a (link).' ) ->set_dependencies( $tag_dependencies ), 'link' => Link_Prop_Type::make(), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( [ Html_Tag_Control::bind_to( 'tag' ) ->set_options( [ [ 'value' => 'div', 'label' => 'Div', ], [ 'value' => 'header', 'label' => 'Header', ], [ 'value' => 'section', 'label' => 'Section', ], [ 'value' => 'article', 'label' => 'Article', ], [ 'value' => 'aside', 'label' => 'Aside', ], [ 'value' => 'footer', 'label' => 'Footer', ], ]) ->set_label( esc_html__( 'HTML Tag', 'elementor' ) ) ->set_fallback_labels( [ 'a' => 'a (link)', ] ), Link_Control::bind_to( 'link' ) ->set_placeholder( __( 'Type or paste your URL', 'elementor' ) ) ->set_label( __( 'Link', 'elementor' ) ) ->set_meta( [ 'topDivider' => true, ] ), Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ] ), ]; } protected function define_base_styles(): array { return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->set_breakpoint( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP ) ->add_prop( 'display', String_Prop_Type::generate( 'grid' ) ) ->add_prop( 'padding', $this->get_base_padding() ) ->add_prop( 'grid-template-columns', Grid_Track_Size_Prop_Type::generate( [ 'size' => 3, 'unit' => 'fr', ] ) ) ->add_prop( 'grid-template-rows', Grid_Track_Size_Prop_Type::generate( [ 'size' => 2, 'unit' => 'fr', ] ) ) ->add_prop( 'gap', Layout_Direction_Prop_Type::generate( [ 'column' => Size_Prop_Type::generate( [ 'size' => 20, 'unit' => 'px', ] ), 'row' => Size_Prop_Type::generate( [ 'size' => 20, 'unit' => 'px', ] ), ] ) ) ) ->add_variant( Style_Variant::make() ->set_breakpoint( Breakpoints_Manager::BREAKPOINT_KEY_MOBILE ) ->add_prop( 'grid-template-columns', Grid_Track_Size_Prop_Type::generate( [ 'size' => 1, 'unit' => 'fr', ] ) ) ), ]; } protected function get_base_padding(): array { return Size_Prop_Type::generate( [ 'size' => 10, 'unit' => 'px', ] ); } protected function add_render_attributes() { parent::add_render_attributes(); $settings = $this->get_atomic_settings(); $base_style_class = $this->get_base_styles_dictionary()[ static::BASE_STYLE_KEY ]; $initial_attributes = $this->define_initial_attributes(); $attributes = [ 'class' => [ 'e-con', 'e-atomic-element', $base_style_class, ...( $settings['classes'] ?? [] ), ], ]; if ( ! empty( $settings['_cssid'] ) ) { $attributes['id'] = esc_attr( $settings['_cssid'] ); } if ( ! empty( $settings['link']['href'] ) ) { $link_attributes = $this->get_link_attributes( $settings['link'] ); $attributes = array_merge( $attributes, $link_attributes ); } $this->add_render_attribute( '_wrapper', array_merge( $initial_attributes, $attributes ) ); } protected function get_templates(): array { return [ 'elementor/elements/grid' => __DIR__ . '/grid.html.twig', ]; } } atomic-widgets/elements/promotions/preserved-element.php 0000644 00000001410 15252521350 0017644 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Promotions; use Elementor\Element_Base; if ( ! defined( 'ABSPATH' ) ) { exit; } class Preserved_Element extends Element_Base { public function get_name() { $widget_type = $this->get_data( 'widgetType' ); if ( ! empty( $widget_type ) ) { return $widget_type; } $el_type = $this->get_data( 'elType' ); return empty( $el_type ) ? 'e-preserved-element' : $el_type; } protected function _get_default_child_type( array $element_data ) { return new self(); } public function get_controls( $control_id = null ) { return []; } public function get_data_for_save() { return $this->get_data(); } public function get_raw_data( $with_html_content = false ) { return $this->get_data(); } } atomic-widgets/elements/promotions/preserves-children-subtree.php 0000644 00000000630 15252521350 0021474 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Promotions; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } trait Preserves_Children_Subtree { protected function _get_default_child_type( array $element_data ) { $document = Plugin::$instance->documents->get_current(); if ( $document && $document->is_saving() ) { return new Preserved_Element(); } return null; } } atomic-widgets/elements/promotions/pro-promotion-data-preservation.php 0000644 00000005431 15252521350 0022477 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Promotions; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Pro_Promotion_Data_Preservation { const EMAIL_ACTION_COUNT = 2; public function register_hooks(): void { if ( Utils::has_pro() ) { return; } add_filter( 'elementor/document/save/data', fn ( $data, $document ) => $this->preserve( $data, $document ), 10, 2 ); add_filter( 'elementor/atomic/form/email_action_count', fn ( $count ) => max( $count, self::EMAIL_ACTION_COUNT ) ); } public function preserve( $data, $document ) { if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) { return $data; } $promotion_types = $this->get_promotion_types(); if ( empty( $promotion_types ) ) { return $data; } $stored = []; $this->map_promotion_elements( $document->get_elements_data(), $promotion_types, $stored ); if ( empty( $stored ) ) { return $data; } $data['elements'] = $this->restore_promotion_elements( $data['elements'], $promotion_types, $stored ); return $data; } private function get_promotion_types(): array { $types = []; foreach ( Plugin::$instance->elements_manager->get_element_types() as $type => $element ) { if ( method_exists( $element, 'get_meta_item' ) && $element->get_meta_item( 'is_pro_promotion' ) ) { $types[] = $type; } } return $types; } private function map_promotion_elements( $elements, array $promotion_types, array &$map ): void { if ( ! is_array( $elements ) ) { return; } foreach ( $elements as $element ) { if ( ! is_array( $element ) ) { continue; } $id = $element['id'] ?? ''; $is_promotion = in_array( $element['elType'] ?? '', $promotion_types, true ); if ( $id && $is_promotion && ! empty( $element['elements'] ) ) { $map[ $id ] = [ 'settings' => $element['settings'] ?? [], 'elements' => $element['elements'], ]; continue; } $this->map_promotion_elements( $element['elements'] ?? [], $promotion_types, $map ); } } private function restore_promotion_elements( array $elements, array $promotion_types, array $map ): array { foreach ( $elements as &$element ) { if ( ! is_array( $element ) ) { continue; } $id = $element['id'] ?? ''; $is_promotion = in_array( $element['elType'] ?? '', $promotion_types, true ); if ( $is_promotion && empty( $element['elements'] ) && isset( $map[ $id ] ) ) { $element['settings'] = $map[ $id ]['settings']; $element['elements'] = $map[ $id ]['elements']; continue; } if ( ! empty( $element['elements'] ) && is_array( $element['elements'] ) ) { $element['elements'] = $this->restore_promotion_elements( $element['elements'], $promotion_types, $map ); } } unset( $element ); return $elements; } } atomic-widgets/elements/atomic-youtube/youtube-handler.js 0000644 00000006454 15252521350 0017724 0 ustar 00 import { register } from '@elementor/frontend-handlers'; const getYoutubeVideoIdFromUrl = ( url ) => { const regex = /^(?:https?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?vi?=|(?:embed|v|vi|user|shorts)\/))([^?&"'>]+)/; const match = url.match( regex ); return match ? match[ 1 ] : null; }; const loadYouTubeAPI = () => { return new Promise( ( resolve ) => { if ( window.YT && window.YT.loaded ) { resolve( window.YT ); return; } const YOUTUBE_IFRAME_API_URL = 'https://www.youtube.com/iframe_api'; if ( ! document.querySelector( `script[src="${ YOUTUBE_IFRAME_API_URL }"]` ) ) { const tag = document.createElement( 'script' ); tag.src = YOUTUBE_IFRAME_API_URL; const firstScriptTag = document.getElementsByTagName( 'script' )[ 0 ]; firstScriptTag.parentNode.insertBefore( tag, firstScriptTag ); } const checkYT = () => { if ( window.YT && window.YT.loaded ) { resolve( window.YT ); } else { setTimeout( checkYT, 350 ); } }; checkYT(); } ); }; register( { elementType: 'e-youtube', id: 'e-youtube-handler', callback: ( { element } ) => { const youtubeElement = document.createElement( 'div' ); youtubeElement.style.height = '100%'; element.appendChild( youtubeElement ); const settingsAttr = element.getAttribute( 'data-settings' ); const parsedSettings = settingsAttr ? JSON.parse( settingsAttr ) : {}; const videoId = getYoutubeVideoIdFromUrl( parsedSettings.source ); if ( ! videoId ) { return; } let player; let observer; const prepareYTVideo = ( YT ) => { const playerOptions = { videoId, events: { onReady: () => { if ( parsedSettings.mute ) { player.mute(); } if ( parsedSettings.autoplay ) { player.playVideo(); } }, onStateChange: ( event ) => { if ( event.data === YT.PlayerState.ENDED && parsedSettings.loop ) { player.seekTo( parsedSettings.start || 0 ); } }, }, playerVars: { controls: parsedSettings.controls ? 1 : 0, rel: parsedSettings.rel ? 0 : 1, cc_load_policy: parsedSettings.cc_load_policy ? 1 : 0, autoplay: parsedSettings.autoplay ? 1 : 0, start: parsedSettings.start, end: parsedSettings.end, }, }; // To handle CORS issues, when the default host is changed, the origin parameter has to be set. if ( parsedSettings.privacy ) { playerOptions.host = 'https://www.youtube-nocookie.com'; playerOptions.origin = window.location.hostname; } player = new YT.Player( youtubeElement, playerOptions ); return player; }; if ( parsedSettings.lazyload ) { observer = new IntersectionObserver( ( entries ) => { if ( entries[ 0 ].isIntersecting ) { loadYouTubeAPI().then( ( apiObject ) => prepareYTVideo( apiObject ) ); observer.unobserve( element ); } }, ); observer.observe( element ); } else { loadYouTubeAPI().then( ( apiObject ) => prepareYTVideo( apiObject ) ); } return () => { if ( player && 'function' === typeof player.destroy ) { player.destroy(); player = null; } if ( element.contains( youtubeElement ) ) { element.removeChild( youtubeElement ); } if ( observer && 'function' === typeof observer.disconnect ) { observer.disconnect(); observer = null; } }; }, } ); atomic-widgets/elements/atomic-youtube/atomic-youtube.php 0000644 00000012574 15252521350 0017736 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Youtube; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Type; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Youtube extends Atomic_Widget_Base { use Has_Template; protected function get_css_id_control_meta(): array { return [ 'layout' => 'two-columns', 'topDivider' => false, ]; } public static function get_element_type(): string { return 'e-youtube'; } public function get_title() { return esc_html__( 'YouTube', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic' ]; } public function get_icon() { return 'eicon-e-youtube'; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'source' => String_Prop_Type::make() ->default( 'https://www.youtube.com/watch?v=XHOmBV4js_E' ) ->alias( 'url', 'video' ), 'start' => String_Prop_Type::make()->meta( Dynamic_Prop_Type::ignore() ), 'end' => String_Prop_Type::make()->meta( Dynamic_Prop_Type::ignore() ), 'autoplay' => Boolean_Prop_Type::make()->default( false ), 'mute' => Boolean_Prop_Type::make()->default( false ), 'loop' => Boolean_Prop_Type::make()->default( false ), 'lazyload' => Boolean_Prop_Type::make()->default( false ), 'player_controls' => Boolean_Prop_Type::make()->default( true ), 'captions' => Boolean_Prop_Type::make()->default( false ), 'privacy_mode' => Boolean_Prop_Type::make()->default( false ), 'rel' => Boolean_Prop_Type::make()->default( true ), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Content', 'elementor' ) ) ->set_id( 'content' ) ->set_items( [ Text_Control::bind_to( 'source' ) ->set_placeholder( esc_html__( 'Type or paste your URL', 'elementor' ) ) ->set_label( esc_html__( 'YouTube URL', 'elementor' ) ), Text_Control::bind_to( 'start' )->set_label( esc_html__( 'Start time', 'elementor' ) ), Text_Control::bind_to( 'end' )->set_label( esc_html__( 'End time', 'elementor' ) ), Switch_Control::bind_to( 'autoplay' )->set_label( esc_html__( 'Autoplay', 'elementor' ) ), Switch_Control::bind_to( 'mute' )->set_label( esc_html__( 'Mute', 'elementor' ) ), Switch_Control::bind_to( 'loop' )->set_label( esc_html__( 'Loop', 'elementor' ) ), Switch_Control::bind_to( 'lazyload' )->set_label( esc_html__( 'Lazy load', 'elementor' ) ), Switch_Control::bind_to( 'player_controls' )->set_label( esc_html__( 'Player controls', 'elementor' ) ), Switch_Control::bind_to( 'captions' )->set_label( esc_html__( 'Captions', 'elementor' ) ), Switch_Control::bind_to( 'privacy_mode' )->set_label( esc_html__( 'Privacy mode', 'elementor' ) ), Switch_Control::bind_to( 'rel' )->set_label( esc_html__( 'Related videos', 'elementor' ) ), ] ), Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( $this->get_settings_controls() ), ]; } protected function get_settings_controls(): array { return [ Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ]; } protected function define_base_styles(): array { return [ 'base' => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'aspect-ratio', String_Prop_Type::generate( '16/9' ) ) ->add_prop( 'overflow', String_Prop_Type::generate( 'hidden' ) ) ), ]; } public function get_script_depends() { return array_merge( parent::get_script_depends(), [ 'elementor-youtube-handler' ], ); } public function register_frontend_handlers() { $assets_url = ELEMENTOR_ASSETS_URL; $min_suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min'; wp_register_script( 'elementor-youtube-handler', "{$assets_url}js/youtube-handler{$min_suffix}.js", [ Frontend_Assets_Loader::FRONTEND_HANDLERS_HANDLE ], ELEMENTOR_VERSION, true ); } protected function get_templates(): array { return [ 'elementor/elements/atomic-youtube' => __DIR__ . '/atomic-youtube.html.twig', ]; } public function render_markdown(): string { $settings = $this->get_atomic_settings(); $url = $settings['source'] ?? ''; if ( empty( $url ) ) { return ''; } return '[Video](' . esc_url( $url ) . ')'; } } atomic-widgets/elements/atomic-youtube/atomic-youtube.html.twig 0000644 00000001402 15252521350 0021050 0 ustar 00 {% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %} {% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %} {% set data_settings = { 'source': settings.source, 'autoplay': settings.autoplay, 'mute': settings.mute, 'controls': settings.player_controls, 'cc_load_policy': settings.captions, 'loop': settings.loop, 'rel': settings.rel, 'start': settings.start, 'end': settings.end, 'privacy': settings.privacy_mode, 'lazyload': settings.lazyload, } %} <div data-id="{{ id }}" data-interaction-id="{{ interaction_id }}" data-e-type="{{ type }}" {{ id_attribute }} class="{{ classes }}" {{ settings.attributes | raw }} data-settings="{{ data_settings|json_encode|e('html_attr') }}"></div> atomic-widgets/elements/atomic-svg/atomic-svg.php 0000644 00000006745 15252521350 0016147 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Svg; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Svg_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Svg_Src_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Svg extends Atomic_Widget_Base { use Has_Template; const BASE_STYLE_KEY = 'base'; const DEFAULT_SVG = 'images/default-svg.svg'; const DEFAULT_SVG_PATH = ELEMENTOR_ASSETS_PATH . self::DEFAULT_SVG; const DEFAULT_SVG_URL = ELEMENTOR_ASSETS_URL . self::DEFAULT_SVG; public static $widget_description = 'Display an SVG image with customizable styles and link options.'; public static function get_element_type(): string { return 'e-svg'; } public function get_title() { return esc_html__( 'SVG', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic' ]; } public function get_icon() { return 'eicon-svg'; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make()->default( [] ), 'svg' => Svg_Src_Prop_Type::make()->default_url( static::DEFAULT_SVG_URL ), 'link' => Link_Prop_Type::make(), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( esc_html__( 'Content', 'elementor' ) ) ->set_id( 'content' ) ->set_items( [ Svg_Control::bind_to( 'svg' ) ->set_label( __( 'SVG', 'elementor' ) ), ] ), Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( $this->get_settings_controls() ), ]; } protected function get_settings_controls(): array { return [ Link_Control::bind_to( 'link' ) ->set_placeholder( __( 'Type or paste your URL', 'elementor' ) ) ->set_label( __( 'Link', 'elementor' ) ), Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ]; } protected function define_base_styles(): array { $display_value = String_Prop_Type::generate( 'inline-block' ); $size = Size_Prop_Type::generate( [ 'size' => 65, 'unit' => 'px', ] ); return [ self::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'display', $display_value ) ->add_prop( 'width', $size ) ->add_prop( 'height', $size ) ), ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-svg' => __DIR__ . '/atomic-svg.html.twig', ]; } public function render_markdown(): string { return ''; } } atomic-widgets/elements/atomic-svg/atomic-svg.html.twig 0000644 00000001276 15252521350 0017267 0 ustar 00 {% import 'elementor/macros' as m %} {%- set classes = settings.classes | merge([base_styles.base]) | join(' ') -%} {%- if settings.link is defined and settings.link.href is defined and settings.link.href is not empty -%} <{{ settings.link.tag | default('a') | e('html_tag') }}{{ m.render_link_attributes(settings.link) }} class="{{ classes }}" data-interaction-id="{{ interaction_id }}" {{- m.render_custom_attributes(settings) }}>{{ settings.svg.html | raw }}</{{ settings.link.tag | default('a') | e('html_tag') }}> {%- else -%} <div class="{{ classes }}" data-interaction-id="{{ interaction_id }}" {{- m.render_custom_attributes(settings) }}>{{ settings.svg.html | raw }}</div> {%- endif -%} atomic-widgets/elements/atomic-button/atomic-button.php 0000644 00000011703 15252521350 0017365 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Button; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Inline_Editing_Control; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Atomic_Button extends Atomic_Widget_Base { use Has_Template; public static function get_element_type(): string { return 'e-button'; } public function get_title() { return esc_html__( 'Button', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic' ]; } public function get_icon() { return 'eicon-e-button'; } protected static function define_props_schema(): array { $props = [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'text' => Html_V3_Prop_Type::make() ->default( [ 'content' => String_Prop_Type::generate( __( 'Click here', 'elementor' ) ), 'children' => [], ] ) ->description( 'The text displayed on the button.' ) ->alias( 'content', 'label' ), 'link' => Link_Prop_Type::make(), 'tag' => String_Prop_Type::make() ->default( 'button' ) ->description( 'The HTML tag for the button element.' ), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; return $props; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Content', 'elementor' ) ) ->set_id( 'content' ) ->set_items( [ Inline_Editing_Control::bind_to( 'text' ) ->set_placeholder( __( 'Type your button text here', 'elementor' ) ) ->set_label( __( 'Button text', 'elementor' ) ), ] ), Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( $this->get_settings_controls() ), ]; } protected function get_settings_controls(): array { return [ Link_Control::bind_to( 'link' ) ->set_placeholder( __( 'Type or paste your URL', 'elementor' ) ) ->set_label( __( 'Link', 'elementor' ) ), Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ]; } protected function define_base_styles(): array { $background_color_value = Background_Prop_Type::generate( [ 'color' => Color_Prop_Type::generate( '#375EFB' ), ] ); $display_value = String_Prop_Type::generate( 'inline-block' ); $padding_value = Dimensions_Prop_Type::generate( [ 'block-start' => Size_Prop_Type::generate( [ 'size' => 12, 'unit' => 'px', ]), 'inline-end' => Size_Prop_Type::generate( [ 'size' => 24, 'unit' => 'px', ]), 'block-end' => Size_Prop_Type::generate( [ 'size' => 12, 'unit' => 'px', ]), 'inline-start' => Size_Prop_Type::generate( [ 'size' => 24, 'unit' => 'px', ]), ]); $border_radius_value = Size_Prop_Type::generate( [ 'size' => 2, 'unit' => 'px', ] ); $border_width_value = Size_Prop_Type::generate( [ 'size' => 0, 'unit' => 'px', ] ); $align_text_value = String_Prop_Type::generate( 'center' ); return [ 'base' => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'background', $background_color_value ) ->add_prop( 'display', $display_value ) ->add_prop( 'padding', $padding_value ) ->add_prop( 'border-radius', $border_radius_value ) ->add_prop( 'border-width', $border_width_value ) ->add_prop( 'text-align', $align_text_value ) ), ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-button' => __DIR__ . '/atomic-button.html.twig', ]; } public function render_markdown(): string { $settings = $this->get_atomic_settings(); $text = wp_strip_all_tags( $settings['text'] ?? '' ); if ( empty( $text ) ) { return ''; } if ( ! empty( $settings['link']['href'] ) ) { return '[' . $text . '](' . esc_url( $settings['link']['href'] ) . ')'; } return '**' . $text . '**'; } } atomic-widgets/elements/atomic-button/atomic-button.html.twig 0000644 00000001520 15252521350 0020507 0 ustar 00 {% import 'elementor/macros' as m %} {%- set allowed_tags = '<b><strong><sup><sub><s><em><i><u><del><span><br>' -%} {%- set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') -%} {%- if settings.link is defined and settings.link.href is defined and settings.link.href is not empty -%} <{{ settings.link.tag | default('a') | e('html_tag') }} {{- m.render_link_attributes(settings.link) }} class="{{ classes }}" data-interaction-id="{{ interaction_id }}" {{- m.render_custom_attributes(settings) }}> {{ settings.text | striptags(allowed_tags) | raw }} </{{ settings.link.tag | default('a') | e('html_tag') }}> {%- else -%} <button class="{{ classes }}" data-interaction-id="{{ interaction_id }}" {{- m.render_custom_attributes(settings) }}> {{ settings.text | striptags(allowed_tags) | raw }} </button> {%- endif %} atomic-widgets/elements/atomic-divider/atomic-divider.php 0000644 00000005700 15252521350 0017613 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Divider; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Atomic_Divider extends Atomic_Widget_Base { use Has_Template; protected function get_css_id_control_meta(): array { return [ 'layout' => 'two-columns', 'topDivider' => false, ]; } public static function get_element_type(): string { return 'e-divider'; } public function get_title() { return esc_html__( 'Divider', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic', 'divider', 'hr', 'line', 'border', 'separator' ]; } public function get_icon() { return 'eicon-e-divider'; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( $this->get_settings_controls() ), ]; } protected function get_settings_controls(): array { return [ Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ]; } protected function define_base_styles(): array { $border_width_value = Size_Prop_Type::generate([ 'size' => 0, 'unit' => 'px', ]); $height_value = Size_Prop_Type::generate([ 'size' => 1, 'unit' => 'px', ]); $background_value = Background_Prop_Type::generate([ 'color' => Color_Prop_Type::generate( '#000' ), ]); return [ 'base' => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'border-width', $border_width_value ) ->add_prop( 'border-color', 'transparent' ) ->add_prop( 'border-style', 'none' ) ->add_prop( 'background', $background_value ) ->add_prop( 'height', $height_value ) ), ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-divider' => __DIR__ . '/atomic-divider.html.twig', ]; } public function render_markdown(): string { return '---'; } } atomic-widgets/elements/atomic-divider/atomic-divider.html.twig 0000644 00000000463 15252521350 0020742 0 ustar 00 {% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %} {% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %} <hr class="{{ classes }}" data-interaction-id="{{ interaction_id }}" {{ id_attribute }} {{ settings.attributes | raw }} /> atomic-widgets/elements/atomic-paragraph/atomic-paragraph.php 0000644 00000010455 15252521350 0020454 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Inline_Editing_Control; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Atomic_Paragraph extends Atomic_Widget_Base { use Has_Template; const LINK_BASE_STYLE_KEY = 'link-base'; public static $widget_description = 'Display a paragraph with customizable tag, styles, and link options.'; public static function get_element_type(): string { return 'e-paragraph'; } public function get_title() { return esc_html__( 'Paragraph', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic' ]; } public function get_icon() { return 'eicon-paragraph'; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'paragraph' => Html_V3_Prop_Type::make() ->default( [ 'content' => String_Prop_Type::generate( __( 'Type your paragraph here', 'elementor' ) ), 'children' => [], ] ) ->description( 'The text content of the paragraph.' ) ->alias( 'text', 'content' ), 'tag' => String_Prop_Type::make() ->enum( [ 'p', 'span' ] ) ->default( 'p' ), 'link' => Link_Prop_Type::make(), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Content', 'elementor' ) ) ->set_id( 'content' ) ->set_items( [ Inline_Editing_Control::bind_to( 'paragraph' ) ->set_placeholder( __( 'Type your paragraph here', 'elementor' ) ) ->set_label( __( 'Paragraph', 'elementor' ) ), ] ), Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( $this->get_settings_controls() ), ]; } protected function get_settings_controls(): array { return [ Select_Control::bind_to( 'tag' ) ->set_options([ [ 'value' => 'p', 'label' => 'p', ], [ 'value' => 'span', 'label' => 'span', ], ]) ->set_label( __( 'Tag', 'elementor' ) ), Link_Control::bind_to( 'link' ) ->set_placeholder( __( 'Type or paste your URL', 'elementor' ) ) ->set_label( __( 'Link', 'elementor' ) ) ->set_meta( [ 'topDivider' => true, ] ), Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ]; } protected function define_base_styles(): array { $margin_value = Size_Prop_Type::generate( [ 'unit' => 'px', 'size' => 0 , ] ); return [ 'base' => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'margin', $margin_value ) ), self::LINK_BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'all', 'unset' ) ->add_prop( 'cursor', 'pointer' ) ), ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-paragraph' => __DIR__ . '/atomic-paragraph.html.twig', ]; } public function render_markdown(): string { $settings = $this->get_atomic_settings(); $content = $settings['paragraph'] ?? ''; if ( empty( $content ) ) { return ''; } return \Elementor\Modules\MarkdownRender\Html_To_Markdown::convert( $content ); } } atomic-widgets/elements/atomic-paragraph/atomic-paragraph.html.twig 0000644 00000001565 15252521350 0021604 0 ustar 00 {% import 'elementor/macros' as m %} {%- set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') -%} <{{ settings.tag | e('html_tag') }} class="{{ classes }}" data-interaction-id="{{ interaction_id }}" {{- m.render_custom_attributes(settings) }}> {%- if settings.link is defined and settings.link.href is defined and settings.link.href is not empty -%} <{{ settings.link.tag | default('a') | e('html_tag') }} {{- m.render_link_attributes(settings.link) }} class="{{ base_styles['link-base'] }}"> {{ settings.paragraph | striptags('<b><strong><sup><sub><s><em><u><ul><ol><li><blockquote><a><del><span><br>') | raw }} </{{ settings.link.tag | default('a') | e('html_tag') }}> {%- else -%} {{ settings.paragraph | striptags('<b><strong><sup><sub><s><em><u><ul><ol><li><blockquote><a><del><span><br>') | raw }} {%- endif %} </{{ settings.tag | e('html_tag') }}> atomic-widgets/elements/atomic-image/atomic-image.html.twig 0000644 00000001531 15252521350 0020027 0 ustar 00 {% import 'elementor/macros' as m %} {%- set has_link = settings.link is defined and settings.link.href is defined and settings.link.href is not empty -%} {%- if has_link -%} <{{ settings.link.tag | default('a') | e('html_tag') }} {{- m.render_link_attributes(settings.link) }} class="{{ base_styles['link-base'] }}" data-interaction-id="{{ interaction_id }}" > {%- endif %} <img class="{{ base_styles['base'] }} {{ settings.classes | join(' ') }}" {%- if not has_link %} data-interaction-id="{{ interaction_id }}"{% endif -%} {{- m.render_custom_attributes(settings) }} {%- for attr, value in settings.image -%} {%- if attr == 'src' %} src="{{ value | e('full_url') }}" {%- else %} {{ attr | e('html_attr') }}="{{ value }}"{% endif -%} {%- endfor %} /> {%- if has_link %} </{{ settings.link.tag | default('a') | e('html_tag') }}> {%- endif %} atomic-widgets/elements/atomic-image/atomic-image.php 0000644 00000007166 15252521350 0016713 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Image; use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\Controls\Types\Image_Control; use Elementor\Modules\AtomicWidgets\Utils\Image\Placeholder_Image; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Image extends Atomic_Widget_Base { use Has_Template; public static $widget_description = 'Display an image with customizable styles and link options.'; const LINK_BASE_STYLE_KEY = 'link-base'; const BASE_STYLE_KEY = 'base'; public static function get_element_type(): string { return 'e-image'; } public function get_title() { return esc_html__( 'Image', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic' ]; } public function get_icon() { return 'eicon-e-image'; } protected static function define_props_schema(): array { $props = [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'image' => Image_Prop_Type::make() ->default_url( Placeholder_Image::get_placeholder_image() ) ->default_size( 'full' ), 'link' => Link_Prop_Type::make(), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; return $props; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( esc_html__( 'Content', 'elementor' ) ) ->set_id( 'content' ) ->set_items( [ Image_Control::bind_to( 'image' ) ->set_label( __( 'Image', 'elementor' ) ), ] ), Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( $this->get_settings_controls() ), ]; } protected function get_settings_controls(): array { return [ Link_Control::bind_to( 'link' ) ->set_placeholder( __( 'Type or paste your URL', 'elementor' ) ) ->set_label( __( 'Link', 'elementor' ) ), Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ]; } protected function define_base_styles(): array { return [ self::LINK_BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'all', 'unset' ) ->add_prop( 'display', 'inherit' ) ->add_prop( 'width', 'fit-content' ) ->add_prop( 'cursor', 'pointer' ) ), self::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'display', 'block' ) ), ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-image' => __DIR__ . '/atomic-image.html.twig', ]; } public function render_markdown(): string { $settings = $this->get_atomic_settings(); $src = $settings['image']['src'] ?? ''; if ( empty( $src ) ) { return ''; } $alt = $settings['image']['alt'] ?? ''; return ' . ')'; } } atomic-widgets/elements/loader/frontend-assets-loader.php 0000644 00000003053 15252521350 0017643 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Loader; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Frontend_Assets_Loader { const ALPINEJS_HANDLE = 'elementor-v2-alpinejs'; const FRONTEND_HANDLERS_HANDLE = 'elementor-v2-frontend-handlers'; const ACTION_LINK_HANDLERS_HANDLE = 'elementor-v2-action-link-handlers'; const FORM_HANDLERS_HANDLE = 'elementor-v2-form-handlers'; /** * @return void */ public function register_scripts() { $this->register_package_scripts(); do_action( 'elementor/atomic-widgets/frontend/loader/scripts/register', $this ); } private function register_package_scripts() { $assets_url = ELEMENTOR_ASSETS_URL; $min_suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min'; wp_register_script( self::ALPINEJS_HANDLE, "{$assets_url}js/packages/alpinejs/alpinejs{$min_suffix}.js", [], ELEMENTOR_VERSION, true ); wp_register_script( self::ACTION_LINK_HANDLERS_HANDLE, "{$assets_url}js/atomic-widgets-action-link-handler{$min_suffix}.js", [ self::FRONTEND_HANDLERS_HANDLE ], ELEMENTOR_VERSION, true ); wp_register_script( self::FORM_HANDLERS_HANDLE, "{$assets_url}js/atomic-widgets-form-handler{$min_suffix}.js", [ self::FRONTEND_HANDLERS_HANDLE, self::ALPINEJS_HANDLE ], ELEMENTOR_VERSION, true ); wp_register_script( self::FRONTEND_HANDLERS_HANDLE, "{$assets_url}js/packages/frontend-handlers/frontend-handlers{$min_suffix}.js", [], ELEMENTOR_VERSION, true ); } } atomic-widgets/elements/atomic-tabs/atomic-tabs-menu/atomic-tabs-menu.php 0000644 00000005327 15252521350 0022535 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Menu; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Tabs_Menu extends Atomic_Element_Base { use Has_Element_Template; const BASE_STYLE_KEY = 'base'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'permanently_locked', true ); } public static function get_type() { return 'e-tabs-menu'; } public static function get_element_type(): string { return 'e-tabs-menu'; } public function get_title() { return esc_html__( 'Tabs menu', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic' ]; } public function get_icon() { return 'eicon-tab-menu'; } public function should_show_in_panel() { return false; } public function define_initial_attributes(): array { return [ 'role' => 'tablist', ]; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( [ Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( [ 'layout' => 'two-columns', ] ), ] ), ]; } protected function define_base_styles(): array { $styles = [ 'display' => String_Prop_Type::generate( 'flex' ), 'justify-content' => String_Prop_Type::generate( 'center' ), ]; return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_props( $styles ) ), ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-tabs-menu' => __DIR__ . '/atomic-tabs-menu.html.twig', ]; } protected function define_allowed_child_types() { return [ 'e-tab', 'container' ]; } } atomic-widgets/elements/atomic-tabs/atomic-tabs-menu/atomic-tabs-menu.html.twig 0000644 00000001415 15252521350 0023655 0 ustar 00 {% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %} <div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" role="tablist" {% if settings._cssid is defined and settings._cssid is not empty %} id="{{ settings._cssid | e('html_attr') }}" {% endif %} {% if settings.attributes is defined and settings.attributes is not empty %} {{ settings.attributes | raw }} {% endif %} {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></div> atomic-widgets/elements/atomic-tabs/atomic-tab-content/atomic-tab-content.php 0000644 00000007506 15252521350 0023404 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab_Content; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Styles\Style_States; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\Elements\Base\Render_Context; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs\Atomic_Tabs; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Tab_Content extends Atomic_Element_Base { use Has_Element_Template; const BASE_STYLE_KEY = 'base'; public static $widget_description = 'A tab content panel. Accepts any widget type inside. The index of this e-tab-content MUST match the index of the corresponding e-tab in the e-tabs-menu.'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'permanently_locked', true ); } public static function get_type() { return 'e-tab-content'; } public static function get_element_type(): string { return 'e-tab-content'; } public function get_title() { return esc_html__( 'Tab content', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic', 'tab', 'content', 'tabs' ]; } public function get_icon() { return 'eicon-layout'; } public function should_show_in_panel() { return false; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'tab-id' => String_Prop_Type::make(), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( [] ), ]; } protected function define_atomic_style_states(): array { $selected_state = Style_States::get_class_states_map()['selected']; return [ $selected_state ]; } protected function define_base_styles(): array { $styles = [ 'display' => String_Prop_Type::generate( 'block' ), 'padding' => Size_Prop_Type::generate( [ 'size' => 10, 'unit' => 'px', ] ), 'min-width' => Size_Prop_Type::generate( [ 'size' => 30, 'unit' => 'px', ] ), ]; return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_props( $styles ) ), ]; } protected function define_initial_attributes() { return [ 'role' => 'tabpanel', ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-tab-content' => __DIR__ . '/atomic-tab-content.html.twig', ]; } protected function build_template_context(): array { $tabs_context = Render_Context::get( Atomic_Tabs::class ); $default_active_tab = $tabs_context['default-active-tab']; $get_tab_content_index = $tabs_context['get-tab-content-index']; $tabs_id = $tabs_context['tabs-id']; $index = $get_tab_content_index( $this->get_id() ); $is_active = $default_active_tab === $index; return array_merge( $this->build_base_template_context(), [ 'is_active' => $is_active, 'tab_id' => Atomic_Tabs::get_tab_id( $tabs_id, $index ), 'tab_content_id' => Atomic_Tabs::get_tab_content_id( $tabs_id, $index ), ] ); } } atomic-widgets/elements/atomic-tabs/atomic-tab-content/atomic-tab-content.html.twig 0000644 00000001512 15252521350 0024521 0 ustar 00 {% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %} <div class="{{ classes }} {{ editor_classes | default('') }}{{ is_active ? ' e--selected' : '' }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" role="tabpanel" x-bind="tabContent" id="{{ tab_content_id }}" aria-labelledby="{{ tab_id }}" {% if not is_active %} hidden="true" style="display: none;"{% endif %} {% if settings.attributes is defined and settings.attributes is not empty %} {{ settings.attributes | raw }} {% endif %} {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></div> atomic-widgets/elements/atomic-tabs/atomic-tabs-content-area/atomic-tabs-content-area.php 0000644 00000005403 15252521350 0025560 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Content_Area; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Tabs_Content_Area extends Atomic_Element_Base { use Has_Element_Template; const BASE_STYLE_KEY = 'base'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'permanently_locked', true ); } public static function get_type() { return 'e-tabs-content-area'; } public static function get_element_type(): string { return 'e-tabs-content-area'; } public function get_title() { return esc_html__( 'Tabs content area', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic' ]; } public function get_icon() { return 'eicon-tab-content'; } public function should_show_in_panel() { return false; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( [ Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( [ 'layout' => 'two-columns', ] ), ] ), ]; } protected function define_base_styles(): array { $styles = [ 'display' => String_Prop_Type::generate( 'block' ), ]; return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_props( $styles ) ), ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-tabs-content-area' => __DIR__ . '/atomic-tabs-content-area.html.twig', ]; } protected function define_allowed_child_types() { return [ 'e-tab-content', 'container' ]; } } atomic-widgets/elements/atomic-tabs/atomic-tabs-content-area/atomic-tabs-content-area.html.twig 0000644 00000001372 15252521350 0026707 0 ustar 00 {% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %} <div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" {% if settings._cssid is defined and settings._cssid is not empty %} id="{{ settings._cssid | e('html_attr') }}" {% endif %} {% if settings.attributes is defined and settings.attributes is not empty %} {{ settings.attributes | raw }} {% endif %} {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></div> atomic-widgets/elements/atomic-tabs/handlers/editor-tabs-state.js 0000644 00000001754 15252521350 0021203 0 ustar 00 import { Alpine } from '@elementor/alpinejs'; import { getTabId } from './utils'; /** * @typedef {Record<string, number>} TabsState - Maps tabsId to the selected tab index. */ const STORE_NAME = 'editor-atomic-tabs-state'; function ensureStore() { if ( ! Alpine.store( STORE_NAME ) ) { Alpine.store( STORE_NAME, /** @type {TabsState} */ ( {} ) ); } return /** @type {TabsState} */ ( Alpine.store( STORE_NAME ) ); } export function getActiveTabId( tabsId, fallback ) { const store = ensureStore(); const storedIndex = store[ tabsId ]; if ( storedIndex === undefined ) { return fallback; } return getTabId( tabsId, storedIndex ); } export function setActiveTabIndex( tabsId, index ) { const store = ensureStore(); store[ tabsId ] = index; } export function validateActiveTab( tabsId, tabCount ) { const store = ensureStore(); const storedIndex = store[ tabsId ]; if ( storedIndex === undefined ) { return; } if ( storedIndex >= tabCount ) { delete store[ tabsId ]; } } atomic-widgets/elements/atomic-tabs/handlers/atomic-tabs-handler.js 0000644 00000004543 15252521350 0021465 0 ustar 00 import { register } from '@elementor/frontend-handlers'; import { Alpine } from '@elementor/alpinejs'; import { TAB_ELEMENT_TYPE, TAB_CONTENT_ELEMENT_TYPE, getTabId, getTabContentId, getIndex, getNextTab, getDirectTabCount } from './utils'; import { getActiveTabId, setActiveTabIndex, validateActiveTab } from './editor-tabs-state'; const SELECTED_CLASS = 'e--selected'; register( { elementType: 'e-tabs', id: 'e-tabs-handler', callback: ( { element, settings } ) => { const tabsId = element.dataset.id; const defaultActiveTab = settings[ 'default-active-tab' ]; Alpine.data( `eTabs${ tabsId }`, () => ( { init() { validateActiveTab( tabsId, getDirectTabCount( this.$el ) ); }, get activeTab() { return getActiveTabId( tabsId, defaultActiveTab ); }, navigateTabs( { key, target: tab } ) { const nextTab = getNextTab( key, tab ); nextTab.focus(); }, tab: { ':id'() { const index = getIndex( this.$el, TAB_ELEMENT_TYPE ); return getTabId( tabsId, index ); }, '@click'() { setActiveTabIndex( tabsId, getIndex( this.$el, TAB_ELEMENT_TYPE ) ); }, '@keydown.arrow-right.prevent'( event ) { this.navigateTabs( event ); }, '@keydown.arrow-left.prevent'( event ) { this.navigateTabs( event ); }, ':class'() { const id = this.$el.id; return { [ SELECTED_CLASS ]: this.activeTab === id }; }, ':aria-selected'() { const id = this.$el.id; return this.activeTab === id ? 'true' : 'false'; }, ':tabindex'() { const id = this.$el.id; return this.activeTab === id ? '0' : '-1'; }, ':aria-controls'() { const index = getIndex( this.$el, TAB_ELEMENT_TYPE ); return getTabContentId( tabsId, index ); }, }, tabContent: { ':aria-labelledby'() { const index = getIndex( this.$el, TAB_CONTENT_ELEMENT_TYPE ); return getTabId( tabsId, index ); }, 'x-show'() { const index = getIndex( this.$el, TAB_CONTENT_ELEMENT_TYPE ); const tabId = getTabId( tabsId, index ); const isActive = this.activeTab === tabId; this.$nextTick( () => { this.$el.classList.toggle( SELECTED_CLASS, isActive ); } ); return isActive; }, ':id'() { const index = getIndex( this.$el, TAB_CONTENT_ELEMENT_TYPE ); return getTabContentId( tabsId, index ); }, }, } ) ); }, } ); atomic-widgets/elements/atomic-tabs/handlers/atomic-tabs-preview-handler.js 0000644 00000002346 15252521350 0023143 0 ustar 00 import { register } from '@elementor/frontend-handlers'; import { Alpine, refreshTree } from '@elementor/alpinejs'; import { TAB_ELEMENT_TYPE, TAB_CONTENT_ELEMENT_TYPE, getIndex } from './utils'; import { setActiveTabIndex } from './editor-tabs-state'; register( { elementType: 'e-tabs', id: 'e-tabs-preview-handler', callback: ( { element, signal, listenToChildren } ) => { window?.parent.addEventListener( 'elementor/navigator/item/click', ( event ) => { const { id, type } = event.detail; if ( type !== TAB_ELEMENT_TYPE && type !== TAB_CONTENT_ELEMENT_TYPE ) { return; } const targetElement = Alpine.$data( element ).$refs[ id ]; if ( ! targetElement ) { return; } const targetIndex = getIndex( targetElement, type ); setActiveTabIndex( element.dataset.id, targetIndex ); }, { signal } ); // Re-initialize Alpine to sync with editor DOM manipulations that bypass Alpine's reactivity. listenToChildren( [ TAB_ELEMENT_TYPE, TAB_CONTENT_ELEMENT_TYPE ] ) .render( ( event ) => { const childElement = event.detail.element; const nearestTabs = childElement.closest( '[data-e-type="e-tabs"]' ); if ( nearestTabs !== element ) { return; } refreshTree( element ); } ); }, } ); atomic-widgets/elements/atomic-tabs/handlers/utils.js 0000644 00000002751 15252521350 0017006 0 ustar 00 export const TAB_ELEMENT_TYPE = 'e-tab'; export const TAB_CONTENT_ELEMENT_TYPE = 'e-tab-content'; export const TABS_CONTENT_AREA_ELEMENT_TYPE = 'e-tabs-content-area'; export const TABS_MENU_ELEMENT_TYPE = 'e-tabs-menu'; const NAVIGATE_UP_KEYS = [ 'ArrowUp', 'ArrowLeft' ]; const NAVIGATE_DOWN_KEYS = [ 'ArrowDown', 'ArrowRight' ]; export const getTabId = ( tabsId, tabIndex ) => { return `${ tabsId }-tab-${ tabIndex }`; }; export const getTabContentId = ( tabsId, tabIndex ) => { return `${ tabsId }-tab-content-${ tabIndex }`; }; export const getChildren = ( el, elementType ) => { const parent = el.parentElement; return Array.from( parent.children ).filter( ( child ) => { return child.dataset.element_type === elementType; } ); }; export const getIndex = ( el, elementType ) => { const children = getChildren( el, elementType ); return children.indexOf( el ); }; export const getDirectTabCount = ( tabsRootElement ) => { return tabsRootElement.querySelectorAll( `:scope > [data-element_type="${ TABS_MENU_ELEMENT_TYPE }"] > [data-element_type="${ TAB_ELEMENT_TYPE }"]`, ).length; }; export const getNextTab = ( key, tab ) => { const tabs = getChildren( tab, TAB_ELEMENT_TYPE ); const tabsLength = tabs.length; const currentIndex = getIndex( tab, TAB_ELEMENT_TYPE ); if ( NAVIGATE_DOWN_KEYS.includes( key ) ) { return tabs[ ( currentIndex + 1 ) % tabsLength ]; } if ( NAVIGATE_UP_KEYS.includes( key ) ) { return tabs[ ( currentIndex - 1 + tabsLength ) % tabsLength ]; } }; atomic-widgets/elements/atomic-tabs/atomic-tab/atomic-tab.php 0000644 00000013044 15252521350 0020256 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Styles\Style_States; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph\Atomic_Paragraph; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\Elements\Base\Render_Context; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs\Atomic_Tabs; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Tab extends Atomic_Element_Base { use Has_Element_Template; const BASE_STYLE_KEY = 'base'; public static $widget_description = 'A tab trigger element. Contains a single heading or paragraph that serves as the tab label. The index of this e-tab MUST match the index of the corresponding e-tab-content in the e-tabs-content-area.'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'permanently_locked', true ); } public static function get_type() { return 'e-tab'; } public static function get_element_type(): string { return 'e-tab'; } public function get_title() { return esc_html__( 'Tab trigger', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic' ]; } public function get_icon() { return 'eicon-layout'; } public function should_show_in_panel() { return false; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( [] ), ]; } protected function define_atomic_style_states(): array { $selected_state = Style_States::get_class_states_map()['selected']; return [ $selected_state ]; } protected function define_base_styles(): array { $styles = [ 'display' => String_Prop_Type::generate( 'block' ), 'cursor' => String_Prop_Type::generate( 'pointer' ), 'color' => Color_Prop_Type::generate( '#0C0D0E' ), 'border-style' => String_Prop_Type::generate( 'solid' ), 'border-color' => Color_Prop_Type::generate( '#E0E0E0' ), 'border-width' => Size_Prop_Type::generate( [ 'size' => 2, 'unit' => 'px', ]), 'padding' => Size_Prop_Type::generate( [ 'size' => 8, 'unit' => 'px', ]), 'width' => Size_Prop_Type::generate( [ 'size' => 160, 'unit' => 'px', ]), 'background' => Background_Prop_Type::generate( [ 'color' => Color_Prop_Type::generate( '#FFFFFF' ), ]), ]; $selected_styles = [ 'outline-width' => Size_Prop_Type::generate( [ 'size' => 0, 'unit' => 'px', ]), 'border-color' => Color_Prop_Type::generate( '#0C0D0E' ), ]; $hover_styles = [ 'background' => Background_Prop_Type::generate( [ 'color' => Color_Prop_Type::generate( '#E0E0E0' ), ]), ]; return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_props( $styles ) ) ->add_variant( Style_Variant::make() ->set_state( Style_States::SELECTED ) ->add_props( $selected_styles ) ) ->add_variant( Style_Variant::make() ->set_state( Style_States::FOCUS ) ->add_props( $selected_styles ) ) ->add_variant( Style_Variant::make() ->set_state( Style_States::HOVER ) ->add_props( $hover_styles ) ), ]; } protected function define_initial_attributes() { return [ 'role' => 'tab', 'tabindex' => '-1', ]; } protected function define_default_html_tag() { return 'button'; } protected function define_default_children() { return [ Atomic_Paragraph::generate() ->settings( [ 'paragraph' => Html_V3_Prop_Type::generate( [ 'content' => String_Prop_Type::generate( 'Tab' ), 'children' => [], ] ), 'tag' => String_Prop_Type::generate( 'span' ), ] ) ->build(), ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-tab' => __DIR__ . '/atomic-tab.html.twig', ]; } protected function build_template_context(): array { $tabs_context = Render_Context::get( Atomic_Tabs::class ); $default_active_tab = $tabs_context['default-active-tab']; $get_tab_index = $tabs_context['get-tab-index']; $tabs_id = $tabs_context['tabs-id']; $index = $get_tab_index( $this->get_id() ); $is_active = $default_active_tab === $index; return array_merge( $this->build_base_template_context(), [ 'is_active' => $is_active, 'tab_id' => Atomic_Tabs::get_tab_id( $tabs_id, $index ), 'tab_content_id' => Atomic_Tabs::get_tab_content_id( $tabs_id, $index ), ] ); } } atomic-widgets/elements/atomic-tabs/atomic-tab/atomic-tab.html.twig 0000644 00000001557 15252521350 0021412 0 ustar 00 {% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %} <button class="{{ classes }} {{ editor_classes | default('') }}{{ is_active ? ' e--selected' : '' }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" role="tab" tabindex="{{ is_active ? '0' : '-1' }}" aria-selected="{{ is_active ? 'true' : 'false' }}" x-bind="tab" x-ref="{{ id }}" id="{{ tab_id }}" aria-controls="{{ tab_content_id }}" {% if settings.attributes is defined and settings.attributes is not empty %} {{ settings.attributes | raw }} {% endif %} {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></button> atomic-widgets/elements/atomic-tabs/atomic-tabs/atomic-tabs.html.twig 0000644 00000002075 15252521350 0021754 0 ustar 00 {% set default_tab_index = settings['default-active-tab'] | default(0) %} {% set default_active_tab_id = id ~ '-tab-' ~ default_tab_index %} {% set e_settings = { 'default-active-tab': default_active_tab_id, } %} {% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %} <div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" x-data="eTabs{{ id }}" data-e-settings="{{ e_settings | json_encode | e('html_attr') }}" {% if settings._cssid is defined and settings._cssid is not empty %} id="{{ settings._cssid | e('html_attr') }}" {% endif %} {% if settings.attributes is defined and settings.attributes is not empty %} {{ settings.attributes | raw }} {% endif %} {{ editor_attributes | default('') | raw }}> <!-- elementor-children-placeholder --> </div> atomic-widgets/elements/atomic-tabs/atomic-tabs/atomic-tabs.php 0000644 00000017746 15252521350 0020641 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph\Atomic_Paragraph; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type; use Elementor\Modules\AtomicWidgets\Controls\Types\Elements\Tabs_Control; use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab\Atomic_Tab; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab_Content\Atomic_Tab_Content; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Menu\Atomic_Tabs_Menu; use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Content_Area\Atomic_Tabs_Content_Area; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; use Elementor\Core\Utils\Collection; use Elementor\Utils; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Tabs extends Atomic_Element_Base { use Has_Element_Template; const BASE_STYLE_KEY = 'base'; const ELEMENT_TYPE_TABS_MENU = 'e-tabs-menu'; const ELEMENT_TYPE_TABS_CONTENT_AREA = 'e-tabs-content-area'; const ELEMENT_TYPE_TAB = 'e-tab'; const ELEMENT_TYPE_TAB_CONTENT = 'e-tab-content'; public static $widget_description = 'Create a tabbed interface with customizable tabs and content areas. Structure: e-tabs contains e-tabs-menu (with e-tab triggers) and e-tabs-content-area (with e-tab-content panels). The number of e-tab elements MUST equal the number of e-tab-content elements. Each e-tab at index N is paired with the e-tab-content at index N.'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'is_container', true ); } public static function get_type() { return 'e-tabs'; } public static function get_element_type(): string { return 'e-tabs'; } public function get_title() { return esc_html__( 'Tabs', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic' ]; } public function get_icon() { return 'eicon-tabs'; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'default-active-tab' => Number_Prop_Type::make() ->default( 0 ) ->meta( Overridable_Prop_Type::ignore() ), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Content', 'elementor' ) ) ->set_id( 'content' ) ->set_items( [ Tabs_Control::make() ->set_label( __( 'Menu items', 'elementor' ) ) ->set_meta( [ 'layout' => 'custom', ] ), ] ), Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( [ Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( [ 'layout' => 'two-columns', ] ), ] ), ]; } protected function define_base_styles(): array { $styles = [ 'display' => String_Prop_Type::generate( 'flex' ), 'flex-direction' => String_Prop_Type::generate( 'column' ), 'gap' => Size_Prop_Type::generate( [ 'size' => 30, 'unit' => 'px', ]), 'padding' => Dimensions_Prop_Type::generate( [ 'block-start' => Size_Prop_Type::generate( [ 'size' => 0, 'unit' => 'px', ]), ] ), ]; return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_props( $styles ) ), ]; } protected function define_default_children() { $default_tab_count = 3; $tab_elements = []; $tab_content_elements = []; foreach ( range( 1, $default_tab_count ) as $i ) { $tab_elements[] = Atomic_Tab::generate() ->editor_settings( [ 'title' => "Tab {$i} trigger", 'initial_position' => $i, ] ) ->build(); $tab_content_elements[] = Atomic_Tab_Content::generate() ->editor_settings( [ 'title' => "Tab {$i} content", 'initial_position' => $i, ] ) ->build(); } $tabs_menu = Atomic_Tabs_Menu::generate() ->children( $tab_elements ) ->build(); $tabs_content_area = Atomic_Tabs_Content_Area::generate() ->children( $tab_content_elements ) ->build(); return [ $tabs_menu, $tabs_content_area, ]; } public function get_script_depends() { $global_depends = parent::get_script_depends(); if ( Plugin::$instance->preview->is_preview_mode() ) { return array_merge( $global_depends, [ 'elementor-tabs-handler', 'elementor-tabs-preview-handler' ] ); } return array_merge( $global_depends, [ 'elementor-tabs-handler' ] ); } public function register_frontend_handlers() { $assets_url = ELEMENTOR_ASSETS_URL; $min_suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min'; wp_register_script( 'elementor-tabs-handler', "{$assets_url}js/tabs-handler{$min_suffix}.js", [ Frontend_Assets_Loader::FRONTEND_HANDLERS_HANDLE, Frontend_Assets_Loader::ALPINEJS_HANDLE ], ELEMENTOR_VERSION, true ); wp_register_script( 'elementor-tabs-preview-handler', "{$assets_url}js/tabs-preview-handler{$min_suffix}.js", [ Frontend_Assets_Loader::FRONTEND_HANDLERS_HANDLE, Frontend_Assets_Loader::ALPINEJS_HANDLE ], ELEMENTOR_VERSION, true ); } private function get_filtered_children_ids( $parent_element, $child_type ) { if ( ! $parent_element ) { return []; } return Collection::make( $parent_element->get_children() ) ->filter( fn( $element ) => $element->get_type() === $child_type ) ->map( fn( $element ) => $element->get_id() ) ->flip() ->all(); } private function get_tab_index( $tab_id ) { $direct_children = Collection::make( $this->get_children() ); $tabs_menu = $direct_children->filter( fn( $child ) => $child->get_type() === self::ELEMENT_TYPE_TABS_MENU )->first(); $tab_ids = $this->get_filtered_children_ids( $tabs_menu, self::ELEMENT_TYPE_TAB ); return $tab_ids[ $tab_id ]; } private function get_tab_content_index( $tab_content_id ) { $direct_children = Collection::make( $this->get_children() ); $tabs_content_area = $direct_children->filter( fn( $child ) => $child->get_type() === self::ELEMENT_TYPE_TABS_CONTENT_AREA )->first(); $tab_content_ids = $this->get_filtered_children_ids( $tabs_content_area, self::ELEMENT_TYPE_TAB_CONTENT ); return $tab_content_ids[ $tab_content_id ]; } protected function define_render_context(): array { $default_active_tab = $this->get_atomic_setting( 'default-active-tab' ); return [ [ 'context' => [ 'default-active-tab' => $default_active_tab, 'get-tab-index' => fn( $tab_id ) => $this->get_tab_index( $tab_id ), 'get-tab-content-index' => fn( $tab_content_id ) => $this->get_tab_content_index( $tab_content_id ), 'tabs-id' => $this->get_id(), ], ], ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-tabs' => __DIR__ . '/atomic-tabs.html.twig', ]; } public static function get_tab_id( $tabs_id, $index ) { return "{$tabs_id}-tab-{$index}"; } public static function get_tab_content_id( $tabs_id, $index ) { return "{$tabs_id}-tab-content-{$index}"; } } atomic-widgets/elements/atomic-heading/atomic-heading.html.twig 0000644 00000001501 15252521350 0020656 0 ustar 00 {% import 'elementor/macros' as m %} {%- set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') -%} <{{ settings.tag | e('html_tag') }} data-interaction-id="{{ interaction_id }}" class="{{ classes }}" {{- m.render_custom_attributes(settings) }}> {%- set allowed_tags = '<b><strong><sup><sub><s><em><i><u><a><del><span><br>' -%} {%- if settings.link is defined and settings.link.href is defined and settings.link.href is not empty -%} <{{ settings.link.tag | default('a') | e('html_tag') }} {{- m.render_link_attributes(settings.link) }} class="{{ base_styles['link-base'] }}"> {{ settings.title | striptags(allowed_tags) | raw }} </{{ settings.link.tag | default('a') | e('html_tag') }}> {%- else -%} {{ settings.title | striptags(allowed_tags) | raw }} {%- endif %} </{{ settings.tag | e('html_tag') }}> atomic-widgets/elements/atomic-heading/atomic-heading.php 0000644 00000011767 15252521350 0017547 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Heading; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Inline_Editing_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Heading extends Atomic_Widget_Base { use Has_Template; const LINK_BASE_STYLE_KEY = 'link-base'; public static $widget_description = 'Display a heading with customizable tag, styles, and link options.'; public static function get_element_type(): string { return 'e-heading'; } public function get_title() { return esc_html__( 'Heading', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic' ]; } public function get_icon() { return 'eicon-e-heading'; } protected static function define_props_schema(): array { return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'tag' => String_Prop_Type::make() ->enum( [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ] ) ->default( 'h2' ) ->description( 'The HTML tag for the heading element. Could be h1, h2, up to h6' ), 'title' => Html_V3_Prop_Type::make() ->default( [ 'content' => String_Prop_Type::generate( __( 'This is a title', 'elementor' ) ), 'children' => [], ] ) ->description( 'The text content of the heading.' ) ->alias( 'text', 'content', 'heading' ), 'link' => Link_Prop_Type::make(), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { $content_section = Section::make() ->set_label( __( 'Content', 'elementor' ) ) ->set_id( 'content' ) ->set_items( [ Inline_Editing_Control::bind_to( 'title' ) ->set_placeholder( __( 'Type your title here', 'elementor' ) ) ->set_label( __( 'Title', 'elementor' ) ), ] ); return [ $content_section, Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( $this->get_settings_controls() ), ]; } protected function get_settings_controls(): array { return [ Select_Control::bind_to( 'tag' ) ->set_options([ [ 'value' => 'h1', 'label' => 'H1', ], [ 'value' => 'h2', 'label' => 'H2', ], [ 'value' => 'h3', 'label' => 'H3', ], [ 'value' => 'h4', 'label' => 'H4', ], [ 'value' => 'h5', 'label' => 'H5', ], [ 'value' => 'h6', 'label' => 'H6', ], ]) ->set_label( __( 'Tag', 'elementor' ) ), Link_Control::bind_to( 'link' ) ->set_placeholder( __( 'Type or paste your URL', 'elementor' ) ) ->set_label( __( 'Link', 'elementor' ) ) ->set_meta( [ 'topDivider' => true, ] ), Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ]; } protected function define_base_styles(): array { $margin_value = Size_Prop_Type::generate( [ 'unit' => 'px', 'size' => 0 , ] ); return [ 'base' => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'margin', $margin_value ) ), self::LINK_BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'all', 'unset' ) ->add_prop( 'cursor', 'pointer' ) ), ]; } protected function get_templates(): array { return [ 'elementor/elements/atomic-heading' => __DIR__ . '/atomic-heading.html.twig', ]; } public function render_markdown(): string { $settings = $this->get_atomic_settings(); $title = wp_strip_all_tags( $settings['title'] ?? '' ); if ( empty( $title ) ) { return ''; } $tag = $settings['tag'] ?? 'h2'; $level_map = [ 'h1' => 1, 'h2' => 2, 'h3' => 3, 'h4' => 4, 'h5' => 5, 'h6' => 6, ]; $level = $level_map[ $tag ] ?? 2; $md = str_repeat( '#', $level ) . ' ' . $title; if ( ! empty( $settings['link']['href'] ) ) { $md = str_repeat( '#', $level ) . ' [' . $title . '](' . esc_url( $settings['link']['href'] ) . ')'; } return $md; } } atomic-widgets/elements/flexbox/flexbox.html.twig 0000644 00000001170 15252521350 0016254 0 ustar 00 {% import 'elementor/macros' as m %} {%- set tag = settings.tag | default('div') -%} {%- if settings.link is defined and settings.link.href is defined and settings.link.href is not empty -%} {%- set tag = settings.link.tag | default('a') -%} {%- endif -%} <{{ tag }} class="{{ m.render_base_classes(id, base_styles, settings) }} {{ editor_classes | default('') }}" {{- ' ' }}{{ m.render_data_attributes(id, type, interaction_id) }} {{- m.render_link_attributes(settings.link | default(null)) }} {{- m.render_custom_attributes(settings, editor_attributes) }}> <!-- elementor-children-placeholder --> </{{ tag }}> atomic-widgets/elements/flexbox/flexbox.php 0000644 00000012700 15252521350 0015127 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Flexbox; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Html_Tag_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Flexbox extends Atomic_Element_Base { use Has_Element_Template; const BASE_STYLE_KEY = 'base'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'is_container', true ); } public static function get_type() { return 'e-flexbox'; } public static function get_element_type(): string { return 'e-flexbox'; } public function get_title() { return esc_html__( 'Flexbox', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic', 'layout' ]; } public function get_icon() { return 'eicon-flexbox'; } protected static function define_props_schema(): array { $tag_dependencies = Dependency_Manager::make( Dependency_Manager::RELATION_AND ) ->where( [ 'operator' => 'ne', 'path' => [ 'link', 'destination' ], 'nestedPath' => [ 'group' ], 'value' => 'action', 'newValue' => [ '$$type' => 'string', 'value' => 'button', ], ] )->where( [ 'operator' => 'not_exist', 'path' => [ 'link', 'destination' ], 'newValue' => [ '$$type' => 'string', 'value' => 'a', ], ] )->get(); return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'tag' => String_Prop_Type::make() ->enum( [ 'div', 'header', 'section', 'article', 'aside', 'footer', 'a', 'button' ] ) ->default( 'div' ) ->description( 'The HTML tag for the flexbox container. Could be div, header, section, article, aside, footer, or a (link).' ) ->set_dependencies( $tag_dependencies ), 'link' => Link_Prop_Type::make(), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; return $schema; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( [ Html_Tag_Control::bind_to( 'tag' ) ->set_options( [ [ 'value' => 'div', 'label' => 'Div', ], [ 'value' => 'header', 'label' => 'Header', ], [ 'value' => 'section', 'label' => 'Section', ], [ 'value' => 'article', 'label' => 'Article', ], [ 'value' => 'aside', 'label' => 'Aside', ], [ 'value' => 'footer', 'label' => 'Footer', ], ]) ->set_label( esc_html__( 'HTML Tag', 'elementor' ) ) ->set_fallback_labels( [ 'a' => 'a (link)', ] ), Link_Control::bind_to( 'link' ) ->set_placeholder( __( 'Type or paste your URL', 'elementor' ) ) ->set_label( __( 'Link', 'elementor' ) ) ->set_meta( [ 'topDivider' => true, ] ), Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ] ), ]; } protected function define_base_styles(): array { $display = String_Prop_Type::generate( 'flex' ); $flex_direction = String_Prop_Type::generate( 'row' ); return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'display', $display ) ->add_prop( 'flex-direction', $flex_direction ) ->add_prop( 'padding', $this->get_base_padding() ) ), ]; } protected function get_base_padding(): array { return Size_Prop_Type::generate( [ 'size' => 10, 'unit' => 'px', ] ); } protected function add_render_attributes() { parent::add_render_attributes(); $settings = $this->get_atomic_settings(); $base_style_class = $this->get_base_styles_dictionary()[ static::BASE_STYLE_KEY ]; $initial_attributes = $this->define_initial_attributes(); $attributes = [ 'class' => [ 'e-con', 'e-atomic-element', $base_style_class, ...( $settings['classes'] ?? [] ), ], ]; if ( ! empty( $settings['_cssid'] ) ) { $attributes['id'] = esc_attr( $settings['_cssid'] ); } if ( ! empty( $settings['link']['href'] ) ) { $link_attributes = $this->get_link_attributes( $settings['link'] ); $attributes = array_merge( $attributes, $link_attributes ); } $this->add_render_attribute( '_wrapper', array_merge( $initial_attributes, $attributes ) ); } protected function get_templates(): array { return [ 'elementor/elements/flexbox' => __DIR__ . '/flexbox.html.twig', ]; } } atomic-widgets/elements/div-block/div-block.php 0000644 00000012617 15252521350 0015544 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\Div_Block; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Definition; use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Variant; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Html_Tag_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Div_Block extends Atomic_Element_Base { use Has_Element_Template; const BASE_STYLE_KEY = 'base'; public function __construct( $data = [], $args = null ) { parent::__construct( $data, $args ); $this->meta( 'is_container', true ); } public static function get_type() { return 'e-div-block'; } public static function get_element_type(): string { return 'e-div-block'; } public function get_title() { return esc_html__( 'Div block', 'elementor' ); } public function get_keywords() { return [ 'ato', 'atom', 'atoms', 'atomic', 'layout' ]; } public function get_icon() { return 'eicon-div-block'; } protected static function define_props_schema(): array { $tag_dependencies = Dependency_Manager::make( Dependency_Manager::RELATION_AND ) ->where( [ 'operator' => 'ne', 'path' => [ 'link', 'destination' ], 'nestedPath' => [ 'group' ], 'value' => 'action', 'newValue' => [ '$$type' => 'string', 'value' => 'button', ], ] )->where( [ 'operator' => 'not_exist', 'path' => [ 'link', 'destination' ], 'newValue' => [ '$$type' => 'string', 'value' => 'a', ], ] )->get(); return [ 'classes' => Classes_Prop_Type::make() ->default( [] ), 'tag' => String_Prop_Type::make() ->enum( [ 'div', 'header', 'section', 'article', 'aside', 'footer', 'a', 'button' ] ) ->default( 'div' ) ->set_dependencies( $tag_dependencies ), 'link' => Link_Prop_Type::make(), 'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ), ]; } protected function define_atomic_controls(): array { return [ Section::make() ->set_label( __( 'Settings', 'elementor' ) ) ->set_id( 'settings' ) ->set_items( [ Html_Tag_Control::bind_to( 'tag' ) ->set_options( [ [ 'value' => 'div', 'label' => 'Div', ], [ 'value' => 'header', 'label' => 'Header', ], [ 'value' => 'section', 'label' => 'Section', ], [ 'value' => 'article', 'label' => 'Article', ], [ 'value' => 'aside', 'label' => 'Aside', ], [ 'value' => 'footer', 'label' => 'Footer', ], ]) ->set_fallback_labels( [ 'a' => 'a (link)', ] ) ->set_label( esc_html__( 'HTML Tag', 'elementor' ) ), Link_Control::bind_to( 'link' ) ->set_placeholder( __( 'Type or paste your URL', 'elementor' ) ) ->set_label( __( 'Link', 'elementor' ) ) ->set_meta( [ 'topDivider' => true, ] ), Text_Control::bind_to( '_cssid' ) ->set_label( __( 'ID', 'elementor' ) ) ->set_meta( $this->get_css_id_control_meta() ), ] ), ]; } protected function define_base_styles(): array { $display = String_Prop_Type::generate( 'block' ); return [ static::BASE_STYLE_KEY => Style_Definition::make() ->add_variant( Style_Variant::make() ->add_prop( 'display', $display ) ->add_prop( 'padding', $this->get_base_padding() ) ->add_prop( 'min-width', $this->get_base_min_width() ) ), ]; } protected function get_base_padding(): array { return Size_Prop_Type::generate( [ 'size' => 10, 'unit' => 'px', ] ); } protected function get_base_min_width(): array { return Size_Prop_Type::generate( [ 'size' => 30, 'unit' => 'px', ] ); } protected function add_render_attributes() { parent::add_render_attributes(); $settings = $this->get_atomic_settings(); $base_style_class = $this->get_base_styles_dictionary()[ static::BASE_STYLE_KEY ]; $initial_attributes = $this->define_initial_attributes(); $attributes = [ 'class' => [ 'e-con', 'e-atomic-element', $base_style_class, ...( $settings['classes'] ?? [] ), ], ]; if ( ! empty( $settings['_cssid'] ) ) { $attributes['id'] = esc_attr( $settings['_cssid'] ); } if ( ! empty( $settings['link']['href'] ) ) { $link_attributes = $this->get_link_attributes( $settings['link'] ); $attributes = array_merge( $attributes, $link_attributes ); } $this->add_render_attribute( '_wrapper', array_merge( $initial_attributes, $attributes ) ); } protected function get_templates(): array { return [ 'elementor/elements/div-block' => __DIR__ . '/div-block.html.twig', ]; } } atomic-widgets/elements/div-block/div-block.html.twig 0000644 00000001170 15252521350 0016662 0 ustar 00 {% import 'elementor/macros' as m %} {%- set tag = settings.tag | default('div') -%} {%- if settings.link is defined and settings.link.href is defined and settings.link.href is not empty -%} {%- set tag = settings.link.tag | default('a') -%} {%- endif -%} <{{ tag }} class="{{ m.render_base_classes(id, base_styles, settings) }} {{ editor_classes | default('') }}" {{- ' ' }}{{ m.render_data_attributes(id, type, interaction_id) }} {{- m.render_link_attributes(settings.link | default(null)) }} {{- m.render_custom_attributes(settings, editor_attributes) }}> <!-- elementor-children-placeholder --> </{{ tag }}> atomic-widgets/elements/template-renderer/single-file-loader.php 0000644 00000004375 15252521350 0021103 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer; use ElementorDeps\Twig\Error\LoaderError; use ElementorDeps\Twig\Loader\LoaderInterface; use ElementorDeps\Twig\Source; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Single_File_Loader implements LoaderInterface { private $templates = []; private $validity_cache = []; public function getSourceContext( string $name ): Source { $path = $this->get_template_path( $name ); return new Source( // This is safe to use because we're validating the file path inside `get_template_path`. // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents file_get_contents( $path ), $name, $path ); } public function getCacheKey( string $name ): string { return $this->get_template_path( $name ); } public function isFresh( string $name, int $time ): bool { $path = $this->get_template_path( $name ); return filemtime( $path ) < $time; } public function exists( string $name ) { $path = $this->templates[ $name ] ?? null; return $this->is_valid_file( $path ); } public function is_registered( string $name ): bool { return isset( $this->templates[ $name ] ); } public function register( string $name, string $path ): self { if ( ! $this->is_valid_file( $path ) ) { throw new LoaderError( esc_html( "Invalid template '{$name}': {$path}" ) ); } $this->templates[ $name ] = $path; return $this; } private function get_template_path( string $name ): string { $path = $this->templates[ $name ] ?? null; if ( ! $this->is_valid_file( $path ) ) { throw new LoaderError( esc_html( "Invalid template '{$name}': {$path}" ) ); } return $path; } private function is_valid_file( $path ): bool { if ( ! $path ) { return false; } if ( isset( $this->validity_cache[ $path ] ) ) { return $this->validity_cache[ $path ]; } // Ref: https://github.com/twigphp/Twig/blob/8432946eeeca009d75fc7fc568f3c3f4650f5a0f/src/Loader/FilesystemLoader.php#L260 if ( str_contains( $path, "\0" ) ) { throw new LoaderError( 'A template name cannot contain NULL bytes.' ); } $is_valid = is_file( $path ) && is_readable( $path ); $this->validity_cache[ $path ] = $is_valid; return $is_valid; } } atomic-widgets/elements/template-renderer/template-renderer.php 0000644 00000002527 15252521350 0021055 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer; use Elementor\Utils; use ElementorDeps\Twig\Environment; use ElementorDeps\Twig\Runtime\EscaperRuntime; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Template_Renderer { private static ?self $instance = null; private Single_File_Loader $loader; private Environment $env; private function __construct() { $this->loader = new Single_File_Loader(); $this->env = new Environment( $this->loader, [ 'debug' => Utils::is_elementor_debug(), 'autoescape' => 'name', ] ); $escaper = $this->env->getRuntime( EscaperRuntime::class ); $escaper->setEscaper( 'full_url', 'esc_url' ); $escaper->setEscaper( 'html_tag', [ Utils::class, 'validate_html_tag' ] ); } public static function instance(): self { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } public static function reset() { self::$instance = null; } public function is_registered( string $name ): bool { return $this->loader->is_registered( $name ); } public function register( string $name, string $path ): self { $this->loader->register( $name, $path ); return $this; } public function render( string $name, array $context = [] ): string { return $this->env->render( $name, $context ); } } atomic-widgets/import-export/modifiers/interactions-props-modifier.php 0000644 00000003021 15252521350 0022442 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\ImportExport\Modifiers; use Elementor\Modules\AtomicWidgets\PropsResolver\Import_Export_Props_Resolver; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Interactions_Props_Modifier { private Import_Export_Props_Resolver $props_resolver; private array $schema; public function __construct( Import_Export_Props_Resolver $props_resolver, array $schema ) { $this->props_resolver = $props_resolver; $this->schema = $schema; } public static function make( Import_Export_Props_Resolver $props_resolver, array $schema ) { return new self( $props_resolver, $schema ); } public function run( array $element ) { if ( ! isset( $element['interactions'] ) ) { return $element; } $interactions = $element['interactions']; if ( is_string( $interactions ) ) { $decoded = json_decode( $interactions, true ); if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $decoded ) ) { return $element; } $interactions = $decoded; } if ( empty( $interactions['items'] ) || ! is_array( $interactions['items'] ) ) { return $element; } foreach ( $interactions['items'] as $index => $item ) { if ( ! is_array( $item ) || empty( $item['$$type'] ) || ! array_key_exists( 'value', $item ) || ! is_array( $item['value'] ) ) { continue; } $interactions['items'][ $index ]['value'] = $this->props_resolver->resolve( $this->schema, $item['value'] ); } $element['interactions'] = $interactions; return $element; } } atomic-widgets/import-export/modifiers/styles-props-modifier.php 0000644 00000002424 15252521350 0021271 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\ImportExport\Modifiers; use Elementor\Modules\AtomicWidgets\PropsResolver\Import_Export_Props_Resolver; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Styles_Props_Modifier { private Import_Export_Props_Resolver $props_resolver; private array $schema; public function __construct( Import_Export_Props_Resolver $props_resolver, array $schema ) { $this->props_resolver = $props_resolver; $this->schema = $schema; } public static function make( Import_Export_Props_Resolver $props_resolver, array $schema ) { return new self( $props_resolver, $schema ); } public function run( array $element ) { if ( empty( $element['styles'] ) && ! is_array( $element['styles'] ) ) { return $element; } foreach ( $element['styles'] as $style_key => $style ) { if ( empty( $style['variants'] ) || ! is_array( $style['variants'] ) ) { continue; } foreach ( $style['variants'] as $variant_key => $variant ) { if ( empty( $variant['props'] ) || ! is_array( $variant['props'] ) ) { continue; } $element['styles'][ $style_key ]['variants'][ $variant_key ]['props'] = $this->props_resolver->resolve( $this->schema, $variant['props'] ); } } return $element; } } atomic-widgets/import-export/modifiers/settings-props-modifier.php 0000644 00000001637 15252521350 0021613 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\ImportExport\Modifiers; use Elementor\Modules\AtomicWidgets\PropsResolver\Import_Export_Props_Resolver; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Settings_Props_Modifier { private Import_Export_Props_Resolver $props_resolver; private array $schema; public function __construct( Import_Export_Props_Resolver $props_resolver, array $schema ) { $this->props_resolver = $props_resolver; $this->schema = $schema; } public static function make( Import_Export_Props_Resolver $props_resolver, array $schema ) { return new self( $props_resolver, $schema ); } public function run( array $element ) { if ( empty( $element['settings'] ) || ! is_array( $element['settings'] ) ) { return $element; } $element['settings'] = $this->props_resolver->resolve( $this->schema, $element['settings'] ); return $element; } } atomic-widgets/import-export/modifiers/styles-ids-modifier.php 0000644 00000003432 15252521350 0020705 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\ImportExport\Modifiers; use Elementor\Core\Utils\Collection; use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type; use Elementor\Modules\AtomicWidgets\Utils\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Styles_Ids_Modifier { private Collection $old_to_new_ids; public static function make() { return new self(); } public function run( array $element ) { $this->old_to_new_ids = Collection::make(); $element = $this->replace_styles_ids( $element ); $element = $this->replace_references( $element ); return $element; } private function replace_styles_ids( array $element ) { if ( empty( $element['styles'] ) || empty( $element['id'] ) ) { return $element; } $styles = Collection::make( $element['styles'] )->map_with_keys( function ( $style, $id ) use ( $element ) { $style['id'] = $this->generate_id( $element['id'], $id ); return [ $style['id'] => $style ]; } )->all(); $element['styles'] = $styles; return $element; } private function replace_references( array $element ) { if ( empty( $element['settings'] ) ) { return $element; } $element['settings'] = Collection::make( $element['settings'] )->map( function ( $setting ) { if ( ! $setting || ! Classes_Prop_Type::make()->validate( $setting ) ) { return $setting; } $setting['value'] = Collection::make( $setting['value'] ) ->map( fn( $style_id ) => $this->old_to_new_ids->get( $style_id ) ?? $style_id ) ->all(); return $setting; } )->all(); return $element; } private function generate_id( $element_id, $old_id ): string { $id = Utils::generate_id( "e-{$element_id}-", $this->old_to_new_ids->values() ); $this->old_to_new_ids[ $old_id ] = $id; return $id; } } atomic-widgets/import-export/modifiers/interactions-ids-modifier.php 0000644 00000002535 15252521350 0022067 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\ImportExport\Modifiers; use Elementor\Modules\AtomicWidgets\Utils\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Interactions_Ids_Modifier { public static function make() { return new self(); } public function run( array $element ) { if ( empty( $element['id'] ) || ! isset( $element['interactions'] ) ) { return $element; } $interactions = $element['interactions']; if ( is_string( $interactions ) ) { $decoded = json_decode( $interactions, true ); if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $decoded ) ) { return $element; } $interactions = $decoded; } if ( empty( $interactions['items'] ) || ! is_array( $interactions['items'] ) ) { return $element; } $existing_ids = []; $prefix = "e-{$element['id']}-"; foreach ( $interactions['items'] as $index => $item ) { if ( ! is_array( $item ) || ( $item['$$type'] ?? '' ) !== 'interaction-item' || ! isset( $item['value'] ) || ! is_array( $item['value'] ) ) { continue; } $new_id = Utils::generate_id( $prefix, $existing_ids ); $existing_ids[] = $new_id; $interactions['items'][ $index ]['value']['interaction_id'] = [ '$$type' => 'string', 'value' => $new_id, ]; } $element['interactions'] = $interactions; return $element; } } atomic-widgets/import-export/atomic-import-export.php 0000644 00000006313 15252521350 0017134 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\ImportExport; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\ImportExport\Modifiers\Interactions_Ids_Modifier; use Elementor\Modules\AtomicWidgets\ImportExport\Modifiers\Interactions_Props_Modifier; use Elementor\Modules\AtomicWidgets\ImportExport\Modifiers\Settings_Props_Modifier; use Elementor\Modules\AtomicWidgets\ImportExport\Modifiers\Styles_Ids_Modifier; use Elementor\Modules\AtomicWidgets\ImportExport\Modifiers\Styles_Props_Modifier; use Elementor\Modules\AtomicWidgets\PropsResolver\Import_Export_Props_Resolver; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Modules\Interactions\Schema\Interactions_Schema; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Atomic_Import_Export { public function register_hooks() { add_filter( 'elementor/template_library/sources/local/import/elements', fn( $elements ) => $this->run( $elements, Import_Export_Props_Resolver::for_import() ) ); add_filter( 'elementor/template_library/sources/cloud/import/elements', fn( $elements ) => $this->run( $elements, Import_Export_Props_Resolver::for_import() ) ); add_filter( 'elementor/template_library/sources/local/export/elements', fn( $elements ) => $this->run( $elements, Import_Export_Props_Resolver::for_export() ) ); add_filter( 'elementor/document/element/replace_id', fn( $element ) => $this->replace_styles_ids( $element ) ); } private function run( $elements, Import_Export_Props_Resolver $props_resolver ) { if ( empty( $elements ) || ! is_array( $elements ) ) { return $elements; } return Plugin::$instance->db->iterate_data( $elements, function ( $element ) use ( $props_resolver ) { $element_instance = Plugin::$instance->elements_manager->create_element_instance( $element ); /** @var Atomic_Element_Base | Atomic_Widget_Base $element_instance */ if ( ! Utils::is_atomic( $element_instance ) ) { return $element; } $interactions_schema = Interactions_Schema::get(); $interaction_item_schema = ! empty( $interactions_schema['items'][0] ) ? $interactions_schema['items'][0]->get_shape() : []; $runners = [ Settings_Props_Modifier::make( $props_resolver, $element_instance::get_props_schema() ), Styles_Props_Modifier::make( $props_resolver, Style_Schema::get() ), Interactions_Props_Modifier::make( $props_resolver, $interaction_item_schema ), ]; foreach ( $runners as $runner ) { $element = $runner->run( $element ); } return $element; } ); } private function replace_styles_ids( $element ) { if ( empty( $element ) || ! is_array( $element ) ) { return $element; } $element_instance = Plugin::$instance->elements_manager->create_element_instance( $element ); /** @var Atomic_Element_Base | Atomic_Widget_Base $element_instance */ if ( ! Utils::is_atomic( $element_instance ) ) { return $element; } $element = Styles_Ids_Modifier::make()->run( $element ); return Interactions_Ids_Modifier::make()->run( $element ); } } atomic-widgets/ajax/render-element-action.php 0000644 00000003325 15252521350 0015304 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Ajax; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Render_Element_Action { const ACTION = 'render_atomic_element'; public function register( Ajax $ajax ): void { $ajax->register_ajax_action( self::ACTION, fn ( $request ) => $this->handle( $request ) ); } public function handle( $request ): array { $post_id = isset( $request['editor_post_id'] ) ? (int) $request['editor_post_id'] : 0; $element_data = $request['data'] ?? null; if ( ! $post_id || ! is_array( $element_data ) ) { throw new \Exception( 'Invalid request payload.' ); } $document = Plugin::$instance->documents->get_with_permissions( $post_id ); $editor = Plugin::$instance->editor; $is_edit_mode = $editor->is_edit_mode(); $editor->set_edit_mode( true ); Plugin::$instance->db->switch_to_query( [ 'p' => $post_id, 'post_type' => 'any', ], true ); Plugin::$instance->documents->switch_to_document( $document ); do_action( 'elementor/atomic_widgets/before_render', $document ); try { $render_html = $this->render_element( $element_data ); } finally { do_action( 'elementor/atomic_widgets/after_render', $document ); $editor->set_edit_mode( $is_edit_mode ); Plugin::$instance->db->restore_current_query(); } return [ 'render' => $render_html, ]; } private function render_element( array $element_data ): string { $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( ! $element ) { throw new \Exception( 'Element could not be instantiated.' ); } ob_start(); $element->print_element(); return (string) ob_get_clean(); } } atomic-widgets/logger/logger.php 0000644 00000005372 15252521350 0012742 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Logger; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Simple logger for Atomic Widgets that writes to wp-content/debug.log (if WP_DEBUG_LOG enabled * and WP_DEBUG_DISPLAY disabled) or optionally to Elementor's DB logger. * Never displays errors on screen. */ class Logger { public static function info( string $message, array $context = [], bool $use_elementor_logger = false ): void { self::log_message( $message, $context, $use_elementor_logger, 'info' ); } public static function warning( string $message, array $context = [], bool $use_elementor_logger = false ): void { self::log_message( $message, $context, $use_elementor_logger, 'warning' ); } public static function error( string $message, array $context = [], bool $use_elementor_logger = false ): void { self::log_message( $message, $context, $use_elementor_logger, 'error' ); } private static function log_message( string $message, array $context, bool $use_elementor_logger, string $level ): void { if ( $use_elementor_logger ) { self::log_to_elementor_db( $message, $context, $level ); return; } self::log_to_wp_debug_file( $message, $context, $level ); } private static function log_to_wp_debug_file( string $message, array $context, string $level ): void { if ( ! self::should_log_to_file() ) { return; } $formatted_message = self::format_message( $message, $context, $level ); error_log( $formatted_message ); } private static function log_to_elementor_db( string $message, array $context, string $level ): void { if ( ! isset( \Elementor\Plugin::$instance->logger ) ) { return; } try { $logger = \Elementor\Plugin::$instance->logger; switch ( $level ) { case 'error': $logger->error( $message, $context ); break; case 'warning': $logger->warning( $message, $context ); break; default: $logger->info( $message, $context ); break; } } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Logger must not throw exceptions } } private static function should_log_to_file(): bool { $debug_log_enabled = defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG; $debug_display_disabled = ! defined( 'WP_DEBUG_DISPLAY' ) || ! WP_DEBUG_DISPLAY; return $debug_log_enabled && $debug_display_disabled; } private static function format_message( string $message, array $context, string $level ): string { $level_prefix = strtoupper( $level ); $formatted = "[Elementor Atomic Widgets] [{$level_prefix}] " . $message; if ( ! empty( $context ) ) { $context_json = wp_json_encode( $context, JSON_UNESCAPED_SLASHES ); if ( false !== $context_json ) { $formatted .= ' | Context: ' . $context_json; } } return $formatted; } } atomic-widgets/controls/base/atomic-control-base.php 0000644 00000002443 15252521350 0016617 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Base; use JsonSerializable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Atomic_Control_Base implements JsonSerializable { private string $bind; private $label = null; private $description = null; private $meta = null; abstract public function get_type(): string; abstract public function get_props(): array; public static function bind_to( string $prop_name ) { return new static( $prop_name ); } protected function __construct( string $prop_name ) { $this->bind = $prop_name; } public function get_bind() { return $this->bind; } public function set_label( string $label ): self { $this->label = html_entity_decode( $label ); return $this; } public function set_description( string $description ): self { $this->description = html_entity_decode( $description ); return $this; } public function set_meta( $meta ): self { $this->meta = $meta; return $this; } public function jsonSerialize(): array { return [ 'type' => 'control', 'value' => [ 'type' => $this->get_type(), 'bind' => $this->get_bind(), 'label' => $this->label, 'description' => $this->description, 'props' => $this->get_props(), 'meta' => $this->meta, ], ]; } } atomic-widgets/controls/base/element-control-base.php 0000644 00000001751 15252521350 0016775 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Base; use JsonSerializable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Element_Control_Base implements JsonSerializable { private $label = null; private $meta = null; abstract public function get_type(): string; abstract public function get_props(): array; public static function make(): self { return new static(); } public function set_label( string $label ): self { $this->label = $label; return $this; } public function get_label(): string { return $this->label; } public function set_meta( $meta ): self { $this->meta = $meta; return $this; } public function get_meta(): array { return $this->meta; } public function jsonSerialize(): array { return [ 'type' => 'element-control', 'value' => [ 'label' => $this->get_label(), 'meta' => $this->get_meta(), 'type' => $this->get_type(), 'props' => $this->get_props(), ], ]; } } atomic-widgets/controls/types/query-chips-control.php 0000644 00000002005 15252521350 0017130 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Query_Chips_Control extends Atomic_Control_Base { private ?array $query_options = null; private ?string $placeholder = null; private ?int $min_input_length = null; public function get_type(): string { return 'query-chips'; } public function set_query_options( array $query_options ): self { $this->query_options = $query_options; return $this; } public function set_placeholder( string $placeholder ): self { $this->placeholder = $placeholder; return $this; } public function set_min_input_length( int $min_input_length ): self { $this->min_input_length = $min_input_length; return $this; } public function get_props(): array { return [ 'queryOptions' => $this->query_options, 'placeholder' => $this->placeholder, 'minInputLength' => $this->min_input_length, ]; } } atomic-widgets/controls/types/date-range-control.php 0000644 00000000602 15252521350 0016667 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Date_Range_Control extends Atomic_Control_Base { public function get_type(): string { return 'date-range'; } public function get_props(): array { return []; } } atomic-widgets/controls/types/email-form-action-control.php 0000644 00000001003 15252521350 0020157 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; if ( ! defined( 'ABSPATH' ) ) { exit; } class Email_Form_Action_Control extends Chips_Control { public function get_type(): string { return 'email'; } public static function get_default_recipient_email(): string { return sanitize_email( (string) get_option( 'admin_email', '' ) ); } public function get_props(): array { return array_merge( parent::get_props(), [ 'toPlaceholder' => self::get_default_recipient_email(), ] ); } } atomic-widgets/controls/types/text-control.php 0000644 00000001101 15252521350 0015637 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Text_Control extends Atomic_Control_Base { private ?string $placeholder = null; public function get_type(): string { return 'text'; } public function set_placeholder( string $placeholder ): self { $this->placeholder = $placeholder; return $this; } public function get_props(): array { return [ 'placeholder' => $this->placeholder, ]; } } atomic-widgets/controls/types/svg-control.php 0000644 00000000732 15252521350 0015463 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; use Elementor\Modules\AtomicWidgets\Utils\Image\Image_Sizes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Svg_Control extends Atomic_Control_Base { public function get_type(): string { return 'svg-media'; } public function get_props(): array { return [ 'type' => $this->get_type(), ]; } } atomic-widgets/controls/types/size-control.php 0000644 00000002464 15252521350 0015642 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Size_Control extends Atomic_Control_Base { private ?string $placeholder = null; private ?string $variant = 'length'; private ?array $units = null; private ?string $default_unit = null; private ?bool $disable_custom = false; public function get_type(): string { return 'size'; } public function set_placeholder( string $placeholder ): self { $this->placeholder = $placeholder; return $this; } public function set_variant( string $variant ): self { $this->variant = $variant; return $this; } public function set_units( array $units ): self { $this->units = $units; return $this; } public function set_default_unit( string $default_unit ): self { $this->default_unit = $default_unit; return $this; } public function set_disable_custom( bool $disable_custom ): self { $this->disable_custom = $disable_custom; return $this; } public function get_props(): array { return [ 'placeholder' => $this->placeholder, 'variant' => $this->variant, 'units' => $this->units, 'defaultUnit' => $this->default_unit, 'disableCustom' => $this->disable_custom, ]; } } atomic-widgets/controls/types/inline-editing-control.php 0000644 00000001125 15252521350 0017560 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Inline_Editing_Control extends Atomic_Control_Base { private ?string $placeholder = null; public function get_type(): string { return 'inline-editing'; } public function set_placeholder( string $placeholder ): self { $this->placeholder = $placeholder; return $this; } public function get_props(): array { return [ 'placeholder' => $this->placeholder, ]; } } atomic-widgets/controls/types/image-control.php 0000644 00000000735 15252521350 0015751 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; use Elementor\Modules\AtomicWidgets\Utils\Image\Image_Sizes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Control extends Atomic_Control_Base { public function get_type(): string { return 'image'; } public function get_props(): array { return [ 'sizes' => Image_Sizes::get_all(), ]; } } atomic-widgets/controls/types/video-control.php 0000644 00000000532 15252521350 0015770 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; } class Video_Control extends Atomic_Control_Base { public function get_type(): string { return 'video'; } public function get_props(): array { return []; } } atomic-widgets/controls/types/time-range-control.php 0000644 00000000601 15252521350 0016707 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Time_Range_Control extends Atomic_Control_Base { public function get_type(): string { return 'time-range'; } public function get_props(): array { return []; } } atomic-widgets/controls/types/query-control.php 0000644 00000002307 15252521350 0016031 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; use Elementor\Modules\AtomicWidgets\Query\Query_Builder_Factory as Query_Builder; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Query_Control extends Atomic_Control_Base { private bool $allow_custom_values = true; private int $minimum_input_length = 2; private ?array $query_config = null; private ?string $placeholder = null; public function get_type(): string { return 'query'; } public function set_placeholder( string $placeholder ): self { $this->placeholder = $placeholder; return $this; } public function set_allow_custom_values( bool $allow_custom_values ): self { $this->allow_custom_values = $allow_custom_values; return $this; } public function set_query_config( $config ): self { $this->query_config = $config; return $this; } public function get_props(): array { return [ 'allowCustomValues' => $this->allow_custom_values, 'placeholder' => $this->placeholder, 'queryOptions' => Query_Builder::create( $this->query_config )->build(), 'minInputLength' => $this->minimum_input_length, ]; } } atomic-widgets/controls/types/link-control.php 0000644 00000002410 15252521350 0015614 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; use Elementor\Modules\AtomicWidgets\Query\Query_Builder_Factory as Query_Builder; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Link_Control extends Atomic_Control_Base { private bool $allow_custom_values = true; private int $minimum_input_length = 2; private ?array $query_config = null; private ?string $placeholder = null; private ?string $aria_label = null; public function get_type(): string { return 'link'; } public function set_placeholder( string $placeholder ): self { $this->placeholder = $placeholder; return $this; } public function set_allow_custom_values( bool $allow_custom_values ): self { $this->allow_custom_values = $allow_custom_values; return $this; } public function set_query_config( $config ): self { $this->query_config = $config; return $this; } public function get_props(): array { return [ 'allowCustomValues' => $this->allow_custom_values, 'placeholder' => $this->placeholder, 'queryOptions' => Query_Builder::create( $this->query_config )->build(), 'minInputLength' => $this->minimum_input_length, 'ariaLabel' => 'Link URL', ]; } } atomic-widgets/controls/types/elements/tabs-control.php 0000644 00000000600 15252521350 0017423 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types\Elements; use Elementor\Modules\AtomicWidgets\Controls\Base\Element_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Tabs_Control extends Element_Control_Base { public function get_type(): string { return 'tabs'; } public function get_props(): array { return []; } } atomic-widgets/controls/types/number-control.php 0000644 00000002453 15252521350 0016156 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Number_Control extends Atomic_Control_Base { private ?string $placeholder = null; private ?int $max = null; private ?int $min = null; private ?int $step = null; private ?bool $should_force_int = null; public function get_type(): string { return 'number'; } public function set_placeholder( string $placeholder ): self { $this->placeholder = $placeholder; return $this; } public function set_max( ?int $max ): self { $this->max = $max; return $this; } public function set_min( ?int $min ): self { $this->min = $min; return $this; } public function set_step( ?int $step ): self { $this->step = $step; return $this; } public function set_should_force_int( ?bool $should_force_int ): self { $this->should_force_int = $should_force_int ?? false; return $this; } public function get_props(): array { return [ 'placeholder' => $this->placeholder, 'max' => $this->max, 'min' => $this->min, 'step' => $this->step, 'shouldForceInt' => $this->should_force_int, ]; } } atomic-widgets/controls/types/html-tag-control.php 0000644 00000000501 15252521350 0016373 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Html_Tag_Control extends Select_Control { public function get_type(): string { return 'html-tag'; } } atomic-widgets/controls/types/repeatable-control.php 0000644 00000005204 15252521350 0016767 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Repeatable_Control extends Atomic_Control_Base { private string $child_control_type; private object $child_control_props; private bool $show_duplicate = true; private bool $show_toggle = true; private string $repeater_label; private ?object $initial_values; private ?string $pattern_label; private ?string $placeholder; private ?string $prop_key = ''; private bool $is_sortable = false; private ?object $add_item_tooltip_props = null; public function get_type(): string { return 'repeatable'; } public function set_child_control_type( $control_type ): self { $this->child_control_type = $control_type; return $this; } public function set_child_control_props( $control_props ): self { $this->child_control_props = (object) $control_props; return $this; } public function hide_duplicate(): self { $this->show_duplicate = false; return $this; } public function hide_toggle(): self { $this->show_toggle = false; return $this; } public function set_initialValues( $initial_values ): self { $this->initial_values = (object) $initial_values; return $this; } public function set_patternLabel( $pattern_label ): self { $this->pattern_label = $pattern_label; return $this; } public function set_repeaterLabel( string $label ): self { $this->repeater_label = $label; return $this; } public function set_placeholder( string $placeholder ): self { $this->placeholder = $placeholder; return $this; } public function set_prop_key( string $prop_key ): self { $this->prop_key = $prop_key; return $this; } public function set_is_sortable( bool $is_sortable ): self { $this->is_sortable = $is_sortable; return $this; } public function set_add_item_tooltip_props( $add_item_tooltip_props ): self { $this->add_item_tooltip_props = (object) $add_item_tooltip_props; return $this; } public function get_props(): array { return [ 'childControlType' => $this->child_control_type, 'childControlProps' => $this->child_control_props, 'showDuplicate' => $this->show_duplicate, 'showToggle' => $this->show_toggle, 'initialValues' => $this->initial_values, 'patternLabel' => $this->pattern_label, 'repeaterLabel' => $this->repeater_label, 'placeholder' => $this->placeholder, 'propKey' => $this->prop_key, 'isSortable' => $this->is_sortable, 'addItemTooltipProps' => $this->add_item_tooltip_props, ]; } } atomic-widgets/controls/types/toggle-control.php 0000644 00000003370 15252521350 0016146 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Toggle_Control extends Atomic_Control_Base { private array $options = []; private bool $full_width = false; private string $size = 'tiny'; private bool $exclusive = true; private bool $convert_options = false; public function get_type(): string { return 'toggle'; } public function add_options( array $control_options ): self { $this->options = []; foreach ( $control_options as $value => $config ) { $this->options[] = [ 'value' => $value, 'label' => $config['title'] ?? $value, 'icon' => $config['atomic-icon'] ?? null, 'showTooltip' => true, 'exclusive' => false, ]; } return $this; } public function set_size( string $size ): self { $allowed_sizes = [ 'tiny', 'small', 'medium', 'large' ]; if ( in_array( $size, $allowed_sizes, true ) ) { $this->size = $size; } return $this; } public function set_full_width( bool $full_width ): self { $this->full_width = $full_width; return $this; } public function set_exclusive( bool $exclusive ): self { $this->exclusive = $exclusive; return $this; } /** * Whether to convert the v3 options to v4 compatible * * @param bool $convert_options * @return $this */ public function set_convert_options( bool $convert_options ): self { $this->convert_options = $convert_options; return $this; } public function get_props(): array { return [ 'options' => $this->options, 'fullWidth' => $this->full_width, 'size' => $this->size, 'exclusive' => $this->exclusive, 'convertOptions' => $this->convert_options, ]; } } atomic-widgets/controls/types/switch-control.php 0000644 00000000571 15252521350 0016166 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Switch_Control extends Atomic_Control_Base { public function get_type(): string { return 'switch'; } public function get_props(): array { return []; } } atomic-widgets/controls/types/textarea-control.php 0000644 00000001127 15252521350 0016500 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Textarea_Control extends Atomic_Control_Base { private $placeholder = null; public function get_type(): string { return 'textarea'; } public function set_placeholder( string $placeholder ): self { $this->placeholder = html_entity_decode( $placeholder ); return $this; } public function get_props(): array { return [ 'placeholder' => $this->placeholder, ]; } } atomic-widgets/controls/types/chips-control.php 0000644 00000001300 15252521350 0015762 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; } class Chips_Control extends Atomic_Control_Base { private array $options = []; private bool $free_chips = false; public function get_type(): string { return 'chips'; } public function set_options( array $options ): self { $this->options = $options; return $this; } public function get_props(): array { return [ 'options' => $this->options, 'freeChips' => $this->free_chips, ]; } public function set_free_chips( bool $free_chips ): self { $this->free_chips = $free_chips; return $this; } } atomic-widgets/controls/types/query-filter-repeater-control.php 0000644 00000002252 15252521350 0021120 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Query_Filter_Repeater_Control extends Atomic_Control_Base { private array $allowed_keys = []; private array $key_config = []; private ?string $label = null; private ?string $chips_placeholder = null; public function get_type(): string { return 'query-filter-repeater'; } public function set_allowed_keys( array $keys ): self { $this->allowed_keys = $keys; return $this; } public function set_key_config( array $config ): self { $this->key_config = $config; return $this; } public function set_label( string $label ): self { $this->label = $label; return $this; } public function set_chips_placeholder( string $placeholder ): self { $this->chips_placeholder = $placeholder; return $this; } public function get_props(): array { return [ 'allowedKeys' => $this->allowed_keys, 'keyConfig' => (object) $this->key_config, 'label' => $this->label, 'chipsPlaceholder' => $this->chips_placeholder, ]; } } atomic-widgets/controls/types/attachment-type-control.php 0000644 00000001066 15252521350 0017774 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Attachment_Type_Control extends Atomic_Control_Base { private array $options = []; public function get_type(): string { return 'attachment-type'; } public function set_options( array $options ): self { $this->options = $options; return $this; } public function get_props(): array { return [ 'options' => $this->options, ]; } } atomic-widgets/controls/types/date-time-control.php 0000644 00000000577 15252521350 0016544 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Date_Time_Control extends Atomic_Control_Base { public function get_type(): string { return 'date-time'; } public function get_props(): array { return []; } } atomic-widgets/controls/types/select-control.php 0000644 00000002341 15252521350 0016141 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls\Types; use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Select_Control extends Atomic_Control_Base { private array $options = []; private ?array $fallback_labels = null; private ?string $collection_id = null; private ?string $placeholder = null; public function get_type(): string { return 'select'; } public function set_options( array $options ): self { $this->options = $options; return $this; } public function set_collection_id( string $collection_id ): self { $this->collection_id = $collection_id; return $this; } public function set_placeholder( string $placeholder ): self { $this->placeholder = $placeholder; return $this; } public function get_props(): array { $props = [ 'options' => $this->options, 'fallbackLabels' => $this->fallback_labels, 'placeholder' => $this->placeholder, ]; if ( $this->collection_id ) { $props['collectionId'] = $this->collection_id; } return $props; } public function set_fallback_labels( array $fallback_labels ): self { $this->fallback_labels = $fallback_labels; return $this; } } atomic-widgets/controls/section.php 0000644 00000002476 15252521350 0013515 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Controls; use JsonSerializable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Section implements JsonSerializable { private ?string $id = null; private $label = null; private $description = null; private array $items = []; public static function make(): self { return new static(); } public function set_id( string $id ): self { $this->id = $id; return $this; } public function get_id() { return $this->id; } public function set_label( string $label ): self { $this->label = html_entity_decode( $label ); return $this; } public function get_label(): ?string { return $this->label; } public function set_description( string $description ): self { $this->description = html_entity_decode( $description ); return $this; } public function set_items( array $items ): self { $this->items = $items; return $this; } public function add_item( $item ): self { $this->items[] = $item; return $this; } public function get_items() { return $this->items; } public function jsonSerialize(): array { return [ 'type' => 'section', 'value' => [ 'id' => $this->id, 'label' => $this->label, 'description' => $this->description, 'items' => $this->items, ], ]; } } atomic-widgets/query/query-builder-base.php 0000644 00000000473 15252521350 0015047 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Query_Builder_Base { protected array $config; public function __construct( array $config ) { $this->config = $config; } abstract public function build(): array; } atomic-widgets/query/term-query-builder.php 0000644 00000002302 15252521350 0015075 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Query; use Elementor\Modules\WpRest\Base\Query as Query_Base; use Elementor\Modules\WpRest\Classes\Term_Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Term_Query_Builder extends Query_Builder_Base { public function build(): array { $config = $this->config; $params = Term_Query::build_query_params( [ Term_Query::KEYS_CONVERSION_MAP_KEY => $config[ Query_Base::KEYS_CONVERSION_MAP_KEY ] ?? null, Term_Query::INCLUDED_TYPE_KEY => $config[ Query_Base::INCLUDED_TYPE_KEY ] ?? null, Term_Query::EXCLUDED_TYPE_KEY => $config[ Query_Base::EXCLUDED_TYPE_KEY ] ?? null, Term_Query::META_QUERY_KEY => $config[ Query_Base::META_QUERY_KEY ] ?? null, Term_Query::IS_PUBLIC_KEY => $config[ Query_Base::IS_PUBLIC_KEY ] ?? null, Term_Query::HIDE_EMPTY_KEY => $config[ Query_Base::HIDE_EMPTY_KEY ] ?? null, Term_Query::ITEMS_COUNT_KEY => $config[ Query_Base::ITEMS_COUNT_KEY ] ?? null, ] ); $endpoint = $config['endpoint'] ?? Term_Query::ENDPOINT; $namespace = $config['namespace'] ?? Term_Query::NAMESPACE; $url = $namespace . '/' . $endpoint; return [ 'params' => $params, 'url' => $url, ]; } } atomic-widgets/query/query-builder-factory.php 0000644 00000001522 15252521350 0015600 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Query; use Elementor\Modules\WpRest\Classes\Post_Query; use Elementor\Modules\WpRest\Classes\Term_Query; use Elementor\Modules\WpRest\Classes\User_Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Query_Builder_Factory { const ENDPOINT_KEY = 'endpoint'; private const BUILDERS = [ Post_Query::ENDPOINT => Post_Query_Builder::class, Term_Query::ENDPOINT => Term_Query_Builder::class, User_Query::ENDPOINT => User_Query_Builder::class, ]; public static function create( ?array $config = [] ): Query_Builder_Base { $endpoint = $config[ self::ENDPOINT_KEY ] ?? Post_Query::ENDPOINT; $class = self::BUILDERS[ $endpoint ] ?? null; if ( ! $class ) { throw new \Exception( 'Unsupported query type' ); } return new $class( $config ?? [] ); } } atomic-widgets/query/post-query-builder.php 0000644 00000002531 15252521350 0015117 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Query; use Elementor\Modules\WpRest\Base\Query as Query_Base; use Elementor\Modules\WpRest\Classes\Post_Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Post_Query_Builder extends Query_Builder_Base { public function build(): array { $config = $this->config; $params = Post_Query::build_query_params( [ Post_Query::KEYS_CONVERSION_MAP_KEY => $config[ Query_Base::KEYS_CONVERSION_MAP_KEY ] ?? null, Post_Query::INCLUDED_TYPE_KEY => $config[ Query_Base::INCLUDED_TYPE_KEY ] ?? null, Post_Query::EXCLUDED_TYPE_KEY => $config[ Query_Base::EXCLUDED_TYPE_KEY ] ?? null, Post_Query::META_QUERY_KEY => $config[ Query_Base::META_QUERY_KEY ] ?? null, Post_Query::TAX_QUERY_KEY => $config[ Query_Base::TAX_QUERY_KEY ] ?? null, Post_Query::IS_PUBLIC_KEY => $config[ Query_Base::IS_PUBLIC_KEY ] ?? null, Post_Query::ITEMS_COUNT_KEY => $config[ Query_Base::ITEMS_COUNT_KEY ] ?? null, Post_Query::SEARCH_IN_CONTENT_KEY => $config[ Post_Query::SEARCH_IN_CONTENT_KEY ] ?? null, ] ); $endpoint = $config['endpoint'] ?? Post_Query::ENDPOINT; $namespace = $config['namespace'] ?? Post_Query::NAMESPACE; $url = $namespace . '/' . $endpoint; return [ 'params' => $params, 'url' => $url, ]; } } atomic-widgets/query/user-query-builder.php 0000644 00000002044 15252521350 0015107 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Query; use Elementor\Modules\WpRest\Base\Query as Query_Base; use Elementor\Modules\WpRest\Classes\User_Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class User_Query_Builder extends Query_Builder_Base { public function build(): array { $config = $this->config; $params = User_Query::build_query_params( [ User_Query::KEYS_CONVERSION_MAP_KEY => $config[ Query_Base::KEYS_CONVERSION_MAP_KEY ] ?? null, User_Query::INCLUDED_TYPE_KEY => $config[ Query_Base::INCLUDED_TYPE_KEY ] ?? null, User_Query::EXCLUDED_TYPE_KEY => $config[ Query_Base::EXCLUDED_TYPE_KEY ] ?? null, User_Query::META_QUERY_KEY => $config[ Query_Base::META_QUERY_KEY ] ?? null, User_Query::ITEMS_COUNT_KEY => $config[ Query_Base::ITEMS_COUNT_KEY ] ?? null, ] ); $endpoint = $config['endpoint'] ?? User_Query::ENDPOINT; $namespace = $config['namespace'] ?? User_Query::NAMESPACE; $url = $namespace . '/' . $endpoint; return [ 'params' => $params, 'url' => $url, ]; } } atomic-widgets/css-converter/expander-registry.php 0000644 00000001063 15252521350 0016446 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Expander_Registry { /** * @var Shorthand_Expander[] */ private array $expanders = []; public function register( Shorthand_Expander $expander ): self { $this->expanders[] = $expander; return $this; } /** * Expanders in registration order. The dispatcher applies the first one that supports a rule. * * @return Shorthand_Expander[] */ public function all(): array { return $this->expanders; } } atomic-widgets/css-converter/css-converter.php 0000644 00000020266 15252521350 0015575 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; use Elementor\Modules\AtomicWidgets\CssConverter\Metrics\Conversion_Failure_Reporter; use Elementor\Modules\AtomicWidgets\Parsers\Props_Parser; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Css_Converter { const BLOCKED_PROPERTIES = [ 'behavior', '-moz-binding' ]; const BLOCKED_VALUE_NEEDLES = [ 'expression(', 'javascript:' ]; private Converter_Registry $registry; private Conversion_Failure_Reporter $failure_reporter; private Expander_Registry $expanders; private ?Variable_Prop_Value_Transformer $variable_transformer; public function __construct( Converter_Registry $registry, Conversion_Failure_Reporter $failure_reporter, ?Expander_Registry $expanders = null, ?Variable_Prop_Value_Transformer $variable_transformer = null ) { $this->registry = $registry; $this->failure_reporter = $failure_reporter; $this->expanders = $expanders ?? new Expander_Registry(); $this->variable_transformer = $variable_transformer; } /** * @return array{props: array, customCss: string, rejected: string[]} */ public function convert( string $css ): array { $rules = $this->dedupe( $this->expand_shorthands( $this->parse( $css ) ) ); $context = new Conversion_Context( $rules ); $leftover = []; foreach ( $rules as $rule ) { if ( ! $this->try_convert( $context, $rule ) ) { $leftover[] = $rule['declaration'] . ';'; } } $props = $context->get_props(); $rejected = $context->get_rejected(); if ( $this->variable_transformer ) { $schema = $this->style_schema(); $props = $this->variable_transformer->transform( $props, $schema ); $ejected = $this->variable_transformer->eject_unresolved_var_props( $props, $schema, $rules ); $props = $ejected['props']; $leftover = array_merge( $leftover, $ejected['custom_css'] ); $rejected = array_merge( $rejected, $ejected['rejected'] ); $props = $this->validate_props( $props, $schema ); } $props = $this->cleanup_props( $props ); return [ 'props' => $props, 'customCss' => implode( ' ', $leftover ), 'rejected' => $rejected, ]; } private function validate_props( array $props, array $schema ): array { if ( empty( $props ) ) { return []; } $null_resets = array_filter( $props, fn( $v ) => null === $v || $this->has_null_leaf( $v ) ); $value_props = array_filter( $props, fn( $v ) => null !== $v && ! $this->has_null_leaf( $v ) ); $validated = empty( $value_props ) ? [] : Props_Parser::make( $schema )->validate( $value_props )->unwrap(); return array_merge( $validated, $null_resets ); } private function has_null_leaf( $value ): bool { if ( ! is_array( $value ) || ! is_array( $value['value'] ?? null ) ) { return false; } foreach ( $value['value'] as $v ) { if ( null === $v || $this->has_null_leaf( $v ) ) { return true; } } return false; } /** * Recursively collapses prop values where all present sub-values are null or empty arrays into a * single null. This propagates null resets up the tree so the client receives a clean signal: * e.g. a Dimensions object where every side was set to null becomes just null at the prop level. */ private function cleanup_props( array $props ): array { $result = []; foreach ( $props as $key => $value ) { $result[ $key ] = $this->cleanup_value( $value ); } return $result; } private function cleanup_value( $value ) { if ( null === $value || ! is_array( $value ) ) { return $value; } $inner = $value['value'] ?? $value; if ( ! is_array( $inner ) ) { return $value; } $cleaned = []; foreach ( $inner as $k => $v ) { $cleaned[ $k ] = $this->cleanup_value( $v ); } if ( $this->is_empty_or_all_null( $cleaned ) ) { return null; } return isset( $value['$$type'] ) ? [ '$$type' => $value['$$type'], 'value' => $cleaned, ] : $cleaned; } private function is_empty_or_all_null( array $arr ): bool { if ( empty( $arr ) ) { return false; } foreach ( $arr as $v ) { if ( null !== $v ) { return false; } } return true; } private function style_schema(): array { if ( function_exists( 'apply_filters' ) ) { return Style_Schema::get(); } return Style_Schema::get_style_schema(); } /** * Pre-processing pass: rewrite shorthands (e.g. `border`) into the longhand declarations the * schema-bound converters understand, in place so the source cascade order is preserved. A rule * with no matching expander, or whose expander declines (empty result) or throws, is kept as-is so * it still reaches the converter loop (and custom_css fallback). * * @param array<int, array{property: string, value: string, declaration: string}> $rules * @return array<int, array{property: string, value: string, declaration: string}> */ private function dedupe( array $rules ): array { $last_index = []; foreach ( $rules as $i => $rule ) { $last_index[ $rule['property'] ] = $i; } return array_values( array_filter( $rules, fn( $rule, $i ) => $last_index[ $rule['property'] ] === $i, ARRAY_FILTER_USE_BOTH ) ); } private function expand_shorthands( array $rules ): array { $expanded = []; foreach ( $rules as $rule ) { foreach ( $this->expand_rule( $rule ) as $result_rule ) { $expanded[] = $result_rule; } } return $expanded; } /** * @param array{property: string, value: string, declaration: string} $rule * @return array<int, array{property: string, value: string, declaration: string}> */ private function expand_rule( array $rule ): array { foreach ( $this->expanders->all() as $expander ) { if ( ! $expander->is_supported( $rule ) ) { continue; } try { $expanded = $expander->expand( $rule ); } catch ( \Throwable $error ) { $this->failure_reporter->report( $rule['property'], Conversion_Failure_Reporter::CATEGORY_EXCEPTION, [ 'message' => $error->getMessage() ] ); return [ $rule ]; } return empty( $expanded ) ? [ $rule ] : $expanded; } return [ $rule ]; } /** * Try-until-success: iterate converters in registration order; the first one that * converts wins. A thrown error is treated as a decline and reported as a defect. * * @param Conversion_Context $context The shared mutable conversion context. * @param array{property: string, value: string} $rule A single parsed CSS declaration. */ private function try_convert( Conversion_Context $context, array $rule ): bool { foreach ( $this->registry->all() as $converter ) { if ( ! $converter->is_supported( $rule ) ) { continue; } try { if ( $converter->convert( $context, $rule ) ) { return true; } } catch ( \Throwable $error ) { $this->failure_reporter->report( $rule['property'], Conversion_Failure_Reporter::CATEGORY_EXCEPTION, [ 'message' => $error->getMessage() ] ); } } return false; } /** * Naive top-level split on ';' (breaks on values containing ';'); acceptable for clean * LLM input. Splits each declaration on the first ':' so values keep colons (e.g. url()). * * @return array<int, array{property: string, value: string}> */ private function parse( string $css ): array { $rules = []; foreach ( explode( ';', $css ) as $declaration ) { $declaration = trim( $declaration ); $separator = strpos( $declaration, ':' ); if ( false === $separator ) { continue; } $property = strtolower( trim( substr( $declaration, 0, $separator ) ) ); $raw_value = trim( substr( $declaration, $separator + 1 ) ); if ( '' === $property || '' === $raw_value || $this->is_blocked( $property, $raw_value ) ) { continue; } $value = 'null' === $raw_value ? null : $raw_value; $rules[] = [ 'property' => $property, 'value' => $value, 'declaration' => $declaration, ]; } return $rules; } private function is_blocked( string $property, string $value ): bool { if ( in_array( $property, self::BLOCKED_PROPERTIES, true ) ) { return true; } $value = strtolower( $value ); foreach ( self::BLOCKED_VALUE_NEEDLES as $needle ) { if ( false !== strpos( $value, $needle ) ) { return true; } } return false; } } atomic-widgets/css-converter/shorthand-expander.php 0000644 00000001775 15252521350 0016602 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * A pre-processing pass that rewrites a single shorthand declaration (e.g. `border`) into the * longhand declarations the schema-bound converters already understand. Runs before the converter * loop so the converter registry stays a 1:1 mirror of Style_Schema and the shorthand never becomes a * prop or a coverage key. */ interface Shorthand_Expander { /** * @param array{property: string, value: string} $rule A single parsed CSS declaration. */ public function is_supported( array $rule ): bool; /** * Rewrite the shorthand into longhand declarations. Returning an empty array declines the * expansion, so the original shorthand is kept and routed to custom_css. * * @param array{property: string, value: string} $rule * @return array<int, array{property: string, value: string, declaration: string}> */ public function expand( array $rule ): array; } atomic-widgets/css-converter/css-converter-rest-api.php 0000644 00000011515 15252521350 0017314 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; use Elementor\Core\Utils\Api\Error_Builder; use Elementor\Core\Utils\Api\Response_Builder; use Elementor\Modules\AtomicWidgets\CssConverter\Metrics\Null_Failure_Reporter; use Elementor\Modules\AtomicWidgets\Module as AtomicWidgetsModule; use Elementor\Modules\Variables\Module as Variables_Module; use Elementor\Modules\Variables\Services\Batch_Operations\Batch_Processor; use Elementor\Modules\Variables\Services\Variables_Service; use Elementor\Modules\Variables\Storage\Variables_Repository; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Css_Converter_REST_API { const API_NAMESPACE = 'elementor/v1'; const API_BASE = 'css-to-atomic'; public function register_hooks() { add_action( 'rest_api_init', fn() => $this->register_routes() ); } private function register_routes() { register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [ [ 'methods' => 'POST', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->convert( $request ) ), 'permission_callback' => fn() => current_user_can( 'edit_posts' ), 'args' => [ 'blocks' => [ 'type' => 'object', 'required' => true, ], ], ], ] ); } private function convert( \WP_REST_Request $request ) { $blocks = $request->get_param( 'blocks' ); if ( ! is_array( $blocks ) ) { return Error_Builder::make( 'invalid_blocks' ) ->set_status( 400 ) ->set_message( __( 'The "blocks" parameter must be an object of named CSS blocks.', 'elementor' ) ) ->build(); } $converter = $this->create_converter(); $results = []; foreach ( $blocks as $name => $block ) { if ( is_string( $block ) ) { $results[ $name ] = $this->convert_css_text( $converter, $block ); continue; } if ( ! is_array( $block ) ) { return Error_Builder::make( 'invalid_block' ) ->set_status( 400 ) ->set_message( __( 'Each block must be a CSS text string or a property declaration map.', 'elementor' ) ) ->build(); } $results[ $name ] = $this->convert_block( $converter, $block ); } return Response_Builder::make( $results )->build(); } /** * @return array{props: object, customCss: string, rejected: string[]} */ private function convert_css_text( Css_Converter $converter, string $css ): array { $result = $converter->convert( $css ); return [ 'props' => (object) $result['props'], 'customCss' => $result['customCss'], 'rejected' => $result['rejected'], ]; } /** * A block is a property->value map. A null value is an explicit reset: it bypasses CSS conversion * and is emitted as a null prop so the editor restores the property to its default. Every non-null * value is serialized back into a CSS declaration for the converter. * * @param Css_Converter $converter The shared CSS converter. * @param array<string, string|null> $declarations The block's property->value map. * @return array{props: object, customCss: string, rejected: string[]} */ private function convert_block( Css_Converter $converter, array $declarations ): array { $css_declarations = []; foreach ( $declarations as $property => $value ) { $is_null_reset = null === $value || 'null' === $value; $css_declarations[] = $property . ': ' . ( $is_null_reset ? 'null' : $value ) . ';'; } $result = $converter->convert( implode( ' ', $css_declarations ) ); return [ 'props' => (object) $result['props'], 'customCss' => $result['customCss'], 'rejected' => $result['rejected'], ]; } private function create_converter(): Css_Converter { $variables_service = $this->create_variables_service(); $variable_transformer = $variables_service ? new Variable_Prop_Value_Transformer( $variables_service ) : null; return new Css_Converter( Converter_Registry_Factory::create( $variables_service ), new Null_Failure_Reporter(), Expander_Registry_Factory::create( $variables_service ), $variable_transformer ); } private function create_variables_service(): ?Variables_Service { if ( ! $this->is_variables_active() ) { return null; } $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return null; } return new Variables_Service( new Variables_Repository( $kit ), new Batch_Processor() ); } private function is_variables_active(): bool { $experiments = Plugin::$instance->experiments; return $experiments->is_feature_active( Variables_Module::EXPERIMENT_NAME ) && $experiments->is_feature_active( AtomicWidgetsModule::EXPERIMENT_NAME ); } private function route_wrapper( callable $cb ) { try { $response = $cb(); } catch ( \Exception $e ) { return Error_Builder::make( 'unexpected_error' ) ->set_message( __( 'Something went wrong', 'elementor' ) ) ->build(); } return $response; } } atomic-widgets/css-converter/css-var-token-resolver.php 0000644 00000004436 15252521350 0017334 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\Variables\Adapters\Prop_Type_Adapter; use Elementor\Modules\Variables\PropTypes\Size_Variable_Prop_Type; use Elementor\Modules\Variables\Services\Variables_Service; use Elementor\Modules\Variables\Utils\Variable_Type_Keys; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Css_Var_Token_Resolver { public static function is_var_only_token( string $token ): bool { $token = trim( $token ); if ( null === Css_Var_Reference::parse( $token ) ) { return false; } return 1 === count( Css_Token_Splitter::split_by_whitespace( $token ) ); } public static function resolve_var_only_token_type( ?Variables_Service $service, string $token ): ?string { if ( ! self::is_var_only_token( $token ) || null === $service ) { return null; } $reference = Css_Var_Reference::parse( trim( $token ) ); if ( null === $reference ) { return null; } $variable = $service->find_by_label_or_id( $reference ); if ( null === $variable ) { return null; } return Variable_Type_Keys::get_resolved_type( $variable['type'] ?? '' ); } /** * If $token is a var-only token that resolves to a known size variable, returns the ready-made * size-variable PropValue `['$$type' => ..., 'value' => $id]`. Returns null if the token is not * a var, the variable is unknown, or the variable type is not a size (caller should then fall * back to the raw Size leaf or decline to customCss based on context). * * @return array{$$type: string, value: string}|null */ public static function resolve_size_var_prop_value( ?Variables_Service $service, string $token ): ?array { if ( 'size' !== self::resolve_var_only_token_type( $service, $token ) ) { return null; } $reference = Css_Var_Reference::parse( trim( $token ) ); $variable = $service->find_by_label_or_id( $reference ); $id = $variable['id'] ?? ''; if ( '' === $id ) { return null; } $prop_type_key = Prop_Type_Adapter::GLOBAL_CUSTOM_SIZE_VARIABLE_KEY === ( $variable['type'] ?? '' ) ? Size_Variable_Prop_Type::get_key() : ( $variable['type'] ?? '' ); return [ '$$type' => $prop_type_key, 'value' => $id, ]; } } atomic-widgets/css-converter/value-parsers/background-image-value-parser.php 0000644 00000014621 15252521350 0023372 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Gradient_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Stop_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Gradient_Color_Stop_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Src_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Position_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Url_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Stateless parser: a raw CSS `background-image` value -> an ordered list of overlay PropValues (one * per comma-separated layer), or null to decline the whole declaration (-> custom_css). * * Each layer token is classified: * - `none` -> layer is silently skipped (not added to the list). * - `url(...)` -> Background_Image_Overlay_Prop_Type PropValue. * - `linear-gradient(...)` -> Background_Gradient_Overlay_Prop_Type PropValue (linear). * - `radial-gradient(...)` -> Background_Gradient_Overlay_Prop_Type PropValue (radial). * - anything else -> decline the entire value. * * Gradient parsing supports: * - Linear: optional leading `Ndeg` angle, then comma-separated color stops. * - Radial: optional leading `circle at <named-position>`, then color stops. * - Color stops: `<color> <N>%` pairs; offset is optional. * - Unsupported syntax (direction keywords, conic, complex shapes) declines. */ class Background_Image_Value_Parser { const DEFAULT_IMAGE_SIZE = 'large'; /** * @return array[]|null Ordered overlay PropValues per layer, or null to decline. */ public static function parse( string $value ): ?array { $tokens = Css_Token_Splitter::split_by_comma( trim( $value ) ); if ( empty( $tokens ) ) { return null; } $overlays = []; foreach ( $tokens as $token ) { $overlay = self::parse_layer( trim( $token ) ); if ( false === $overlay ) { return null; } if ( null !== $overlay ) { $overlays[] = $overlay; } } return $overlays; } /** * @return array|null|false PropValue, null for `none`, false to decline. */ private static function parse_layer( string $token ) { $lower = strtolower( $token ); if ( 'none' === $lower ) { return null; } if ( 0 === strpos( $lower, 'url(' ) ) { return self::parse_url( $token ); } if ( preg_match( '/^([\w-]+-gradient)\s*\((.+)\)$/si', $token, $m ) ) { return self::parse_gradient( strtolower( $m[1] ), $m[2] ); } return false; } /** * @return array|false */ private static function parse_url( string $token ) { if ( ! preg_match( '/^url\s*\(\s*([\'"]?)(.+?)\1\s*\)$/i', $token, $m ) ) { return false; } $url = $m[2]; return Background_Image_Overlay_Prop_Type::generate( [ 'image' => Image_Prop_Type::generate( [ 'src' => Image_Src_Prop_Type::generate( [ 'url' => Url_Prop_Type::generate( $url ), ] ), 'size' => String_Prop_Type::generate( self::DEFAULT_IMAGE_SIZE ), ] ), ] ); } /** * @return array|false */ private static function parse_gradient( string $func_name, string $args ) { if ( 'linear-gradient' === $func_name ) { return self::parse_linear_gradient( $args ); } if ( 'radial-gradient' === $func_name ) { return self::parse_radial_gradient( $args ); } return false; } /** * @return array|false */ private static function parse_linear_gradient( string $args ) { $tokens = Css_Token_Splitter::split_by_comma( trim( $args ) ); if ( empty( $tokens ) ) { return false; } $angle = null; $stop_start = 0; $first = trim( $tokens[0] ); if ( preg_match( '/^(-?\d+(?:\.\d+)?)deg$/i', $first, $m ) ) { $angle = (float) $m[1]; $stop_start = 1; } elseif ( 0 === strpos( strtolower( $first ), 'to ' ) ) { return false; } $stops = self::parse_color_stops( array_slice( $tokens, $stop_start ) ); if ( null === $stops ) { return false; } $value = [ 'type' => String_Prop_Type::generate( 'linear' ), 'stops' => Gradient_Color_Stop_Prop_Type::generate( $stops ), ]; if ( null !== $angle ) { $value['angle'] = Number_Prop_Type::generate( $angle ); } return Background_Gradient_Overlay_Prop_Type::generate( $value ); } /** * @return array|false */ private static function parse_radial_gradient( string $args ) { $tokens = Css_Token_Splitter::split_by_comma( trim( $args ) ); if ( empty( $tokens ) ) { return false; } $positions = null; $stop_start = 0; $first = trim( $tokens[0] ); if ( preg_match( '/^circle\s+at\s+(.+)$/i', $first, $m ) ) { $pos = trim( $m[1] ); if ( ! Position_Prop_Type::is_valid_radial_position( $pos ) ) { return false; } $positions = $pos; $stop_start = 1; } $stops = self::parse_color_stops( array_slice( $tokens, $stop_start ) ); if ( null === $stops ) { return false; } $value = [ 'type' => String_Prop_Type::generate( 'radial' ), 'stops' => Gradient_Color_Stop_Prop_Type::generate( $stops ), ]; if ( null !== $positions ) { $value['positions'] = String_Prop_Type::generate( $positions ); } return Background_Gradient_Overlay_Prop_Type::generate( $value ); } /** * @return array[]|null */ private static function parse_color_stops( array $tokens ): ?array { if ( empty( $tokens ) ) { return null; } $stops = []; foreach ( $tokens as $token ) { $stop = self::parse_color_stop( trim( $token ) ); if ( null === $stop ) { return null; } $stops[] = $stop; } return $stops; } private static function parse_color_stop( string $token ): ?array { $parts = Css_Token_Splitter::split_by_whitespace( $token ); if ( empty( $parts ) || count( $parts ) > 2 ) { return null; } $stop_value = [ 'color' => Color_Prop_Type::generate( $parts[0] ) ]; if ( isset( $parts[1] ) ) { if ( ! preg_match( '/^(-?\d+(?:\.\d+)?)%$/', $parts[1], $m ) ) { return null; } $stop_value['offset'] = Number_Prop_Type::generate( (float) $m[1] ); } return Color_Stop_Prop_Type::generate( $stop_value ); } } atomic-widgets/css-converter/value-parsers/css-token-splitter.php 0000644 00000003336 15252521350 0021342 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Stateless tokenizer shared by the box shorthands and shorthand expanders. Splits a CSS value on * whitespace runs that sit at parenthesis depth 0, so function values such as calc(50% - 10px) or * rgb(0, 0, 0) stay intact as a single token. */ class Css_Token_Splitter { /** * Split a CSS value on top-level commas (paren-aware), trimming each segment. * * @return string[] */ public static function split_by_comma( string $value ): array { $tokens = []; $current = ''; $depth = 0; $length = strlen( $value ); for ( $i = 0; $i < $length; $i++ ) { $char = $value[ $i ]; if ( '(' === $char ) { ++$depth; } elseif ( ')' === $char ) { $depth = max( 0, $depth - 1 ); } if ( 0 === $depth && ',' === $char ) { $tokens[] = trim( $current ); $current = ''; continue; } $current .= $char; } $last = trim( $current ); if ( '' !== $last ) { $tokens[] = $last; } return $tokens; } /** * @return string[] */ public static function split_by_whitespace( string $value ): array { $tokens = []; $current = ''; $depth = 0; $length = strlen( $value ); for ( $i = 0; $i < $length; $i++ ) { $char = $value[ $i ]; if ( '(' === $char ) { ++$depth; } elseif ( ')' === $char ) { $depth = max( 0, $depth - 1 ); } if ( 0 === $depth && ( ' ' === $char || "\t" === $char || "\n" === $char ) ) { if ( '' !== $current ) { $tokens[] = $current; $current = ''; } continue; } $current .= $char; } if ( '' !== $current ) { $tokens[] = $current; } return $tokens; } } atomic-widgets/css-converter/value-parsers/filter-value-parser.php 0000644 00000014651 15252521350 0021463 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Css_Filter_Func_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions\Blur_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions\Color_Tone_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions\Drop_Shadow_Filter_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions\Hue_Rotate_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Functions\Intensity_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Stateless parser: a raw CSS `filter`/`backdrop-filter` value -> the ordered list of * `css-filter-func` PropValues an Array(Filter) accepts, or null to decline (the caller routes the * whole declaration to custom_css). It never touches the registry, context, or the wrapping prop type. * * Conversion is all-or-nothing per declaration: a single unsupported function or unparsable argument * declines the entire value. Single-argument functions (blur/brightness/contrast/saturate/hue-rotate/ * grayscale/invert/sepia) reuse Size_Value_Parser; drop-shadow expands to xAxis/yAxis/blur/color. */ class Filter_Value_Parser { const DROP_SHADOW = 'drop-shadow'; const DEFAULT_DROP_SHADOW_BLUR = [ 'size' => 10, 'unit' => Size_Constants::UNIT_PX, ]; const DEFAULT_DROP_SHADOW_COLOR = 'rgba(0, 0, 0, 1)'; const FUNCTION_GROUPS = [ 'blur' => 'blur', 'brightness' => 'intensity', 'contrast' => 'intensity', 'saturate' => 'intensity', 'grayscale' => 'color-tone', 'invert' => 'color-tone', 'sepia' => 'color-tone', 'hue-rotate' => 'hue-rotate', self::DROP_SHADOW => self::DROP_SHADOW, ]; const GROUP_PROP_TYPES = [ 'blur' => Blur_Prop_Type::class, 'intensity' => Intensity_Prop_Type::class, 'color-tone' => Color_Tone_Prop_Type::class, 'hue-rotate' => Hue_Rotate_Prop_Type::class, ]; const DROP_SHADOW_MIN_SIZES = 2; const DROP_SHADOW_MAX_SIZES = 3; /** * @return array<int, array>|null */ public static function parse( string $value ): ?array { $functions = self::split_functions( trim( $value ) ); if ( null === $functions ) { return null; } $items = []; foreach ( $functions as [$name, $args] ) { $item = self::parse_function( $name, $args ); if ( null === $item ) { return null; } $items[] = $item; } return empty( $items ) ? null : $items; } /** * @return array{0: string, 1: string}[]|null */ private static function split_functions( string $value ): ?array { $functions = []; $length = strlen( $value ); $i = 0; while ( $i < $length ) { $i = self::skip_whitespace( $value, $i, $length ); if ( $i >= $length ) { break; } $name_start = $i; while ( $i < $length && ( ctype_alpha( $value[ $i ] ) || '-' === $value[ $i ] ) ) { ++$i; } $name = substr( $value, $name_start, $i - $name_start ); $i = self::skip_whitespace( $value, $i, $length ); if ( '' === $name || $i >= $length || '(' !== $value[ $i ] ) { return null; } $args_start = $i + 1; $depth = 0; for ( ; $i < $length; $i++ ) { if ( '(' === $value[ $i ] ) { ++$depth; } elseif ( ')' === $value[ $i ] ) { --$depth; if ( 0 === $depth ) { break; } } } if ( 0 !== $depth || $i >= $length ) { return null; } $functions[] = [ strtolower( $name ), trim( substr( $value, $args_start, $i - $args_start ) ) ]; ++$i; } return empty( $functions ) ? null : $functions; } private static function parse_function( string $name, string $args ): ?array { $group = self::FUNCTION_GROUPS[ $name ] ?? null; if ( null === $group ) { return null; } $parsed_args = self::DROP_SHADOW === $group ? self::parse_drop_shadow( $args ) : self::parse_single_size( $group, $args ); if ( null === $parsed_args ) { return null; } return Css_Filter_Func_Prop_Type::generate( [ 'func' => String_Prop_Type::generate( $name ), 'args' => $parsed_args, ] ); } private static function parse_single_size( string $group, string $args ): ?array { $size = Size_Value_Parser::parse( $args ); if ( null === $size ) { return null; } $prop_type = self::GROUP_PROP_TYPES[ $group ]; return $prop_type::generate( [ 'size' => Size_Prop_Type::generate( $size ) ] ); } private static function parse_drop_shadow( string $args ): ?array { $tokens = self::split_top_level( $args ); if ( empty( $tokens ) ) { return null; } $sizes = []; $color = null; foreach ( $tokens as $token ) { $size = Size_Value_Parser::parse( $token ); if ( null !== $size ) { $sizes[] = $size; continue; } if ( null !== $color ) { return null; } $color = $token; } $size_count = count( $sizes ); if ( $size_count < self::DROP_SHADOW_MIN_SIZES || $size_count > self::DROP_SHADOW_MAX_SIZES ) { return null; } $blur = $sizes[2] ?? self::DEFAULT_DROP_SHADOW_BLUR; return Drop_Shadow_Filter_Prop_Type::generate( [ 'xAxis' => Size_Prop_Type::generate( $sizes[0] ), 'yAxis' => Size_Prop_Type::generate( $sizes[1] ), 'blur' => Size_Prop_Type::generate( $blur ), 'color' => Color_Prop_Type::generate( $color ?? self::DEFAULT_DROP_SHADOW_COLOR ), ] ); } private static function skip_whitespace( string $value, int $index, int $length ): int { while ( $index < $length && ctype_space( $value[ $index ] ) ) { ++$index; } return $index; } /** * Split on whitespace runs at parenthesis depth 0 so function colors such as "rgba(0, 0, 0, .5)" * stay intact as a single token. * * @return string[] */ private static function split_top_level( string $value ): array { $tokens = []; $current = ''; $depth = 0; $length = strlen( $value ); for ( $i = 0; $i < $length; $i++ ) { $char = $value[ $i ]; if ( '(' === $char ) { ++$depth; } elseif ( ')' === $char ) { $depth = max( 0, $depth - 1 ); } if ( 0 === $depth && ctype_space( $char ) ) { if ( '' !== $current ) { $tokens[] = $current; $current = ''; } continue; } $current .= $char; } if ( '' !== $current ) { $tokens[] = $current; } return $tokens; } } atomic-widgets/css-converter/value-parsers/size-value-parser.php 0000644 00000005460 15252521350 0021146 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Stateless leaf parser: a raw CSS length-ish value -> the {size, unit} leaf a Size_Prop_Type accepts, * or null to decline (the caller then routes the declaration to custom_css). It never touches the * registry, context, or PropTypes. * * - "auto" -> { size: null, unit: 'auto' } * - calc()/clamp()/min()/max()/var()/env() -> { size: '<raw>', unit: 'custom' } (kept verbatim) * - "<number><unit>" (unit in all_supported_units) -> { size: <number>, unit: <unit> } * - unitless "0" -> { size: 0, unit: 'px' } * - unitless non-zero, $allow_unitless on -> { size: '<raw>', unit: 'custom' } (e.g. line-height: 1.1) * - anything else (unitless non-zero with $allow_unitless off, unknown unit, multi-value) -> null */ class Size_Value_Parser { const NUMBER_WITH_UNIT_PATTERN = '/^(-?\d*\.?\d+)([a-z%]*)$/i'; const DYNAMIC_FUNCTION_PATTERN = '/(?:calc|clamp|min|max|var|env)\(/i'; /** * @param string $value The raw CSS value. * @param bool $allow_unitless When true, a unitless non-zero number (e.g. a line-height multiplier) * is kept verbatim as a `custom` unit instead of declining. * * @return array{size: mixed, unit: string}|null */ public static function parse( string $value, bool $allow_unitless = false ): ?array { $value = trim( $value ); if ( '' === $value ) { return null; } if ( Size_Constants::UNIT_AUTO === strtolower( $value ) ) { return [ 'size' => null, 'unit' => Size_Constants::UNIT_AUTO, ]; } if ( self::is_dynamic_value( $value ) ) { return [ 'size' => $value, 'unit' => Size_Constants::UNIT_CUSTOM, ]; } return self::parse_number_with_unit( $value, $allow_unitless ); } /** * @return array{size: mixed, unit: string}|null */ private static function parse_number_with_unit( string $value, bool $allow_unitless ): ?array { if ( ! preg_match( self::NUMBER_WITH_UNIT_PATTERN, $value, $matches ) ) { return null; } $size = $matches[1] + 0; $unit = strtolower( $matches[2] ); if ( '' === $unit ) { if ( 0.0 === (float) $size ) { return [ 'size' => $size, 'unit' => Size_Constants::DEFAULT_UNIT, ]; } return $allow_unitless ? [ 'size' => $matches[1], 'unit' => Size_Constants::UNIT_CUSTOM, ] : null; } if ( ! in_array( $unit, Size_Constants::all_supported_units(), true ) ) { return null; } return [ 'size' => $size, 'unit' => $unit, ]; } private static function is_dynamic_value( string $value ): bool { return 1 === preg_match( self::DYNAMIC_FUNCTION_PATTERN, $value ); } } atomic-widgets/css-converter/value-parsers/box-shorthand-parser.php 0000644 00000004322 15252521350 0021636 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Stateless parser for the CSS box shorthands (padding/margin/border-width/border-radius): a raw * value -> 1..4 Size leaves, or null to decline (the caller routes the declaration to custom_css). * It never touches the registry, context, or PropTypes; the caller maps the result onto its own keys. * * Tokenizing is paren-aware so function values such as calc(100% / 4) survive as one token (and are * parsed as a Size leaf). This also makes the elliptical border-radius form (a "/" separator) decline * for free: a bare "/" is an unparsable token, and "a / b" exceeds four tokens. * * - 1 token -> [ 'single' => <Size leaf> ] (the union's Size member) * - 2-4 tokens -> [ 'sides' => [ s0, s1, s2, s3 ] ] (expanded via the CSS box rule) * - 0 / >4 tokens, or any unparsable token -> null */ class Box_Shorthand_Parser { const MAX_SIDES = 4; /** * @return array{single: array}|array{sides: array<int, array>}|null */ public static function parse( string $value ): ?array { $tokens = Css_Token_Splitter::split_by_whitespace( trim( $value ) ); $count = count( $tokens ); if ( $count < 1 || $count > self::MAX_SIDES ) { return null; } $sizes = []; foreach ( $tokens as $token ) { $parsed = Size_Value_Parser::parse( $token ); if ( null === $parsed ) { return null; } $sizes[] = $parsed; } if ( 1 === $count ) { return [ 'single' => $sizes[0] ]; } return [ 'sides' => self::expand_box( $sizes ) ]; } /** * Expand 2..4 values onto four sides via the CSS box rule. The result is positional and the same * for every box shorthand; the caller assigns the four slots to its own keys. * * @param array<int, array> $sizes 2..4 parsed Size leaves. * @return array{0: array, 1: array, 2: array, 3: array} */ private static function expand_box( array $sizes ): array { switch ( count( $sizes ) ) { case 2: return [ $sizes[0], $sizes[1], $sizes[0], $sizes[1] ]; case 3: return [ $sizes[0], $sizes[1], $sizes[2], $sizes[1] ]; default: return [ $sizes[0], $sizes[1], $sizes[2], $sizes[3] ]; } } } atomic-widgets/css-converter/property-converter-base.php 0000644 00000002231 15252521350 0017571 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Property_Converter_Base implements Property_Converter { /** * Exact, enumerated property names this converter owns. * * @return string[] */ abstract protected function get_supported_properties(): array; public function is_supported( array $rule ): bool { $property = $rule['property'] ?? null; if ( ! is_string( $property ) || '' === $property ) { return false; } return in_array( $property, $this->get_supported_properties(), true ); } public function convert( Conversion_Context $context, array $rule ): bool { if ( null === $rule['value'] ) { return $this->convert_null( $context, $rule ); } return $this->do_convert( $context, $rule ); } /** * Override to customize null-reset behavior. Default: set the prop to null directly. */ protected function convert_null( Conversion_Context $context, array $rule ): bool { $context->set_prop( $rule['property'], null ); return true; } abstract protected function do_convert( Conversion_Context $context, array $rule ): bool; } atomic-widgets/css-converter/shorthand-expander-base.php 0000644 00000003156 15252521350 0017505 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Shorthand_Expander_Base implements Shorthand_Expander { /** * Exact, enumerated shorthand property names this expander owns. * * @return string[] */ abstract protected function get_supported_properties(): array; public function is_supported( array $rule ): bool { $property = $rule['property'] ?? null; if ( ! is_string( $property ) || '' === $property ) { return false; } return in_array( $property, $this->get_supported_properties(), true ); } public function expand( array $rule ): array { if ( null === $rule['value'] ) { return $this->expand_null( $rule ); } return $this->do_expand( $rule ); } /** * Override to fan out null resets to all longhand properties. * Default: re-emit the same property with a null value (covers simple renamers). * * @return array<int, array{property: string, value: null, declaration: string}> */ protected function expand_null( array $rule ): array { return [ $this->null_rule( $rule['property'] ) ]; } /** * @return array{property: string, value: null, declaration: string} */ protected function null_rule( string $property ): array { return [ 'property' => $property, 'value' => null, 'declaration' => $property . ': ', ]; } /** * @param array{property: string, value: string} $rule A rule with a guaranteed non-null value. * @return array<int, array{property: string, value: string, declaration: string}> */ abstract protected function do_expand( array $rule ): array; } atomic-widgets/css-converter/converter-registry-factory.php 0000644 00000045631 15252521350 0020325 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Background_Image_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Rejected_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Background_Layer_Field_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Background_Position_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Box_Shadow_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Border_Radius_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Color_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Dimensions_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Filter_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Flex_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Transform_Origin_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Transform_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Transition_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Noop_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Number_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Object_Field_Merge_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Object_Position_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Object_Side_Merge_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Size_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\Span_Property_Converter; use Elementor\Modules\AtomicWidgets\CssConverter\Converters\String_Property_Converter; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Overlay_Size_Scale_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Border_Radius_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Border_Width_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; use Elementor\Modules\Variables\Services\Variables_Service; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Converter_Registry_Factory { /** * Every Style_Schema property whose value is a single Size leaf (the schema prop is a Size_Prop_Type, * or a Union with a Size member like `gap` — a size PropValue validates against the union). All are * handled uniformly by Size_Property_Converter + Size_Value_Parser; per-property unit sets are not * enforced here because Size_Prop_Type::validate accepts any all_supported_units() unit. */ const SIZE_PROPERTIES = [ 'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height', 'inset-block-start', 'inset-inline-end', 'inset-block-end', 'inset-inline-start', 'scroll-margin-top', 'font-size', 'letter-spacing', 'word-spacing', 'column-gap', 'line-height', 'outline-width', 'outline-offset', 'opacity', 'gap', 'grid-auto-rows', 'grid-auto-columns', ]; /** * Size properties that also accept a unitless number (a multiplier, e.g. `line-height: 1.1`). The * value is kept verbatim as a `custom` unit so it renders without a unit; every other size property * declines a unitless non-zero value to custom_css. */ const UNITLESS_SIZE_PROPERTIES = [ 'line-height', ]; /** * Every Style_Schema property backed by a Number_Prop_Type. Handled uniformly by * Number_Property_Converter (strict numeric, no units/functions). */ const NUMBER_PROPERTIES = [ 'z-index', 'column-count', 'order', ]; /** * Every Style_Schema property backed by a plain Color_Prop_Type. Handled uniformly by * Color_Property_Converter (raw passthrough; any non-empty value is a valid color). */ const COLOR_PROPERTIES = [ 'color', 'border-color', 'outline-color', ]; /** * Every Style_Schema property backed by a Span_Prop_Type (grid placement). Handled uniformly by * Span_Property_Converter, with the validation regex sourced from the live schema. */ const SPAN_PROPERTIES = [ 'grid-column', 'grid-row', ]; /** * Union props whose string member accepts a raw value (e.g. Union(String | Grid_Track_Size)). A * free-string String_Property_Converter emits a `string` PropValue that validates against the union's * String member, covering `1fr 1fr`, `repeat(3, 1fr)`, `minmax(...)`, named lines, etc. The structured * member is intentionally not produced (raw passthrough, mirroring color). NOT wired via * STRING_PROPERTIES because the schema entry is a Union and has no get_enum(). */ const STRING_PASSTHROUGH_PROPERTIES = [ 'grid-template-columns', 'grid-template-rows', ]; /** * Box shorthands backed by Union(Dimensions | Size). Handled uniformly by * Dimensions_Property_Converter (single value -> Size; 2-4 values -> logical Dimensions). */ const DIMENSIONS_PROPERTIES = [ 'padding', 'margin', ]; /** * Props that each own a bespoke single-property converter (no shared family). Listed here for the * covered set; each is wired explicitly in real_converters() (unlike the family arrays above, the * members do not share a converter class, so there is no uniform loop). Distinct from * NOOP_PROPERTIES, which have no real converter yet. * * - border-radius: Union(Border_Radius | Size); single value -> Size, 2-4 values -> logical * Border_Radius. Elliptical "/" values decline to custom_css (no two-radii-per-corner shape). * - border-width: Union(Border_Width | Size); shares the four logical sides with the Dimensions * shorthand, so it reuses Dimensions_Property_Converter with the Border_Width wrapper injected. */ const OTHER_PROPERTIES = [ 'border-radius', 'border-width', 'object-position', 'flex', 'transition', 'transform', 'transform-origin', 'box-shadow', ]; /** * Scalar background longhands that each fill one field of the aggregate `background` object. Not * Style_Schema properties (the schema only has the `background` aggregate); they are accumulated into * it by Object_Field_Merge_Converter, wired explicitly in real_converters() so each field's leaf prop * type / enum is sourced from the live schema. The overlay array (image/gradient layers) is separate. */ const BACKGROUND_FIELD_PROPERTIES = [ 'background-color', 'background-clip', ]; /** * Background overlay longhands that are not Style_Schema properties. `background-image` creates the * image layer array in the aggregate; the rest update fields on existing layers via * Background_Layer_Field_Converter. Enums are hardcoded to match the background-image-overlay shape * without needing to instantiate the prop type (which would require WP for image sizes). */ const BACKGROUND_LAYER_PROPERTIES = [ 'background-image', 'background-repeat', 'background-attachment', 'background-size', 'background-position', ]; const BACKGROUND_REPEAT_ENUM = [ 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' ]; const BACKGROUND_ATTACHMENT_ENUM = [ 'fixed', 'scroll' ]; const BACKGROUND_SIZE_ENUM = [ 'auto', 'cover', 'contain' ]; /** * Logical side keys of the Border_Width / Dimensions objects, and corner keys of the Border_Radius * object, in the order used to seed every side/corner from a single Size. */ const BORDER_WIDTH_SIDE_KEYS = [ 'block-start', 'inline-end', 'block-end', 'inline-start' ]; const BORDER_RADIUS_CORNER_KEYS = [ 'start-start', 'start-end', 'end-end', 'end-start' ]; /** * Physical padding/margin longhands -> [ target schema prop, logical side key ]. * Not Style_Schema properties themselves; accumulated into the schema aggregate by * Object_Side_Merge_Converter (same pattern as border_side_specs()). */ const DIMENSIONS_SIDE_SPECS = [ 'padding-top' => [ 'padding', 'block-start' ], 'padding-right' => [ 'padding', 'inline-end' ], 'padding-bottom' => [ 'padding', 'block-end' ], 'padding-left' => [ 'padding', 'inline-start' ], 'padding-block-start' => [ 'padding', 'block-start' ], 'padding-block-end' => [ 'padding', 'block-end' ], 'padding-inline-start' => [ 'padding', 'inline-start' ], 'padding-inline-end' => [ 'padding', 'inline-end' ], 'margin-top' => [ 'margin', 'block-start' ], 'margin-right' => [ 'margin', 'inline-end' ], 'margin-bottom' => [ 'margin', 'block-end' ], 'margin-left' => [ 'margin', 'inline-start' ], 'margin-block-start' => [ 'margin', 'block-start' ], 'margin-block-end' => [ 'margin', 'block-end' ], 'margin-inline-start' => [ 'margin', 'inline-start' ], 'margin-inline-end' => [ 'margin', 'inline-end' ], ]; /** * Filter-function lists backed by Array(Css_Filter_Func) (filter, backdrop-filter). Handled * uniformly by Filter_Property_Converter + Filter_Value_Parser; the two share inner items and * differ only by the wrapping $$type, which is sourced from the live schema. */ const FILTER_PROPERTIES = [ 'filter', 'backdrop-filter', ]; /** * Hardcoded Style_Schema properties with no real converter yet (objects, unions, shorthands). They * still get a Noop_Converter so they keep routing to custom_css. Combined with the real-converter * families via covered_properties() to form the exhaustive covered set. Intentionally NOT derived * from Style_Schema: a coverage test diffs the live schema against the covered set so adding a schema * property without coverage fails CI until it is added here. */ const NOOP_PROPERTIES = [ // SVG-only family. The editor UI does not expose stroke controls, so there is no // LLM-facing use case worth converting. All stroke-* longhands are listed explicitly // (rather than relying on silent fallthrough) so the intent is "preserve verbatim in // customCss by-design", not "forgot to wire". `stroke` itself is the only top-level // Style_Schema entry; the longhands are not in the schema and only need coverage here. 'stroke', 'stroke-width', 'stroke-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', // Last-resort fallback for `background` values the shorthand expander cannot decompose // (e.g. exotic syntax). The expander handles the common forms; this entry keeps the // raw declaration in customCss when expansion fails. 'background', ]; /** * Properties that are structurally incompatible with Elementor's inline style system. * They are explicitly rejected (not routed to customCss) so the client can surface a * hint to the LLM that these constructs are unsupported in element style definitions. * * `animation` and its longhands rely on @keyframes which cannot be declared inline. */ const REJECTED_PROPERTIES = [ 'animation', 'animation-name', 'animation-duration', 'animation-timing-function', 'animation-delay', 'animation-iteration-count', 'animation-direction', 'animation-fill-mode', 'animation-play-state', ]; /** * Every Style_Schema property backed by a plain String_Prop_Type. Enum-backed and free-string * props are handled the same way: get_enum() returns the allowlist (enum props) or null * (free-string props), so the allowlist is always sourced from the schema, never duplicated. */ const STRING_PROPERTIES = [ 'overflow', 'aspect-ratio', 'object-fit', 'position', 'font-family', 'font-weight', 'text-align', 'font-style', 'text-decoration', 'text-transform', 'direction', 'all', 'cursor', 'border-style', 'outline-style', 'mix-blend-mode', 'display', 'flex-direction', 'flex-wrap', 'grid-auto-flow', 'justify-content', 'justify-items', 'align-content', 'align-items', 'align-self', 'content', 'appearance', 'clip-path', ]; /** * The exhaustive covered set: every family with a real converter plus the remaining no-ops. Single * source of truth for the coverage test, with no property listed twice. * * @return string[] */ public static function covered_properties(): array { return array_merge( self::STRING_PROPERTIES, self::SIZE_PROPERTIES, self::NUMBER_PROPERTIES, self::COLOR_PROPERTIES, self::SPAN_PROPERTIES, self::STRING_PASSTHROUGH_PROPERTIES, self::DIMENSIONS_PROPERTIES, self::FILTER_PROPERTIES, self::OTHER_PROPERTIES, array_keys( self::border_side_specs() ), self::BACKGROUND_FIELD_PROPERTIES, self::BACKGROUND_LAYER_PROPERTIES, self::NOOP_PROPERTIES, self::REJECTED_PROPERTIES ); } /** * Per-side/per-corner border longhands that each contribute one fragment to an aggregate object prop * (border-width / border-radius), keyed by input property -> [ target prop, object key ]. These are * not Style_Schema properties; they are accumulated into the schema aggregate by * Object_Side_Merge_Converter. Per-side style/color have no faithful single-valued representation, so * they are intentionally absent and route to custom_css. * * @return array<string, array{0: string, 1: string}> */ private static function border_side_specs(): array { return [ 'border-top-width' => [ 'border-width', 'block-start' ], 'border-right-width' => [ 'border-width', 'inline-end' ], 'border-bottom-width' => [ 'border-width', 'block-end' ], 'border-left-width' => [ 'border-width', 'inline-start' ], 'border-top-left-radius' => [ 'border-radius', 'start-start' ], 'border-top-right-radius' => [ 'border-radius', 'start-end' ], 'border-bottom-right-radius' => [ 'border-radius', 'end-end' ], 'border-bottom-left-radius' => [ 'border-radius', 'end-start' ], ]; } public static function create( ?Variables_Service $variables_service = null ): Converter_Registry { $registry = new Converter_Registry(); $real_converters = self::real_converters( $variables_service ); foreach ( $real_converters as $converter ) { $registry->register( $converter ); } foreach ( self::REJECTED_PROPERTIES as $property ) { $registry->register( new Rejected_Converter( $property ) ); } foreach ( self::covered_properties() as $property ) { if ( isset( $real_converters[ $property ] ) ) { continue; } if ( in_array( $property, self::REJECTED_PROPERTIES, true ) ) { continue; } $registry->register( new Noop_Converter( $property ) ); } return $registry; } /** * Real converters keyed by the property they own. Every remaining covered_properties() entry * falls back to a Noop_Converter, so the "exactly one converter per property" invariant holds. * Enum allowlists are sourced from the live Style_Schema to avoid duplicating (and drifting from) * the schema's enums. * * @return array<string, \Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter> */ private static function real_converters( ?Variables_Service $variables_service = null ): array { $schema = Style_Schema::get_style_schema(); $converters = []; foreach ( self::STRING_PROPERTIES as $property ) { $converters[ $property ] = new String_Property_Converter( $property, $schema[ $property ]->get_enum() ); } foreach ( self::SIZE_PROPERTIES as $property ) { $allow_unitless = in_array( $property, self::UNITLESS_SIZE_PROPERTIES, true ); $converters[ $property ] = new Size_Property_Converter( $property, $allow_unitless ); } foreach ( self::NUMBER_PROPERTIES as $property ) { $converters[ $property ] = new Number_Property_Converter( $property ); } foreach ( self::COLOR_PROPERTIES as $property ) { $converters[ $property ] = new Color_Property_Converter( $property ); } foreach ( self::SPAN_PROPERTIES as $property ) { $converters[ $property ] = new Span_Property_Converter( $property, $schema[ $property ]->get_regex() ); } foreach ( self::STRING_PASSTHROUGH_PROPERTIES as $property ) { $converters[ $property ] = new String_Property_Converter( $property ); } foreach ( self::DIMENSIONS_PROPERTIES as $property ) { $converters[ $property ] = new Dimensions_Property_Converter( $property ); } $converters['border-radius'] = new Border_Radius_Property_Converter( 'border-radius' ); $converters['border-width'] = new Dimensions_Property_Converter( 'border-width', Border_Width_Prop_Type::class ); $converters['object-position'] = new Object_Position_Property_Converter(); $converters['flex'] = new Flex_Property_Converter(); $converters['transition'] = new Transition_Property_Converter(); $converters['transform'] = new Transform_Property_Converter(); $converters['transform-origin'] = new Transform_Origin_Property_Converter(); $converters['box-shadow'] = new Box_Shadow_Property_Converter(); foreach ( self::DIMENSIONS_SIDE_SPECS as $property => [ $target, $side_key ] ) { $converters[ $property ] = new Object_Side_Merge_Converter( $property, $target, Dimensions_Prop_Type::get_key(), $side_key, self::BORDER_WIDTH_SIDE_KEYS, Dimensions_Prop_Type::class, $variables_service ); } foreach ( self::border_side_specs() as $property => [ $target, $side_key ] ) { $is_radius = 'border-radius' === $target; $converters[ $property ] = new Object_Side_Merge_Converter( $property, $target, $target, $side_key, $is_radius ? self::BORDER_RADIUS_CORNER_KEYS : self::BORDER_WIDTH_SIDE_KEYS, $is_radius ? Border_Radius_Prop_Type::class : Border_Width_Prop_Type::class, $variables_service ); } foreach ( self::FILTER_PROPERTIES as $property ) { $converters[ $property ] = new Filter_Property_Converter( $property, $schema[ $property ]->get_key() ); } $background_key = Background_Prop_Type::get_key(); $converters['background-color'] = new Object_Field_Merge_Converter( 'background-color', $background_key, $background_key, 'color', Color_Prop_Type::class, Background_Prop_Type::class, null, true ); $converters['background-clip'] = new Object_Field_Merge_Converter( 'background-clip', $background_key, $background_key, 'clip', String_Prop_Type::class, Background_Prop_Type::class, $schema[ $background_key ]->get_shape_field( 'clip' )->get_enum() ); $converters['background-image'] = new Background_Image_Converter(); $converters['background-repeat'] = new Background_Layer_Field_Converter( 'background-repeat', 'repeat', self::BACKGROUND_REPEAT_ENUM ); $converters['background-attachment'] = new Background_Layer_Field_Converter( 'background-attachment', 'attachment', self::BACKGROUND_ATTACHMENT_ENUM ); $converters['background-size'] = new Background_Layer_Field_Converter( 'background-size', 'size', self::BACKGROUND_SIZE_ENUM, Background_Image_Overlay_Size_Scale_Prop_Type::class, [ 'width', 'height' ] ); $converters['background-position'] = new Background_Position_Property_Converter(); return $converters; } } atomic-widgets/css-converter/converter-registry.php 0000644 00000001175 15252521350 0016653 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Converter_Registry { /** * @var Property_Converter[] */ private array $converters = []; public function register( Property_Converter $converter ): self { $this->converters[] = $converter; return $this; } /** * Converters in registration order. The dispatcher iterates these and applies * the try-until-success flow (is_supported -> convert -> fallthrough on failure). * * @return Property_Converter[] */ public function all(): array { return $this->converters; } } atomic-widgets/css-converter/conversion-context.php 0000644 00000003171 15252521350 0016643 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Conversion_Context { private array $props = []; private array $rejected = []; private array $rules; private array $global_variables; /** * @param array<int, array{property: string, value: string}> $rules The full set of sibling declarations. * @param array $global_variables Wired for forward compatibility, empty in v1. */ public function __construct( array $rules = [], array $global_variables = [] ) { $this->rules = $rules; $this->global_variables = $global_variables; } /** * @return array<int, array{property: string, value: string}> */ public function get_rules(): array { return $this->rules; } public function get_global_variables(): array { return $this->global_variables; } public function get_props(): array { return $this->props; } public function has_prop( string $property ): bool { return array_key_exists( $property, $this->props ); } /** * @return mixed */ public function get_prop( string $property ) { return $this->props[ $property ] ?? null; } /** * @param string $property The output property name the converter owns. * @param mixed $value The canonical PropValue contributed for the property. */ public function set_prop( string $property, $value ): void { $this->props[ $property ] = $value; } public function reject( string $declaration ): void { $this->rejected[] = $declaration; } public function get_rejected(): array { return $this->rejected; } } atomic-widgets/css-converter/converters/transition-property-converter.php 0000644 00000007561 15252521350 0023256 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Key_Value_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Selection_Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transition_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for the `transition` CSS property -> Transition_Prop_Type (array of Selection_Size). * * Parses comma-separated transition layers. Each layer is whitespace-split into tokens: * <property> <duration> [<easing>] [<delay>] * * Only the property name and the FIRST duration/delay time value are mapped — the schema has no * field for easing or delay, so they are intentionally dropped (silently). * * The `property` token must be in ALLOWED_PROPERTIES (sourced from the Elementor UI data). * Layers with an unrecognised property decline the entire declaration to customCss. * * Time values must be in `s` or `ms`; any other unit declines the layer. */ class Transition_Property_Converter extends Property_Converter_Base { const ALLOWED_PROPERTIES = [ 'all', 'background-color', 'background-position', 'border', 'border-color', 'border-radius', 'border-width', 'box-shadow', 'color', 'filter', 'flex', 'flex-basis', 'flex-grow', 'flex-shrink', 'font-size', 'font-variation-settings', 'height', 'inset-block-end', 'inset-block-start', 'inset-inline-end', 'inset-inline-start', 'letter-spacing', 'line-height', 'margin', 'margin-block-end', 'margin-block-start', 'margin-inline-end', 'margin-inline-start', 'max-height', 'max-width', 'min-height', 'min-width', 'opacity', 'padding', 'padding-block-end', 'padding-block-start', 'padding-inline-end', 'padding-inline-start', 'transform', 'width', 'word-spacing', '-webkit-text-stroke-color', 'z-index', ]; const TIME_UNITS = [ 's', 'ms' ]; protected function get_supported_properties(): array { return [ 'transition' ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $layers = Css_Token_Splitter::split_by_comma( trim( $rule['value'] ) ); if ( empty( $layers ) ) { return false; } $items = []; foreach ( $layers as $layer ) { $item = $this->parse_layer( trim( $layer ) ); if ( null === $item ) { return false; } $items[] = $item; } $context->set_prop( 'transition', Transition_Prop_Type::generate( $items ) ); return true; } private function parse_layer( string $layer ): ?array { $tokens = Css_Token_Splitter::split_by_whitespace( $layer ); if ( empty( $tokens ) ) { return null; } $property = strtolower( $tokens[0] ); if ( ! in_array( $property, self::ALLOWED_PROPERTIES, true ) ) { return null; } $duration = $this->find_first_time( array_slice( $tokens, 1 ) ); if ( null === $duration ) { return null; } return Selection_Size_Prop_Type::generate( [ 'selection' => Key_Value_Prop_Type::generate( [ 'key' => String_Prop_Type::generate( $property ), 'value' => String_Prop_Type::generate( $property ), ] ), 'size' => Size_Prop_Type::generate( $duration ), ] ); } private function find_first_time( array $tokens ): ?array { foreach ( $tokens as $token ) { $parsed = Size_Value_Parser::parse( $token ); if ( null !== $parsed && in_array( $parsed['unit'], self::TIME_UNITS, true ) ) { return $parsed; } } return null; } } atomic-widgets/css-converter/converters/background-image-converter.php 0000644 00000004527 15252521350 0022400 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Background_Image_Value_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for `background-image`. Delegates parsing to Background_Image_Value_Parser which returns * fully-constructed overlay PropValues (image overlays for url(), gradient overlays for linear/radial- * gradient()). The resulting ordered list is stored as `background-overlay` in the `background` * aggregate context object, preserving any existing scalar fields (color, clip). * * Sibling background longhands processed afterwards (background-repeat, background-size, etc.) read * and update the image-overlay items via Background_Layer_Field_Converter. */ class Background_Image_Converter extends Property_Converter_Base { protected function get_supported_properties(): array { return [ 'background-image' ]; } protected function convert_null( Conversion_Context $context, array $rule ): bool { $fields = $this->current_background_fields( $context->get_prop( 'background' ) ); $fields['background-overlay'] = null; $context->set_prop( 'background', Background_Prop_Type::generate( $fields ) ); return true; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $overlays = Background_Image_Value_Parser::parse( trim( $rule['value'] ) ); if ( null === $overlays ) { return false; } $fields = $this->current_background_fields( $context->get_prop( 'background' ) ); $fields['background-overlay'] = Background_Overlay_Prop_Type::generate( $overlays ); $context->set_prop( 'background', Background_Prop_Type::generate( $fields ) ); return true; } private function current_background_fields( $existing ): array { if ( ! is_array( $existing ) ) { return []; } $type = $existing['$$type'] ?? null; if ( Background_Prop_Type::get_key() === $type && is_array( $existing['value'] ?? null ) ) { return $existing['value']; } return []; } } atomic-widgets/css-converter/converters/string-property-converter.php 0000644 00000003160 15252521350 0022361 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Reusable converter for properties backed by a plain String_Prop_Type. One instance per property. * When an allowlist is provided the value must be one of it (enum-backed props); otherwise any * non-empty value is accepted (free-string props). Emits the canonical PropValue from generate(). */ class String_Property_Converter extends Property_Converter_Base { private string $property; /** * @var string[]|null */ private ?array $allowed_values; /** * @param string $property The schema property this converter owns. * @param string[]|null $allowed_values Enum allowlist, or null for a free-string property. */ public function __construct( string $property, ?array $allowed_values = null ) { $this->property = $property; $this->allowed_values = $allowed_values; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $value = trim( $rule['value'] ); if ( '' === $value ) { return false; } if ( null !== $this->allowed_values && ! in_array( $value, $this->allowed_values, true ) ) { return false; } $context->set_prop( $this->property, String_Prop_Type::generate( $value ) ); return true; } } atomic-widgets/css-converter/converters/transform-property-converter.php 0000644 00000024630 15252521350 0023073 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Move_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Rotate_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Scale_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Functions_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for the `transform` CSS property -> Transform_Prop_Type. * * Supported CSS functions -> schema type: * translate(x) -> transform-move {x, y:0, z:0} * translate(x, y) -> transform-move {x, y, z:0} * translateX(n) -> transform-move {x:n, y:0, z:0} * translateY(n) -> transform-move {x:0, y:n, z:0} * translateZ(n) -> transform-move {x:0, y:0, z:n} * translate3d(x, y, z) -> transform-move {x, y, z} * scale(n) -> transform-scale {x:n, y:n, z:1} * scale(x, y) -> transform-scale {x, y, z:1} * scaleX(n) -> transform-scale {x:n, y:1, z:1} * scaleY(n) -> transform-scale {x:1, y:n, z:1} * scaleZ(n) -> transform-scale {x:1, y:1, z:n} * scale3d(x, y, z) -> transform-scale {x, y, z} * rotate(a) -> transform-rotate {x:0, y:0, z:a} * rotateX(a) -> transform-rotate {x:a, y:0, z:0} * rotateY(a) -> transform-rotate {x:0, y:a, z:0} * rotateZ(a) -> transform-rotate {x:0, y:0, z:a} * rotate3d not supported -> decline * matrix / perspective / skew / etc -> decline * * Move units: % px em rem vw custom * Rotate units: deg rad grad turn custom * Scale values: unitless numbers (floats) * * Any unrecognised function declines the entire declaration to customCss. */ class Transform_Property_Converter extends Property_Converter_Base { const MOVE_UNITS = [ '%', 'px', 'em', 'rem', 'vw', 'custom' ]; const ROTATE_UNITS = [ 'deg', 'rad', 'grad', 'turn', 'custom' ]; const ZERO_MOVE = [ 'size' => 0, 'unit' => 'px', ]; const ZERO_ROTATE = [ 'size' => 0, 'unit' => 'deg', ]; const ONE_SCALE = 1.0; protected function get_supported_properties(): array { return [ 'transform' ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $functions = $this->parse_functions( trim( $rule['value'] ) ); if ( null === $functions ) { return false; } $existing = $context->get_prop( 'transform' ); $fields = $this->current_fields( $existing ); $fields['transform-functions'] = Transform_Functions_Prop_Type::generate( $functions ); $context->set_prop( 'transform', Transform_Prop_Type::generate( $fields ) ); return true; } private function parse_functions( string $value ): ?array { if ( '' === $value || 'none' === strtolower( $value ) ) { return []; } $raw_functions = $this->split_functions( $value ); if ( null === $raw_functions ) { return null; } $items = []; foreach ( $raw_functions as $fn ) { $item = $this->parse_function( $fn ); if ( null === $item ) { return null; } $items[] = $item; } return $items; } private function split_functions( string $value ): ?array { $functions = []; $current = ''; $depth = 0; for ( $i = 0, $len = strlen( $value ); $i < $len; $i++ ) { $char = $value[ $i ]; if ( '(' === $char ) { ++$depth; } elseif ( ')' === $char ) { --$depth; if ( 0 === $depth ) { $current .= $char; $functions[] = trim( $current ); $current = ''; ++$i; while ( $i < $len && ' ' === $value[ $i ] ) { ++$i; } --$i; continue; } } $current .= $char; } if ( '' !== trim( $current ) || 0 !== $depth ) { return null; } return $functions; } private function parse_function( string $callback ): ?array { if ( ! preg_match( '/^([\w-]+)\s*\((.+)\)$/s', $callback, $m ) ) { return null; } $name = strtolower( $m[1] ); $args_str = $m[2]; $args = array_map( 'trim', Css_Token_Splitter::split_by_comma( $args_str ) ); switch ( $name ) { case 'translate': return $this->parse_translate( $args ); case 'translatex': return $this->parse_translate_axis( $args, 'x' ); case 'translatey': return $this->parse_translate_axis( $args, 'y' ); case 'translatez': return $this->parse_translate_axis( $args, 'z' ); case 'translate3d': return $this->parse_translate_3d( $args ); case 'scale': return $this->parse_scale( $args ); case 'scalex': return $this->parse_scale_axis( $args, 'x' ); case 'scaley': return $this->parse_scale_axis( $args, 'y' ); case 'scalez': return $this->parse_scale_axis( $args, 'z' ); case 'scale3d': return $this->parse_scale_3d( $args ); case 'rotate': case 'rotatez': return $this->parse_rotate_axis( $args, 'z' ); case 'rotatex': return $this->parse_rotate_axis( $args, 'x' ); case 'rotatey': return $this->parse_rotate_axis( $args, 'y' ); default: return null; } } private function parse_translate( array $args ): ?array { if ( 1 === count( $args ) ) { $x = $this->move_size( $args[0] ); if ( null === $x ) { return null; } return Transform_Move_Prop_Type::generate( [ 'x' => Size_Prop_Type::generate( $x ), 'y' => Size_Prop_Type::generate( self::ZERO_MOVE ), 'z' => Size_Prop_Type::generate( self::ZERO_MOVE ), ] ); } if ( 2 === count( $args ) ) { $x = $this->move_size( $args[0] ); $y = $this->move_size( $args[1] ); if ( null === $x || null === $y ) { return null; } return Transform_Move_Prop_Type::generate( [ 'x' => Size_Prop_Type::generate( $x ), 'y' => Size_Prop_Type::generate( $y ), 'z' => Size_Prop_Type::generate( self::ZERO_MOVE ), ] ); } return null; } private function parse_translate_axis( array $args, string $axis ): ?array { if ( 1 !== count( $args ) ) { return null; } $val = $this->move_size( $args[0] ); if ( null === $val ) { return null; } return Transform_Move_Prop_Type::generate( [ 'x' => Size_Prop_Type::generate( 'x' === $axis ? $val : self::ZERO_MOVE ), 'y' => Size_Prop_Type::generate( 'y' === $axis ? $val : self::ZERO_MOVE ), 'z' => Size_Prop_Type::generate( 'z' === $axis ? $val : self::ZERO_MOVE ), ] ); } private function parse_translate_3d( array $args ): ?array { if ( 3 !== count( $args ) ) { return null; } $x = $this->move_size( $args[0] ); $y = $this->move_size( $args[1] ); $z = $this->move_size( $args[2] ); if ( null === $x || null === $y || null === $z ) { return null; } return Transform_Move_Prop_Type::generate( [ 'x' => Size_Prop_Type::generate( $x ), 'y' => Size_Prop_Type::generate( $y ), 'z' => Size_Prop_Type::generate( $z ), ] ); } private function parse_scale( array $args ): ?array { if ( 1 === count( $args ) ) { $n = $this->scale_number( $args[0] ); if ( null === $n ) { return null; } return Transform_Scale_Prop_Type::generate( [ 'x' => Number_Prop_Type::generate( $n ), 'y' => Number_Prop_Type::generate( $n ), 'z' => Number_Prop_Type::generate( self::ONE_SCALE ), ] ); } if ( 2 === count( $args ) ) { $x = $this->scale_number( $args[0] ); $y = $this->scale_number( $args[1] ); if ( null === $x || null === $y ) { return null; } return Transform_Scale_Prop_Type::generate( [ 'x' => Number_Prop_Type::generate( $x ), 'y' => Number_Prop_Type::generate( $y ), 'z' => Number_Prop_Type::generate( self::ONE_SCALE ), ] ); } return null; } private function parse_scale_axis( array $args, string $axis ): ?array { if ( 1 !== count( $args ) ) { return null; } $n = $this->scale_number( $args[0] ); if ( null === $n ) { return null; } return Transform_Scale_Prop_Type::generate( [ 'x' => Number_Prop_Type::generate( 'x' === $axis ? $n : self::ONE_SCALE ), 'y' => Number_Prop_Type::generate( 'y' === $axis ? $n : self::ONE_SCALE ), 'z' => Number_Prop_Type::generate( 'z' === $axis ? $n : self::ONE_SCALE ), ] ); } private function parse_scale_3d( array $args ): ?array { if ( 3 !== count( $args ) ) { return null; } $x = $this->scale_number( $args[0] ); $y = $this->scale_number( $args[1] ); $z = $this->scale_number( $args[2] ); if ( null === $x || null === $y || null === $z ) { return null; } return Transform_Scale_Prop_Type::generate( [ 'x' => Number_Prop_Type::generate( $x ), 'y' => Number_Prop_Type::generate( $y ), 'z' => Number_Prop_Type::generate( $z ), ] ); } private function parse_rotate_axis( array $args, string $axis ): ?array { if ( 1 !== count( $args ) ) { return null; } $val = $this->rotate_size( $args[0] ); if ( null === $val ) { return null; } return Transform_Rotate_Prop_Type::generate( [ 'x' => Size_Prop_Type::generate( 'x' === $axis ? $val : self::ZERO_ROTATE ), 'y' => Size_Prop_Type::generate( 'y' === $axis ? $val : self::ZERO_ROTATE ), 'z' => Size_Prop_Type::generate( 'z' === $axis ? $val : self::ZERO_ROTATE ), ] ); } private function move_size( string $token ): ?array { $parsed = Size_Value_Parser::parse( $token ); if ( null === $parsed || ! in_array( $parsed['unit'], self::MOVE_UNITS, true ) ) { return null; } return $parsed; } private function rotate_size( string $token ): ?array { $parsed = Size_Value_Parser::parse( $token ); if ( null === $parsed || ! in_array( $parsed['unit'], self::ROTATE_UNITS, true ) ) { return null; } return $parsed; } private function scale_number( string $token ): ?float { if ( ! is_numeric( $token ) ) { return null; } return (float) $token; } private function current_fields( $existing ): array { if ( is_array( $existing ) && isset( $existing['value'] ) ) { return $existing['value']; } return []; } } atomic-widgets/css-converter/converters/border-radius-property-converter.php 0000644 00000004357 15252521350 0023626 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Box_Shorthand_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Border_Radius_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Reusable converter for border-radius (Union(Border_Radius | Size)). One instance per property. * Delegates tokenizing/expansion to Box_Shorthand_Parser; a null parse declines (-> custom_css). * * A single token emits the Size member. Two-to-four tokens expand via the CSS corner rule * (top-left/top-right/bottom-right/bottom-left) and map physical->logical (TL=start-start, * TR=start-end, BR=end-end, BL=end-start) into a Border_Radius PropValue. Elliptical values (a "/" * separating horizontal/vertical radii) cannot be modeled by the single-Size-per-corner shape, so * they decline to custom_css — the parser already rejects the "/" form while keeping calc() division * (a single paren-wrapped token) convertible. */ class Border_Radius_Property_Converter extends Property_Converter_Base { private string $property; public function __construct( string $property ) { $this->property = $property; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $parsed = Box_Shorthand_Parser::parse( $rule['value'] ); if ( null === $parsed ) { return false; } if ( isset( $parsed['single'] ) ) { $context->set_prop( $this->property, Size_Prop_Type::generate( $parsed['single'] ) ); return true; } [ $top_left, $top_right, $bottom_right, $bottom_left ] = $parsed['sides']; $context->set_prop( $this->property, Border_Radius_Prop_Type::generate( [ 'start-start' => Size_Prop_Type::generate( $top_left ), 'start-end' => Size_Prop_Type::generate( $top_right ), 'end-end' => Size_Prop_Type::generate( $bottom_right ), 'end-start' => Size_Prop_Type::generate( $bottom_left ), ] ) ); return true; } } atomic-widgets/css-converter/converters/object-field-merge-converter.php 0000644 00000011115 15252521350 0022614 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for a single scalar longhand that contributes one field to a flat aggregate object prop, * e.g. background-color -> the `color` field of `background` (Background), background-clip -> `clip`. * One instance per input property. * * The target object is accumulated in the shared context across sibling declarations: each instance * reads the current target prop, re-populates it when it is already this object, or starts a fresh one * otherwise, then writes the single field it owns. Unlike Object_Side_Merge_Converter there is no * single-member seeding because the aggregate has no scalar union member. * * The leaf is produced by the injected leaf prop type's generate(). An optional allowlist rejects * out-of-enum values, and an optional single-token guard rejects multi-token values (a faithful single * color must be one paren-aware token). A rejected value declines the declaration (-> custom_css) and * leaves the accumulated object untouched. */ class Object_Field_Merge_Converter extends Property_Converter_Base { private string $property; private string $target_property; private string $type_key; private string $field_key; /** * @var class-string */ private string $leaf_prop_type; /** * @var class-string */ private string $object_prop_type; /** * @var string[]|null */ private ?array $allowed_values; private bool $single_token_only; /** * @param string $property The input longhand this converter owns (e.g. background-color). * @param string $target_property The aggregate prop it contributes to (e.g. background). * @param string $type_key The aggregate object's $$type (e.g. background). * @param string $field_key The object field this longhand fills (e.g. color). * @param string $leaf_prop_type Prop_Type class whose generate() wraps the leaf value. * @param string $object_prop_type Object_Prop_Type class used to wrap the merged fields. * @param string[]|null $allowed_values Enum allowlist for the leaf, or null to accept any value. * @param bool $single_token_only Reject values that split into more than one top-level token. */ public function __construct( string $property, string $target_property, string $type_key, string $field_key, string $leaf_prop_type, string $object_prop_type, ?array $allowed_values = null, bool $single_token_only = false ) { $this->property = $property; $this->target_property = $target_property; $this->type_key = $type_key; $this->field_key = $field_key; $this->leaf_prop_type = $leaf_prop_type; $this->object_prop_type = $object_prop_type; $this->allowed_values = $allowed_values; $this->single_token_only = $single_token_only; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function convert_null( Conversion_Context $context, array $rule ): bool { $fields = $this->current_fields( $context->get_prop( $this->target_property ) ); $fields[ $this->field_key ] = null; $context->set_prop( $this->target_property, ( $this->object_prop_type )::generate( $fields ) ); return true; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $value = trim( $rule['value'] ); if ( '' === $value ) { return false; } if ( null !== $this->allowed_values && ! in_array( $value, $this->allowed_values, true ) ) { return false; } if ( $this->single_token_only && 1 !== count( Css_Token_Splitter::split_by_whitespace( $value ) ) ) { return false; } $fields = $this->current_fields( $context->get_prop( $this->target_property ) ); $fields[ $this->field_key ] = ( $this->leaf_prop_type )::generate( $value ); $context->set_prop( $this->target_property, ( $this->object_prop_type )::generate( $fields ) ); return true; } /** * @param mixed $existing The current target prop value, if any. * @return array<string, array> Field key -> leaf PropValue. */ private function current_fields( $existing ): array { if ( ! is_array( $existing ) ) { return []; } $type = $existing['$$type'] ?? null; if ( $this->type_key === $type && is_array( $existing['value'] ?? null ) ) { return $existing['value']; } return []; } } atomic-widgets/css-converter/converters/box-shadow-property-converter.php 0000644 00000007606 15252521350 0023137 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Box_Shadow_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Shadow_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for the `box-shadow` CSS property -> Box_Shadow_Prop_Type (array of Shadow_Prop_Type). * * Per-layer CSS grammar: * <shadow> = <color>? && [<length>{2,4}] && inset? * * Token classification (paren-aware so rgb(255 0 0) stays one token): * - `inset` -> position keyword * - parses as Size -> length token * - anything else -> color (at most one per layer) * * Length mapping by count: * 2 -> hOffset, vOffset * 3 -> hOffset, vOffset, blur * 4 -> hOffset, vOffset, blur, spread * Any other count, multiple colors, or unrecognised tokens decline the entire declaration to custom_css. * * Defaults: * - missing color -> `currentColor` (CSS spec default) * - missing blur/spread -> 0 px * * Special values: * - `none` -> empty Box_Shadow array (clears the prop). */ class Box_Shadow_Property_Converter extends Property_Converter_Base { const INSET_KEYWORD = 'inset'; const DEFAULT_COLOR = 'currentColor'; const ZERO_SIZE = [ 'size' => 0, 'unit' => 'px', ]; protected function get_supported_properties(): array { return [ 'box-shadow' ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $value = trim( $rule['value'] ); if ( '' === $value ) { return false; } if ( 'none' === strtolower( $value ) ) { $context->set_prop( 'box-shadow', Box_Shadow_Prop_Type::generate( [] ) ); return true; } $layers = Css_Token_Splitter::split_by_comma( $value ); $shadows = []; foreach ( $layers as $layer ) { $shadow = $this->parse_layer( trim( $layer ) ); if ( null === $shadow ) { return false; } $shadows[] = $shadow; } $context->set_prop( 'box-shadow', Box_Shadow_Prop_Type::generate( $shadows ) ); return true; } private function parse_layer( string $layer ): ?array { $tokens = Css_Token_Splitter::split_by_whitespace( $layer ); if ( empty( $tokens ) ) { return null; } $lengths = []; $color = null; $is_inset = false; foreach ( $tokens as $token ) { if ( self::INSET_KEYWORD === strtolower( $token ) ) { if ( $is_inset ) { return null; } $is_inset = true; continue; } $size = Size_Value_Parser::parse( $token ); if ( null !== $size ) { $lengths[] = $size; continue; } if ( null !== $color ) { return null; } $color = $token; } $length_count = count( $lengths ); if ( $length_count < 2 || $length_count > 4 ) { return null; } return Shadow_Prop_Type::generate( $this->build_shadow_fields( $lengths, $color, $is_inset ) ); } private function build_shadow_fields( array $lengths, ?string $color, bool $is_inset ): array { $fields = [ 'hOffset' => Size_Prop_Type::generate( $lengths[0] ), 'vOffset' => Size_Prop_Type::generate( $lengths[1] ), 'blur' => Size_Prop_Type::generate( $lengths[2] ?? self::ZERO_SIZE ), 'spread' => Size_Prop_Type::generate( $lengths[3] ?? self::ZERO_SIZE ), 'color' => Color_Prop_Type::generate( $color ?? self::DEFAULT_COLOR ), ]; if ( $is_inset ) { $fields['position'] = String_Prop_Type::generate( self::INSET_KEYWORD ); } return $fields; } } atomic-widgets/css-converter/converters/object-side-merge-converter.php 0000644 00000011255 15252521350 0022462 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\Css_Var_Token_Resolver; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\Variables\Services\Variables_Service; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for a single side/corner longhand that contributes a fragment to a multi-side object prop: * border-{side}-width -> a side of `border-width` (Border_Width), border-{corner}-radius -> a corner of * `border-radius` (Border_Radius). One instance per input property. * * The target object is accumulated in the shared context across sibling declarations: each instance * reads the current target prop and re-populates it. If the target is already this object it merges the * one side in; if it is the single Size member (e.g. a prior `border-width: 1px`) all sides are seeded * from that single before the override, so the CSS cascade stays faithful; otherwise a fresh object is * created. A value the Size parser rejects declines the declaration (-> custom_css) and leaves the * accumulated object untouched. */ class Object_Side_Merge_Converter extends Property_Converter_Base { const SIZE_TYPE = 'size'; private string $property; private string $target_property; private string $type_key; private string $side_key; /** * @var string[] */ private array $all_side_keys; /** * @var class-string */ private string $object_prop_type; private ?Variables_Service $variables_service; /** * @param string $property The input longhand this converter owns (e.g. border-top-width). * @param string $target_property The aggregate prop it contributes to (e.g. border-width). * @param string $type_key The aggregate object's $$type (e.g. border-width). * @param string $side_key The object key this longhand fills (e.g. block-start). * @param string[] $all_side_keys Every key of the object, used to seed from a single Size. * @param string $object_prop_type Object_Prop_Type class used to wrap the merged sides. * @param Variables_Service|null $variables_service When provided, a var-only value that resolves to a * known size variable is emitted as a variable PropValue * instead of a raw Size leaf. */ public function __construct( string $property, string $target_property, string $type_key, string $side_key, array $all_side_keys, string $object_prop_type, ?Variables_Service $variables_service = null ) { $this->property = $property; $this->target_property = $target_property; $this->type_key = $type_key; $this->side_key = $side_key; $this->all_side_keys = $all_side_keys; $this->object_prop_type = $object_prop_type; $this->variables_service = $variables_service; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function convert_null( Conversion_Context $context, array $rule ): bool { $sides = $this->current_sides( $context->get_prop( $this->target_property ) ); $sides[ $this->side_key ] = null; $context->set_prop( $this->target_property, ( $this->object_prop_type )::generate( $sides ) ); return true; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $value = trim( $rule['value'] ); $leaf = Size_Value_Parser::parse( $value ); if ( null === $leaf ) { return false; } $side_value = Css_Var_Token_Resolver::resolve_size_var_prop_value( $this->variables_service, $value ) ?? Size_Prop_Type::generate( $leaf ); $sides = $this->current_sides( $context->get_prop( $this->target_property ) ); $sides[ $this->side_key ] = $side_value; $context->set_prop( $this->target_property, ( $this->object_prop_type )::generate( $sides ) ); return true; } /** * @param mixed $existing The current target prop value, if any. * @return array<string, array> Side key -> Size PropValue. */ private function current_sides( $existing ): array { if ( ! is_array( $existing ) ) { return []; } $type = $existing['$$type'] ?? null; if ( $this->type_key === $type && is_array( $existing['value'] ?? null ) ) { return $existing['value']; } if ( self::SIZE_TYPE === $type ) { return array_fill_keys( $this->all_side_keys, $existing ); } return []; } } atomic-widgets/css-converter/converters/filter-property-converter.php 0000644 00000002470 15252521350 0022343 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Filter_Value_Parser; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Reusable converter for the filter-function lists backed by Array(Css_Filter_Func) (filter/ * backdrop-filter). One instance per property; the wrapping $$type differs only by key, so it is * injected. Delegates the whole value to Filter_Value_Parser; a null parse declines (-> custom_css). */ class Filter_Property_Converter extends Property_Converter_Base { private string $property; private string $type_key; public function __construct( string $property, string $type_key ) { $this->property = $property; $this->type_key = $type_key; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $items = Filter_Value_Parser::parse( $rule['value'] ); if ( null === $items ) { return false; } $context->set_prop( $this->property, [ '$$type' => $this->type_key, 'value' => $items, ] ); return true; } } atomic-widgets/css-converter/converters/background-layer-field-converter.php 0000644 00000013115 15252521350 0023504 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Overlay_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for scalar sub-layer longhands of `background-image`: background-repeat, * background-attachment, background-size, background-position. One instance per CSS property. * * Reads the ordered list of `background-image-overlay` items currently in the `background` context * object. If no image layers exist yet the declaration is declined (-> custom_css) because there is * nothing to attach the value to. CSS comma-separated lists are correlated to layers by position: a * single value applies to all layers; multiple values are distributed 1:1. A mismatch in count * declines the declaration. * * Each per-layer token is validated first against an optional string enum (e.g. repeat, cover). * When a token is not in the enum and a pair prop type is configured, the token is re-parsed as two * whitespace-separated Size values (e.g. `50% 50%` for size, `10px 20px` for position offset). * Any token that fails both checks declines the whole declaration. */ class Background_Layer_Field_Converter extends Property_Converter_Base { private string $property; private string $field_key; /** * @var string[]|null */ private ?array $allowed_values; /** * @var class-string|null */ private ?string $pair_prop_type; /** * @var string[] */ private array $pair_keys; /** * @param string $property The CSS longhand property this converter owns. * @param string $field_key The field key inside each background-image-overlay value. * @param string[]|null $allowed_values String enum allowlist, or null to skip enum check. * @param string|null $pair_prop_type Object_Prop_Type class for size-pair values, or null. * @param string[] $pair_keys The two field keys of the pair type (e.g. ['x','y']). */ public function __construct( string $property, string $field_key, ?array $allowed_values, ?string $pair_prop_type = null, array $pair_keys = [] ) { $this->property = $property; $this->field_key = $field_key; $this->allowed_values = $allowed_values; $this->pair_prop_type = $pair_prop_type; $this->pair_keys = $pair_keys; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function convert_null( Conversion_Context $context, array $rule ): bool { return true; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $overlay_items = $this->get_overlay_items( $context ); $image_indices = array_keys( array_filter( $overlay_items, fn( $item ) => Background_Image_Overlay_Prop_Type::get_key() === ( $item['$$type'] ?? null ) ) ); if ( empty( $image_indices ) ) { return false; } $tokens = Css_Token_Splitter::split_by_comma( trim( $rule['value'] ) ); $prop_values = []; foreach ( $tokens as $token ) { $leaf = $this->parse_token( $token ); if ( null === $leaf ) { return false; } $prop_values[] = $leaf; } $layer_count = count( $image_indices ); $value_count = count( $prop_values ); if ( 1 !== $value_count && $layer_count !== $value_count ) { return false; } foreach ( $image_indices as $order => $index ) { $value_index = 1 === $value_count ? 0 : $order; $overlay_items[ $index ]['value'][ $this->field_key ] = $prop_values[ $value_index ]; } $background = $context->get_prop( 'background' ); $fields = is_array( $background ) && isset( $background['value'] ) ? $background['value'] : []; $fields['background-overlay'] = Background_Overlay_Prop_Type::generate( array_values( $overlay_items ) ); $context->set_prop( 'background', Background_Prop_Type::generate( $fields ) ); return true; } private function get_overlay_items( Conversion_Context $context ): array { $background = $context->get_prop( 'background' ); if ( ! is_array( $background ) ) { return []; } $overlay = $background['value']['background-overlay'] ?? null; if ( ! is_array( $overlay ) ) { return []; } return $overlay['value'] ?? []; } protected function parse_token( string $token ): ?array { $token = trim( $token ); if ( null !== $this->allowed_values && in_array( $token, $this->allowed_values, true ) ) { return String_Prop_Type::generate( $token ); } if ( null !== $this->pair_prop_type ) { return $this->parse_size_pair( $token ); } return null; } private function parse_size_pair( string $token ): ?array { $parts = Css_Token_Splitter::split_by_whitespace( $token ); if ( 2 !== count( $parts ) ) { return null; } $first = Size_Value_Parser::parse( $parts[0] ); $second = Size_Value_Parser::parse( $parts[1] ); if ( null === $first || null === $second ) { return null; } return ( $this->pair_prop_type )::generate( [ $this->pair_keys[0] => Size_Prop_Type::generate( $first ), $this->pair_keys[1] => Size_Prop_Type::generate( $second ), ] ); } } atomic-widgets/css-converter/converters/background-position-property-converter.php 0000644 00000004616 15252521350 0025043 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Position_Offset_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Position_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for `background-position`. * * The schema accepts only two shapes for a position value: * - String enum (one of Position_Prop_Type::get_position_enum_values()) * - Background_Image_Position_Offset_Prop_Type { x: Size, y: Size } * * This subclass adds keyword normalization to the generic Background_Layer_Field_Converter * so common LLM-emitted inputs reach the enum branch instead of declining: * - single keyword: center -> "center center", top -> "top center", left -> "center left", ... * - swapped pair: left top -> "top left" (enum is y-then-x ordered) * * Anything that cannot be normalized to an enum string or to two parseable Sizes * (e.g. center 20%, bottom 4px, anything containing calc()) declines to custom_css. */ class Background_Position_Property_Converter extends Background_Layer_Field_Converter { const SINGLE_KEYWORD_TO_PAIR = [ 'center' => 'center center', 'top' => 'top center', 'bottom' => 'bottom center', 'left' => 'center left', 'right' => 'center right', ]; const X_ONLY_KEYWORDS = [ 'left', 'right' ]; const Y_ONLY_KEYWORDS = [ 'top', 'bottom' ]; public function __construct() { parent::__construct( 'background-position', 'position', Position_Prop_Type::get_position_enum_values(), Background_Image_Position_Offset_Prop_Type::class, [ 'x', 'y' ] ); } protected function parse_token( string $token ): ?array { return parent::parse_token( $this->normalize_keywords( trim( $token ) ) ); } private function normalize_keywords( string $token ): string { $parts = Css_Token_Splitter::split_by_whitespace( $token ); if ( 1 === count( $parts ) ) { $lower = strtolower( $parts[0] ); return self::SINGLE_KEYWORD_TO_PAIR[ $lower ] ?? $token; } if ( 2 === count( $parts ) ) { $first = strtolower( $parts[0] ); $second = strtolower( $parts[1] ); if ( in_array( $first, self::X_ONLY_KEYWORDS, true ) && in_array( $second, self::Y_ONLY_KEYWORDS, true ) ) { return $second . ' ' . $first; } } return $token; } } atomic-widgets/css-converter/converters/noop-converter.php 0000644 00000001514 15252521350 0020145 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Placeholder that explicitly claims a schema property but declines conversion, so the rule * routes to customCss. Real converters replace the no-op for their property (Phase 1+). */ class Noop_Converter extends Property_Converter_Base { private string $property; public function __construct( string $property ) { $this->property = $property; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { return false; } } atomic-widgets/css-converter/converters/dimensions-property-converter.php 0000644 00000004641 15252521350 0023230 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Box_Shorthand_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Reusable converter for box shorthands backed by Union(<sides object> | Size): padding/margin * (Dimensions) and border-width (Border_Width) all share the four logical sides, differing only by the * wrapping object prop type, which is injected. One instance per property. Delegates * tokenizing/expansion to Box_Shorthand_Parser; a null parse declines (-> custom_css). * * A single token emits the Size member (the union accepts it). Two-to-four tokens expand via the CSS * box rule and map physical->logical (top=block-start, right=inline-end, bottom=block-end, * left=inline-start) into the injected object's PropValue. */ class Dimensions_Property_Converter extends Property_Converter_Base { private string $property; private string $object_prop_type; /** * @param string $property The CSS property this instance owns. * @param string $object_prop_type Object_Prop_Type class used to wrap the four expanded sides. */ public function __construct( string $property, string $object_prop_type = Dimensions_Prop_Type::class ) { $this->property = $property; $this->object_prop_type = $object_prop_type; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $parsed = Box_Shorthand_Parser::parse( $rule['value'] ); if ( null === $parsed ) { return false; } if ( isset( $parsed['single'] ) ) { $context->set_prop( $this->property, Size_Prop_Type::generate( $parsed['single'] ) ); return true; } [ $top, $right, $bottom, $left ] = $parsed['sides']; $context->set_prop( $this->property, ( $this->object_prop_type )::generate( [ 'block-start' => Size_Prop_Type::generate( $top ), 'inline-end' => Size_Prop_Type::generate( $right ), 'block-end' => Size_Prop_Type::generate( $bottom ), 'inline-start' => Size_Prop_Type::generate( $left ), ] ) ); return true; } } atomic-widgets/css-converter/converters/span-property-converter.php 0000644 00000003103 15252521350 0022011 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\PropTypes\Span_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Reusable converter for properties backed by a Span_Prop_Type (grid-column/grid-row). One instance * per property. Accepts any non-empty string that matches the schema regex (URLs/semicolons are * rejected by the live grid-* pattern); a non-matching value declines (-> custom_css). Emits the * canonical Span PropValue from generate(). */ class Span_Property_Converter extends Property_Converter_Base { private string $property; private ?string $pattern; /** * @param string $property The schema property this converter owns. * @param string|null $pattern The Span regex sourced from the schema, or null for no constraint. */ public function __construct( string $property, ?string $pattern = null ) { $this->property = $property; $this->pattern = $pattern; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $value = trim( $rule['value'] ); if ( '' === $value ) { return false; } if ( null !== $this->pattern && 1 !== preg_match( $this->pattern, $value ) ) { return false; } $context->set_prop( $this->property, Span_Prop_Type::generate( $value ) ); return true; } } atomic-widgets/css-converter/converters/object-position-property-converter.php 0000644 00000004002 15252521350 0024157 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Position_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for `object-position`: Union(String enum | Position_Prop_Type{x, y}). * * Named keyword pairs (e.g. `center left`) emit a String PropValue validated against the enum. * Two size tokens (e.g. `50% 30%`, `10px 20px`) emit a Position_Prop_Type PropValue. * Anything else declines to custom_css. */ class Object_Position_Property_Converter extends Property_Converter_Base { protected function get_supported_properties(): array { return [ 'object-position' ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $value = trim( $rule['value'] ); if ( in_array( $value, Position_Prop_Type::get_position_enum_values(), true ) ) { $context->set_prop( 'object-position', String_Prop_Type::generate( $value ) ); return true; } return $this->try_size_pair( $context, $value ); } private function try_size_pair( Conversion_Context $context, string $value ): bool { $tokens = Css_Token_Splitter::split_by_whitespace( $value ); if ( 2 !== count( $tokens ) ) { return false; } $x = Size_Value_Parser::parse( $tokens[0] ); $y = Size_Value_Parser::parse( $tokens[1] ); if ( null === $x || null === $y ) { return false; } $context->set_prop( 'object-position', Position_Prop_Type::generate( [ 'x' => Size_Prop_Type::generate( $x ), 'y' => Size_Prop_Type::generate( $y ), ] ) ); return true; } } atomic-widgets/css-converter/converters/number-property-converter.php 0000644 00000002233 15252521350 0022343 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Reusable converter for properties backed by a Number_Prop_Type. One instance per property. * Accepts only a strict numeric value (no units, no functions); anything else declines (-> custom_css). * Emits the canonical Number PropValue from generate(). */ class Number_Property_Converter extends Property_Converter_Base { private string $property; public function __construct( string $property ) { $this->property = $property; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $value = trim( $rule['value'] ); if ( ! is_numeric( $value ) ) { return false; } $context->set_prop( $this->property, Number_Prop_Type::generate( $value + 0 ) ); return true; } } atomic-widgets/css-converter/converters/size-property-converter.php 0000644 00000002730 15252521350 0022027 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Reusable converter for properties backed by a Size_Prop_Type. One instance per property. * Delegates value parsing to Size_Value_Parser; a null parse declines (-> custom_css). On success * it emits the canonical Size PropValue from generate(). $allow_unitless opts a property into * keeping unitless multipliers (e.g. line-height: 1.1) instead of declining them. */ class Size_Property_Converter extends Property_Converter_Base { private string $property; private bool $allow_unitless; public function __construct( string $property, bool $allow_unitless = false ) { $this->property = $property; $this->allow_unitless = $allow_unitless; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $parsed = Size_Value_Parser::parse( $rule['value'], $this->allow_unitless ); if ( null === $parsed ) { return false; } $context->set_prop( $this->property, Size_Prop_Type::generate( $parsed ) ); return true; } } atomic-widgets/css-converter/converters/color-property-converter.php 0000644 00000003234 15252521350 0022173 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Reusable converter for properties backed by a Color_Prop_Type. One instance per property. * Color_Prop_Type extends String_Prop_Type with no enum/regex, so any non-empty value is valid: * named colors, hex, rgb()/hsl(), var(), color-mix(), currentcolor, transparent. Raw passthrough, * apart from one guard: the model holds a single color, so a multi-color value (e.g. the per-side * `border-color: red green blue`) is declined to custom_css. Multiplicity is detected paren-aware so a * single functional color with internal spaces (`rgb(255 0 0)`, `color-mix(in srgb, red, blue)`) stays * one token. Emits the canonical Color PropValue from generate(). */ class Color_Property_Converter extends Property_Converter_Base { private string $property; public function __construct( string $property ) { $this->property = $property; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $color = trim( $rule['value'] ); if ( 1 !== count( Css_Token_Splitter::split_by_whitespace( $color ) ) ) { return false; } $context->set_prop( $this->property, Color_Prop_Type::generate( $color ) ); return true; } } atomic-widgets/css-converter/converters/transform-origin-property-converter.php 0000644 00000011057 15252521350 0024357 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Origin_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for the `transform-origin` CSS property. * * Maps into the `transform-origin` field nested inside the `transform` Prop_Type * (merging with any prior transform fields already in the context). * * Accepted CSS syntax (1, 2, or 3 tokens, order = x y z): * - keywords: left | right | center | top | bottom * - length: <number><px|em|rem> * - percentage: <number>% * * Unit rules: * - x/y: % px em rem * - z: px em rem (no %) * * Anything else declines the entire declaration to customCss. */ class Transform_Origin_Property_Converter extends Property_Converter_Base { const XY_UNITS = [ '%', 'px', 'em', 'rem' ]; const Z_UNITS = [ 'px', 'em', 'rem' ]; const X_KEYWORDS = [ 'left' => 0, 'center' => 50, 'right' => 100, ]; const Y_KEYWORDS = [ 'top' => 0, 'center' => 50, 'bottom' => 100, ]; const CENTER = [ 'size' => 50, 'unit' => '%', ]; protected function get_supported_properties(): array { return [ 'transform-origin' ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $axes = $this->parse( trim( $rule['value'] ) ); if ( null === $axes ) { return false; } $existing = $context->get_prop( 'transform' ); $fields = $this->current_fields( $existing ); $fields['transform-origin'] = Transform_Origin_Prop_Type::generate( [ 'x' => Size_Prop_Type::generate( $axes['x'] ), 'y' => Size_Prop_Type::generate( $axes['y'] ), 'z' => Size_Prop_Type::generate( $axes['z'] ), ] ); $context->set_prop( 'transform', Transform_Prop_Type::generate( $fields ) ); return true; } private function parse( string $value ): ?array { $tokens = Css_Token_Splitter::split_by_whitespace( $value ); $count = count( $tokens ); if ( $count < 1 || $count > 3 ) { return null; } [ $x, $y ] = $this->parse_xy( $tokens ); if ( null === $x || null === $y ) { return null; } $z = 3 === $count ? $this->parse_z( $tokens[2] ) : [ 'size' => 0, 'unit' => 'px', ]; if ( null === $z ) { return null; } return [ 'x' => $x, 'y' => $y, 'z' => $z, ]; } private function parse_xy( array $tokens ): array { if ( 1 === count( $tokens ) ) { return $this->parse_single_xy( strtolower( $tokens[0] ) ); } return [ $this->parse_axis( strtolower( $tokens[0] ), self::X_KEYWORDS ), $this->parse_axis( strtolower( $tokens[1] ), self::Y_KEYWORDS ), ]; } private function parse_single_xy( string $token ): array { if ( isset( self::X_KEYWORDS[ $token ] ) && ! isset( self::Y_KEYWORDS[ $token ] ) ) { return [ $this->keyword_size( self::X_KEYWORDS[ $token ] ), self::CENTER ]; } if ( isset( self::Y_KEYWORDS[ $token ] ) && ! isset( self::X_KEYWORDS[ $token ] ) ) { return [ self::CENTER, $this->keyword_size( self::Y_KEYWORDS[ $token ] ) ]; } if ( 'center' === $token ) { return [ self::CENTER, self::CENTER ]; } $size = $this->xy_size( $token ); return null === $size ? [ null, null ] : [ $size, self::CENTER ]; } private function parse_axis( string $token, array $keywords ): ?array { if ( isset( $keywords[ $token ] ) ) { return $this->keyword_size( $keywords[ $token ] ); } return $this->xy_size( $token ); } private function xy_size( string $token ): ?array { $parsed = Size_Value_Parser::parse( $token ); if ( null === $parsed || ! in_array( $parsed['unit'], self::XY_UNITS, true ) ) { return null; } return $parsed; } private function parse_z( string $token ): ?array { $parsed = Size_Value_Parser::parse( $token ); if ( null === $parsed || ! in_array( $parsed['unit'], self::Z_UNITS, true ) ) { return null; } return $parsed; } private function keyword_size( int $percent ): array { return [ 'size' => $percent, 'unit' => '%', ]; } private function current_fields( $existing ): array { if ( is_array( $existing ) && isset( $existing['value'] ) ) { return $existing['value']; } return []; } } atomic-widgets/css-converter/converters/rejected-converter.php 0000644 00000002041 15252521350 0020753 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Claims a property and unconditionally rejects it: the declaration is added to the `rejected` * bucket instead of `customCss`. Used for properties that are structurally incompatible with * Elementor's style system (e.g. `animation`, `@keyframes`), so the client can surface a hint * to the LLM rather than silently emitting broken CSS. */ class Rejected_Converter extends Property_Converter_Base { private string $property; public function __construct( string $property ) { $this->property = $property; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $context->reject( $rule['declaration'] . ';' ); return true; } } atomic-widgets/css-converter/converters/flex-property-converter.php 0000644 00000006343 15252521350 0022017 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Converters; use Elementor\Modules\AtomicWidgets\CssConverter\Conversion_Context; use Elementor\Modules\AtomicWidgets\CssConverter\Property_Converter_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\AtomicWidgets\PropTypes\Flex_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Converter for the `flex` shorthand: Flex_Prop_Type{flexGrow: Number, flexShrink: Number, flexBasis: Size}. * * Supported forms: * flex: none -> 0 1 auto (CSS spec equivalent) * flex: auto -> 1 1 auto * flex: <grow> -> <grow> 1 0 (unitless number only) * flex: <grow> <basis> (unitless grow + size basis) * flex: <grow> <shrink> <basis> * * Declines to custom_css for any unrecognised syntax. */ class Flex_Property_Converter extends Property_Converter_Base { const AUTO_BASIS = [ 'size' => 'auto', 'unit' => 'custom', ]; const ZERO_BASIS = [ 'size' => 0, 'unit' => 'px', ]; protected function get_supported_properties(): array { return [ 'flex' ]; } protected function do_convert( Conversion_Context $context, array $rule ): bool { $value = trim( $rule['value'] ); $parsed = $this->parse( $value ); if ( null === $parsed ) { return false; } [ $grow, $shrink, $basis ] = $parsed; $context->set_prop( 'flex', Flex_Prop_Type::generate( [ 'flexGrow' => Number_Prop_Type::generate( $grow ), 'flexShrink' => Number_Prop_Type::generate( $shrink ), 'flexBasis' => Size_Prop_Type::generate( $basis ), ] ) ); return true; } private function parse( string $value ): ?array { $lower = strtolower( $value ); if ( 'none' === $lower ) { return [ 0, 1, self::AUTO_BASIS ]; } if ( 'auto' === $lower ) { return [ 1, 1, self::AUTO_BASIS ]; } $tokens = Css_Token_Splitter::split_by_whitespace( $value ); if ( 1 === count( $tokens ) ) { $grow = $this->parse_number( $tokens[0] ); if ( null === $grow ) { return null; } return [ $grow, 1, self::ZERO_BASIS ]; } if ( 2 === count( $tokens ) ) { $grow = $this->parse_number( $tokens[0] ); if ( null === $grow ) { return null; } $basis = Size_Value_Parser::parse( $tokens[1] ); if ( null === $basis ) { return null; } return [ $grow, 1, $basis ]; } if ( 3 === count( $tokens ) ) { $grow = $this->parse_number( $tokens[0] ); $shrink = $this->parse_number( $tokens[1] ); $basis = $this->parse_basis( $tokens[2] ); if ( null === $grow || null === $shrink || null === $basis ) { return null; } return [ $grow, $shrink, $basis ]; } return null; } private function parse_number( string $token ): ?float { if ( ! is_numeric( $token ) ) { return null; } return (float) $token; } private function parse_basis( string $token ): ?array { $lower = strtolower( $token ); if ( 'auto' === $lower ) { return self::AUTO_BASIS; } return Size_Value_Parser::parse( $token ); } } atomic-widgets/css-converter/expanders/outline-shorthand-expander.php 0000644 00000005215 15252521350 0022241 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Expanders; use Elementor\Modules\AtomicWidgets\CssConverter\Shorthand_Expander_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Expands the `outline` shorthand into its supported longhands (outline-width, outline-style, * outline-color). Token classification mirrors the border expander: a style keyword -> style; a * length or width keyword -> width; anything else -> color. Duplicate roles or unclassifiable * tokens cause the whole expansion to decline (-> custom_css). * * On null reset, outline-offset is also included because it is a supported prop type even though * it is not part of the outline shorthand syntax. */ class Outline_Shorthand_Expander extends Shorthand_Expander_Base { const STYLE_KEYWORDS = [ 'none', 'auto', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset', ]; const WIDTH_KEYWORDS = [ 'thin', 'medium', 'thick' ]; const SHORTHAND_LONGHANDS = [ 'width' => 'outline-width', 'style' => 'outline-style', 'color' => 'outline-color', ]; const ALL_LONGHANDS = [ 'outline-width', 'outline-style', 'outline-color', 'outline-offset' ]; protected function get_supported_properties(): array { return [ 'outline' ]; } protected function expand_null( array $rule ): array { return array_map( fn( $p ) => $this->null_rule( $p ), self::ALL_LONGHANDS ); } protected function do_expand( array $rule ): array { $tokens = Css_Token_Splitter::split_by_whitespace( trim( $rule['value'] ) ); if ( empty( $tokens ) ) { return []; } $slots = [ 'width' => null, 'style' => null, 'color' => null, ]; foreach ( $tokens as $token ) { $role = $this->classify_token( $token ); if ( null === $role || null !== $slots[ $role ] ) { return []; } $slots[ $role ] = $token; } $rules = []; foreach ( $slots as $role => $value ) { if ( null === $value ) { continue; } $property = self::SHORTHAND_LONGHANDS[ $role ]; $rules[] = [ 'property' => $property, 'value' => $value, 'declaration' => $property . ': ' . $value, ]; } return $rules; } private function classify_token( string $token ): ?string { $lower = strtolower( $token ); if ( in_array( $lower, self::STYLE_KEYWORDS, true ) ) { return 'style'; } if ( in_array( $lower, self::WIDTH_KEYWORDS, true ) || null !== Size_Value_Parser::parse( $token ) ) { return 'width'; } return 'color'; } } atomic-widgets/css-converter/expanders/physical-to-logical-expander.php 0000644 00000002477 15252521350 0022445 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Expanders; use Elementor\Modules\AtomicWidgets\CssConverter\Shorthand_Expander_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Rewrites physical inset properties (top, right, bottom, left) to their logical equivalents * (inset-block-start, inset-inline-end, inset-block-end, inset-inline-start) so the standard * Size converters can handle them without needing separate converter registrations. * * Assumes LTR writing mode, which matches the Elementor canvas default. */ class Physical_To_Logical_Expander extends Shorthand_Expander_Base { const PHYSICAL_TO_LOGICAL = [ 'top' => 'inset-block-start', 'right' => 'inset-inline-end', 'bottom' => 'inset-block-end', 'left' => 'inset-inline-start', ]; protected function get_supported_properties(): array { return array_keys( self::PHYSICAL_TO_LOGICAL ); } protected function expand_null( array $rule ): array { return [ $this->null_rule( self::PHYSICAL_TO_LOGICAL[ $rule['property'] ] ) ]; } protected function do_expand( array $rule ): array { $logical = self::PHYSICAL_TO_LOGICAL[ $rule['property'] ]; return [ [ 'property' => $logical, 'value' => $rule['value'], 'declaration' => $logical . ': ' . $rule['value'], ], ]; } } atomic-widgets/css-converter/expanders/border-shorthand-expander.php 0000644 00000010716 15252521350 0022041 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Expanders; use Elementor\Modules\AtomicWidgets\CssConverter\Css_Var_Token_Resolver; use Elementor\Modules\AtomicWidgets\CssConverter\Shorthand_Expander_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\Variables\Services\Variables_Service; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Expands a `border` / `border-{side}` shorthand into its width / style / color longhands. There is no * aggregate Border prop type; these are independent longhands, so this is a split, not a merge. The * concrete longhand property names are injected, so the same logic serves the all-sides `border` * (border-width/style/color) and each per-side shorthand (e.g. border-top-width/style/color). * * Each token is classified once: a border-style keyword (enum from the live schema) -> style; a length * the Size parser accepts, or a width keyword (thin/medium/thick) -> width; anything else -> color (the * catch-all, mirroring the raw-passthrough color converter). Only the parts present are emitted (omitted * parts are not reset to CSS initials). A second token for an already filled role is ambiguous, so the * whole expansion declines and the original shorthand is kept for custom_css. A produced longhand can * still individually decline downstream (e.g. `border-{side}-style`, which has no converter), degrading * to custom_css for that part only. */ class Border_Shorthand_Expander extends Shorthand_Expander_Base { const WIDTH_KEYWORDS = [ 'thin', 'medium', 'thick' ]; const ROLE_WIDTH = 'width'; const ROLE_STYLE = 'style'; const ROLE_COLOR = 'color'; private string $property; /** * @var array<string, string> Role (width|style|color) -> emitted longhand property name. */ private array $longhands; /** * @var string[] */ private array $style_keywords; private ?Variables_Service $variables_service; /** * @param string $property The shorthand this expander owns (border, border-top, ...). * @param array<string, string> $longhands Role -> longhand property name to emit. * @param string[] $style_keywords The border-style enum, sourced from the live schema. */ public function __construct( string $property, array $longhands, array $style_keywords, ?Variables_Service $variables_service = null ) { $this->property = $property; $this->longhands = $longhands; $this->style_keywords = $style_keywords; $this->variables_service = $variables_service; } protected function get_supported_properties(): array { return [ $this->property ]; } protected function expand_null( array $rule ): array { return array_map( fn( $p ) => $this->null_rule( $p ), array_values( $this->longhands ) ); } protected function do_expand( array $rule ): array { $value = $rule['value']; $tokens = Css_Token_Splitter::split_by_whitespace( trim( $value ) ); if ( empty( $tokens ) ) { return []; } $slots = [ self::ROLE_WIDTH => null, self::ROLE_STYLE => null, self::ROLE_COLOR => null, ]; foreach ( $tokens as $token ) { $role = $this->classify_token( $token ); if ( null === $role || null !== $slots[ $role ] ) { return []; } $slots[ $role ] = $token; } $rules = []; foreach ( $slots as $role => $slot_value ) { if ( null === $slot_value ) { continue; } $property = $this->longhands[ $role ]; $rules[] = [ 'property' => $property, 'value' => $slot_value, 'declaration' => $property . ': ' . $slot_value, ]; } return $rules; } private function classify_token( string $token ): ?string { if ( Css_Var_Token_Resolver::is_var_only_token( $token ) ) { $resolved_type = Css_Var_Token_Resolver::resolve_var_only_token_type( $this->variables_service, $token ); if ( 'color' === $resolved_type ) { return self::ROLE_COLOR; } if ( 'size' === $resolved_type ) { return self::ROLE_WIDTH; } return null; } return $this->classify_literal_token( $token ); } private function classify_literal_token( string $token ): string { $lower = strtolower( $token ); if ( in_array( $lower, $this->style_keywords, true ) ) { return self::ROLE_STYLE; } if ( in_array( $lower, self::WIDTH_KEYWORDS, true ) || null !== Size_Value_Parser::parse( $token ) ) { return self::ROLE_WIDTH; } return self::ROLE_COLOR; } } atomic-widgets/css-converter/expanders/background-shorthand-expander.php 0000644 00000020165 15252521350 0022702 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Expanders; use Elementor\Modules\AtomicWidgets\CssConverter\Css_Var_Token_Resolver; use Elementor\Modules\AtomicWidgets\CssConverter\Shorthand_Expander_Base; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; use Elementor\Modules\Variables\Services\Variables_Service; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Expands a `background` shorthand into its constituent longhand declarations so the * existing per-property converters can process each one independently. * * Each comma-separated layer is parsed into role slots: image, repeat, attachment, clip, position, * size, and (last layer only) color. Slot values are then aggregated across layers to form the * longhand values: * - background-image: comma-joined per layer (none for layers without an explicit image). * - background-repeat: comma-joined if all layers that have an image also specify it. * - background-attachment: same. * - background-position: same. * - background-size: same (position must also be present when size is used). * - background-color: from the last layer only. * - background-clip: from the last layer only (single scalar in our model). * * A layer whose tokens cannot be unambiguously classified declines the entire shorthand (returns []) * so the original declaration is kept and routed to custom_css. */ class Background_Shorthand_Expander extends Shorthand_Expander_Base { private ?Variables_Service $variables_service; const REPEAT_ENUM = [ 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' ]; const ATTACHMENT_ENUM = [ 'fixed', 'scroll' ]; const CLIP_ENUM = [ 'border-box', 'padding-box', 'content-box', 'text' ]; const POSITION_KEYWORDS = [ 'top', 'bottom', 'left', 'right', 'center' ]; const SIZE_KEYWORDS = [ 'cover', 'contain', 'auto' ]; public function __construct( ?Variables_Service $variables_service = null ) { $this->variables_service = $variables_service; } protected function get_supported_properties(): array { return [ 'background' ]; } const ALL_LONGHANDS = [ 'background-image', 'background-repeat', 'background-attachment', 'background-position', 'background-size', 'background-clip', 'background-color', ]; protected function expand_null( array $rule ): array { return array_map( fn( $p ) => $this->null_rule( $p ), self::ALL_LONGHANDS ); } protected function do_expand( array $rule ): array { $layers = Css_Token_Splitter::split_by_comma( trim( $rule['value'] ) ); if ( empty( $layers ) ) { return []; } $parsed = []; foreach ( $layers as $layer ) { $result = $this->parse_layer( trim( $layer ) ); if ( null === $result ) { return []; } $parsed[] = $result; } return $this->build_rules( $parsed ); } /** * @return array{image:string|null,repeat:string|null,attachment:string|null,clip:string|null,position:string|null,size:string|null,color:string|null}|null */ private function parse_layer( string $layer ): ?array { if ( $this->is_var_only_layer( $layer ) ) { return $this->parse_var_only_layer( $layer ); } $tokens = Css_Token_Splitter::split_by_whitespace( $layer ); $image = null; $repeat = null; $attachment = null; $clip = null; $color = null; $position_tokens = []; $size_tokens = []; $after_slash = false; $n = count( $tokens ); for ( $i = 0; $i < $n; $i++ ) { $token = $tokens[ $i ]; $lower = strtolower( $token ); if ( '/' === $token ) { $after_slash = true; continue; } if ( $after_slash ) { if ( $this->is_size_token( $token ) ) { $size_tokens[] = $token; if ( count( $size_tokens ) >= 2 ) { $after_slash = false; } continue; } $after_slash = false; } if ( $this->is_image_token( $lower ) ) { if ( null !== $image ) { return null; } $image = $token; continue; } if ( in_array( $lower, self::REPEAT_ENUM, true ) ) { if ( null !== $repeat ) { return null; } $repeat = $token; continue; } if ( in_array( $lower, self::ATTACHMENT_ENUM, true ) ) { if ( null !== $attachment ) { return null; } $attachment = $token; continue; } if ( in_array( $lower, self::CLIP_ENUM, true ) ) { if ( null !== $clip ) { return null; } $clip = $token; continue; } if ( $this->is_position_token( $lower ) ) { $position_tokens[] = $token; if ( count( $position_tokens ) > 2 ) { return null; } continue; } if ( null !== $color ) { return null; } $color = $token; } if ( ! empty( $size_tokens ) && empty( $position_tokens ) ) { return null; } $position = empty( $position_tokens ) ? null : implode( ' ', $position_tokens ); $size = empty( $size_tokens ) ? null : implode( ' ', $size_tokens ); return compact( 'image', 'repeat', 'attachment', 'clip', 'color', 'position', 'size' ); } private function is_var_only_layer( string $layer ): bool { return Css_Var_Token_Resolver::is_var_only_token( $layer ); } /** * @return array{image:string|null,repeat:string|null,attachment:string|null,clip:string|null,position:string|null,size:string|null,color:string|null}|null */ private function parse_var_only_layer( string $layer ): ?array { $resolved_type = Css_Var_Token_Resolver::resolve_var_only_token_type( $this->variables_service, $layer ); $empty_layer = $this->empty_layer(); if ( 'color' === $resolved_type ) { $empty_layer['color'] = $layer; return $empty_layer; } if ( 'size' === $resolved_type ) { $empty_layer['position'] = 'center center'; $empty_layer['size'] = $layer; return $empty_layer; } return null; } /** * @return array{image:string|null,repeat:string|null,attachment:string|null,clip:string|null,position:string|null,size:string|null,color:string|null} */ private function empty_layer(): array { return [ 'image' => null, 'repeat' => null, 'attachment' => null, 'clip' => null, 'position' => null, 'size' => null, 'color' => null, ]; } private function is_image_token( string $lower ): bool { return 'none' === $lower || 0 === strpos( $lower, 'url(' ) || false !== strpos( $lower, '-gradient(' ); } private function is_size_token( string $token ): bool { return in_array( strtolower( $token ), self::SIZE_KEYWORDS, true ) || null !== Size_Value_Parser::parse( $token ); } private function is_position_token( string $lower ): bool { return in_array( $lower, self::POSITION_KEYWORDS, true ) || null !== Size_Value_Parser::parse( $lower ); } /** * @param array[] $parsed * @return array[] */ private function build_rules( array $parsed ): array { $layer_count = count( $parsed ); $rules = []; $has_image = ! empty( array_filter( $parsed, fn( $l ) => null !== $l['image'] ) ); if ( $has_image ) { $images = array_map( fn( $l ) => $l['image'] ?? 'none', $parsed ); $rules['background-image'] = implode( ', ', $images ); } $per_layer_slots = [ 'repeat' => 'background-repeat', 'attachment' => 'background-attachment', 'position' => 'background-position', 'size' => 'background-size', ]; foreach ( $per_layer_slots as $slot => $property ) { $values = array_column( $parsed, $slot ); $non_null = array_filter( $values, fn( $v ) => null !== $v ); if ( empty( $non_null ) ) { continue; } if ( count( $non_null ) === 1 || count( $non_null ) === $layer_count ) { $rules[ $property ] = implode( ', ', $non_null ); } } $last = end( $parsed ); if ( null !== $last['clip'] ) { $rules['background-clip'] = $last['clip']; } if ( null !== $last['color'] ) { $rules['background-color'] = $last['color']; } return $this->emit_rules( $rules ); } /** * @param array<string, string> $longhands * @return array[] */ private function emit_rules( array $longhands ): array { $rules = []; foreach ( $longhands as $property => $value ) { $rules[] = [ 'property' => $property, 'value' => $value, 'declaration' => $property . ': ' . $value, ]; } return $rules; } } atomic-widgets/css-converter/expander-registry-factory.php 0000644 00000003701 15252521350 0020114 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; use Elementor\Modules\AtomicWidgets\CssConverter\Expanders\Background_Shorthand_Expander; use Elementor\Modules\AtomicWidgets\CssConverter\Expanders\Border_Shorthand_Expander; use Elementor\Modules\AtomicWidgets\CssConverter\Expanders\Outline_Shorthand_Expander; use Elementor\Modules\AtomicWidgets\CssConverter\Expanders\Physical_To_Logical_Expander; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; use Elementor\Modules\Variables\Services\Variables_Service; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Expander_Registry_Factory { const BORDER_SIDES = [ 'top', 'right', 'bottom', 'left' ]; public static function create( ?Variables_Service $variables_service = null ): Expander_Registry { $schema = Style_Schema::get_style_schema(); $style_enum = $schema['border-style']->get_enum(); $registry = ( new Expander_Registry() ) ->register( new Physical_To_Logical_Expander() ) ->register( new Background_Shorthand_Expander( $variables_service ) ) ->register( new Outline_Shorthand_Expander() ) ->register( new Border_Shorthand_Expander( 'border', self::border_longhands( '' ), $style_enum, $variables_service ) ); foreach ( self::BORDER_SIDES as $side ) { $registry->register( new Border_Shorthand_Expander( "border-$side", self::border_longhands( "$side-" ), $style_enum, $variables_service ) ); } return $registry; } /** * Role -> longhand property name for the all-sides (`border`, infix '') or per-side (e.g. 'top-') * shorthand. Per-side style/color have no converter and route to custom_css. * * @return array<string, string> */ public static function border_longhands( string $infix ): array { return [ Border_Shorthand_Expander::ROLE_WIDTH => "border-{$infix}width", Border_Shorthand_Expander::ROLE_STYLE => "border-{$infix}style", Border_Shorthand_Expander::ROLE_COLOR => "border-{$infix}color", ]; } } atomic-widgets/css-converter/property-converter.php 0000644 00000001244 15252521350 0016664 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Property_Converter { /** * @param array{property: string, value: string} $rule A single parsed CSS declaration. */ public function is_supported( array $rule ): bool; /** * Mutates the shared context and returns whether the rule was converted. * * @param Conversion_Context $context The shared mutable conversion context. * @param array{property: string, value: string} $rule A single parsed CSS declaration. */ public function convert( Conversion_Context $context, array $rule ): bool; } atomic-widgets/css-converter/metrics/conversion-failure-reporter.php 0000644 00000001104 15252521350 0022106 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Metrics; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Conversion_Failure_Reporter { const CATEGORY_EXCEPTION = 'exception'; const CATEGORY_NULL_RETURN = 'null_return'; /** * @param string $property The CSS property that failed to convert. * @param string $category One of the CATEGORY_* constants. * @param array $context Sanitized reproduction context, free of user content. */ public function report( string $property, string $category, array $context ): void; } atomic-widgets/css-converter/metrics/null-failure-reporter.php 0000644 00000000631 15252521350 0020677 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter\Metrics; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Default no-op reporter. Real telemetry (channel, payload, PII policy) is deferred to Phase 4. */ class Null_Failure_Reporter implements Conversion_Failure_Reporter { public function report( string $property, string $category, array $context ): void { } } atomic-widgets/css-converter/css-var-reference.php 0000644 00000000664 15252521350 0016312 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Css_Var_Reference { public static function parse( string $value ): ?string { $value = trim( $value ); if ( ! preg_match( '/^var\(\s*(--)?([^,\s)]+)/i', $value, $matches ) ) { return null; } $token = trim( $matches[2] ); return '' === $token ? null : ltrim( $token, '-' ); } } atomic-widgets/css-converter/variable-prop-value-transformer.php 0000644 00000021761 15252521350 0021216 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CssConverter; use Elementor\Modules\AtomicWidgets\CssConverter\Converter_Registry_Factory; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; use Elementor\Modules\Variables\Adapters\Prop_Type_Adapter; use Elementor\Modules\Variables\PropTypes\Size_Variable_Prop_Type; use Elementor\Modules\Variables\Services\Variables_Service; use Elementor\Modules\Variables\Utils\Variable_Type_Keys; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Variable_Prop_Value_Transformer { private Variables_Service $variables_service; public function __construct( Variables_Service $variables_service ) { $this->variables_service = $variables_service; } /** * @param array $props * @param array $schema * @param array<int, array{property: string, value: string, declaration: string}> $rules * @return array{props: array, custom_css: string[], rejected: string[]} */ public function eject_unresolved_var_props( array $props, array $schema, array $rules ): array { $custom_css = []; $rejected = []; foreach ( $props as $key => $prop_value ) { if ( ! isset( $schema[ $key ] ) || ! ( $schema[ $key ] instanceof Prop_Type ) ) { continue; } $reference = $this->find_var_reference_in_value( $prop_value, $schema[ $key ] ); if ( null === $reference ) { continue; } $variable = $this->variables_service->find_by_label_or_id( $reference ); if ( null !== $variable && $this->variable_fits_prop_type( $schema[ $key ], $variable['type'] ?? '' ) ) { continue; } unset( $props[ $key ] ); $declaration = $this->declaration_for_ejected_prop( $rules, $key ); if ( null === $declaration ) { continue; } $declaration = $declaration . ';'; if ( null === $variable ) { $custom_css[] = $declaration; continue; } $rejected[] = $declaration; } return [ 'props' => $props, 'custom_css' => $custom_css, 'rejected' => $rejected, ]; } public function transform( array $props, array $schema ): array { $transformed = []; foreach ( $props as $key => $prop_value ) { if ( ! isset( $schema[ $key ] ) || ! ( $schema[ $key ] instanceof Prop_Type ) ) { $transformed[ $key ] = $prop_value; continue; } $transformed[ $key ] = $this->transform_value( $prop_value, $schema[ $key ] ); } return $transformed; } /** * @param mixed $prop_value * @return mixed */ private function transform_value( $prop_value, Prop_Type $prop_type ) { if ( ! is_array( $prop_value ) || ! isset( $prop_value['$$type'] ) ) { return $prop_value; } if ( $prop_type instanceof Union_Prop_Type ) { $promoted = $this->try_promote_to_variable( $prop_value, $prop_type ); if ( null !== $promoted ) { return $promoted; } $branch = $prop_type->get_prop_type( $prop_value['$$type'] ); if ( ! $branch ) { return $prop_value; } return $this->transform_value( $prop_value, $branch ); } if ( $prop_type instanceof Object_Prop_Type ) { if ( ! is_array( $prop_value['value'] ?? null ) ) { return $prop_value; } $shape = $prop_type->get_shape(); foreach ( $shape as $field => $field_type ) { if ( ! isset( $prop_value['value'][ $field ] ) ) { continue; } $prop_value['value'][ $field ] = $this->transform_value( $prop_value['value'][ $field ], $field_type ); } return $prop_value; } if ( $prop_type instanceof Array_Prop_Type ) { if ( ! is_array( $prop_value['value'] ?? null ) ) { return $prop_value; } foreach ( $prop_value['value'] as $index => $item ) { $prop_value['value'][ $index ] = $this->transform_value( $item, $prop_type->get_item_type() ); } return $prop_value; } return $prop_value; } private function try_promote_to_variable( array $prop_value, Union_Prop_Type $union ): ?array { $reference = $this->extract_var_reference( $prop_value ); if ( null === $reference ) { return null; } $variable = $this->variables_service->find_by_label_or_id( $reference ); if ( null === $variable ) { return null; } $variable_type = $this->resolve_variable_prop_type_key( $variable['type'] ?? '', $union ); if ( null === $variable_type ) { return null; } $id = $variable['id'] ?? ''; if ( '' === $id ) { return null; } return [ '$$type' => $variable_type, 'value' => $id, ]; } private function extract_var_reference( array $prop_value ): ?string { $type = $prop_value['$$type'] ?? ''; $value = $prop_value['value'] ?? null; if ( in_array( $type, [ 'color', 'string' ], true ) && is_string( $value ) ) { return Css_Var_Reference::parse( $value ); } if ( 'size' === $type && is_array( $value ) ) { $unit = $value['unit'] ?? ''; if ( Size_Constants::UNIT_CUSTOM !== $unit || ! is_string( $value['size'] ?? null ) ) { return null; } return Css_Var_Reference::parse( $value['size'] ); } return null; } /** * @param mixed $prop_value */ private function find_var_reference_in_value( $prop_value, Prop_Type $prop_type ): ?string { if ( ! is_array( $prop_value ) || ! isset( $prop_value['$$type'] ) ) { return null; } if ( Variable_Type_Keys::is_variable_type( $prop_value['$$type'] ) ) { return null; } if ( $prop_type instanceof Union_Prop_Type ) { $reference = $this->extract_var_reference( $prop_value ); if ( null !== $reference ) { return $reference; } $branch = $prop_type->get_prop_type( $prop_value['$$type'] ); if ( ! $branch ) { return null; } return $this->find_var_reference_in_value( $prop_value, $branch ); } if ( $prop_type instanceof Object_Prop_Type ) { if ( ! is_array( $prop_value['value'] ?? null ) ) { return null; } foreach ( $prop_type->get_shape() as $field => $field_type ) { if ( ! isset( $prop_value['value'][ $field ] ) ) { continue; } $reference = $this->find_var_reference_in_value( $prop_value['value'][ $field ], $field_type ); if ( null !== $reference ) { return $reference; } } return null; } if ( $prop_type instanceof Array_Prop_Type ) { if ( ! is_array( $prop_value['value'] ?? null ) ) { return null; } foreach ( $prop_value['value'] as $item ) { $reference = $this->find_var_reference_in_value( $item, $prop_type->get_item_type() ); if ( null !== $reference ) { return $reference; } } return null; } return $this->extract_var_reference( $prop_value ); } private function variable_fits_prop_type( Prop_Type $prop_type, string $variable_type ): bool { if ( '' === $variable_type ) { return false; } if ( $prop_type instanceof Union_Prop_Type ) { return null !== $this->resolve_variable_prop_type_key( $variable_type, $prop_type ); } $resolved_type = Variable_Type_Keys::get_resolved_type( $variable_type ); return null !== $resolved_type && $resolved_type === $prop_type::get_key(); } /** * @param array<int, array{property: string, value: string, declaration: string}> $rules */ private function declaration_for_ejected_prop( array $rules, string $property ): ?string { $declaration = $this->declaration_for_property( $rules, $property ); if ( null !== $declaration ) { return $declaration; } $longhands_by_aggregate = [ 'background' => [ 'background-color', 'background-clip', 'background-image', 'background-repeat', 'background-attachment', 'background-position', 'background-size', ], 'padding' => array_keys( Converter_Registry_Factory::DIMENSIONS_SIDE_SPECS ), 'margin' => array_keys( Converter_Registry_Factory::DIMENSIONS_SIDE_SPECS ), ]; foreach ( $longhands_by_aggregate[ $property ] ?? [] as $longhand ) { $declaration = $this->declaration_for_property( $rules, $longhand ); if ( null !== $declaration ) { return $declaration; } } return null; } /** * @param array<int, array{property: string, value: string, declaration: string}> $rules */ private function declaration_for_property( array $rules, string $property ): ?string { foreach ( $rules as $rule ) { if ( $rule['property'] === $property ) { return $rule['declaration']; } } return null; } private function resolve_variable_prop_type_key( string $variable_type, Union_Prop_Type $union ): ?string { if ( '' === $variable_type ) { return null; } if ( $union->get_prop_type( $variable_type ) ) { return $variable_type; } if ( Prop_Type_Adapter::GLOBAL_CUSTOM_SIZE_VARIABLE_KEY === $variable_type && $union->get_prop_type( Size_Variable_Prop_Type::get_key() ) ) { return Size_Variable_Prop_Type::get_key(); } return null; } } atomic-widgets/dynamic-tags/dynamic-tags-converter.php 0000644 00000006704 15252521350 0017151 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\DynamicTags; use Elementor\Modules\AtomicWidgets\Utils\Image\Placeholder_Image; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Date_Time_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Query_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Dynamic_Tags_Converter { /** * @param array $control * @return Plain_Prop_Type|Object_Prop_Type|null */ public static function convert_control_to_prop_type( array $control ) { $control_type = $control['type']; switch ( $control_type ) { case 'text': case 'textarea': case 'select': $prop_type = String_Prop_Type::make() ->default( $control['default'] ?? null ); break; case 'date_time': $prop_type = Date_Time_Prop_Type::make() ->default( $control['default'] ?? null ); break; case 'number': $prop_type = Number_Prop_Type::make() ->set_required( $control['required'] ?? false ) ->default( $control['default'] ?? null ); break; case 'switcher': $default = $control['default']; $prop_type = Boolean_Prop_Type::make() ->default( 'yes' === $default || true === $default ); break; case 'choose': $prop_type = String_Prop_Type::make() ->default( $control['default'] ?? null ) ->enum( array_keys( $control['options'] ?? [] ) ); break; case 'query': $prop_type = Query_Prop_Type::make() ->set_required( $control['required'] ?? false ) ->default( $control['default'] ?? null ); break; case 'media': $prop_type = Image_Prop_Type::make() ->default_url( Placeholder_Image::get_placeholder_image() ) ->default_size( 'full' ) ->set_shape_meta( 'src', [ 'isDynamic' => true ] ); break; default: return null; } $prop_type->set_dependencies( self::create_dependencies_from_condition( $control['condition'] ?? null ) ); return $prop_type; } private static function create_dependencies_from_condition( $condition ): ?array { if ( ! is_array( $condition ) || empty( $condition ) ) { return null; } $manager = Dependency_Manager::make( Dependency_Manager::RELATION_AND ); foreach ( $condition as $raw_key => $value ) { $is_negated = false !== strpos( (string) $raw_key, '!' ); $key = rtrim( (string) $raw_key, '!' ); $path = self::parse_condition_path( $key ); if ( is_array( $value ) ) { $manager->where( [ 'operator' => $is_negated ? 'nin' : 'in', 'path' => $path, 'value' => $value, ] ); continue; } $manager->where( [ 'operator' => $is_negated ? 'ne' : 'eq', 'path' => $path, 'value' => $value, ] ); } return $manager->get(); } private static function parse_condition_path( string $key ): array { if ( false === strpos( $key, '[' ) ) { return [ $key ]; } $key = str_replace( ']', '', $key ); $tokens = explode( '[', $key ); return array_values( array_filter( $tokens, static fn( $t ) => '' !== $t ) ); } } atomic-widgets/dynamic-tags/dynamic-prop-types-mapping.php 0000644 00000005642 15252521350 0017761 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\DynamicTags; use Elementor\Modules\AtomicWidgets\PropTypes\Utils\Prop_Types_Schema_Extender; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Src_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Svg_Src_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Url_Prop_Type; use Elementor\Modules\DynamicTags\Module as V1_Dynamic_Tags_Module; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Dynamic_Prop_Types_Mapping extends Prop_Types_Schema_Extender { public static function make(): self { return new static(); } /** * Get the dynamic prop type to add to the prop type * * @param Prop_Type $prop_type */ protected function get_prop_types_to_add( Prop_Type $prop_type ): array { $categories = []; $transformable_prop_types = $prop_type instanceof Union_Prop_Type ? $prop_type->get_prop_types() : [ $prop_type ]; foreach ( $transformable_prop_types as $transformable_prop_type ) { if ( $transformable_prop_type instanceof Transformable_Prop_Type ) { // When the prop type is originally a union, we need to merge all the categories // of each prop type in the union and create one dynamic prop type with all the categories. $categories = array_merge( $categories, $this->get_related_categories( $transformable_prop_type ) ); } } if ( empty( $categories ) ) { return []; } return [ Dynamic_Prop_Type::make()->categories( $categories ) ]; } private function get_related_categories( Transformable_Prop_Type $prop_type ): array { if ( ! $prop_type->get_meta_item( Dynamic_Prop_Type::META_KEY, true ) ) { return []; } if ( $prop_type instanceof Number_Prop_Type ) { return [ V1_Dynamic_Tags_Module::NUMBER_CATEGORY ]; } if ( $prop_type instanceof Svg_Src_Prop_Type ) { return [ V1_Dynamic_Tags_Module::SVG_CATEGORY ]; } if ( $prop_type instanceof Image_Src_Prop_Type ) { return [ V1_Dynamic_Tags_Module::IMAGE_CATEGORY ]; } if ( $prop_type instanceof Url_Prop_Type ) { return [ V1_Dynamic_Tags_Module::URL_CATEGORY ]; } if ( $prop_type instanceof Html_V3_Prop_Type ) { return [ V1_Dynamic_Tags_Module::TEXT_CATEGORY ]; } if ( $prop_type instanceof Color_Prop_Type ) { return [ V1_Dynamic_Tags_Module::COLOR_CATEGORY ]; } if ( $prop_type instanceof String_Prop_Type && empty( $prop_type->get_enum() ) ) { return [ V1_Dynamic_Tags_Module::TEXT_CATEGORY ]; } return []; } } atomic-widgets/dynamic-tags/import-export/dynamic-transformer.php 0000644 00000001737 15252521350 0021402 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\DynamicTags\ImportExport; use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Type; use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Tags_Module; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Dynamic_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ): ?array { if ( empty( $value['name'] ) || ! is_string( $value['name'] ) ) { return null; } $tag = Dynamic_Tags_Module::instance()->registry->get_tag( $value['name'] ); if ( ! $tag ) { return null; } $group = $value['group'] ?? $tag['group'] ?? ''; return Dynamic_Prop_Type::generate( [ 'name' => $value['name'], 'group' => $group, 'settings' => $value['settings'] ?? [], ], $context->is_disabled() ); } } atomic-widgets/dynamic-tags/dynamic-prop-type.php 0000644 00000004254 15252521350 0016143 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\DynamicTags; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type; use Elementor\Modules\AtomicWidgets\Parsers\Props_Parser; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Dynamic_Prop_Type extends Plain_Prop_Type { const META_KEY = 'dynamic'; /** * Return a tuple that lets the developer ignore the dynamic prop type in the props schema * using `Prop_Type::meta()`, e.g. `String_Prop_Type::make()->meta( Dynamic_Prop_Type::ignore() )`. */ public static function ignore(): array { return [ static::META_KEY, false ]; } public static function get_key(): string { return 'dynamic'; } public function categories( array $categories ) { $this->settings['categories'] = $categories; return $this; } public function get_categories() { return $this->settings['categories'] ?? []; } public static function is_dynamic_prop_value( $value ): bool { return isset( $value['$$type'] ) && self::get_key() === $value['$$type']; } protected function validate_value( $value ): bool { $is_valid_structure = ( isset( $value['name'] ) && is_string( $value['name'] ) && isset( $value['group'] ) && is_string( $value['group'] ) && isset( $value['settings'] ) && is_array( $value['settings'] ) ); if ( ! $is_valid_structure ) { return false; } $tag = Dynamic_Tags_Module::instance()->registry->get_tag( $value['name'] ); if ( ! $tag || ! $this->is_tag_in_supported_categories( $tag ) ) { return false; } return Props_Parser::make( $tag['props_schema'] ) ->validate( $value['settings'] ) ->is_valid(); } protected function sanitize_value( $value ): array { $tag = Dynamic_Tags_Module::instance()->registry->get_tag( $value['name'] ); $sanitized = Props_Parser::make( $tag['props_schema'] ) ->sanitize( $value['settings'] ) ->unwrap(); return [ 'name' => $value['name'], 'group' => $value['group'], 'settings' => $sanitized, ]; } private function is_tag_in_supported_categories( array $tag ): bool { $intersection = array_intersect( $tag['categories'], $this->get_categories() ); return ! empty( $intersection ); } } atomic-widgets/dynamic-tags/dynamic-transformer.php 0000644 00000002666 15252521350 0016553 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\DynamicTags; use Elementor\Core\DynamicTags\Manager as Dynamic_Tags_Manager; use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Dynamic_Transformer extends Transformer_Base { private Dynamic_Tags_Manager $dynamic_tags_manager; private Dynamic_Tags_Schemas $dynamic_tags_schemas; private Render_Props_Resolver $props_resolver; public function __construct( Dynamic_Tags_Manager $dynamic_tags_manager, Dynamic_Tags_Schemas $dynamic_tags_schemas, Render_Props_Resolver $props_resolver ) { $this->dynamic_tags_manager = $dynamic_tags_manager; $this->dynamic_tags_schemas = $dynamic_tags_schemas; $this->props_resolver = $props_resolver; } public function transform( $value, $key ) { if ( ! isset( $value['name'] ) || ! is_string( $value['name'] ) ) { throw new \Exception( 'Dynamic tag name must be a string' ); } if ( isset( $value['settings'] ) && ! is_array( $value['settings'] ) ) { throw new \Exception( 'Dynamic tag settings must be an array' ); } $schema = $this->dynamic_tags_schemas->get( $value['name'] ); $settings = $this->props_resolver->resolve( $schema, $value['settings'] ?? [] ); return $this->dynamic_tags_manager->get_tag_data_content( null, $value['name'], $settings ); } } atomic-widgets/dynamic-tags/dynamic-tags-editor-config.php 0000644 00000027275 15252521350 0017701 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\DynamicTags; use Elementor\Modules\AtomicWidgets\Controls\Section; use Elementor\Modules\AtomicWidgets\Controls\Types\Date_Time_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Image_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Toggle_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Query_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Number_Control; use Elementor\Modules\AtomicWidgets\Controls\Types\Textarea_Control; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; use Elementor\Modules\AtomicWidgets\Query\Query_Builder; use Elementor\Modules\AtomicWidgets\Query\Query_Builder_Factory; use Elementor\Modules\WpRest\Base\Query as Query_Base; use Elementor\Modules\WpRest\Classes\Post_Query; use Elementor\Modules\WpRest\Classes\Term_Query; use Elementor\Modules\WpRest\Classes\User_Query; use Elementor\TemplateLibrary\Source_Local; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Dynamic_Tags_Editor_Config { private Dynamic_Tags_Schemas $schemas; private ?array $tags = null; public function __construct( Dynamic_Tags_Schemas $schemas ) { $this->schemas = $schemas; } public function get_tags(): array { if ( null !== $this->tags ) { return $this->tags; } $atomic_tags = []; $dynamic_tags = Plugin::$instance->dynamic_tags->get_tags_config(); foreach ( $dynamic_tags as $name => $tag ) { $atomic_tag = $this->convert_dynamic_tag_to_atomic( $tag ); if ( $atomic_tag ) { $atomic_tags[ $name ] = $atomic_tag; } } $this->tags = $atomic_tags; return $this->tags; } /** * @param string $name * * @return null|array{ * name: string, * categories: string[], * label: string, * group: string, * atomic_controls: array, * props_schema: array<string, Transformable_Prop_Type> * } */ public function get_tag( string $name ): ?array { $tags = $this->get_tags(); return $tags[ $name ] ?? null; } private function convert_dynamic_tag_to_atomic( $tag ) { if ( empty( $tag['name'] ) || empty( $tag['categories'] ) ) { return null; } $converted_tag = [ 'name' => $tag['name'], 'categories' => $tag['categories'], 'label' => $tag['title'] ?? '', 'group' => $tag['atomic_group'] ?? $tag['group'] ?? '', 'atomic_controls' => [], 'props_schema' => $this->schemas->get( $tag['name'] ), 'meta' => $tag['meta'] ?? [], ]; if ( ! isset( $tag['controls'] ) ) { return $converted_tag; } try { $atomic_controls = $this->convert_controls_to_atomic( $tag ); } catch ( \Exception $e ) { return null; } if ( null === $atomic_controls ) { return null; } $converted_tag['atomic_controls'] = $atomic_controls; return $converted_tag; } private function convert_controls_to_atomic( $tag ) { $atomic_controls = []; $controls = $tag['controls'] ?? null; $force = $tag['force_convert_to_atomic'] ?? false; if ( ! is_array( $controls ) ) { return null; } foreach ( $controls as $control ) { if ( 'section' === $control['type'] ) { continue; } $atomic_control = $this->convert_control_to_atomic( $control, $tag ); if ( ! $atomic_control ) { if ( $force ) { continue; } return null; } $section_name = $control['section']; if ( ! isset( $atomic_controls[ $section_name ] ) ) { $atomic_controls[ $section_name ] = Section::make() ->set_label( $controls[ $section_name ]['label'] ); } $atomic_controls[ $section_name ] = $atomic_controls[ $section_name ]->add_item( $atomic_control ); } return array_values( $atomic_controls ); } private function convert_control_to_atomic( $control, $tag = [] ) { $map = [ 'select' => fn( $control ) => $this->convert_select_control_to_atomic( $control, $tag ), 'text' => fn( $control ) => $this->convert_text_control_to_atomic( $control ), 'textarea' => fn( $control ) => $this->convert_textarea_control_to_atomic( $control ), 'switcher' => fn( $control ) => $this->convert_switch_control_to_atomic( $control ), 'number' => fn( $control ) => $this->convert_number_control_to_atomic( $control ), 'query' => fn( $control ) => $this->convert_autocomplete_control_to_atomic( $control ), 'choose' => fn( $control ) => $this->convert_choose_control_to_atomic( $control ), 'media' => fn( $control ) => $this->convert_media_control_to_atomic( $control ), 'date_time' => fn( $control ) => $this->convert_date_time_control_to_atomic( $control ), ]; if ( ! isset( $map[ $control['type'] ] ) ) { return null; } $is_convertible = ! isset( $control['name'], $control['section'], $control['label'], $control['default'] ); if ( $is_convertible ) { throw new \Exception( 'Control must have name, section, label, and default' ); } return $map[ $control['type'] ]( $control ); } /** * @param $control * * @return Select_Control * @throws \Exception If control is missing options. */ private function convert_select_control_to_atomic( $control, $tag = [] ) { $options = $this->extract_select_options_from_control( $control ); if ( empty( $options ) ) { throw new \Exception( 'Select control must have options' ); } $options = apply_filters( 'elementor/atomic/dynamic_tags/select_control_options', $options, $control, $tag ); $options = array_map( fn( $key, $value ) => [ 'value' => $key, 'label' => $value, ], array_keys( $options ), $options ); $select_control = Select_Control::bind_to( $control['name'] ) ->set_placeholder( $control['placeholder'] ?? '' ) ->set_options( $options ) ->set_label( $control['atomic_label'] ?? $control['label'] ); if ( isset( $control['collection_id'] ) ) { $select_control->set_collection_id( $control['collection_id'] ); } return $select_control; } private function extract_select_options_from_control( $control ): array { $options = $control['options'] ?? []; if ( ! empty( $options ) ) { return $options; } if ( empty( $control['groups'] ) || ! is_array( $control['groups'] ) ) { return $options; } foreach ( $control['groups'] as $group ) { if ( empty( $group['options'] ) || ! is_array( $group['options'] ) ) { continue; } $filtered = array_filter( $group['options'], static function ( $label, $key ) { return is_string( $key ); }, ARRAY_FILTER_USE_BOTH ); $options = array_merge( $options, $filtered ); } return $options; } /** * @param $control * * @return Text_Control */ private function convert_text_control_to_atomic( $control ) { return Text_Control::bind_to( $control['name'] ) ->set_label( $control['label'] ); } private function convert_date_time_control_to_atomic( $control ) { return Date_Time_Control::bind_to( $control['name'] ) ->set_label( $control['label'] ); } /** * @param $control * * @return Switch_Control */ private function convert_switch_control_to_atomic( $control ) { return Switch_Control::bind_to( $control['name'] ) ->set_label( $control['atomic_label'] ?? $control['label'] ); } /** * @param $control * * @return Number_Control */ private function convert_number_control_to_atomic( $control ) { return Number_Control::bind_to( $control['name'] ) ->set_placeholder( $control['placeholder'] ?? '' ) ->set_max( $control['max'] ?? null ) ->set_min( $control['min'] ?? null ) ->set_step( $control['step'] ?? null ) ->set_should_force_int( $control['should_force_int'] ?? false ) ->set_label( $control['label'] ); } private function convert_textarea_control_to_atomic( $control ) { return Textarea_Control::bind_to( $control['name'] ) ->set_placeholder( $control['placeholder'] ?? '' ) ->set_label( $control['label'] ); } private function convert_autocomplete_control_to_atomic( $control ) { $query_config = []; $query_type = Post_Query::ENDPOINT; switch ( true ) { case $this->is_querying_wp_terms( $control ): $query_type = Term_Query::ENDPOINT; $included_types = null; $excluded_types = null; break; case $this->is_control_elementor_query( $control ): $included_types = [ Source_Local::CPT ]; $excluded_types = []; break; case $this->is_querying_wp_media( $control ): $included_types = [ 'attachment' ]; $excluded_types = []; $query_config[ Query_Base::IS_PUBLIC_KEY ] = false; break; case $this->is_querying_wp_users( $control ): $included_types = [ $control['autocomplete']['object'] ]; $excluded_types = null; $query_type = User_Query::ENDPOINT; break; default: $included_types = isset( $control['autocomplete']['query']['post_type'] ) ? $control['autocomplete']['query']['post_type'] : []; $included_types = ! empty( $included_types ) && 'any' !== $included_types ? $included_types : null; $excluded_types = null; } $query_config[ Query_Base::ITEMS_COUNT_KEY ] = $this->extract_item_count_from_control( $control ); $post_status[ Query_Base::IS_PUBLIC_KEY ] = $this->extract_post_status_from_control( $control ); $query_config[ Query_Base::INCLUDED_TYPE_KEY ] = $included_types; $query_config[ Query_Base::EXCLUDED_TYPE_KEY ] = $excluded_types; $query_config[ Query_Builder_Factory::ENDPOINT_KEY ] = $query_type; $query_config[ Query_Base::META_QUERY_KEY ] = $this->extract_meta_query_from_control( $control ); $query_control = Query_Control::bind_to( $control['name'] ); $query_control->set_query_config( $query_config ); $query_control->set_placeholder( $control['placeholder'] ?? '' ); $query_control->set_label( $control['label'] ); $query_control->set_allow_custom_values( false ); return $query_control; } private function is_control_elementor_query( $control ): bool { return isset( $control['autocomplete']['object'] ) && 'library_template' === $control['autocomplete']['object']; } private function is_querying_wp_terms( $control ): bool { return isset( $control['autocomplete']['object'] ) && in_array( $control['autocomplete']['object'], [ 'tax', 'taxonomy', 'term' ], true ); } private function is_querying_wp_media( $control ): bool { return isset( $control['autocomplete']['object'] ) && 'attachment' === $control['autocomplete']['object']; } private function is_querying_wp_users( $control ): bool { global $wp_roles; $roles = array_keys( $wp_roles->roles ); return isset( $control['autocomplete']['object'] ) && in_array( $control['autocomplete']['object'], $roles, true ); } private function convert_choose_control_to_atomic( $control ) { return Toggle_Control::bind_to( $control['name'] ) ->set_label( $control['atomic_label'] ?? $control['label'] ) ->add_options( $control['options'] ) ->set_size( 'tiny' ) ->set_exclusive( true ) ->set_convert_options( true ); } private function convert_media_control_to_atomic( $control ) { return Image_Control::bind_to( $control['name'] ) ->set_label( $control['label'] ); } private function extract_post_status_from_control( $control ): ?bool { $status = $control['autocomplete']['query']['post_status'] ?? null; return isset( $status ) && in_array( 'private', $status ) ? false : null; } private function extract_item_count_from_control( $control ): ?int { $count = $control['autocomplete']['query']['posts_per_page'] ?? null; return isset( $count ) && is_numeric( $count ) ? $count : null; } private function extract_meta_query_from_control( $control ): ?array { return $control['autocomplete']['query']['meta_query'] ?? null; } } atomic-widgets/dynamic-tags/dynamic-tags-module.php 0000644 00000006005 15252521350 0016421 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\DynamicTags; use Elementor\Modules\AtomicWidgets\DynamicTags\ImportExport\Dynamic_Transformer as Import_Export_Dynamic_Transformer; use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers_Registry; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Dynamic_Tags_Module { private static ?self $instance = null; public Dynamic_Tags_Editor_Config $registry; private Dynamic_Tags_Schemas $schemas; private function __construct() { $this->schemas = new Dynamic_Tags_Schemas(); $this->registry = new Dynamic_Tags_Editor_Config( $this->schemas ); } public static function instance( $fresh = false ): self { if ( null === static::$instance || $fresh ) { static::$instance = new static(); } return static::$instance; } public static function fresh(): self { return static::instance( true ); } public function register_hooks() { add_filter( 'elementor/editor/localize_settings', fn( array $settings ) => $this->add_atomic_dynamic_tags_to_editor_settings( $settings ) ); add_filter( 'elementor/atomic-widgets/props-schema', fn( array $schema ) => Dynamic_Prop_Types_Mapping::make()->get_extended_schema( $schema ) ); add_filter( 'elementor/atomic-widgets/styles/schema', fn( array $schema ) => Dynamic_Prop_Types_Mapping::make()->get_extended_style_schema( $schema ), 8, 2 ); add_action( 'elementor/atomic-widgets/settings/transformers/register', fn ( $transformers, $prop_resolver ) => $this->register_transformers( $transformers, $prop_resolver ), 10, 2 ); add_action( 'elementor/atomic-widgets/styles/transformers/register', fn ( $transformers, $prop_resolver ) => $this->register_transformers( $transformers, $prop_resolver ), 10, 2 ); add_action( 'elementor/atomic-widgets/import/transformers/register', fn ( $transformers ) => $this->register_import_export_transformer( $transformers ) ); add_action( 'elementor/atomic-widgets/export/transformers/register', fn ( $transformers ) => $this->register_import_export_transformer( $transformers ) ); } private function add_atomic_dynamic_tags_to_editor_settings( $settings ) { if ( isset( $settings['dynamicTags']['tags'] ) ) { $settings['atomicDynamicTags'] = [ 'tags' => $this->registry->get_tags(), 'groups' => Plugin::$instance->dynamic_tags->get_config()['groups'], ]; } return $settings; } private function register_transformers( Transformers_Registry $transformers, Render_Props_Resolver $props_resolver ) { $transformers->register( Dynamic_Prop_Type::get_key(), new Dynamic_Transformer( Plugin::$instance->dynamic_tags, $this->schemas, $props_resolver ) ); } private function register_import_export_transformer( Transformers_Registry $transformers ) { $transformers->register( Dynamic_Prop_Type::get_key(), new Import_Export_Dynamic_Transformer() ); } } atomic-widgets/dynamic-tags/dynamic-tags-schemas.php 0000644 00000003366 15252521350 0016566 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\DynamicTags; use Elementor\Core\DynamicTags\Base_Tag; use Elementor\Modules\AtomicWidgets\Utils\Image\Placeholder_Image; use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type; use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Query_Prop_Type; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Dynamic_Tags_Schemas { private array $tags_schemas = []; public function get( string $tag_name ) { if ( isset( $this->tags_schemas[ $tag_name ] ) ) { return $this->tags_schemas[ $tag_name ]; } $tag = $this->get_tag( $tag_name ); $this->tags_schemas[ $tag_name ] = []; foreach ( $tag->get_controls() as $control ) { if ( ! isset( $control['type'] ) || 'section' === $control['type'] ) { continue; } $prop_type = Dynamic_Tags_Converter::convert_control_to_prop_type( $control ); if ( ! $prop_type ) { continue; } $this->tags_schemas[ $tag_name ][ $control['name'] ] = $prop_type; } return $this->tags_schemas[ $tag_name ]; } private function get_tag( string $tag_name ): Base_Tag { $tag_info = Plugin::$instance->dynamic_tags->get_tag_info( $tag_name ); if ( ! $tag_info || empty( $tag_info['instance'] ) ) { throw new \Exception( 'Tag not found' ); } if ( ! $tag_info['instance'] instanceof Base_Tag ) { throw new \Exception( 'Tag is not an instance of Tag' ); } return $tag_info['instance']; } } atomic-widgets/prop-type-migrations/migrations-orchestrator.php 0000644 00000030162 15252521350 0021201 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypeMigrations; use Elementor\Core\Base\Document; use Elementor\Core\Upgrade\Manager as Upgrade_Manager; use Elementor\Modules\AtomicWidgets\Logger\Logger; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Elementor\Modules\Components\PropTypes\Component_Override_Parser; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; use Elementor\Modules\Components\PropTypes\Override_Prop_Type; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Migrations_Orchestrator { const EXPERIMENT_BC_MIGRATIONS = 'e_bc_migrations'; const MIGRATIONS_URL = 'https://editor.elementor.com/v1/migrations/'; const BUNDLED_MIGRATIONS_DIRECTORY = 'migrations/'; private static ?self $instance = null; private ?Migrations_Loader $loader = null; private ?Migrations_Loader $local_loader = null; private ?Migrations_Loader $remote_loader = null; private ?string $migrations_path = null; private function __construct( ?string $migrations_path = null ) { $this->migrations_path = $migrations_path; } public function register_hooks() { if ( ! self::is_active() ) { return; } add_filter( 'elementor/document/load/data', fn ( $data, $document ) => $this->migrate_doc( $data, $document ), 10, 2 ); } public static function is_active(): bool { return Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_BC_MIGRATIONS ); } public static function make( ?string $migrations_path = null ): self { if ( null === self::$instance ) { self::$instance = new self( $migrations_path ); } return self::$instance; } public static function destroy(): void { Migrations_Loader::destroy(); if ( null !== self::$instance ) { self::$instance->loader = null; self::$instance->local_loader = null; self::$instance->remote_loader = null; } self::$instance = null; } public static function is_rollback(): bool { /** @var Upgrade_Manager $upgrade_manager */ $upgrade_manager = Plugin::$instance->upgrade; $stored_version = get_option( $upgrade_manager->get_version_option_name() ); if ( ! $stored_version ) { return false; } return version_compare( ELEMENTOR_VERSION, $stored_version, '<' ); } public static function register_affecting_feature_flag_hooks( array $features ): void { if ( ! self::is_active() ) { return; } foreach ( $features as $feature ) { add_action( 'elementor/experiments/feature-state-change/' . $feature, [ __CLASS__, 'clear_migration_cache' ], 10, 2 ); } } public static function clear_migration_cache(): void { Migrations_Cache::clear_all(); } public static function clear_entity_migration_cache( int $id, string $data_identifier ): void { Migrations_Cache::clear_migration_cache( $id, $data_identifier ); } /** * Migrations orchestrator should follow the following steps: * 1. Check cache to see if the data is already migrated * 2. Resolve the schema for the current element * 3. Walk through the data and migrate the props if type mismatch (between data and schema) is found * 4. Migrate the widget keys * 5. Save migrated data to the database * 6. Save the migrated state to the cache * * @param array $data Data structure to migrate will return modified data by reference * @param int $entity_id Unique ID for caching mechanism, so we don't run the migration again for the same data * @param string $data_identifier Unique identifier for DB table, the data type (e.g., '_elementor_data', '_elementor_global_classes') * @param callable $save_callback Function to persist migrated data if changes occurred */ public function migrate( array &$data, int $entity_id, string $data_identifier, callable $save_callback ): void { $this->loader = $this->get_active_loader(); try { if ( Migrations_Cache::is_migrated( $entity_id, $data_identifier, $this->loader->get_manifest_hash() ) ) { return; } $has_changes = $this->walk_and_migrate( $data, [] ); if ( $has_changes ) { $save_callback( $data ); } Migrations_Cache::mark_as_migrated( $entity_id, $data_identifier, $this->loader->get_manifest_hash() ); } catch ( \Exception $e ) { Logger::warning( 'Migration failed', [ 'entity_id' => $entity_id, 'data_identifier' => $data_identifier, 'error' => $e->getMessage(), ] ); } } private function walk_and_migrate( array &$data, array $path ): bool { $has_changes = false; Schema_Resolver::update_widget_context( $data ); if ( $this->handle_widget_key_migrations( $data, $path ) ) { $has_changes = true; } foreach ( $data as $key => &$value ) { if ( ! is_array( $value ) || empty( $value ) ) { continue; } $path[] = $key; if ( isset( $value['$$type'] ) ) { $prop_type = Schema_Resolver::resolve( $key, $path ); if ( $prop_type instanceof Prop_Type && $this->migrate_prop( $value, $prop_type ) ) { $has_changes = true; } } elseif ( $this->walk_and_migrate( $value, $path ) ) { $has_changes = true; } array_pop( $path ); } return $has_changes; } private function handle_widget_key_migrations( array &$data, array $path ): bool { if ( end( $path ) !== 'settings' ) { return false; } $element_type = Schema_Resolver::get_widget_context(); if ( ! $element_type ) { return false; } $schema = Schema_Resolver::get_widget_schema( $element_type ); if ( ! $schema ) { return false; } $schema_keys = array_keys( $schema ); $data_keys_with_type = array_keys( array_filter( $data, fn( $value ) => is_array( $value ) && isset( $value['$$type'] ) ) ); $orphaned_keys = array_diff( $data_keys_with_type, $schema_keys ); $missing_keys = array_diff( $schema_keys, $data_keys_with_type ); $pending_widget_key_migrations = []; foreach ( $orphaned_keys as $orphaned_key ) { $target_key = $this->loader->find_widget_key_migration( $orphaned_key, $missing_keys, $element_type ); if ( $target_key ) { $pending_widget_key_migrations[ $target_key ][] = [ 'from' => $orphaned_key, 'value' => $data[ $orphaned_key ], ]; } } return $this->migrate_pending_widget_keys( $data, $pending_widget_key_migrations ); } private function migrate_pending_widget_keys( array &$data, array $pending_widget_key_migrations ): bool { $has_changes = false; foreach ( $pending_widget_key_migrations as $target_key => $sources ) { if ( count( $sources ) !== 1 ) { continue; } $data[ $target_key ] = $sources[0]['value']; unset( $data[ $sources[0]['from'] ] ); $has_changes = true; } return $has_changes; } private function migrate_prop( &$value, Prop_Type $prop_type ): bool { if ( ! is_array( $value ) || ! isset( $value['$$type'] ) || ! isset( $value['value'] ) ) { return false; } $actual_prop_type = $prop_type; if ( $prop_type instanceof Union_Prop_Type ) { $actual_prop_type = $this->resolve_union_type( $value, $prop_type ); if ( ! $actual_prop_type ) { return false; } } $has_changes = false; if ( $actual_prop_type instanceof Object_Prop_Type && is_array( $value['value'] ) ) { $shape = $actual_prop_type->get_shape(); foreach ( $value['value'] as $child_key => &$child ) { if ( isset( $shape[ $child_key ] ) && $shape[ $child_key ] instanceof Prop_Type ) { if ( $this->migrate_prop( $child, $shape[ $child_key ] ) ) { $has_changes = true; } } } } elseif ( $actual_prop_type instanceof Array_Prop_Type && is_array( $value['value'] ) ) { $item_type = $actual_prop_type->get_item_type(); foreach ( $value['value'] as &$item ) { if ( $this->migrate_prop( $item, $item_type ) ) { $has_changes = true; } } } elseif ( $actual_prop_type instanceof Overridable_Prop_Type && is_array( $value['value'] ) ) { $origin_prop_type = $actual_prop_type->get_origin_prop_type(); if ( $origin_prop_type instanceof Prop_Type && isset( $value['value']['origin_value'] ) ) { if ( $this->migrate_prop( $value['value']['origin_value'], $origin_prop_type ) ) { $has_changes = true; } } } elseif ( $actual_prop_type instanceof Override_Prop_Type && is_array( $value['value'] ) ) { if ( $this->migrate_override_value( $value['value'] ) ) { $has_changes = true; } } $found_type = $value['$$type']; $expected_type = $actual_prop_type::get_key(); if ( $found_type !== $expected_type ) { $path_result = $this->loader->find_migration_path( $found_type, $expected_type ); if ( $path_result ) { $value = $this->execute_prop_migration( $value, $path_result['migrations'], $path_result['direction'] ); $has_changes = true; } } return $has_changes; } private function resolve_union_type( array $value, Union_Prop_Type $union_prop_type ): ?Prop_Type { $found_type = $value['$$type'] ?? null; if ( ! $found_type ) { return null; } $variant = $union_prop_type->get_prop_type( $found_type ); if ( $variant ) { return $variant; } foreach ( $union_prop_type->get_prop_types() as $variant_type ) { if ( $this->loader->find_migration_path( $found_type, $variant_type::get_key() ) ) { return $variant_type; } } return null; } private function migrate_override_value( array &$data ): bool { $override_key = $data['override_key'] ?? null; $override_value = $data['override_value'] ?? null; $schema_source = $data['schema_source'] ?? null; if ( ! $override_key || ! is_array( $override_value ) || ! isset( $override_value['$$type'] ) || ! is_array( $schema_source ) ) { return false; } $component_id = $schema_source['id'] ?? null; if ( ! $component_id ) { return false; } $prop_type = Component_Override_Parser::make()->resolve_override_value_prop_type( $override_key, (int) $component_id ); if ( ! ( $prop_type instanceof Prop_Type ) ) { return false; } return $this->migrate_prop( $data['override_value'], $prop_type ); } private function execute_prop_migration( $prop_value, array $migrations, string $direction ) { foreach ( $migrations as $migration ) { try { $operations = $this->loader->load_operations( $migration['id'] ); if ( ! $operations || ! isset( $operations[ $direction ] ) ) { continue; } $prop_value = Migration_Interpreter::run( [ $direction => $operations[ $direction ] ], $prop_value, $direction ); } catch ( \Exception $e ) { Logger::warning( 'Migration operation failed', [ 'migration_id' => $migration['id'], 'direction' => $direction, 'error' => $e->getMessage(), ] ); return $prop_value; } } return $prop_value; } private function get_active_loader(): Migrations_Loader { if ( $this->migrations_path ) { return Migrations_Loader::make( $this->migrations_path ); } if ( self::is_rollback() ) { return $this->get_remote_loader(); } return $this->get_local_loader(); } private function get_local_loader(): Migrations_Loader { if ( null === $this->local_loader ) { $this->local_loader = Migrations_Loader::make( self::get_local_migrations_path() ); } return $this->local_loader; } private function get_remote_loader(): Migrations_Loader { if ( null === $this->remote_loader ) { $this->remote_loader = Migrations_Loader::make( self::MIGRATIONS_URL, 'manifest.json', self::get_local_migrations_path() ); } return $this->remote_loader; } private static function get_local_migrations_path(): string { if ( defined( 'ELEMENTOR_MIGRATIONS_PATH' ) ) { return constant( 'ELEMENTOR_MIGRATIONS_PATH' ); } return ELEMENTOR_PATH . self::BUNDLED_MIGRATIONS_DIRECTORY; } private function migrate_doc( array $data, Document $document ): array { $this->migrate( $data, $document->get_post()->ID, Document::ELEMENTOR_DATA_META_KEY, function( $migrated_data ) use ( $document ) { $document->delete_meta( Document::CACHE_META_KEY ); $document->update_json_meta( Document::ELEMENTOR_DATA_META_KEY, $migrated_data ); do_action( 'elementor/document/after_migrate', $document, $migrated_data ); } ); return $data; } } atomic-widgets/prop-type-migrations/migrations-cache.php 0000644 00000006534 15252521350 0017533 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypeMigrations; use Elementor\Modules\AtomicWidgets\Logger\Logger; if ( ! defined( 'ABSPATH' ) ) { exit; } class Migrations_Cache { private const MIGRATIONS_STATE_META_KEY = '_elementor_migrations_state'; /** * Is data already migrated? * * @param int $id Meta ID, can be post ID or any other unique ID * @param string $data_identifier Unique identifier for Data. Different DB tables migrate separately, and therefore cached individually * @param string $manifest_hash Manifest can change independently from code version (pulled from remote), we need to rerun migrations if it changes * @return bool */ public static function is_migrated( int $id, string $data_identifier, string $manifest_hash ): bool { $cache_meta_key = self::get_cache_meta_key( $data_identifier ); $current_state = self::get_migration_state( $manifest_hash ); if ( empty( $current_state ) ) { return false; } $stored_state = get_post_meta( $id, $cache_meta_key, true ); return $current_state === $stored_state; } /** * Mark data as migrated * * @param int $id Meta ID, can be post ID or any other unique ID * @param string $data_identifier Unique identifier for Data. Different DB tables migrate separately, and therefore cached individually * @param string $manifest_hash Manifest can change independently from code version (pulled from remote), we need to rerun migrations if it changes * @return void */ public static function mark_as_migrated( int $id, string $data_identifier, string $manifest_hash ): void { $cache_meta_key = self::get_cache_meta_key( $data_identifier ); update_post_meta( $id, $cache_meta_key, self::get_migration_state( $manifest_hash ) ); } /** * Clear migration cache for a specific entity * * @param int $id Meta ID, can be post ID or any other unique ID * @param string $data_identifier Unique identifier for Data. Different DB tables migrate separately, and therefore cached individually * @return void */ public static function clear_migration_cache( int $id, string $data_identifier ): void { $cache_meta_key = self::get_cache_meta_key( $data_identifier ); delete_post_meta( $id, $cache_meta_key ); } public static function clear_all(): void { global $wpdb; $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE %s", $wpdb->esc_like( self::MIGRATIONS_STATE_META_KEY ) . '%' ) ); if ( false === $deleted ) { Logger::error( 'Failed to clear migration caches', [ 'error' => $wpdb->last_error, 'reason' => 'clear migration cache called', ] ); return; } Logger::info( 'Cleared migration caches', [ 'deleted_count' => $deleted, 'reason' => 'clear migration cache called', ] ); } public static function get_version_fingerprint(): string { $fingerprint = ELEMENTOR_VERSION; if ( defined( 'ELEMENTOR_PRO_VERSION' ) ) { $fingerprint .= ':' . ELEMENTOR_PRO_VERSION; } return $fingerprint; } private static function get_cache_meta_key( string $data_identifier ): string { return self::MIGRATIONS_STATE_META_KEY . '_' . substr( md5( $data_identifier ), 0, 4 ); } private static function get_migration_state( string $manifest_hash ): string { if ( empty( $manifest_hash ) ) { return ''; } return self::get_version_fingerprint() . ':' . $manifest_hash; } } atomic-widgets/prop-type-migrations/schema-resolver.php 0000644 00000004475 15252521350 0017417 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypeMigrations; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Style_Schema; use Elementor\Modules\GlobalClasses\Utils\Atomic_Elements_Utils; use Elementor\Modules\Interactions\Schema\Interactions_Schema; if ( ! defined( 'ABSPATH' ) ) { exit; } class Schema_Resolver { const WIDGET_SETTINGS_PATH = 'settings'; const STYLE_VARIANTS_PATH = 'variants'; const STYLE_PROPS_PATH = 'props'; const INTERACTIONS_ITEMS_PATH = 'items'; const INTERACTIONS_PATH = 'interactions'; private static ?string $widget_context = null; public static function update_widget_context( array $data ): void { if ( isset( $data['elType'] ) || isset( $data['widgetType'] ) ) { self::$widget_context = $data['widgetType'] ?? $data['elType'] ?? null; } } public static function get_widget_context(): ?string { return self::$widget_context; } public static function resolve( string $key, array $path ): ?Prop_Type { if ( in_array( self::WIDGET_SETTINGS_PATH, $path, true ) && self::$widget_context ) { $widget_context = self::make_widget_context( self::$widget_context ); return $widget_context['schema'][ $key ] ?? null; } elseif ( in_array( self::STYLE_VARIANTS_PATH, $path, true ) && in_array( self::STYLE_PROPS_PATH, $path, true ) ) { $style_schema = Style_Schema::get(); return $style_schema[ $key ] ?? null; } elseif ( in_array( self::INTERACTIONS_PATH, $path, true ) && in_array( self::INTERACTIONS_ITEMS_PATH, $path, true ) ) { $interactions_schema = Interactions_Schema::get(); return $interactions_schema[ self::INTERACTIONS_ITEMS_PATH ][0] ?? null; } return null; } private static function make_widget_context( string $element_type ): ?array { $schema = self::get_widget_schema( $element_type ); if ( ! $schema ) { return null; } return [ 'schema' => $schema, 'element_type' => $element_type, ]; } public static function get_widget_schema( string $element_type ): ?array { $instance = Atomic_Elements_Utils::get_element_instance( $element_type ); if ( ! $instance || ! method_exists( $instance, 'get_props_schema' ) ) { return null; } $schema = call_user_func( [ $instance, 'get_props_schema' ] ); return is_array( $schema ) && ! empty( $schema ) ? $schema : null; } } atomic-widgets/prop-type-migrations/migration-interpreter.php 0000644 00000032146 15252521350 0020646 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypeMigrations; if ( ! defined( 'ABSPATH' ) ) { exit; } class Migration_Interpreter { public static function run( array $migration_schema, array $element_data, string $direction = 'up' ): array { if ( ! in_array( $direction, [ 'up', 'down' ], true ) ) { throw new \InvalidArgumentException( sprintf( 'Invalid direction "%s". Must be "up" or "down".', esc_html( $direction ) ) ); } $operations = $migration_schema[ $direction ] ?? []; if ( empty( $operations ) ) { return $element_data; } foreach ( $operations as $operation_def ) { $element_data = self::apply_operation( $operation_def, $element_data ); } return $element_data; } private static function apply_operation( array $operation_def, array $element_data ): array { $op = $operation_def['op'] ?? []; $condition = $operation_def['condition'] ?? null; $fn = $op['fn'] ?? null; $path_pattern = $op['path'] ?? null; if ( null === $fn ) { return $element_data; } if ( 'move' === $fn ) { self::execute_move( $op, $element_data ); return $element_data; } if ( null === $path_pattern ) { return $element_data; } $resolved_paths = Path_Resolver::resolve( $path_pattern, $element_data ); if ( empty( $resolved_paths ) && 'set' === $fn && ! self::has_wildcard( $path_pattern ) ) { $resolved_paths = [ [ 'path' => $path_pattern, 'wildcard_values' => [], ], ]; } foreach ( $resolved_paths as $path_info ) { $resolved_path = $path_info['path']; $wildcard_values = $path_info['wildcard_values']; if ( null !== $condition && ! self::check_condition( $condition, $wildcard_values, $element_data ) ) { continue; } self::execute_operation( $fn, $op, $resolved_path, $element_data ); } return $element_data; } private static function has_wildcard( string $path ): bool { return strpos( $path, '*' ) !== false; } private static function execute_operation( string $fn, array $op, string $path, array &$data ): void { switch ( $fn ) { case 'set': self::execute_set( $op, $path, $data ); break; case 'delete': $clean = $op['clean'] ?? true; self::execute_delete( $data, $path, $clean ); break; case 'move': self::execute_move( $op, $data ); break; } } private static function execute_set( array $op, string $path, array &$data ): void { $has_value = array_key_exists( 'value', $op ); $has_key = array_key_exists( 'key', $op ); $merge = $op['merge'] ?? true; if ( $has_key ) { $new_key = $op['key']; self::rename_key_at_path( $data, $path, $new_key ); $path = self::get_renamed_path( $path, $new_key ); } if ( $has_value ) { $current_value = Path_Resolver::get( $path, $data ); $new_value = self::resolve_value( $op['value'], $current_value ); if ( $merge && is_array( $current_value ) && is_array( $new_value ) ) { $new_value = self::deep_merge( $current_value, $new_value ); } self::set_at_path( $data, $path, $new_value ); } elseif ( ! $has_key ) { self::set_at_path( $data, $path, new \stdClass() ); } } private static function execute_move( array $op, array &$data ): void { $src = $op['src'] ?? null; $dest = $op['dest'] ?? null; $clean = $op['clean'] ?? true; if ( null === $src || null === $dest ) { throw new \InvalidArgumentException( 'Move operation requires both "src" and "dest" parameters' ); } $value = Path_Resolver::get( $src, $data ); if ( null === $value ) { return; } self::set_at_path( $data, $dest, $value ); if ( $clean ) { self::execute_delete( $data, $src, true ); } } private static function check_condition( array $condition, array $wildcard_values, array $data ): bool { $fn = $condition['fn'] ?? null; if ( null === $fn ) { return true; } if ( 'and' === $fn ) { return self::check_and_condition( $condition, $wildcard_values, $data ); } if ( 'or' === $fn ) { return self::check_or_condition( $condition, $wildcard_values, $data ); } $path_pattern = $condition['path'] ?? null; if ( null === $path_pattern ) { return false; } $resolved_path = Path_Resolver::resolve_with_wildcard_binding( $path_pattern, $data, $wildcard_values ); if ( null === $resolved_path ) { if ( 'not_exists' === $fn ) { return true; } return false; } $value = Path_Resolver::get( $resolved_path, $data ); $expected = $condition['value'] ?? null; return self::evaluate_condition( $fn, $value, $expected ); } private static function check_and_condition( array $condition, array $wildcard_values, array $data ): bool { $conditions = $condition['conditions'] ?? []; foreach ( $conditions as $condition ) { if ( ! self::check_condition( $condition, $wildcard_values, $data ) ) { return false; } } return true; } private static function check_or_condition( array $condition, array $wildcard_values, array $data ): bool { $conditions = $condition['conditions'] ?? []; foreach ( $conditions as $condition ) { if ( self::check_condition( $condition, $wildcard_values, $data ) ) { return true; } } return false; } private static function evaluate_condition( string $fn, $value, $expected ): bool { switch ( $fn ) { case 'equals': return $value === $expected; case 'not_equals': return $value !== $expected; case 'exists': return null !== $value; case 'not_exists': return null === $value; case 'in': return is_array( $expected ) && in_array( $value, $expected, true ); case 'not_in': return is_array( $expected ) && ! in_array( $value, $expected, true ); case 'is_primitive': return is_scalar( $value ); case 'is_object': return is_array( $value ) && self::is_associative_array( $value ); case 'is_array': return is_array( $value ) && ! self::is_associative_array( $value ); default: return false; } } private static function is_associative_array( array $array ): bool { if ( empty( $array ) ) { return true; } return array_keys( $array ) !== range( 0, count( $array ) - 1 ); } private static function resolve_value( $value_definition, $current_value ) { if ( ! self::is_reference( $value_definition ) ) { return $value_definition; } if ( is_string( $value_definition ) ) { return self::resolve_string_reference( $value_definition, $current_value ); } if ( is_array( $value_definition ) ) { return self::resolve_array_reference( $value_definition, $current_value ); } return $value_definition; } private static function is_reference( $value ): bool { if ( is_string( $value ) ) { return '$$current' === $value || 0 === strpos( $value, '$$current.' ); } if ( is_array( $value ) ) { foreach ( $value as $item ) { if ( self::is_reference( $item ) ) { return true; } } } return false; } private static function resolve_string_reference( string $value, $current_value ) { if ( '$$current' === $value ) { return $current_value; } if ( 0 === strpos( $value, '$$current.' ) ) { $path = substr( $value, 10 ); return self::get_nested_value( $current_value, $path ); } return $value; } private static function resolve_array_reference( array $value, $current_value ): array { $result = []; foreach ( $value as $key => $item ) { $result[ $key ] = self::resolve_value( $item, $current_value ); } return $result; } private static function get_nested_value( $data, string $path ) { if ( ! is_array( $data ) ) { return null; } $keys = explode( '.', $path ); foreach ( $keys as $key ) { if ( ! is_array( $data ) || ! array_key_exists( $key, $data ) ) { return null; } $data = $data[ $key ]; } return $data; } private static function deep_merge( array $base, array $overlay ): array { foreach ( $overlay as $key => $value ) { if ( is_array( $value ) && isset( $base[ $key ] ) && is_array( $base[ $key ] ) ) { $base[ $key ] = self::deep_merge( $base[ $key ], $value ); } else { $base[ $key ] = $value; } } return $base; } private static function get_renamed_path( string $path, string $new_key ): string { $last_dot = strrpos( $path, '.' ); $last_bracket = strrpos( $path, '[' ); if ( false === $last_dot && false === $last_bracket ) { return $new_key; } $last_separator = max( false === $last_dot ? -1 : $last_dot, false === $last_bracket ? -1 : $last_bracket ); if ( $last_separator === $last_bracket ) { $close_bracket = strpos( $path, ']', $last_bracket ); if ( false === $close_bracket ) { throw new \Exception( sprintf( 'Malformed path: missing closing bracket in "%s"', esc_html( $path ) ) ); } return substr( $path, 0, $last_bracket + 1 ) . $new_key . substr( $path, $close_bracket ); } return substr( $path, 0, $last_dot + 1 ) . $new_key; } private static function set_at_path( array &$data, string $path, $value ): void { $segments = self::parse_path_segments( $path ); if ( empty( $segments ) ) { return; } self::set_recursive( $data, $segments, $value ); } private static function execute_delete( array &$data, string $path, bool $clean = true ): void { $segments = self::parse_path_segments( $path ); if ( empty( $segments ) ) { return; } self::delete_recursive( $data, $segments, $clean, false ); } private static function rename_key_at_path( array &$data, string $path, string $new_key ): void { $segments = self::parse_path_segments( $path ); if ( empty( $segments ) ) { return; } self::rename_key_recursive( $data, $segments, $new_key ); } private static function parse_path_segments( string $path ): array { $segments = []; $current = ''; $length = strlen( $path ); for ( $i = 0; $i < $length; $i++ ) { $char = $path[ $i ]; if ( '.' === $char ) { self::flush_segment( $segments, $current ); } elseif ( '[' === $char ) { self::flush_segment( $segments, $current ); $i = self::parse_bracket_segment( $path, $i, $segments ); } else { $current .= $char; } } self::flush_segment( $segments, $current ); return $segments; } private static function flush_segment( array &$segments, string &$current ): void { if ( '' !== $current ) { $segments[] = $current; $current = ''; } } private static function parse_bracket_segment( string $path, int $start_pos, array &$segments ): int { $end = strpos( $path, ']', $start_pos ); if ( false === $end ) { throw new \Exception( sprintf( 'Malformed path: unmatched opening bracket in "%s"', esc_html( $path ) ) ); } $index = substr( $path, $start_pos + 1, $end - $start_pos - 1 ); if ( '' === $index ) { $segments[] = '[]'; } else { $segments[] = $index; } return $end; } private static function set_recursive( array &$data, array $segments, $value ): void { $key = array_shift( $segments ); if ( '[]' === $key ) { if ( empty( $segments ) ) { $data[] = $value; } else { $new_index = count( $data ); $data[ $new_index ] = []; self::set_recursive( $data[ $new_index ], $segments, $value ); } return; } if ( empty( $segments ) ) { $data[ $key ] = $value; return; } if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) { $data[ $key ] = []; } self::set_recursive( $data[ $key ], $segments, $value ); } private static function delete_recursive( array &$data, array $segments, bool $clean = true, bool $can_convert_to_object = true ): bool { $key = array_shift( $segments ); if ( ! array_key_exists( $key, $data ) ) { return false; } if ( empty( $segments ) ) { $was_associative = ! self::is_indexed_array( $data ); unset( $data[ $key ] ); if ( ! $was_associative ) { $data = array_values( $data ); } if ( $can_convert_to_object && empty( $data ) && $was_associative ) { $data = new \stdClass(); } if ( $clean && ( $data instanceof \stdClass || empty( $data ) ) ) { return true; } return false; } if ( is_array( $data[ $key ] ) ) { $should_delete_parent = self::delete_recursive( $data[ $key ], $segments, $clean, true ); if ( $should_delete_parent ) { $was_parent_associative = ! self::is_indexed_array( $data ); unset( $data[ $key ] ); if ( ! $was_parent_associative ) { $data = array_values( $data ); } if ( $can_convert_to_object && empty( $data ) && $was_parent_associative ) { $data = new \stdClass(); } if ( $clean && ( $data instanceof \stdClass || empty( $data ) ) ) { return true; } } } return false; } private static function is_indexed_array( array $array ): bool { if ( empty( $array ) ) { return false; } $keys = array_keys( $array ); return range( 0, count( $array ) - 1 ) === $keys; } private static function rename_key_recursive( array &$data, array $segments, string $new_key ): void { $key = array_shift( $segments ); if ( ! array_key_exists( $key, $data ) ) { return; } if ( empty( $segments ) ) { $new_data = []; foreach ( $data as $k => $v ) { if ( $k === $key ) { $new_data[ $new_key ] = $v; } else { $new_data[ $k ] = $v; } } $data = $new_data; return; } if ( is_array( $data[ $key ] ) ) { self::rename_key_recursive( $data[ $key ], $segments, $new_key ); } } } atomic-widgets/prop-type-migrations/path-resolver.php 0000644 00000014146 15252521350 0017107 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypeMigrations; if ( ! defined( 'ABSPATH' ) ) { exit; } class Path_Resolver { public static function resolve( string $pattern, array $data, bool $allow_missing_leaf = true ): array { $segments = self::parse_path( $pattern ); return self::resolve_segments( $segments, $data, [], '', $allow_missing_leaf ); } public static function resolve_with_wildcard_binding( string $pattern, array $data, array $wildcard_values ): ?string { $segments = self::parse_path( $pattern ); $wildcard_index = 0; return self::resolve_with_binding( $segments, $data, '', $wildcard_values, $wildcard_index ); } public static function get( string $path, array $data ) { $segments = self::parse_concrete_path( $path ); $current = $data; foreach ( $segments as $segment ) { if ( ! is_array( $current ) ) { return null; } $key = $segment['value']; if ( ! array_key_exists( $key, $current ) ) { return null; } $current = $current[ $key ]; } return $current; } private static function parse_path( string $path ): array { $segments = []; $current = ''; $length = strlen( $path ); for ( $i = 0; $i < $length; $i++ ) { $char = $path[ $i ]; if ( '.' === $char ) { self::flush_current_segment( $segments, $current ); } elseif ( '[' === $char ) { self::flush_current_segment( $segments, $current ); $end = strpos( $path, ']', $i ); if ( false === $end ) { throw new \Exception( sprintf( 'Malformed path: unmatched opening bracket in "%s"', esc_html( $path ) ) ); } $index = substr( $path, $i + 1, $end - $i - 1 ); $segments[] = self::create_segment( $index, 'index' ); $i = $end; } elseif ( ']' === $char ) { throw new \Exception( sprintf( 'Malformed path: unmatched closing bracket in "%s"', esc_html( $path ) ) ); } else { $current .= $char; } } self::flush_current_segment( $segments, $current ); return $segments; } private static function flush_current_segment( array &$segments, string &$current ): void { if ( '' !== $current ) { $segments[] = self::create_segment( $current, 'key' ); $current = ''; } } private static function parse_concrete_path( string $path ): array { return self::parse_path( $path ); } private static function create_segment( string $value, string $type ): array { return [ 'value' => $value, 'type' => $type, 'is_wildcard' => '*' === $value, ]; } private static function resolve_segments( array $segments, $data, array $wildcard_values, string $current_path = '', bool $allow_missing_leaf = true ): array { if ( empty( $segments ) ) { return [ [ 'path' => $current_path, 'wildcard_values' => $wildcard_values, ], ]; } if ( ! is_array( $data ) ) { return []; } $segment = array_shift( $segments ); $is_last_segment = empty( $segments ); if ( $segment['is_wildcard'] ) { return self::resolve_wildcard_segment( $segment, $segments, $data, $wildcard_values, $current_path, $allow_missing_leaf ); } return self::resolve_concrete_segment( $segment, $segments, $data, $wildcard_values, $current_path, $is_last_segment, $allow_missing_leaf ); } private static function resolve_wildcard_segment( array $segment, array $segments, array $data, array $wildcard_values, string $current_path, bool $allow_missing_leaf ): array { $results = []; $keys = array_keys( $data ); if ( 'index' === $segment['type'] ) { $keys = array_filter( $keys, 'is_int' ); } else { $keys = array_filter( $keys, 'is_string' ); } foreach ( $keys as $key ) { $new_wildcard_values = $wildcard_values; $new_wildcard_values[] = [ 'key' => $key, 'type' => $segment['type'], ]; $new_path = self::append_to_path( $current_path, $key, $segment['type'] ); $results = array_merge( $results, self::resolve_segments( $segments, $data[ $key ], $new_wildcard_values, $new_path, $allow_missing_leaf ) ); } return $results; } private static function resolve_concrete_segment( array $segment, array $segments, array $data, array $wildcard_values, string $current_path, bool $is_last_segment, bool $allow_missing_leaf ): array { $key = $segment['value']; $key_exists = array_key_exists( $key, $data ); if ( ! $key_exists && ! ( $allow_missing_leaf && $is_last_segment ) ) { return []; } $new_path = self::append_to_path( $current_path, $key, $segment['type'] ); if ( $is_last_segment ) { return [ [ 'path' => $new_path, 'wildcard_values' => $wildcard_values, ], ]; } return self::resolve_segments( $segments, $data[ $key ], $wildcard_values, $new_path, $allow_missing_leaf ); } private static function resolve_with_binding( array $segments, $data, string $prefix, array $wildcard_values, int &$wildcard_index ): ?string { if ( empty( $segments ) ) { return $prefix; } if ( ! is_array( $data ) ) { return null; } $segment = array_shift( $segments ); if ( $segment['is_wildcard'] ) { if ( ! isset( $wildcard_values[ $wildcard_index ] ) ) { return null; } $key = $wildcard_values[ $wildcard_index ]['key']; ++$wildcard_index; if ( ! array_key_exists( $key, $data ) ) { return null; } $new_prefix = self::append_to_path( $prefix, $key, $segment['type'] ); return self::resolve_with_binding( $segments, $data[ $key ], $new_prefix, $wildcard_values, $wildcard_index ); } $key = $segment['value']; if ( ! array_key_exists( $key, $data ) ) { return null; } $new_prefix = self::append_to_path( $prefix, $key, $segment['type'] ); return self::resolve_with_binding( $segments, $data[ $key ], $new_prefix, $wildcard_values, $wildcard_index ); } private static function build_path_string( array $wildcard_values ): string { $path = ''; foreach ( $wildcard_values as $entry ) { $path = self::append_to_path( $path, $entry['key'], $entry['type'] ); } return $path; } private static function append_to_path( string $prefix, $key, string $type ): string { if ( 'index' === $type ) { return $prefix . '[' . $key . ']'; } if ( '' === $prefix ) { return (string) $key; } return $prefix . '.' . $key; } } atomic-widgets/prop-type-migrations/migrations-loader.php 0000644 00000022073 15252521350 0017732 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\PropTypeMigrations; use Elementor\Modules\AtomicWidgets\Logger\Logger; if ( ! defined( 'ABSPATH' ) ) { exit; } class Migrations_Loader { private const TRANSIENT_KEY = 'elementor_migrations_manifest'; private const TRANSIENT_TTL = 12 * HOUR_IN_SECONDS; private ?array $manifest = null; private ?string $manifest_hash = null; private string $base_path; private string $manifest_file; private ?string $fallback_base_path; private function __construct( string $base_path, string $manifest_file = 'manifest.json', ?string $fallback_base_path = null ) { $this->base_path = rtrim( $base_path, '/' ) . '/'; $this->manifest_file = $manifest_file; $this->fallback_base_path = $fallback_base_path ? rtrim( $fallback_base_path, '/' ) . '/' : null; } public static function make( string $base_path, string $manifest_file = 'manifest.json', ?string $fallback_base_path = null ): self { return new self( $base_path, $manifest_file, $fallback_base_path ); } public static function destroy(): void { delete_transient( self::TRANSIENT_KEY ); } public function find_migration_path( string $source_type, string $target_type ): ?array { $graph = $this->build_migration_graph(); $path = $this->find_shortest_path( $graph, $source_type, $target_type ); if ( $path ) { return [ 'migrations' => $path, 'direction' => 'up', ]; } $path = $this->find_shortest_path( $graph, $target_type, $source_type ); if ( $path ) { return [ 'migrations' => array_reverse( $path ), 'direction' => 'down', ]; } return null; } public function find_widget_key_migration( string $orphaned_key, array $missing_keys, string $widget_type ): ?string { $graph = $this->build_widget_key_graph( $widget_type ); $valid_targets = []; foreach ( $missing_keys as $missing_key ) { if ( $this->key_path_exists( $graph, $orphaned_key, $missing_key ) || $this->key_path_exists( $graph, $missing_key, $orphaned_key ) ) { $valid_targets[] = $missing_key; } } if ( count( $valid_targets ) === 1 ) { return $valid_targets[0]; } return null; } private function build_migration_graph(): array { $manifest = $this->get_manifest(); $graph = []; $prop_types = $manifest['propTypes'] ?? []; foreach ( $prop_types as $id => $migration ) { $from = $migration['fromType']; if ( ! isset( $graph[ $from ] ) ) { $graph[ $from ] = []; } $migration['id'] = $id; $graph[ $from ][] = $migration; } return $graph; } private function build_widget_key_graph( string $widget_type ): array { $manifest = $this->get_manifest(); $graph = []; $widget_keys = $manifest['widgetKeys'] ?? []; if ( ! isset( $widget_keys[ $widget_type ] ) ) { return $graph; } foreach ( $widget_keys[ $widget_type ] as $mapping ) { $from = $mapping['from']; $to = $mapping['to']; if ( ! isset( $graph[ $from ] ) ) { $graph[ $from ] = []; } $graph[ $from ][] = $to; } return $graph; } private function key_path_exists( array $graph, string $start, string $end ): bool { if ( $start === $end ) { return false; } if ( ! isset( $graph[ $start ] ) ) { return false; } if ( in_array( $end, $graph[ $start ], true ) ) { return true; } $visited = [ $start => true ]; $queue = [ $start ]; while ( ! empty( $queue ) ) { $current = array_shift( $queue ); if ( ! isset( $graph[ $current ] ) ) { continue; } foreach ( $graph[ $current ] as $next ) { if ( isset( $visited[ $next ] ) ) { continue; } if ( $next === $end ) { return true; } $visited[ $next ] = true; $queue[] = $next; } } return false; } private function find_shortest_path( array $graph, string $start, string $end ): ?array { if ( $start === $end ) { return []; } $queue = [ [ $start, [] ] ]; $visited = [ $start => true ]; while ( ! empty( $queue ) ) { list( $current, $path ) = array_shift( $queue ); if ( ! isset( $graph[ $current ] ) ) { continue; } foreach ( $graph[ $current ] as $migration ) { $next = $migration['toType']; if ( isset( $visited[ $next ] ) ) { continue; } $new_path = array_merge( $path, [ $migration ] ); if ( $next === $end ) { return $new_path; } $visited[ $next ] = true; $queue[] = [ $next, $new_path ]; } } return null; } public function load_operations( string $migration_id ): ?array { $manifest = $this->get_manifest(); $prop_types = $manifest['propTypes'] ?? []; if ( ! isset( $prop_types[ $migration_id ] ) ) { return null; } $migration = $prop_types[ $migration_id ]; if ( ! isset( $migration['path'] ) ) { return null; } $file_path = $this->base_path . $migration['path']; $contents = $this->read_source( $file_path ); if ( false === $contents ) { Logger::warning( 'Migration operation file not found', [ 'migration_id' => $migration_id, 'path' => $file_path, ] ); return null; } $operations = json_decode( $contents, true ); if ( json_last_error() !== JSON_ERROR_NONE ) { Logger::warning( 'Invalid migration operation JSON', [ 'migration_id' => $migration_id, 'path' => $file_path, 'error' => json_last_error_msg(), ] ); return null; } return $operations; } public function get_manifest_hash(): string { if ( null !== $this->manifest_hash ) { return $this->manifest_hash; } $manifest = $this->get_manifest(); if ( empty( $manifest ) ) { $this->manifest_hash = ''; return $this->manifest_hash; } $this->manifest_hash = md5( wp_json_encode( $manifest ) ); return $this->manifest_hash; } private function get_manifest(): array { if ( null !== $this->manifest ) { return $this->manifest; } $manifest_path = $this->base_path . $this->manifest_file; $fetched_from_remote = false; if ( $this->is_url( $manifest_path ) ) { $this->manifest = $this->get_manifest_from_transient(); if ( null !== $this->manifest ) { return $this->manifest; } $contents = $this->read_remote_source( $manifest_path ); $fetched_from_remote = false !== $contents; } else { $contents = $this->read_local_source( $manifest_path ); } if ( false === $contents ) { $fallback_path = $this->resolve_fallback_path( $manifest_path ); if ( $fallback_path ) { $contents = $this->read_local_source( $fallback_path ); } } if ( false === $contents ) { Logger::warning( 'Migrations manifest not found', [ 'path' => $manifest_path, ] ); $this->manifest = $this->empty_manifest(); return $this->manifest; } $manifest = json_decode( $contents, true ); if ( json_last_error() !== JSON_ERROR_NONE ) { Logger::warning( 'Invalid migrations manifest JSON', [ 'path' => $manifest_path, 'error' => json_last_error_msg(), ] ); $this->manifest = $this->empty_manifest(); return $this->manifest; } $this->manifest = $manifest; if ( $fetched_from_remote ) { $this->save_manifest_to_transient( $manifest ); } return $this->manifest; } private function get_manifest_from_transient(): ?array { $cached = get_transient( self::TRANSIENT_KEY ); if ( ! is_array( $cached ) ) { return null; } if ( ( $cached['version'] ?? '' ) !== Migrations_Cache::get_version_fingerprint() ) { delete_transient( self::TRANSIENT_KEY ); return null; } return $cached['manifest'] ?? null; } private function save_manifest_to_transient( array $manifest ): void { set_transient( self::TRANSIENT_KEY, [ 'version' => Migrations_Cache::get_version_fingerprint(), 'manifest' => $manifest, ], self::TRANSIENT_TTL ); } private function empty_manifest(): array { return [ 'widgetKeys' => [], 'propTypes' => [], ]; } private function read_source( string $path ) { if ( $this->is_url( $path ) ) { $contents = $this->read_remote_source( $path ); if ( false !== $contents ) { return $contents; } $fallback_path = $this->resolve_fallback_path( $path ); if ( ! $fallback_path ) { return false; } return $this->read_local_source( $fallback_path ); } return $this->read_local_source( $path ); } private function read_remote_source( string $path ) { $response = wp_remote_get( $path, [ 'timeout' => 3 ] ); if ( is_wp_error( $response ) ) { return false; } $response_code = wp_remote_retrieve_response_code( $response ); if ( $response_code < 200 || $response_code >= 300 ) { return false; } return wp_remote_retrieve_body( $response ); } private function read_local_source( string $path ) { if ( ! file_exists( $path ) ) { return false; } return file_get_contents( $path ); } private function resolve_fallback_path( string $path ): ?string { if ( ! $this->fallback_base_path ) { return null; } if ( $this->is_url( $path ) && str_starts_with( $path, $this->base_path ) ) { return $this->fallback_base_path . substr( $path, strlen( $this->base_path ) ); } return $this->fallback_base_path . basename( $path ); } private function is_url( string $path ): bool { return str_starts_with( $path, 'http://' ) || str_starts_with( $path, 'https://' ); } } atomic-widgets/parsers/props-parser.php 0000644 00000004041 15252521350 0014310 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Parsers; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Core\Utils\Api\Parse_Result; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Props_Parser { private array $schema; public function __construct( array $schema ) { $this->schema = $schema; } public static function make( array $schema ): self { return new static( $schema ); } /** * @param array $props * The key of each item represents the prop name (should match the schema), * and the value is the prop value to validate */ public function validate( array $props ): Parse_Result { $result = Parse_Result::make(); $validated = []; foreach ( $this->schema as $key => $prop_type ) { if ( ! ( $prop_type instanceof Prop_Type ) ) { continue; } $value = $props[ $key ] ?? null; $is_valid = $prop_type->validate( $value ?? $prop_type->get_default() ); if ( ! $is_valid ) { $result->errors()->add( $key, 'invalid_value' ); continue; } if ( ! is_null( $value ) ) { $validated[ $key ] = $value; } } return $result->wrap( $validated ); } /** * @param array $props * The key of each item represents the prop name (should match the schema), * and the value is the prop value to sanitize */ public function sanitize( array $props ): Parse_Result { $sanitized = []; foreach ( $this->schema as $key => $prop_type ) { if ( ! isset( $props[ $key ] ) ) { continue; } $sanitized[ $key ] = $prop_type->sanitize( $props[ $key ] ); } return Parse_Result::make()->wrap( $sanitized ); } /** * @param array $props * The key of each item represents the prop name (should match the schema), * and the value is the prop value to parse */ public function parse( array $props ): Parse_Result { $validate_result = $this->validate( $props ); $sanitize_result = $this->sanitize( $validate_result->unwrap() ); $sanitize_result->errors()->merge( $validate_result->errors() ); return $sanitize_result; } } atomic-widgets/parsers/style-parser.php 0000644 00000015316 15252521350 0014314 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\Parsers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use Elementor\Modules\AtomicWidgets\OptIn\Opt_In; use Elementor\Plugin; use Elementor\Utils; use Elementor\Core\Utils\Api\Parse_Result; use Elementor\Modules\AtomicWidgets\Styles\Style_States; class Style_Parser { const VALID_TYPES = [ 'class', ]; private array $schema; public function __construct( array $schema ) { $this->schema = $schema; } public static function make( array $schema ): self { return new static( $schema ); } /** * @param array $style * the style object to validate */ private function validate( array $style ): Parse_Result { $validated_style = $style; $result = Parse_Result::make(); if ( ! isset( $style['id'] ) || ! is_string( $style['id'] ) ) { $result->errors()->add( 'id', 'missing_or_invalid' ); } if ( ! isset( $style['type'] ) || ! in_array( $style['type'], self::VALID_TYPES, true ) ) { $result->errors()->add( 'type', 'missing_or_invalid' ); } if ( ! isset( $style['label'] ) || ! is_string( $style['label'] ) ) { $result->errors()->add( 'label', 'missing_or_invalid' ); } elseif ( Plugin::$instance->experiments->is_feature_active( Opt_In::EXPERIMENT_NAME ) ) { $label_validation = $this->validate_style_label( $style['label'] ); if ( ! $label_validation['is_valid'] ) { $result->errors()->add( 'label', $label_validation['error_message'] ); } } if ( ! isset( $style['variants'] ) || ! is_array( $style['variants'] ) ) { $result->errors()->add( 'variants', 'missing_or_invalid' ); unset( $validated_style['variants'] ); return $result->wrap( $validated_style ); } $props_parser = Props_Parser::make( $this->schema ); foreach ( $style['variants'] as $variant_index => $variant ) { if ( ! isset( $variant['meta'] ) ) { $result->errors()->add( 'meta', 'missing' ); continue; } $meta_result = $this->validate_meta( $variant['meta'] ); $custom_css_result = $this->validate_custom_css( $variant ); $result->errors()->merge( $meta_result->errors(), 'meta' ); $result->errors()->merge( $custom_css_result->errors(), 'custom_css' ); if ( $meta_result->is_valid() ) { $variant_result = $props_parser->validate( $variant['props'] ); $result->errors()->merge( $variant_result->errors(), "variants[$variant_index]" ); $validated_style['variants'][ $variant_index ]['props'] = $variant_result->unwrap(); } else { unset( $validated_style['variants'][ $variant_index ] ); } } return $result->wrap( $validated_style ); } private function validate_style_label( string $label ): array { $label = strtolower( $label ); $reserved_class_names = [ 'container' ]; if ( strlen( $label ) > 50 ) { return [ 'is_valid' => false, 'error_message' => 'class_name_too_long', ]; } if ( strlen( $label ) < 2 ) { return [ 'is_valid' => false, 'error_message' => 'class_name_too_short', ]; } if ( in_array( $label, $reserved_class_names, true ) ) { return [ 'is_valid' => false, 'error_message' => 'reserved_class_name', ]; } $regexes = [ [ 'pattern' => '/^(|[^0-9].*)$/', 'message' => 'class_name_starts_with_digit', ], [ 'pattern' => '/^\S*$/', 'message' => 'class_name_contains_spaces', ], [ 'pattern' => '/^(|[a-zA-Z0-9_-]+)$/', 'message' => 'class_name_invalid_chars', ], [ 'pattern' => '/^(?!--).*/', 'message' => 'class_name_double_hyphen', ], [ 'pattern' => '/^(?!-[0-9])/', 'message' => 'class_name_starts_with_hyphen_digit', ], ]; foreach ( $regexes as $rule ) { if ( ! preg_match( $rule['pattern'], $label ) ) { return [ 'is_valid' => false, 'error_message' => $rule['message'], ]; } } return [ 'is_valid' => true, 'error_message' => null, ]; } private function validate_meta( $meta ): Parse_Result { $result = Parse_Result::make(); if ( ! is_array( $meta ) ) { $result->errors()->add( 'meta', 'invalid_type' ); return $result; } if ( ! array_key_exists( 'state', $meta ) || ! Style_States::is_valid_state( $meta['state'] ) ) { $result->errors()->add( 'state', 'missing_or_invalid_value' ); return $result; } // TODO: Validate breakpoint based on the existing breakpoints in the system [EDS-528] if ( ! isset( $meta['breakpoint'] ) || ! is_string( $meta['breakpoint'] ) ) { $result->errors()->add( 'breakpoint', 'missing_or_invalid_value' ); return $result; } return $result; } private function validate_custom_css( array $variant ): Parse_Result { $result = Parse_Result::make(); if ( ! empty( $variant['custom_css']['raw'] ) && ( ! is_string( $variant['custom_css']['raw'] ) || null === Utils::decode_string( $variant['custom_css']['raw'], null ) ) ) { $result->errors()->add( 'custom_css', 'invalid_type' ); } return $result; } private function sanitize_meta( $meta ) { if ( ! is_array( $meta ) ) { return []; } if ( isset( $meta['breakpoint'] ) ) { $meta['breakpoint'] = sanitize_key( $meta['breakpoint'] ); } return $meta; } private function sanitize_custom_css( array $variant ) { if ( empty( $variant['custom_css']['raw'] ) ) { return null; } $custom_css = Utils::decode_string( $variant['custom_css']['raw'] ); $custom_css = sanitize_textarea_field( $custom_css ); $custom_css = [ 'raw' => Utils::encode_string( $custom_css ) ]; return empty( $custom_css['raw'] ) ? null : $custom_css; } /** * @param array $style * the style object to sanitize */ private function sanitize( array $style ): Parse_Result { $props_parser = Props_Parser::make( $this->schema ); if ( isset( $style['label'] ) ) { $style['label'] = sanitize_text_field( $style['label'] ); } if ( isset( $style['id'] ) ) { $style['id'] = sanitize_key( $style['id'] ); } if ( isset( $style['sync_to_v3'] ) ) { $style['sync_to_v3'] = (bool) $style['sync_to_v3']; } if ( ! empty( $style['variants'] ) ) { foreach ( $style['variants'] as $variant_index => $variant ) { $style['variants'][ $variant_index ]['props'] = $props_parser->sanitize( $variant['props'] )->unwrap(); $style['variants'][ $variant_index ]['meta'] = $this->sanitize_meta( $variant['meta'] ); $style['variants'][ $variant_index ]['custom_css'] = $this->sanitize_custom_css( $variant ); } } return Parse_Result::make()->wrap( $style ); } /** * @param array $style * the style object to parse */ public function parse( array $style ): Parse_Result { $validate_result = $this->validate( $style ); $sanitize_result = $this->sanitize( $validate_result->unwrap() ); $sanitize_result->errors()->merge( $validate_result->errors() ); return $sanitize_result; } } atomic-widgets/opt-in/opt-in.php 0000644 00000006236 15252521350 0012620 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\OptIn; use Elementor\Core\Common\Modules\Ajax\Module as Ajax; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Modules\GlobalClasses\Module as GlobalClassesModule; use Elementor\Modules\NestedElements\Module as NestedElementsModule; use Elementor\Modules\AtomicWidgets\Module as AtomicWidgetsModule; use Elementor\Modules\Variables\Module as VariablesModule; use Elementor\Modules\Components\Module as ComponentsModule; use Elementor\Plugin; class Opt_In { const EXPERIMENT_NAME = 'e_opt_in_v4'; const OPT_OUT_FEATURES = [ self::EXPERIMENT_NAME, AtomicWidgetsModule::EXPERIMENT_NAME, GlobalClassesModule::NAME, VariablesModule::EXPERIMENT_NAME, ComponentsModule::EXPERIMENT_NAME, ]; const OPT_IN_FEATURES = [ self::EXPERIMENT_NAME, 'container', NestedElementsModule::EXPERIMENT_NAME, AtomicWidgetsModule::EXPERIMENT_NAME, GlobalClassesModule::NAME, VariablesModule::EXPERIMENT_NAME, ComponentsModule::EXPERIMENT_NAME, ]; public function init() { $this->register_feature(); add_action( 'elementor/ajax/register_actions', fn( Ajax $ajax ) => $this->add_ajax_actions( $ajax ) ); add_action( 'rest_api_init', fn() => $this->register_routes() ); } private function register_feature() { Plugin::$instance->experiments->add_feature([ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Editor V4', 'elementor' ), 'description' => esc_html__( 'Enable Editor V4.', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_INACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_ALPHA, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '4.0.0', ], ]); } private function opt_out_v4() { foreach ( self::OPT_OUT_FEATURES as $feature ) { $feature_key = Plugin::$instance->experiments->get_feature_option_key( $feature ); update_option( $feature_key, Experiments_Manager::STATE_INACTIVE ); } } private function opt_in_v4() { foreach ( self::OPT_IN_FEATURES as $feature ) { $feature_key = Plugin::$instance->experiments->get_feature_option_key( $feature ); update_option( $feature_key, Experiments_Manager::STATE_ACTIVE ); } } public function ajax_opt_out_v4() { if ( ! current_user_can( 'manage_options' ) ) { throw new \Exception( 'Permission denied' ); } $this->opt_out_v4(); } public function ajax_opt_in_v4() { if ( ! current_user_can( 'manage_options' ) ) { throw new \Exception( 'Permission denied' ); } $this->opt_in_v4(); } private function add_ajax_actions( Ajax $ajax ) { $ajax->register_ajax_action( 'editor_v4_opt_in', fn() => $this->ajax_opt_in_v4() ); $ajax->register_ajax_action( 'editor_v4_opt_out', fn() => $this->ajax_opt_out_v4() ); } private function handle_rest_opt_in_v4() { $this->ajax_opt_in_v4(); return new \WP_REST_Response( [ 'success' => true, ], 200 ); } private function register_routes() { register_rest_route( 'elementor/v1', '/operations/opt-in-v4', [ 'methods' => 'POST', 'callback' => fn() => $this->handle_rest_opt_in_v4(), 'permission_callback' => fn() => current_user_can( 'manage_options' ), ] ); } } atomic-widgets/cache-validity/cache-validity-item.php 0000644 00000000726 15252521350 0016712 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CacheValidity; use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity_Item as New_Cache_Validity_Item; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } // TODO: Remove this class after 3.37 is released. /** * @deprecated 3.35 Use \Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity_Item instead. */ class Cache_Validity_Item extends New_Cache_Validity_Item {} atomic-widgets/cache-validity/cache-validity.php 0000644 00000000675 15252521350 0015761 0 ustar 00 <?php namespace Elementor\Modules\AtomicWidgets\CacheValidity; use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity as New_Cache_Validity; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } // TODO: Remove this class after 3.37 is released. /** * @deprecated 3.35 Use \Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity instead. */ class Cache_Validity extends New_Cache_Validity {} pro-install/module.php 0000644 00000004714 15252521350 0011014 0 ustar 00 <?php namespace Elementor\Modules\ProInstall; use Elementor\Core\Common\Modules\Connect\Module as ConnectModule; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; use Elementor\Utils; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { public function get_name() { return 'pro-install'; } public static function is_active() { return ! Utils::has_pro() && current_user_can( 'manage_options' ); } public function __construct() { parent::__construct(); add_action( 'admin_post_elementor_do_pro_install', [ $this, 'admin_post_elementor_do_pro_install' ] ); add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) { $connect_module->register_app( 'pro-install', Connect::get_class_name() ); } ); add_action( 'elementor/editor-one/menu/excluded_level3_slugs', function ( array $excluded_slugs ): array { $excluded_slugs[] = 'elementor-connect'; return $excluded_slugs; } ); add_action( 'elementor/editor-one/menu/register', function( Menu_Data_Provider $menu_data_provider ) { $menu_data_provider->register_menu( new Editor_One_Connect_Account_Menu_Item( $this->get_connect_app(), $this->get_pro_install_page_assets() ) ); } ); } private function get_connect_app(): Connect { return Plugin::$instance->common->get_component( 'connect' )->get_app( 'pro-install' ); } public function admin_post_elementor_do_pro_install() { if ( ! current_user_can( 'install_plugins' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'elementor' ) ); } check_admin_referer( 'elementor_do_pro_install' ); $app = $this->get_connect_app(); $download_link = $app->get_download_link(); if ( empty( $download_link ) ) { wp_die( esc_html__( 'There are no available subscriptions at the moment.', 'elementor' ) ); } $plugin_installer = new Plugin_Installer( 'elementor-pro', $download_link ); $response = $plugin_installer->install(); if ( is_wp_error( $response ) ) { wp_die( esc_html( $response->get_error_message() ) ); } wp_safe_redirect( admin_url( 'admin.php?page=elementor-license' ) ); } private function get_pro_install_page_assets(): array { return [ 'elementor-pro-install-events', $this->get_js_assets_url( 'pro-install-events' ), [ 'elementor-common' ], ELEMENTOR_VERSION, true, ]; } } pro-install/connect-page-renderer.php 0000644 00000013270 15252521350 0013673 0 ustar 00 <?php namespace Elementor\Modules\ProInstall; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Connect_Page_Renderer { private Connect $connect; private string $page_url; private ?array $script_config; public function __construct( Connect $connect, string $page_url, ?array $script_config = null ) { $this->connect = $connect; $this->page_url = $page_url; $this->script_config = $script_config; } public function render() { if ( $this->script_config ) { wp_enqueue_script( ...$this->script_config ); } ?> <div class="wrap elementor-admin-page-license"> <h2 class="wp-heading-inline"><?php echo esc_html__( 'Connect Settings', 'elementor' ); ?></h2> <?php if ( ! $this->connect->is_connected() ) { $this->render_connect_box(); } else { $this->render_license_box(); } ?> </div> <?php } private function render_connect_box() { $connect_url = $this->connect->get_admin_url( 'authorize', [ 'utm_source' => 'license-page-connect-free', 'utm_medium' => 'wp-dash', 'utm_campaign' => 'connect-and-activate-license', 'redirect_to' => $this->page_url, ] ); ?> <div class="<?php echo esc_attr( $this->get_license_box_classes() ); ?>"> <h3><?php echo esc_html__( 'Connect your Elementor Account', 'elementor' ); ?></h3> <p> <?php echo esc_html__( 'Gain access to dozens of professionally designed templates, and connect your site to your My Elementor Dashboard.', 'elementor' ); ?> </p> <div class="elementor-box-action"> <a id="elementor-connect-license" class="button button-primary" href="<?php echo esc_url( $connect_url ); ?>"> <?php echo esc_html__( 'Connect to Elementor', 'elementor' ); ?> </a> </div> </div> <?php } private function render_license_box() { $disconnect_url = $this->connect->get_admin_url( 'disconnect', [ 'redirect_to' => $this->page_url, ] ); $download_link = $this->connect->get_download_link(); ?> <div class="<?php echo esc_attr( $this->get_license_box_classes() ); ?>"> <h3> <?php echo esc_html__( 'Status', 'elementor' ); ?>: <span style="color: #008000; font-style: italic;"><?php echo esc_html__( 'Connected', 'elementor' ); ?></span> <small> <a class="button" href="https://go.elementor.com/my-account/" target="_blank"> <?php echo esc_html__( 'My Account', 'elementor' ); ?> </a> </small> </h3> <p class="e-row-stretch e-row-divider-bottom"> <span> <?php $connected_user = $this->get_connected_account(); if ( $connected_user ) : printf( /* translators: %s: Connected user. */ esc_html__( 'You\'re connected as %s.', 'elementor' ), '<strong>' . esc_html( $connected_user ) . '</strong>' ); endif; ?> </span> </p> <p class="e-row-stretch"> <span><?php echo esc_html__( 'Want to disconnect for any reason?', 'elementor' ); ?></span> <a class="button" href="<?php echo esc_url( $disconnect_url ); ?>"> <?php echo esc_html__( 'Disconnect', 'elementor' ); ?> </a> </p> </div> <?php if ( empty( $download_link ) ) { $this->render_promotion_box(); } else { $this->render_install_or_activate_box(); } } private function get_connected_account() { $user = $this->connect->get( 'user' ); $email = ''; if ( $user ) { $email = $user->email; } return $email; } private function render_promotion_box() { ?> <div class="<?php echo esc_attr( $this->get_license_box_classes( 'elementor-pro-connect-promotion' ) ); ?>"> <div> <h2><?php echo esc_html__( 'Upgrade to Pro to unlock powerful design tools and advanced features.', 'elementor' ); ?></h2> <p><?php echo esc_html__( 'Build custom headers, footers, forms, popups, and WooCommerce stores.', 'elementor' ); ?></p> <div class="elementor-box-action"> <a class="button button-upgrade" href="https://go.elementor.com/go-pro-connect-account-screen" target="_blank"> <i class="eicon-upgrade-crown" aria-hidden="true"></i> <?php echo esc_html__( 'Upgrade Now', 'elementor' ); ?> </a> </div> </div> <img src="https://assets.elementor.com/free-to-pro-upsell/v1/images/connect-pro-upgrade.jpg" alt="<?php echo esc_attr__( 'Pro Upgrade', 'elementor' ); ?>" /> </div> <?php } private function render_install_or_activate_box() { $cta_data = $this->get_cta_data(); $cta_url = wp_nonce_url( admin_url( 'admin-post.php?action=elementor_do_pro_install' ), 'elementor_do_pro_install' ); $cta_id = Utils::is_pro_installed_and_not_active() ? 'elementor-connect-activate-pro' : 'elementor-connect-install-pro'; ?> <div class="<?php echo esc_attr( $this->get_license_box_classes() ); ?>"> <h3><?php echo esc_html__( 'You\'ve got Elementor Pro', 'elementor' ); ?></h3> <p><?php echo esc_html( $cta_data['description'] ); ?></p> <p class="elementor-box-action"> <a id="<?php echo esc_attr( $cta_id ); ?>" class="button button-primary" href="<?php echo esc_url( $cta_url ); ?>"> <?php echo esc_html( $cta_data['button_text'] ); ?> </a> </p> </div> <?php } private function get_cta_data(): array { return [ 'description' => esc_html__( 'Enjoy full access to powerful design tools, advanced widgets, and everything you need to create next-level websites.', 'elementor' ), 'button_text' => Utils::is_pro_installed_and_not_active() ? esc_html__( 'Activate Elementor Pro', 'elementor' ) : esc_html__( 'Install & Activate', 'elementor' ), ]; } private function get_license_box_classes( string $additional_classes = '' ): string { $classes = [ 'elementor-license-box' ]; if ( $additional_classes ) { $classes[] = $additional_classes; } $classes[] = 'e-one-section-outlined'; return implode( ' ', $classes ); } } pro-install/pro-install-menu-item.php 0000644 00000002006 15252521350 0013661 0 ustar 00 <?php namespace Elementor\Modules\ProInstall; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Pro_Install_Menu_Item implements Admin_Menu_Item_With_Page { private Connect_Page_Renderer $renderer; public function __construct( Connect $connect, array $script_config ) { $page_url = admin_url( 'admin.php?page=elementor-connect-account' ); $this->renderer = new Connect_Page_Renderer( $connect, $page_url, $script_config ); } public function get_label(): string { return esc_html__( 'Connect Account', 'elementor' ); } public function get_page_title(): string { return esc_html__( 'Connect Settings', 'elementor' ); } public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Settings::PAGE_ID; } public function is_visible(): bool { return true; } public function render() { $this->renderer->render(); } } pro-install/connect.php 0000644 00000001565 15252521350 0011161 0 ustar 00 <?php namespace Elementor\Modules\ProInstall; use Elementor\Core\Common\Modules\Connect\Apps\Library; use Elementor\Utils as ElementorUtils; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Connect extends Library { const API_URL = 'https://my.elementor.com/api/v2/artifacts/PLUGIN/'; public function get_title() { return esc_html__( 'pro-install', 'elementor' ); } protected function get_api_url() { return static::API_URL . '/'; } public function get_download_link() { $response = $this->http_request( 'GET', 'latest/download-link', [], [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, 'with_error_data' => true, ] ); if ( is_wp_error( $response ) || empty( $response['downloadLink'] ) ) { return false; } return $response['downloadLink']; } protected function init() {} } pro-install/plugin-installer.php 0000644 00000004454 15252521350 0013021 0 ustar 00 <?php namespace Elementor\Modules\ProInstall; class Plugin_Installer { private $plugin_slug; private $package_url; public function __construct( $plugin_slug, $package_url = null ) { $this->plugin_slug = $plugin_slug; $this->package_url = $package_url; } public function install() { $this->includes_dependencies(); $plugin_data = $this->get_plugin_path(); if ( empty( $plugin_data ) ) { $install_result = $this->do_install(); if ( null === $install_result || is_wp_error( $install_result ) ) { return new \WP_Error( 'cant_installed', esc_html__( 'There are no available subscriptions at the moment.', 'elementor' ) ); } } $is_activated = $this->activate(); if ( is_wp_error( $is_activated ) ) { return $is_activated; } return true; } private function get_package_url() { return $this->package_url; } private function includes_dependencies() { include_once ABSPATH . '/wp-admin/includes/admin.php'; include_once ABSPATH . '/wp-admin/includes/plugin-install.php'; include_once ABSPATH . '/wp-admin/includes/plugin.php'; include_once ABSPATH . '/wp-admin/includes/class-wp-upgrader.php'; include_once ABSPATH . '/wp-admin/includes/class-plugin-upgrader.php'; } private function do_install() { $package_url = $this->get_package_url(); if ( empty( $package_url ) ) { return new \WP_Error( 'no_package_url', sprintf( 'The requested plugin `%s` has no package URL', $this->plugin_slug ) ); } $upgrader = new \Plugin_Upgrader( new \Automatic_Upgrader_Skin() ); return $upgrader->install( $package_url ); } private function get_plugin_path() { $plugins = get_plugins(); $installed_plugins = []; foreach ( $plugins as $path => $plugin ) { $path_parts = explode( '/', $path ); $slug = $path_parts[0]; $installed_plugins[ $slug ] = $path; } if ( empty( $installed_plugins[ $this->plugin_slug ] ) ) { return false; } return $installed_plugins[ $this->plugin_slug ]; } public function activate() { $plugin_path = $this->get_plugin_path(); if ( ! $plugin_path ) { return new \WP_Error( 'no_installed', sprintf( 'The requested plugin `%s` is not installed', $this->plugin_slug ) ); } $activate_result = activate_plugin( $plugin_path ); if ( is_wp_error( $activate_result ) ) { return $activate_result; } return true; } } pro-install/editor-one-connect-account-menu-item.php 0000644 00000002725 15252521350 0016553 0 ustar 00 <?php namespace Elementor\Modules\ProInstall; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Connect_Account_Menu_Item implements Menu_Item_Interface { private $connect; private $script_config; public function __construct( Connect $connect, array $script_config ) { $this->connect = $connect; $this->script_config = $script_config; } public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'Connect Account', 'elementor' ); } public function get_page_title(): string { return esc_html__( 'Connect Settings', 'elementor' ); } public function get_position(): int { return 1000; } public function get_slug(): string { return 'elementor-connect-account'; } public function get_icon(): string { return 'sync'; } public function get_group_id(): string { return Menu_Config::SYSTEM_GROUP_ID; } public function has_children(): bool { return false; } public function render() { $page_url = admin_url( 'admin.php?page=elementor-connect-account' ); $renderer = new Connect_Page_Renderer( $this->connect, $page_url, $this->script_config ); $renderer->render(); } } cloud-kit-library/module.php 0000644 00000012001 15252521350 0012071 0 ustar 00 <?php namespace Elementor\Modules\CloudKitLibrary; use Elementor\Modules\CloudKitLibrary\Data\Controller as Cloud_Kits_Controller; use Elementor\Core\Utils\Exceptions; use Elementor\Plugin; use Elementor\Core\Base\Module as BaseModule; use Elementor\Modules\CloudKitLibrary\Connect\Cloud_Kits; use Elementor\Core\Common\Modules\Connect\Module as ConnectModule; use Elementor\App\Modules\ImportExportCustomization\Module as ImportExportCustomization_Module; use Elementor\App\Modules\KitLibrary\Connect\Kit_Library as Kit_Library_Api; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { public function get_name(): string { return 'cloud-kit-library'; } public function __construct() { parent::__construct(); add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) { $connect_module->register_app( 'cloud-kits', Cloud_Kits::get_class_name() ); } ); add_filter( 'elementor/export/kit/export-result', [ $this, 'handle_export_kit_result' ], 10, 6 ); add_filter( 'elementor/import/kit/result/cloud', [ $this, 'handle_import_kit_from_cloud' ], 10, 1 ); add_filter( 'elementor/import/kit_thumbnail', [ $this, 'handle_import_kit_thumbnail' ], 10, 3 ); add_action( 'elementor/kit_library/registered', function () { Plugin::$instance->data_manager_v2->register_controller( new Cloud_Kits_Controller() ); } ); } public function handle_import_kit_thumbnail( $thumbnail, $kit_id, $referrer ) { if ( ImportExportCustomization_Module::REFERRER_KIT_LIBRARY === $referrer ) { if ( empty( $kit_id ) ) { return ''; } $api = new Kit_Library_Api(); $kit = $api->get_by_id( $kit_id ); if ( is_wp_error( $kit ) ) { return ''; } return $kit->thumbnail; } if ( ImportExportCustomization_Module::REFERRER_CLOUD === $referrer ) { if ( empty( $kit_id ) ) { return ''; } $kit = self::get_app()->get_kit( [ 'id' => $kit_id ] ); if ( is_wp_error( $kit ) ) { return ''; } return $kit['thumbnailUrl'] ?? ''; } return $thumbnail; } public function handle_export_kit_result( $result, $source, $export, $settings, $file, $file_size ) { if ( ImportExportCustomization_Module::EXPORT_SOURCE_CLOUD !== $source ) { return $result; } unset( $result['file'] ); $raw_screen_shot = base64_decode( substr( $settings['screenShotBlob'], strlen( 'data:image/png;base64,' ) ) ); $title = $export['manifest']['title']; $description = $export['manifest']['description']; $kit = self::get_app()->create_kit( $title, $description, $file, $raw_screen_shot, $settings['include'], $settings['customization']['content']['mediaFormat'] ?? 'link', $file_size, ); if ( is_wp_error( $kit ) ) { return $kit; } $result['kit'] = $kit; return $result; } public function handle_import_kit_from_cloud( $args ) { $kit = self::get_app()->get_kit( [ 'id' => $args['kit_id'], ] ); if ( is_wp_error( $kit ) ) { throw new \Error( ImportExportCustomization_Module::CLOUD_KIT_LIBRARY_ERROR_LOADING_RESOURCE ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } if ( empty( $kit['downloadUrl'] ) ) { throw new \Error( ImportExportCustomization_Module::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $data = [ 'file_name' => self::get_remote_kit_zip( $kit['downloadUrl'] ), 'referrer' => ImportExportCustomization_Module::REFERRER_CLOUD, 'file_url' => $kit['downloadUrl'], 'kit' => $kit, ]; if ( ! empty( $kit['mediaDownloadUrl'] ) ) { $media_zip = self::get_remote_kit_zip( $kit['mediaDownloadUrl'], 'media.zip' ); $data['media_file_name'] = $media_zip; } return $data; } public static function get_remote_kit_zip( $url, $file_name = 'kit.zip' ) { $remote_zip_request = wp_safe_remote_get( $url, [ 'timeout' => 300, ] ); if ( is_wp_error( $remote_zip_request ) ) { Plugin::$instance->logger->get_logger()->error( $remote_zip_request->get_error_message() ); throw new \Error( ImportExportCustomization_Module::CLOUD_KIT_LIBRARY_ERROR_LOADING_RESOURCE ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } if ( 200 !== $remote_zip_request['response']['code'] ) { Plugin::$instance->logger->get_logger()->error( $remote_zip_request['response']['message'] ); throw new \Error( ImportExportCustomization_Module::CLOUD_KIT_LIBRARY_ERROR_LOADING_RESOURCE ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return Plugin::$instance->uploads_manager->create_temp_file( $remote_zip_request['body'], $file_name ); } public static function get_app(): Cloud_Kits { $cloud_kits_app = Plugin::$instance->common->get_component( 'connect' )->get_app( 'cloud-kits' ); if ( ! $cloud_kits_app ) { $error_message = esc_html__( 'Cloud-Kits is not instantiated.', 'elementor' ); throw new \Exception( $error_message, Exceptions::FORBIDDEN ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return $cloud_kits_app; } } cloud-kit-library/data/controller.php 0000644 00000003462 15252521350 0013713 0 ustar 00 <?php namespace Elementor\Modules\CloudKitLibrary\Data; use Elementor\Modules\CloudKitLibrary\Connect\Cloud_Kits; use Elementor\Modules\CloudKitLibrary\Module as CloudKitLibrary; use Elementor\App\Modules\KitLibrary\Data\Base_Controller; use Elementor\Core\Utils\Collection; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Base_Controller { public function get_name() { return 'cloud-kits'; } public function get_items( $request ) { $data = $this->get_app()->get_all(); if ( is_wp_error( $data ) ) { return [ 'data' => [], ]; } $kits = ( new Collection( $data ) )->map( function ( $kit ) { return [ 'id' => $kit['id'], 'title' => $kit['title'], 'thumbnail_url' => $kit['thumbnailUrl'], 'created_at' => $kit['createdAt'], 'updated_at' => $kit['updatedAt'], 'status' => isset( $kit['status'] ) ? $kit['status'] : 'active', ]; } ); return [ 'data' => $kits->values(), ]; } public function delete_item( $request ) { return [ 'data' => $this->get_app()->delete_kit( $request->get_param( 'id' ) ), ]; } public function get_item( $request ) { return [ 'data' => $this->get_app()->get_kit( [ 'id' => $request->get_param( 'id' ) ] ), ]; } public function register_endpoints() { $this->index_endpoint->register_item_route( \WP_REST_Server::DELETABLE, [ 'id' => [ 'description' => 'Unique identifier for the object.', 'type' => 'integer', 'required' => true, ], ] ); $this->register_endpoint( new Endpoints\Eligibility( $this ) ); $this->register_endpoint( new Endpoints\Quota( $this ) ); } public function get_permission_callback( $request ) { return current_user_can( 'manage_options' ); } protected function get_app(): Cloud_Kits { return CloudKitLibrary::get_app(); } } cloud-kit-library/data/endpoints/quota.php 0000644 00000001111 15252521350 0014651 0 ustar 00 <?php namespace Elementor\Modules\CloudKitLibrary\Data\Endpoints; use Elementor\Modules\CloudKitLibrary\Data\Controller; use Elementor\Modules\CloudKitLibrary\Module as CloudKitLibrary; use Elementor\Data\V2\Base\Endpoint; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @property Controller $controller */ class Quota extends Endpoint { public function get_name() { return 'quota'; } public function get_format() { return 'cloud-kits/quota'; } public function get_items( $request ) { return CloudKitLibrary::get_app()->get_quota(); } } cloud-kit-library/data/endpoints/eligibility.php 0000644 00000001143 15252521350 0016033 0 ustar 00 <?php namespace Elementor\Modules\CloudKitLibrary\Data\Endpoints; use Elementor\Modules\CloudKitLibrary\Data\Controller; use Elementor\Modules\CloudKitLibrary\Module as CloudKitLibrary; use Elementor\Data\V2\Base\Endpoint; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * @property Controller $controller */ class Eligibility extends Endpoint { public function get_name() { return 'eligibility'; } public function get_format() { return 'cloud-kits/eligibility'; } public function get_items( $request ) { return CloudKitLibrary::get_app()->check_eligibility(); } } cloud-kit-library/connect/cloud-kits.php 0000644 00000015071 15252521350 0014325 0 ustar 00 <?php namespace Elementor\Modules\CloudKitLibrary\Connect; use Elementor\Core\Common\Modules\Connect\Apps\Library; use Elementor\Core\Utils\Exceptions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Cloud_Kits extends Library { const THRESHOLD_UNLIMITED = -1; const FAILED_TO_FETCH_QUOTA_KEY = 'failed-to-fetch-quota'; const FAILED_TO_UPLOAD_KIT = 'cloud-upload-failed'; const INSUFFICIENT_QUOTA_KEY = 'insufficient-quota'; const INSUFFICIENT_STORAGE_QUOTA = 'insufficient-storage-quota'; public function get_title() { return esc_html__( 'Cloud Kits', 'elementor' ); } protected function get_api_url(): string { return 'https://cloud-library.prod.builder.elementor.red/api/v1/cloud-library'; } /** * @return array|\WP_Error */ public function get_all( $args = [] ) { return $this->http_request( 'GET', 'kits', [], [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); } /** * @return array|\WP_Error */ public function get_quota() { if ( ! $this->is_connected() ) { return new \WP_Error( 'not_connected', esc_html__( 'Not connected', 'elementor' ) ); } return $this->http_request( 'GET', 'quota/kits', [], [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); } public function validate_quota( $quota ) { if ( is_wp_error( $quota ) ) { throw new \Error( static::FAILED_TO_FETCH_QUOTA_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $is_unlimited = self::THRESHOLD_UNLIMITED === $quota['threshold']; $has_quota = $quota['currentUsage'] < $quota['threshold']; if ( ! $is_unlimited && ! $has_quota ) { throw new \Error( static::INSUFFICIENT_QUOTA_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } } public function validate_storage_quota( $intended_usage, $quota ) { if ( is_wp_error( $quota ) ) { throw new \Error( static::FAILED_TO_FETCH_QUOTA_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } if ( empty( $quota['storage']['currentUsage'] ) ) { return; } $has_quota = $quota['storage']['currentUsage'] + $intended_usage < $quota['storage']['threshold']; if ( ! $has_quota ) { throw new \Error( static::INSUFFICIENT_STORAGE_QUOTA ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } } public function check_eligibility() { $quota = $this->get_quota(); if ( is_wp_error( $quota ) ) { return [ 'is_eligible' => false, 'subscription_id' => '', ]; } return [ 'is_eligible' => isset( $quota['threshold'] ) && 0 !== $quota['threshold'], 'subscription_id' => ! empty( $quota['subscriptionId'] ) ? $quota['subscriptionId'] : '', ]; } public function create_kit( $title, $description, $content_file_data, $preview_file_data, array $includes, string $media_format = 'link', $file_size = 0 ) { $quota = $this->get_quota(); $this->validate_quota( $quota ); $this->validate_storage_quota( $file_size, $quota ); $endpoint = 'kits'; $boundary = wp_generate_password( 24, false ); $headers = [ 'Content-Type' => 'multipart/form-data; boundary=' . $boundary, ]; $body = $this->create_multipart_body( [ 'title' => $title, 'description' => $description, 'includes' => wp_json_encode( $includes ), 'mediaFormat' => $media_format, ], [ 'previewFile' => [ 'filename' => 'preview.png', 'content' => $preview_file_data, 'content_type' => 'image/png', ], ], $boundary ); $payload = [ 'headers' => $headers, 'body' => $body, 'timeout' => 120, ]; $response = $this->http_request( 'POST', $endpoint, $payload, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); if ( is_wp_error( $response ) || empty( $response['id'] ) ) { throw new \Exception( static::FAILED_TO_UPLOAD_KIT, Exceptions::INTERNAL_SERVER_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } if ( empty( $response['uploadUrl'] ) ) { $this->delete_kit( $response['id'] ); throw new \Exception( static::FAILED_TO_UPLOAD_KIT, Exceptions::INTERNAL_SERVER_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $upload_success = $this->upload_content_file( $response['uploadUrl'], $content_file_data ); if ( ! $upload_success ) { $this->delete_kit( $response['id'] ); throw new \Exception( static::FAILED_TO_UPLOAD_KIT, Exceptions::INTERNAL_SERVER_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return $response; } public function upload_content_file( $upload_url, $content_file_data ) { $upload_response = wp_remote_request( $upload_url, [ 'method' => 'PUT', 'body' => $content_file_data, 'headers' => [ 'Content-Type' => 'application/zip', 'Content-Length' => strlen( $content_file_data ), ], 'timeout' => 120, ] ); if ( is_wp_error( $upload_response ) ) { return false; } $response_code = wp_remote_retrieve_response_code( $upload_response ); return $response_code >= 200 && $response_code < 300; } public function get_kit( array $args ) { $args = array_merge_recursive( $args, [ 'timeout' => 60, // just in case if zip is big ] ); return $this->http_request( 'GET', 'kits/' . $args['id'], $args, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); } public function delete_kit( int $id ) { return $this->http_request( 'DELETE', 'kits/' . $id, [], [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); } private function create_multipart_body( $fields, $files, $boundary ): string { $eol = "\r\n"; $body = ''; foreach ( $fields as $name => $value ) { $body .= "--{$boundary}{$eol}"; $body .= "Content-Disposition: form-data; name=\"{$name}\"{$eol}{$eol}"; $body .= "{$value}{$eol}"; } foreach ( $files as $name => $file ) { $filename = basename( $file['filename'] ); $content_type = $file['content_type']; $content = $file['content']; $body .= "--{$boundary}{$eol}"; $body .= "Content-Disposition: form-data; name=\"{$name}\"; filename=\"{$filename}\"{$eol}"; $body .= "Content-Type: {$content_type}{$eol}{$eol}"; $body .= $content . $eol; } $body .= "--{$boundary}--{$eol}"; return $body; } public function update_kit( $id, array $kit_data ) { $endpoint = 'kits/' . $id; $request = $this->http_request( 'PATCH', $endpoint, [ 'body' => wp_json_encode( $kit_data ), 'headers' => [ 'Content-Type' => 'application/json', ], ], [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ], ); if ( is_wp_error( $request ) ) { return false; } return true; } protected function init() {} } shapes/widgets/text-path.php 0000644 00000035322 15252521350 0012131 0 ustar 00 <?php namespace Elementor\Modules\Shapes\Widgets; use Elementor\Controls_Manager; use Elementor\Core\Kits\Documents\Tabs\Global_Typography; use Elementor\Group_Control_Typography; use Elementor\Modules\Shapes\Module as Shapes_Module; use Elementor\Group_Control_Text_Stroke; use Elementor\Plugin; use Elementor\Widget_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor WordArt widget. * * Elementor widget that displays text along SVG path. */ class TextPath extends Widget_Base { const DEFAULT_PATH_FILL = '#E8178A'; /** * Get widget name. * * Retrieve Text Path widget name. * * @return string Widget name. * @access public */ public function get_name() { return 'text-path'; } public function get_group_name() { return 'shapes'; } /** * Get widget title. * * Retrieve Text Path widget title. * * @return string Widget title. * @access public */ public function get_title() { return esc_html__( 'Text Path', 'elementor' ); } /** * Get widget icon. * * Retrieve Text Path widget icon. * * @return string Widget icon. * @access public */ public function get_icon() { return 'eicon-wordart'; } /** * Get widget keywords. * * Retrieve the list of keywords the widget belongs to. * * @return array Widget keywords. * @access public */ public function get_keywords() { return [ 'text path', 'word path', 'text on path', 'wordart', 'word art' ]; } /** * Get style dependencies. * * Retrieve the list of style dependencies the widget requires. * * @since 3.24.0 * @access public * * @return array Widget style dependencies. */ public function get_style_depends(): array { return [ 'widget-text-path' ]; } public function has_widget_inner_wrapper(): bool { return ! Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' ); } /** * Register content controls under content tab. */ protected function register_content_tab() { $this->start_controls_section( 'section_content_text_path', [ 'label' => esc_html__( 'Text Path', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); $this->add_control( 'text', [ 'label' => esc_html__( 'Text', 'elementor' ), 'type' => Controls_Manager::TEXT, 'label_block' => true, 'default' => esc_html__( 'Add Your Curvy Text Here', 'elementor' ), 'frontend_available' => true, 'render_type' => 'none', 'dynamic' => [ 'active' => true, ], ] ); $this->add_control( 'path', [ 'label' => esc_html__( 'Path Type', 'elementor' ), 'type' => Controls_Manager::SELECT, 'options' => Shapes_Module::get_paths(), 'default' => 'wave', ] ); $this->add_control( 'custom_path', [ 'label' => esc_html__( 'SVG', 'elementor' ), 'type' => Controls_Manager::MEDIA, 'media_types' => [ 'svg', ], 'condition' => [ 'path' => 'custom', ], 'dynamic' => [ 'active' => true, ], 'description' => sprintf( '%1$s <a target="_blank" href="https://go.elementor.com/text-path-create-paths/">%2$s</a>', esc_html__( 'Want to create custom text paths with SVG?', 'elementor' ), esc_html__( 'Learn more', 'elementor' ) ), ] ); $this->add_control( 'link', [ 'label' => esc_html__( 'Link', 'elementor' ), 'type' => Controls_Manager::URL, 'label_block' => true, 'dynamic' => [ 'active' => true, ], 'placeholder' => esc_html__( 'Paste URL or type', 'elementor' ), 'frontend_available' => true, ] ); $this->add_responsive_control( 'align', [ 'label' => esc_html__( 'Alignment', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'default' => '', 'options' => [ 'left' => [ 'title' => esc_html__( 'Left', 'elementor' ), 'icon' => 'eicon-text-align-left', ], 'center' => [ 'title' => esc_html__( 'Center', 'elementor' ), 'icon' => 'eicon-text-align-center', ], 'right' => [ 'title' => esc_html__( 'Right', 'elementor' ), 'icon' => 'eicon-text-align-right', ], ], 'selectors' => [ '{{WRAPPER}}' => '--alignment: {{VALUE}}', ], 'frontend_available' => true, ] ); $this->add_control( 'text_path_direction', [ 'label' => esc_html__( 'Text Direction', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => '', 'options' => [ '' => esc_html__( 'Default', 'elementor' ), 'rtl' => esc_html__( 'RTL', 'elementor' ), 'ltr' => esc_html__( 'LTR', 'elementor' ), ], 'selectors' => [ '{{WRAPPER}}' => '--direction: {{VALUE}}', ], 'frontend_available' => true, ] ); $this->add_control( 'show_path', [ 'label' => esc_html__( 'Show Path', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'On', 'elementor' ), 'label_off' => esc_html__( 'Off', 'elementor' ), 'return_value' => self::DEFAULT_PATH_FILL, 'separator' => 'before', 'default' => '', 'selectors' => [ '{{WRAPPER}}' => '--path-stroke: {{VALUE}}; --path-fill: transparent;', ], ] ); $this->end_controls_section(); } /** * Register style controls under style tab. */ protected function register_style_tab() { /** * Text Path styling section. */ $this->start_controls_section( 'section_style_text_path', [ 'label' => esc_html__( 'Text Path', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_responsive_control( 'size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'range' => [ '%' => [ 'min' => 0, 'max' => 100, 'step' => 10, ], 'px' => [ 'max' => 800, 'step' => 50, ], ], 'default' => [ 'size' => 500, ], 'tablet_default' => [ 'size' => 500, ], 'mobile_default' => [ 'size' => 500, ], 'selectors' => [ '{{WRAPPER}}' => '--width: {{SIZE}}{{UNIT}};', ], ] ); $this->add_responsive_control( 'rotation', [ 'label' => esc_html__( 'Rotate', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'deg', 'grad', 'rad', 'turn', 'custom' ], 'default' => [ 'unit' => 'deg', ], 'tablet_default' => [ 'unit' => 'deg', ], 'mobile_default' => [ 'unit' => 'deg', ], 'selectors' => [ '{{WRAPPER}}' => '--rotate: {{SIZE}}{{UNIT}};', ], ] ); $this->add_control( 'text_heading', [ 'label' => esc_html__( 'Text', 'elementor' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'text_typography', 'selector' => '{{WRAPPER}}', 'global' => [ 'default' => Global_Typography::TYPOGRAPHY_TEXT, ], 'fields_options' => [ 'font_size' => [ 'default' => [ 'size' => '20', 'unit' => 'px', ], 'size_units' => [ 'px' ], ], // Text decoration isn't an inherited property, so it's required to explicitly // target the specific `textPath` element. 'text_decoration' => [ 'selectors' => [ '{{WRAPPER}} textPath' => 'text-decoration: {{VALUE}};', ], ], ], ] ); $this->add_group_control( Group_Control_Text_Stroke::get_type(), [ 'name' => 'text_stroke', 'selector' => '{{WRAPPER}} textPath', ] ); $this->add_responsive_control( 'word_spacing', [ 'label' => esc_html__( 'Word Spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'range' => [ 'px' => [ 'min' => -20, 'max' => 20, ], 'em' => [ 'min' => -1, 'max' => 1, ], 'rem' => [ 'min' => -1, 'max' => 1, ], ], 'default' => [ 'size' => '', ], 'tablet_default' => [ 'size' => '', ], 'mobile_default' => [ 'size' => '', ], 'selectors' => [ '{{WRAPPER}}' => '--word-spacing: {{SIZE}}{{UNIT}};', ], ] ); $this->add_control( 'start_point', [ 'label' => esc_html__( 'Starting Point', 'elementor' ) . ' (%)', 'type' => Controls_Manager::SLIDER, 'size_units' => [ '%' ], 'range' => [ 'px' => [ 'min' => -100, 'max' => 100, 'step' => 1, ], ], 'default' => [ 'unit' => '%', 'size' => 0, ], 'frontend_available' => true, 'render_type' => 'none', ] ); $this->start_controls_tabs( 'text_style' ); /** * Normal tab. */ $this->start_controls_tab( 'text_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_control( 'text_color_normal', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}}' => '--text-color: {{VALUE}};', ], ] ); $this->end_controls_tab(); /** * Hover tab. */ $this->start_controls_tab( 'text_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_control( 'text_color_hover', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}}' => '--text-color-hover: {{VALUE}};', ], ] ); $this->add_control( 'hover_animation', [ 'label' => esc_html__( 'Hover Animation', 'elementor' ), 'type' => Controls_Manager::HOVER_ANIMATION, ] ); $this->add_control( 'hover_transition', [ 'label' => esc_html__( 'Transition Duration', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 's', 'ms', 'custom' ], 'default' => [ 'unit' => 's', 'size' => 0.3, ], 'selectors' => [ '{{WRAPPER}}' => '--transition: {{SIZE}}{{UNIT}}', ], ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->end_controls_section(); /** * Path styling section. */ $this->start_controls_section( 'section_style_path', [ 'label' => esc_html__( 'Path', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, 'condition' => [ 'show_path!' => '', ], ] ); $this->start_controls_tabs( 'path_style' ); /** * Normal tab. */ $this->start_controls_tab( 'path_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_control( 'path_fill_normal', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}}' => '--path-fill: {{VALUE}};', ], ] ); $this->add_control( 'stroke_heading_normal', [ 'label' => esc_html__( 'Stroke', 'elementor' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_control( 'stroke_color_normal', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'default' => self::DEFAULT_PATH_FILL, 'selectors' => [ '{{WRAPPER}}' => '--stroke-color: {{VALUE}};', ], ] ); $this->add_control( 'stroke_width_normal', [ 'label' => esc_html__( 'Width', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'default' => [ 'size' => 1, ], 'range' => [ 'px' => [ 'min' => 1, 'max' => 20, ], 'em' => [ 'max' => 2, ], 'rem' => [ 'max' => 2, ], ], 'selectors' => [ '{{WRAPPER}}' => '--stroke-width: {{SIZE}}{{UNIT}}', ], ] ); $this->end_controls_tab(); /** * Hover tab. */ $this->start_controls_tab( 'path_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_control( 'path_fill_hover', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}}' => '--path-fill-hover: {{VALUE}};', ], ] ); $this->add_control( 'stroke_heading_hover', [ 'label' => esc_html__( 'Stroke', 'elementor' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_control( 'stroke_color_hover', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}}' => '--stroke-color-hover: {{VALUE}};', ], ] ); $this->add_control( 'stroke_width_hover', [ 'label' => esc_html__( 'Width', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'default' => [ 'size' => '', ], 'range' => [ 'px' => [ 'min' => 1, 'max' => 20, ], 'em' => [ 'max' => 2, ], 'rem' => [ 'max' => 2, ], ], 'selectors' => [ '{{WRAPPER}}' => '--stroke-width-hover: {{SIZE}}{{UNIT}}', ], ] ); $this->add_control( 'stroke_transition', [ 'label' => esc_html__( 'Transition Duration', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 's', 'ms', 'custom' ], 'default' => [ 'unit' => 's', 'size' => 0.3, ], 'selectors' => [ '{{WRAPPER}}' => '--stroke-transition: {{SIZE}}{{UNIT}}', ], ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->end_controls_section(); } /** * Register Text Path widget controls. * * Adds different input fields to allow the user to change and customize the widget settings. * * @access protected */ protected function register_controls() { $this->register_content_tab(); $this->register_style_tab(); } /** * Render Text Path widget output on the frontend. * * Written in PHP and used to generate the final HTML. * * @access protected */ protected function render() { $settings = $this->get_settings_for_display(); // Get the path URL. $path_url = ( 'custom' === $settings['path'] ) ? wp_get_attachment_url( $settings['custom_path']['id'] ) : Shapes_Module::get_path_url( $settings['path'] ); // Remove the HTTP protocol to prevent Mixed Content error. $path_url = preg_replace( '/^https?:/i', '', $path_url ); // Add Text Path attributes. $this->add_render_attribute( 'text_path', [ 'class' => 'e-text-path', 'data-text' => htmlentities( esc_attr( $settings['text'] ) ), 'data-url' => esc_url( $path_url ), 'data-link-url' => esc_url( $settings['link']['url'] ?? '' ), ] ); // Add hover animation. if ( ! empty( $settings['hover_animation'] ) ) { $this->add_render_attribute( 'text_path', 'class', 'elementor-animation-' . $settings['hover_animation'] ); } // Render. ?> <div <?php $this->print_render_attribute_string( 'text_path' ); ?>></div> <?php } } shapes/module.php 0000644 00000003563 15252521350 0010034 0 ustar 00 <?php namespace Elementor\Modules\Shapes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends \Elementor\Core\Base\Module { public function __construct() { parent::__construct(); add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] ); } /** * Register styles. * * At build time, Elementor compiles `/modules/shapes/assets/scss/frontend.scss` * to `/assets/css/widget-shapes.min.css`. * * @return void */ public function register_styles() { wp_register_style( 'widget-text-path', $this->get_css_assets_url( 'widget-text-path', null, true, true ), [ 'elementor-frontend' ], ELEMENTOR_VERSION ); } /** * Return a translated user-friendly list of the available SVG shapes. * * @param bool $add_custom Determine if the output should include the `Custom` option. * * @return array List of paths. */ public static function get_paths( $add_custom = true ) { $paths = [ 'wave' => esc_html__( 'Wave', 'elementor' ), 'arc' => esc_html__( 'Arc', 'elementor' ), 'circle' => esc_html__( 'Circle', 'elementor' ), 'line' => esc_html__( 'Line', 'elementor' ), 'oval' => esc_html__( 'Oval', 'elementor' ), 'spiral' => esc_html__( 'Spiral', 'elementor' ), ]; if ( $add_custom ) { $paths['custom'] = esc_html__( 'Custom', 'elementor' ); } return $paths; } /** * Get an SVG Path URL from the pre-defined ones. * * @param string $path - Path name. * * @return string */ public static function get_path_url( $path ) { return ELEMENTOR_ASSETS_URL . 'svg-paths/' . $path . '.svg'; } /** * Get the module's associated widgets. * * @return string[] */ protected function get_widgets() { return [ 'TextPath', ]; } /** * Retrieve the module name. * * @return string */ public function get_name() { return 'shapes'; } } home/transformations/base/transformations-abstract.php 0000644 00000003563 15252521350 0017411 0 ustar 00 <?php namespace Elementor\Modules\Home\Transformations\Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use Elementor\Core\Isolation\Elementor_Adapter; use Elementor\Core\Isolation\Elementor_Adapter_Interface; use Elementor\Core\Isolation\Plugin_Status_Adapter; use Elementor\Core\Isolation\Plugin_Status_Adapter_Interface; use Elementor\Core\Isolation\Wordpress_Adapter; use Elementor\Core\Isolation\Wordpress_Adapter_Interface; abstract class Transformations_Abstract { protected const USER_TIER_FREE = 'free'; protected const USER_TIER_PRO = 'pro'; protected const USER_TIER_AGENCY = 'agency'; protected const USER_TIER_ONE = 'one'; protected Wordpress_Adapter_Interface $wordpress_adapter; protected Plugin_Status_Adapter_Interface $plugin_status_adapter; protected Elementor_Adapter_Interface $elementor_adapter; /** * @param $args ?array{ * wordpress_adapter: Wordpress_Adapter_Interface, * plugin_status_adapter: Plugin_Status_Adapter_Interface, * elementor_adapter: Elementor_Adapter_Interface, * } the adapters to use in the transformations */ public function __construct( array $args = [] ) { $this->wordpress_adapter = $args['wordpress_adapter'] ?? new Wordpress_Adapter(); $this->plugin_status_adapter = $args['plugin_status_adapter'] ?? new Plugin_Status_Adapter( $this->wordpress_adapter ); $this->elementor_adapter = $args['elementor_adapter'] ?? new Elementor_Adapter(); } protected function get_tier() { $tier = $this->elementor_adapter->get_tier(); $filtered_tier = apply_filters( 'elementor/admin/homescreen_promotion_tier', $tier ) ?? $tier; return $this->normalize_tier( $filtered_tier ); } private function normalize_tier( string $tier ): string { return self::USER_TIER_AGENCY === $tier ? self::USER_TIER_ONE : $tier; } abstract public function transform( array $home_screen_data ): array; } home/transformations/create-site-settings-url.php 0000644 00000002323 15252521350 0016301 0 ustar 00 <?php namespace Elementor\Modules\Home\Transformations; use Elementor\Core\DocumentTypes\Page; use Elementor\Includes\EditorAssetsAPI; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Create_Site_Settings_Url extends Base\Transformations_Abstract { const SITE_SETTINGS_ITEMS = [ 'Site Settings', 'Site Logo', 'Global Colors', 'Global Fonts' ]; public function transform( array $home_screen_data ): array { if ( ! EditorAssetsAPI::has_valid_nested_array( $home_screen_data, [ 'get_started', 'repeater' ] ) ) { return $home_screen_data; } $site_settings_url_config = Page::get_site_settings_url_config(); $home_screen_data['get_started']['repeater'] = array_map( function( $repeater_item ) use ( $site_settings_url_config ) { if ( ! in_array( $repeater_item['title'], static::SITE_SETTINGS_ITEMS, true ) ) { return $repeater_item; } if ( ! empty( $repeater_item['tab_id'] ) ) { $site_settings_url_config['url'] = add_query_arg( [ 'active-tab' => $repeater_item['tab_id'] ], $site_settings_url_config['url'] ); } return array_merge( $repeater_item, $site_settings_url_config ); }, $home_screen_data['get_started']['repeater'] ); return $home_screen_data; } } home/transformations/filter-get-started-by-license.php 0000644 00000003204 15252521350 0017173 0 ustar 00 <?php namespace Elementor\Modules\Home\Transformations; use Elementor\Includes\EditorAssetsAPI; use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Filter_Get_Started_By_License extends Transformations_Abstract { public bool $has_pro; private array $supported_tiers; public function __construct( $args ) { parent::__construct( $args ); $this->has_pro = Utils::has_pro(); $this->supported_tiers = [ self::USER_TIER_FREE, self::USER_TIER_PRO, self::USER_TIER_ONE, ]; } private function is_valid_item( $item ) { $user_tier = $this->get_tier(); if ( ! $this->has_pro && self::USER_TIER_FREE === $item['license'][0] ) { return true; } if ( $user_tier === $item['license'][0] ) { return true; } return $this->is_fallback_for_unsupported_licenses( $item['license'][0], $user_tier ); } private function is_fallback_for_unsupported_licenses( $item_tier, $user_tier ): bool { $is_supported_user_tier = in_array( $user_tier, $this->supported_tiers, true ); return ! $is_supported_user_tier && self::USER_TIER_PRO === $item_tier; } public function transform( array $home_screen_data ): array { if ( ! EditorAssetsAPI::has_valid_nested_array( $home_screen_data, [ 'get_started' ] ) ) { return $home_screen_data; } $new_get_started = []; foreach ( $home_screen_data['get_started'] as $index => $item ) { if ( $this->is_valid_item( $item ) ) { $new_get_started[] = $item; } } $home_screen_data['get_started'] = reset( $new_get_started ); return $home_screen_data; } } home/transformations/create-new-page-url.php 0000644 00000000752 15252521350 0015206 0 ustar 00 <?php namespace Elementor\Modules\Home\Transformations; use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Create_New_Page_Url extends Transformations_Abstract { public function transform( array $home_screen_data ): array { $home_screen_data['button_cta_url'] = Plugin::$instance->documents->get_create_new_post_url( 'page' ); return $home_screen_data; } } home/transformations/create-edit-website-url.php 0000644 00000001003 15252521350 0016056 0 ustar 00 <?php namespace Elementor\Modules\Home\Transformations; use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Create_Edit_Website_Url extends Transformations_Abstract { public function transform( array $home_screen_data ): array { $home_screen_data['edit_website_url'] = wp_nonce_url( admin_url( 'admin.php?action=elementor_edit_website_redirect' ), 'elementor_action_edit_website' ); return $home_screen_data; } } home/transformations/filter-sidebar-promotion-by-license.php 0000644 00000002435 15252521350 0020412 0 ustar 00 <?php namespace elementor\modules\home\transformations; use Elementor\Includes\EditorAssetsAPI; use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Filter_Sidebar_Promotion_By_License extends Transformations_Abstract { public function transform( array $home_screen_data ): array { if ( ! EditorAssetsAPI::has_valid_nested_array( $home_screen_data, [ 'sidebar_promotion_variants' ] ) ) { return $home_screen_data; } $user_tier = $this->get_tier(); $new_sidebar_promotion = array_filter( $home_screen_data['sidebar_promotion_variants'], function( $item ) use ( $user_tier ) { return $this->is_enabled( $item ) && $this->is_tier_acceptable( $item, $user_tier ); }); if ( empty( $new_sidebar_promotion ) ) { unset( $home_screen_data['sidebar_promotion_variants'] ); return $home_screen_data; } $home_screen_data['sidebar_promotion_variants'] = reset( $new_sidebar_promotion ); return $home_screen_data; } private function is_enabled( $item ) { return ! empty( $item['is_enabled'] ) && 'true' === $item['is_enabled']; } private function is_tier_acceptable( $item, $user_tier ) { return ! empty( $item['license'] ) && in_array( $user_tier, $item['license'] ); } } home/transformations/site-builder-config.php 0000644 00000010103 15252521350 0015264 0 ustar 00 <?php namespace Elementor\Modules\Home\Transformations; use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Site_Builder_Config extends Transformations_Abstract { const ASSETS_BASE_URL = 'https://assets.elementor.com/'; const SITE_BUILDER_URL = '/wp-admin/admin.php?page=elementor-app#site-builder'; const PLANNER_STEPS = [ 'INIT' => 0, 'CHAT' => 1, 'SITEMAP' => 2, 'WIREFRAMES' => 3, 'DEPLOYING' => 4, 'DEPLOYED_TO_PLUGIN' => 6, ]; public function transform( array $home_screen_data ): array { $site_builder = Plugin::$instance->app->get_component( 'site-builder' ); if ( ! $site_builder ) { unset( $home_screen_data['site_builder'] ); return $home_screen_data; } $site_builder_config = $site_builder->get_config(); if ( ! is_array( $site_builder_config ) ) { unset( $home_screen_data['site_builder'] ); return $home_screen_data; } $step_config = isset( $home_screen_data['site_builder'] ) && is_array( $home_screen_data['site_builder'] ) ? $home_screen_data['site_builder'] : []; $validated_step_config = $this->validate_and_sanitize_step_config( $step_config ); $snapshot = $this->wordpress_adapter->get_option( 'elementor_site_builder_snapshot' ); $home_screen_data['site_builder'] = array_merge( $site_builder_config, [ 'siteBuilderUrl' => self::SITE_BUILDER_URL, 'stepImages' => [ self::PLANNER_STEPS['INIT'] => self::ASSETS_BASE_URL . 'home-screen/v1/images/site-builder-start.png', self::PLANNER_STEPS['CHAT'] => self::ASSETS_BASE_URL . 'home-screen/v1/images/site-builder-start.png', self::PLANNER_STEPS['SITEMAP'] => self::ASSETS_BASE_URL . 'home-screen/v1/images/site-builder-sitemap.png', self::PLANNER_STEPS['WIREFRAMES'] => self::ASSETS_BASE_URL . 'home-screen/v1/images/site-builder-design.png', self::PLANNER_STEPS['DEPLOYING'] => self::ASSETS_BASE_URL . 'home-screen/v1/images/site-builder-expand.png', self::PLANNER_STEPS['DEPLOYED_TO_PLUGIN'] => self::ASSETS_BASE_URL . 'home-screen/v1/images/site-builder-expand.png', ], 'bgImage' => self::ASSETS_BASE_URL . 'home-screen/v1/images/site-planner-bg.jpg', 'plannerSteps' => self::PLANNER_STEPS, 'stepConfig' => $validated_step_config, 'site_builder_snapshot' => is_array( $snapshot ) ? $snapshot : [], ] ); return $home_screen_data; } private function validate_and_sanitize_step_config( array $step_config ): array { $validated = []; $valid_steps = array_values( self::PLANNER_STEPS ); foreach ( $step_config as $step_key => $step_data ) { if ( ! is_numeric( $step_key ) || ! in_array( (int) $step_key, $valid_steps, true ) ) { continue; } if ( ! is_array( $step_data ) ) { continue; } $validated_step = []; if ( isset( $step_data['hasInput'] ) ) { $validated_step['hasInput'] = (bool) $step_data['hasInput']; } if ( isset( $step_data['title'] ) && is_string( $step_data['title'] ) ) { $sanitized_title = sanitize_text_field( $step_data['title'] ); $validated_step['title'] = mb_substr( $sanitized_title, 0, 200 ); } if ( isset( $step_data['buttonLabel'] ) && is_string( $step_data['buttonLabel'] ) ) { $sanitized_label = sanitize_text_field( $step_data['buttonLabel'] ); $validated_step['buttonLabel'] = mb_substr( $sanitized_label, 0, 100 ); } $has_input = $validated_step['hasInput'] ?? false; if ( $has_input && isset( $step_data['placeholder'] ) && is_string( $step_data['placeholder'] ) ) { $sanitized_placeholder = sanitize_text_field( $step_data['placeholder'] ); $validated_step['placeholder'] = mb_substr( $sanitized_placeholder, 0, 200 ); } if ( ! $has_input && isset( $step_data['text'] ) && is_string( $step_data['text'] ) ) { $sanitized_text = sanitize_text_field( $step_data['text'] ); $validated_step['text'] = mb_substr( $sanitized_text, 0, 300 ); } if ( ! empty( $validated_step ) ) { $validated[ $step_key ] = $validated_step; } } return $validated; } } home/transformations/filter-top-section-by-license.php 0000644 00000003626 15252521350 0017224 0 ustar 00 <?php namespace elementor\modules\home\transformations; use Elementor\Core\Common\Modules\Connect\Module as ConnectModule; use Elementor\Includes\EditorAssetsAPI; use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Filter_Top_Section_By_License extends Transformations_Abstract { public bool $has_pro; private array $supported_tiers; public function __construct( array $args = [] ) { parent::__construct( $args ); $this->has_pro = Utils::has_pro(); $this->supported_tiers = [ ConnectModule::ACCESS_TIER_FREE, ConnectModule::ACCESS_TIER_PRO_LEGACY, self::USER_TIER_ONE, ]; } private function is_valid_item( $item ) { if ( isset( $item['license'] ) ) { $item_tier = $item['license'][0]; $user_tier = $this->get_tier(); return $this->validate_tier( $item_tier, $user_tier ); } return false; } private function validate_tier( $item_tier, $user_tier ): bool { if ( $user_tier === $item_tier ) { return true; } $is_user_tier_supported = in_array( $user_tier, $this->supported_tiers, true ); if ( $is_user_tier_supported ) { return false; } $is_item_tier_free = ConnectModule::ACCESS_TIER_FREE === $item_tier; $is_valid = $this->has_pro !== $is_item_tier_free; $is_supported_item_tier = in_array( $item_tier, $this->supported_tiers, true ); return $is_valid && $is_supported_item_tier; } public function transform( array $home_screen_data ): array { if ( ! EditorAssetsAPI::has_valid_nested_array( $home_screen_data, [ 'top_with_licences' ] ) ) { return $home_screen_data; } $new_top = []; foreach ( $home_screen_data['top_with_licences'] as $index => $item ) { if ( $this->is_valid_item( $item ) ) { $new_top = $item; break; } } $home_screen_data['top_with_licences'] = $new_top; return $home_screen_data; } } home/classes/transformations-manager.php 0000644 00000004106 15252521350 0014504 0 ustar 00 <?php namespace Elementor\Modules\Home\Classes; use Elementor\Core\Isolation\Wordpress_Adapter; use Elementor\Core\Isolation\Plugin_Status_Adapter; use Elementor\Includes\EditorAssetsAPI; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Transformations_Manager { private static $cached_data = []; private const TRANSFORMATIONS = [ 'Create_New_Page_Url', 'Create_Edit_Website_Url', 'Filter_Get_Started_By_License', 'Filter_Sidebar_Promotion_By_License', 'Create_Site_Settings_Url', 'Filter_Top_Section_By_License', 'Site_Builder_Config', ]; protected array $home_screen_data; protected Wordpress_Adapter $wordpress_adapter; protected Plugin_Status_Adapter $plugin_status_adapter; protected array $transformation_classes = []; public function __construct( $home_screen_data ) { $this->home_screen_data = $home_screen_data; $this->wordpress_adapter = new Wordpress_Adapter(); $this->plugin_status_adapter = new Plugin_Status_Adapter( $this->wordpress_adapter ); $this->transformation_classes = $this->get_transformation_classes(); } public function run_transformations(): array { if ( ! EditorAssetsAPI::is_valid_data( $this->home_screen_data ) ) { return []; } if ( ! empty( self::$cached_data ) ) { return self::$cached_data; } $transformations = self::TRANSFORMATIONS; foreach ( $transformations as $transformation_id ) { $this->home_screen_data = $this->transformation_classes[ $transformation_id ]->transform( $this->home_screen_data ); } self::$cached_data = $this->home_screen_data; return $this->home_screen_data; } private function get_transformation_classes(): array { $classes = []; $transformations = self::TRANSFORMATIONS; $arguments = [ 'wordpress_adapter' => $this->wordpress_adapter, 'plugin_status_adapter' => $this->plugin_status_adapter, ]; foreach ( $transformations as $transformation_id ) { $class_name = '\\Elementor\\Modules\\Home\\Transformations\\' . $transformation_id; $classes[ $transformation_id ] = new $class_name( $arguments ); } return $classes; } } home/module.php 0000644 00000005066 15252521350 0007501 0 ustar 00 <?php namespace Elementor\Modules\Home; use Elementor\Core\Base\App as BaseApp; use Elementor\Includes\EditorAssetsAPI; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseApp { const PAGE_ID = 'home_screen'; public function get_name(): string { return 'home'; } public function __construct() { parent::__construct(); add_filter( 'elementor/document/urls/edit', [ $this, 'add_active_document_to_edit_link' ] ); } public function enqueue_fonts(): void { wp_enqueue_style( 'elementor-home-screen-fonts', 'https://fonts.googleapis.com/css2?family=Poppins:wght@400&display=swap', [], ELEMENTOR_VERSION ); } public function enqueue_home_screen_scripts(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } $this->enqueue_fonts(); $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_script( 'e-home-screen', ELEMENTOR_ASSETS_URL . 'js/e-home-screen' . $min_suffix . '.js', [ 'react', 'react-dom', 'elementor-common', 'elementor-v2-ui', ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'e-home-screen', 'elementor' ); wp_localize_script( 'e-home-screen', 'elementorHomeScreenData', $this->get_app_js_config() ); wp_enqueue_style( 'e-home-screen', $this->get_css_assets_url( 'modules/home/e-home-screen' ), [], ELEMENTOR_VERSION ); } public function add_active_document_to_edit_link( $edit_link ) { $active_document = Utils::get_super_global_value( $_GET, 'active-document' ) ?? null; $active_tab = Utils::get_super_global_value( $_GET, 'active-tab' ) ?? null; if ( $active_document ) { $edit_link = add_query_arg( 'active-document', $active_document, $edit_link ); } if ( $active_tab ) { $edit_link = add_query_arg( 'active-tab', $active_tab, $edit_link ); } return $edit_link; } private function get_app_js_config(): array { $editor_assets_api = new EditorAssetsAPI( $this->get_api_config() ); $api = new API( $editor_assets_api ); $config = $api->get_home_screen_items(); $config['wpRestNonce'] = wp_create_nonce( 'wp_rest' ); return $config; } private function get_api_config(): array { return [ EditorAssetsAPI::ASSETS_DATA_URL => 'https://assets.elementor.com/home-screen/v1/home-screen.json', EditorAssetsAPI::ASSETS_DATA_TRANSIENT_KEY => '_elementor_home_screen_data', EditorAssetsAPI::ASSETS_DATA_KEY => 'home-screen', ]; } public static function get_elementor_settings_page_id(): string { return 'elementor-settings'; } } home/api.php 0000644 00000001447 15252521350 0006764 0 ustar 00 <?php namespace Elementor\Modules\Home; use Elementor\Includes\EditorAssetsAPI; use Elementor\Modules\Home\Classes\Transformations_Manager; class API { protected EditorAssetsAPI $editor_assets_api; public function __construct( EditorAssetsAPI $editor_assets_api ) { $this->editor_assets_api = $editor_assets_api; } public function get_home_screen_items( $force_request = false ): array { $assets_data = $this->editor_assets_api->get_assets_data( $force_request ); $assets_data = apply_filters( 'elementor/core/admin/homescreen', $assets_data ); return $this->transform_home_screen_data( $assets_data ); } private function transform_home_screen_data( $json_data ): array { $transformers = new Transformations_Manager( $json_data ); return $transformers->run_transformations(); } } components/components-access-controller.php 0000644 00000002617 15252521350 0015255 0 ustar 00 <?php namespace Elementor\Modules\Components; if ( ! defined( 'ABSPATH' ) ) { exit; } class Components_Access_Controller { const TIER_CORE = 'core'; const TIER_EXPIRED = 'expired'; const TIER_PRO = 'pro'; public static function get_access_tier(): string { if ( ! class_exists( '\ElementorPro\License\API' ) ) { return self::TIER_CORE; } if ( \ElementorPro\License\API::is_license_active() ) { return self::TIER_PRO; } if ( \ElementorPro\License\API::is_license_expired() ) { return self::TIER_EXPIRED; } return self::TIER_CORE; } public static function is_pro_tier(): bool { return self::TIER_PRO === self::get_access_tier(); } public static function is_expired_or_pro_tier(): bool { $tier = self::get_access_tier(); return self::TIER_EXPIRED === $tier || self::TIER_PRO === $tier; } public static function can_create(): bool { return self::is_pro_tier(); } public static function can_delete(): bool { return self::is_pro_tier(); } public static function can_rename(): bool { return self::is_pro_tier(); } public static function can_publish(): bool { return self::is_expired_or_pro_tier(); } public static function can_add_to_page(): bool { return self::is_pro_tier(); } public static function can_edit(): bool { return self::is_expired_or_pro_tier(); } public static function can_lock(): bool { return self::is_expired_or_pro_tier(); } } components/styles/component-styles.php 0000644 00000006775 15252521350 0014327 0 ustar 00 <?php namespace Elementor\Modules\Components\Styles; use Elementor\Core\Base\Document; use Elementor\Core\Utils\Collection; use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity; use Elementor\Modules\AtomicWidgets\Utils\Utils; /** * Component styles fetching for render */ class Component_Styles { const CACHE_ROOT_KEY = 'component-styles-related-posts'; public function register_hooks() { add_action( 'elementor/post/render', fn( $post_id ) => $this->render_post( $post_id ) ); add_action( 'elementor/document/after_save', fn( Document $document ) => $this->invalidate_cache( [ $document->get_main_post()->ID ] ), 20, 2 ); add_action( 'elementor/core/files/clear_cache', fn() => $this->invalidate_cache(), ); add_filter( 'elementor/document/related_posts', fn( array $related, $post_id ) => $this->get_related_posts( $related, $post_id ), 10, 2 ); } private function render_post( string $post_id ) { $component_ids = $this->get_component_ids_from_post_cached( $post_id ); $this->declare_components_rendered( $component_ids ); } /** * Handler for the `elementor/document/related_posts` filter. * * @param int[] $related Accumulated ids from previous filter handlers. * @param string|int $post_id Parent post id being inspected. * @return int[] Merged list of related post ids. */ private function get_related_posts( array $related, $post_id ): array { $component_ids = $this->get_component_ids_from_post_cached( (string) $post_id ); return array_values( array_unique( array_merge( $related, array_map( 'intval', $component_ids ) ) ) ); } /** * Returns the component ids embedded in $post_id, reading from the * traversal cache when available and writing to it otherwise. * * Both the render-action listener and the `elementor/document/related_posts` * filter handler delegate here so the traversal logic lives in one place. */ private function get_component_ids_from_post_cached( string $post_id ): array { $cache_validity = new Cache_Validity(); if ( $cache_validity->is_valid( [ self::CACHE_ROOT_KEY, $post_id ] ) ) { return (array) $cache_validity->get_meta( [ self::CACHE_ROOT_KEY, $post_id ] ); } $components = $this->get_components_from_post( $post_id ); $component_ids = Collection::make( $components ) ->filter( fn( $component ) => isset( $component['settings']['component_instance']['value']['component_id']['value'] ) ) ->map( fn( $component ) => $component['settings']['component_instance']['value']['component_id']['value'] ) ->unique() ->all(); $cache_validity->validate( [ self::CACHE_ROOT_KEY, $post_id ], $component_ids ); return $component_ids; } private function declare_components_rendered( array $post_ids ) { foreach ( $post_ids as $post_id ) { do_action( 'elementor/post/render', $post_id ); } } private function get_components_from_post( string $post_id ): array { $components = []; Utils::traverse_post_elements( $post_id, function( $element_data ) use ( &$components ) { if ( isset( $element_data['widgetType'] ) && 'e-component' === $element_data['widgetType'] ) { $components[] = $element_data; } } ); return $components; } private function invalidate_cache( ?array $post_ids = null ) { $cache_validity = new Cache_Validity(); if ( empty( $post_ids ) ) { $cache_validity->invalidate( [ self::CACHE_ROOT_KEY ] ); return; } foreach ( $post_ids as $post_id ) { $cache_validity->invalidate( [ self::CACHE_ROOT_KEY, $post_id ] ); } } } components/components-repository.php 0000644 00000013414 15252521350 0014047 0 ustar 00 <?php namespace Elementor\Modules\Components; use Elementor\Core\Utils\Collection; use Elementor\Modules\Components\Documents\Component as Component_Document; use Elementor\Plugin; use Elementor\Core\Base\Document; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Components_Repository { public static function make(): Components_Repository { return new self(); } public function all(): Collection { // Components count is limited to 100, if we increase this number, we need to iterate the posts in batches. $posts = get_posts( [ 'post_type' => Component_Document::TYPE, 'post_status' => 'any', 'posts_per_page' => Components_REST_API::MAX_COMPONENTS, ] ); $components = []; foreach ( $posts as $post ) { $component = $this->get( $post->ID ); if ( ! $component ) { continue; } $components[] = [ 'id' => $component->get_main_id(), 'title' => $component->get_post()->post_title, 'uid' => $component->get_component_uid(), 'is_archived' => $component->get_is_archived(), 'styles' => $this->extract_styles( $component->get_elements_data() ), ]; } return Collection::make( $components ); } public function get( $id, bool $include_autosave = true ) { $doc = $include_autosave ? Plugin::$instance->documents->get_doc_or_auto_save( $id, get_current_user_id() ) : Plugin::$instance->documents->get( $id ); if ( ! $doc instanceof Component_Document ) { return null; } return $doc; } public function create( string $title, array $content, string $status, string $uid, array $settings = [] ) { $document = Plugin::$instance->documents->create( Component_Document::get_type(), [ 'post_title' => $title, 'post_status' => $status, ], [ Component_Document::COMPONENT_UID_META_KEY => $uid, ] ); try { $saved = $document->save( [ 'elements' => $content, 'settings' => $settings, ] ); } catch ( \Exception $e ) { $document->force_delete(); throw $e; } if ( ! $saved ) { $document->force_delete(); throw new \Exception( 'Failed to create component' ); } return $document->get_main_id(); } private function extract_styles( array $elements, array $styles = [] ) { foreach ( $elements as $element ) { if ( isset( $element['styles'] ) ) { $styles = array_merge( $styles, $element['styles'] ); } if ( isset( $element['elements'] ) ) { $styles = $this->extract_styles( $element['elements'], $styles ); } } return $styles; } public function archive( array $ids, string $status ) { $failed_ids = []; $success_ids = []; foreach ( $ids as $id ) { try { $component = $this->get_component_for_edit( $id, $status ); if ( ! $component ) { $failed_ids[] = $id; continue; } $component->archive(); $success_ids[] = $id; } catch ( \Exception $e ) { $failed_ids[] = $id; } } return [ 'failedIds' => $failed_ids, 'successIds' => $success_ids, ]; } public function update_title( int $component_id, string $title, string $status ): bool { $component = $this->get_component_for_edit( $component_id, $status ); if ( ! $component ) { return false; } return $component->update_title( $title ); } /** * Get the component for edit. * * @param int $component_id The component ID. * @param string $target_status The target status, means the status the component should be saved as. * @return ?Component_Document The component document for edit. * * If target status is an autosave / draft: * - If the component main document is autosave / draft, it will return the main document. * - If the component main document is published, it will create a new autosave document and return it. * If target status is publish: * - Will return the main document. If it's an autosave, it will be published later by the publish_component method. */ private function get_component_for_edit( int $component_id, string $target_status ): ?Component_Document { $component = $this->get( $component_id ); if ( ! $component ) { return null; } $autosave_statuses = [ Document::STATUS_AUTOSAVE, Document::STATUS_DRAFT ]; $autosave_exists = $component->is_autosave(); $should_create_autosave = in_array( $target_status, $autosave_statuses, true ) && ! $autosave_exists; if ( ! $should_create_autosave ) { return $component; } // Create a new autosave document, based on the published version. return $component->get_autosave( 0, true ); } public function publish_component( Component_Document $component ): bool { try { $main_id = $component->get_main_id(); $main_component = $this->get( $main_id, false ); $autosave = $main_component->get_newer_autosave(); if ( $autosave ) { $success = $this->copy_autosave_data_to_main_component_document_and_publish( $autosave, $main_component, $main_id ); } else { $success = $main_component->update_status( Document::STATUS_PUBLISH ); } if ( ! $success ) { throw new \Exception( 'Failed to publish component' ); } } catch ( \Exception $e ) { return false; } return true; } private function copy_autosave_data_to_main_component_document_and_publish( Component_Document $autosave, Component_Document $main_component_document, int $main_id ): bool { $autosave_id = $autosave->get_post()->ID; // Copy component custom meta keys from the autosave to the main component. Plugin::$instance->db->copy_elementor_meta( $autosave_id, $main_id, Component_Document::COMPONENT_CUSTOM_META_KEYS ); $autosave_elements = $autosave->get_elements_data(); $autosave_title = $autosave->get_post()->post_title; return $main_component_document->save( [ 'elements' => $autosave_elements, 'settings' => [ 'post_status' => Document::STATUS_PUBLISH, 'post_title' => $autosave_title, ], ] ); } } components/widgets/component-instance.php 0000644 00000007145 15252521350 0014723 0 ustar 00 <?php namespace Elementor\Modules\Components\Widgets; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template; use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver; use Elementor\Modules\Components\Components_Repository; use Elementor\Modules\Components\PropTypes\Component_Instance_Prop_Type; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; use Elementor\Modules\Components\Transformers\Overridable_Transformer; use Elementor\Modules\Components\Utils\Format_Component_Elements_Id; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Component_Instance extends Atomic_Widget_Base { use Has_Template; public static function get_element_type(): string { return 'e-component'; } public function show_in_panel() { return false; } public function get_title() { return esc_html__( 'Component', 'elementor' ); } public function get_keywords() { return [ 'component' ]; } public function get_icon() { return 'eicon-components'; } protected static function define_props_schema(): array { return [ 'component_instance' => Component_Instance_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() )->required(), ]; } protected function parse_editor_settings( array $data ): array { $editor_data = parent::parse_editor_settings( $data ); if ( isset( $data['component_uid'] ) && is_string( $data['component_uid'] ) ) { $editor_data['component_uid'] = sanitize_text_field( $data['component_uid'] ); } return $editor_data; } protected function define_atomic_controls(): array { return []; } protected function get_templates(): array { return [ 'elementor/elements/component' => __DIR__ . '/component.html.twig', ]; } protected function define_render_context(): array { $resolved_overrides = $this->get_resolved_overrides(); $merged_overrides = $this->get_merged_overrides( $resolved_overrides ); return [ [ 'context_key' => Overridable_Transformer::class, 'context' => [ 'overrides' => $merged_overrides ], ], [ 'context' => [ 'instance_id' => $this->get_id() ], ], ]; } private function get_resolved_overrides(): array { $props = $this->get_settings(); $overrides = $props['component_instance']['value']['overrides'] ?? null; if ( ! $overrides ) { return []; } $component_schema = $this->get_props_schema(); $overrides_schema = $component_schema['component_instance']->get_shape_field( 'overrides' ); return Render_Props_Resolver::for_settings()->resolve( [ 'overrides' => $overrides_schema ], [ 'overrides' => $overrides ] ); } private function get_merged_overrides( $value ): array { $overrides_array = $value['overrides'] ?? []; $overrides = []; foreach ( $overrides_array as $override ) { $overrides[ $override['override_key'] ] = $override['override_value']; } return $overrides; } public function get_inner_elements_data_for_search(): array { $component_id = $this->get_component_id(); if ( null === $component_id ) { return []; } $repository = new Components_Repository(); $component = $repository->get( $component_id ); if ( ! $component ) { return []; } $elements_data = $component->get_elements_data(); return Format_Component_Elements_Id::format( $elements_data, [ $this->get_id() ] ); } private function get_component_id(): ?int { $settings = $this->get_settings(); if ( ! isset( $settings['component_instance']['value']['component_id']['value'] ) ) { return null; } return (int) $settings['component_instance']['value']['component_id']['value']; } } components/widgets/component.html.twig 0000644 00000000050 15252521350 0014233 0 ustar 00 {{ settings.component_instance | raw }} components/utils/parsing-utils.php 0000644 00000003274 15252521350 0013411 0 ustar 00 <?php namespace Elementor\Modules\Components\Utils; use Elementor\Plugin; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Parsing_Utils { public static function get_prop_type( string $el_type, string $widget_type, string $prop_key ): Prop_Type { $element = Plugin::$instance->elements_manager->get_element( $el_type, $widget_type ); if ( ! $element ) { throw new \Exception( esc_html( "Invalid element: Element type $el_type with widget type $widget_type is not registered." ) ); } $element_instance = new $element(); /** @var Atomic_Element_Base | Atomic_Widget_Base $element_instance */ if ( ! Utils::is_atomic( $element_instance ) ) { throw new \Exception( esc_html( "Invalid element: Element type $el_type with widget type $widget_type is not an atomic element/widget." ) ); } $props_schema = $element_instance->get_props_schema(); if ( ! isset( $props_schema[ $prop_key ] ) ) { throw new \Exception( esc_html( "Prop key '$prop_key' does not exist in the schema of element '{$element_instance->get_element_type()}'." ) ); } return $props_schema[ $prop_key ]; } public static function get_duplicates( array $array ): array { $duplicates = []; $seen = []; foreach ( $array as $item ) { if ( in_array( $item, $seen, true ) ) { if ( ! in_array( $item, $duplicates, true ) ) { $duplicates[] = $item; } } else { $seen[] = $item; } } return $duplicates; } } components/utils/format-component-elements-id.php 0000644 00000003427 15252521350 0016304 0 ustar 00 <?php namespace Elementor\Modules\Components\Utils; use Elementor\Plugin; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Format_Component_Elements_Id { public static function format( array $elements, array $path ) { return array_map( function( $element ) use ( $path ) { $origin_id = $element['id']; $nesting_path = [ ...$path, $origin_id ]; $element['id'] = self::hash_string( implode( '_', $nesting_path ), 7 ); $element['origin_id'] = $origin_id; $element['elements'] = self::format( $element['elements'], $nesting_path ); return $element; }, $elements ); } /** * This is a copy of the hashString function in ts utils package. * It's important to keep it in synced with the ts implementation * to make component inner elements ids consistent between the editor and the frontend. * * @param string $str - The string to hash. * @param $length - The length of the hash to return, optional. * @return string - The hashed string. */ public static function hash_string( string $str, ?int $length ): string { $hash_basis = 5381; $i = strlen( $str ); while ( $i > 0 ) { --$i; $hash_basis = ( $hash_basis * 33 ) ^ ord( $str[ $i ] ); // Keep hash within 32-bit range to match JavaScript bitwise operations. $hash_basis = $hash_basis & 0xFFFFFFFF; } $result = base_convert( (string) $hash_basis, 10, 36 ); if ( ! isset( $length ) ) { return $result; } $sliced = substr( $result, -$length ); return str_pad( $sliced, $length, '0', STR_PAD_LEFT ); } } components/prop-types/overridable-prop-type.php 0000644 00000003677 15252521350 0016034 0 ustar 00 <?php namespace Elementor\Modules\Components\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Overridable_Prop_Type extends Plain_Prop_Type { const META_KEY = 'overridable'; /** * Return a tuple that lets the developer ignore the component overridable prop type in the props schema * using `Prop_Type::meta()`, e.g. `String_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() )`. */ public static function ignore(): array { return [ static::META_KEY, false ]; } public static function get_key(): string { return 'overridable'; } protected function validate_value( $value ): bool { if ( ! is_array( $value ) ) { return false; } if ( ! array_key_exists( 'override_key', $value ) || ! is_string( $value['override_key'] ) ) { return false; } if ( ! array_key_exists( 'origin_value', $value ) ) { return false; } $origin_prop_type = $this->get_origin_prop_type(); if ( ! $origin_prop_type ) { return false; } return $origin_prop_type->validate( $value['origin_value'] ); } protected function sanitize_value( $value ): ?array { ['override_key' => $override_key, 'origin_value' => $origin_value] = $value; $origin_prop_type = $this->get_origin_prop_type(); if ( ! $origin_prop_type ) { return null; } $sanitized_override_key = sanitize_key( $override_key ); $sanitized_origin_value = is_null( $origin_value ) ? null : $origin_prop_type->sanitize( $origin_value ); return [ 'override_key' => $sanitized_override_key, 'origin_value' => $sanitized_origin_value, ]; } public function set_origin_prop_type( Prop_Type $origin_prop_type ) { $this->settings['origin_prop_type'] = $origin_prop_type; return $this; } public function get_origin_prop_type() { return $this->settings['origin_prop_type'] ?? null; } } components/prop-types/component-override-parser.php 0000644 00000011140 15252521350 0016672 0 ustar 00 <?php namespace Elementor\Modules\Components\PropTypes; use Elementor\Plugin; use Elementor\Modules\Components\Components_Repository; use Elementor\Modules\Components\Documents\Component_Overridable_Prop; use Elementor\Modules\Components\Documents\Component_Overridable_Props; use Elementor\Modules\Components\Utils\Parsing_Utils; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Component_Override_Parser extends Override_Parser { private static $repository; public static function get_override_type(): string { return 'component'; } private ?Component_Overridable_Props $component_overridable_props = null; public static function make(): self { return new static(); } public function resolve_override_value_prop_type( string $override_key, int $component_id ): ?Prop_Type { $component_overridable_props = $this->get_component_overridable_props( $component_id ); $matching = $this->get_matching_component_overridable_prop( $override_key, $component_overridable_props ); if ( ! $matching ) { return null; } try { return $this->get_overridable_prop_type( $matching ); } catch ( \Exception $e ) { return null; } } public function validate_override( string $override_key, ?array $override_value, array $schema_source ): bool { if ( ! isset( $schema_source['id'] ) ) { return false; } $component_id = $schema_source['id']; $component_overridable_props = $this->get_component_overridable_props( $component_id ); try { $matching_overridable_prop = $this->get_matching_component_overridable_prop( sanitize_key( $override_key ), $component_overridable_props ); if ( ! $matching_overridable_prop ) { // If the override is not one of the component overridable props we'll remove it in sanitize_value method. // This is a valid scenario, as the user can delete overridable props from the component after the override created. return true; } $prop_type = $this->get_overridable_prop_type( $matching_overridable_prop ); if ( null === $override_value ) { return true; } return $prop_type->validate( $override_value ); } catch ( \Exception $e ) { return false; } } public function sanitize( $value ) { ['override_key' => $override_key, 'override_value' => $override_value, 'schema_source' => $schema_source] = $value; $sanitized_override_key = sanitize_key( $override_key ); $sanitized_schema_source = [ 'type' => sanitize_text_field( $schema_source['type'] ), 'id' => (int) $schema_source['id'], ]; $component_id = $sanitized_schema_source['id']; $component_overridable_props = $this->get_component_overridable_props( $component_id ); try { $matching_overridable_prop = $this->get_matching_component_overridable_prop( $sanitized_override_key, $component_overridable_props ); if ( ! $matching_overridable_prop ) { return null; } $prop_type = $this->get_overridable_prop_type( $matching_overridable_prop ); return [ 'override_key' => $sanitized_override_key, 'override_value' => null === $override_value ? null : $prop_type->sanitize( $override_value ), 'schema_source' => $sanitized_schema_source, ]; } catch ( \Exception $e ) { return null; } } private function get_matching_component_overridable_prop( string $override_key, ?Component_Overridable_Props $component_overridable_props ): ?Component_Overridable_Prop { if ( ! $component_overridable_props || ! isset( $component_overridable_props->props[ $override_key ] ) ) { return null; } return $component_overridable_props->props[ $override_key ]; } private function get_overridable_prop_type( Component_Overridable_Prop $overridable ): ?Prop_Type { if ( $overridable->origin_prop_fields ) { ['el_type' => $el_type, 'widget_type' => $widget_type, 'prop_key' => $prop_key] = $overridable->origin_prop_fields; return Parsing_Utils::get_prop_type( $el_type, $widget_type, $prop_key ); } return Parsing_Utils::get_prop_type( $overridable->el_type, $overridable->widget_type, $overridable->prop_key ); } private function get_component_overridable_props( int $component_id ) { if ( $this->component_overridable_props ) { return $this->component_overridable_props; } $component = $this->get_repository()->get( $component_id ); if ( ! $component ) { return null; } $this->component_overridable_props = $component->get_overridable_props(); return $this->component_overridable_props; } private function get_repository() { if ( ! self::$repository ) { self::$repository = new Components_Repository(); } return self::$repository; } } components/prop-types/override-prop-type.php 0000644 00000003201 15252521350 0015334 0 ustar 00 <?php namespace Elementor\Modules\Components\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Override_Prop_Type extends Plain_Prop_Type { public static function get_key(): string { return 'override'; } protected function validate_value( $value ): bool { if ( ! is_array( $value ) ) { return false; } $required_fields = [ 'override_key' => 'is_string', 'override_value' => fn( $value ) => is_null( $value ) || is_array( $value ), 'schema_source' => 'is_array', ]; $is_valid_structure = true; foreach ( $required_fields as $field => $validator ) { if ( ! array_key_exists( $field, $value ) || ! call_user_func( $validator, $value[ $field ] ) ) { $is_valid_structure = false; break; } } if ( ! $is_valid_structure ) { return false; } $parser = $this->get_parser( sanitize_text_field( $value['schema_source']['type'] ) ); if ( ! $parser || ! $parser instanceof Override_Parser ) { return false; } return $parser->validate( $value ); } protected function sanitize_value( $value ): ?array { $parser = $this->get_parser( sanitize_text_field( $value['schema_source']['type'] ) ); if ( ! $parser ) { return null; } return $parser->sanitize( $value ); } private function get_parser( string $schema_source_type ): ?Override_Parser { switch ( $schema_source_type ) { case Component_Override_Parser::get_override_type(): return Component_Override_Parser::make(); default: return null; } } } components/prop-types/overrides-prop-type.php 0000644 00000002062 15252521350 0015523 0 ustar 00 <?php namespace Elementor\Modules\Components\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\Components\PropTypes\Override_Parser; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Overrides_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'overrides'; } protected function define_item_type(): Prop_Type { return Override_Prop_Type::make(); } public function sanitize_value( $value ): array { $sanitized = parent::sanitize_value( $value ); // array_values is used to format filtered overrides to indexed array return array_values( array_filter( $sanitized, function( $item ) { switch ( $item['$$type'] ) { case 'override': return null !== $item['value']; case 'overridable': $override = $item['value']['origin_value']; return null !== $override['value']; } } ) ); } } components/prop-types/component-instance-prop-type.php 0000644 00000002445 15252521350 0017332 0 ustar 00 <?php namespace Elementor\Modules\Components\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Component_Instance_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'component-instance'; } protected function define_shape(): array { return [ 'component_id' => Number_Prop_Type::make()->required(), 'overrides' => Overrides_Prop_Type::make()->optional(), ]; } public function validate_value( $value ): bool { if ( ! parent::validate_value( $value ) ) { return false; } $sanitized = parent::sanitize_value( $value ); $overrides = $sanitized['overrides']['value'] ?? []; foreach ( $overrides as $item ) { $component_id = null; switch ( $item['$$type'] ) { case Override_Prop_Type::get_key(): $component_id = $item['value']['schema_source']['id']; break; case Overridable_Prop_Type::get_key(): $override = $item['value']['origin_value']; $component_id = $override['value']['schema_source']['id']; break; } if ( $component_id !== $sanitized['component_id']['value'] ) { return false; } } return true; } } components/prop-types/override-parser.php 0000644 00000002022 15252521350 0014671 0 ustar 00 <?php namespace Elementor\Modules\Components\PropTypes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Override_Parser { public static function make(): self { return new static(); } abstract public static function get_override_type(): string; /** * @param array{override_key: string, override_value: ?array, schema_source: array} $value */ public function validate( $value ): bool { [ 'override_key' => $override_key, 'override_value' => $override_value, 'schema_source' => $schema_source ] = $value; if ( ! isset( $schema_source['type'] ) || $schema_source['type'] !== $this->get_override_type() ) { return false; } return $this->validate_override( $override_key, $override_value, $schema_source ); } abstract public function validate_override( string $override_key, ?array $override_value, array $schema_source ): bool; /** * @param array{override_key: string, override_value: array, schema_source: array} $value */ abstract public function sanitize( $value ); } components/document-lock-manager.php 0000644 00000010775 15252521350 0013630 0 ustar 00 <?php namespace Elementor\Modules\Components; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Manages document locking for Elementor documents. * * This class handles locking/unlocking documents to prevent multiple users * from editing the same document simultaneously. */ class Document_Lock_Manager { // 5 minutes const DEFAULT_TIME = 60 * 5; private $lock_duration; private const LOCK_USER_META = '_lock_user'; private const LOCK_TIME_META = '_lock_time'; private const LOCK_EDIT_LOCK_META = '_edit_lock'; /** * Initialize the lock manager. * * @param int $lock_duration Lock duration in seconds (default: 300 = 5 minutes) */ public function __construct( $lock_duration = self::DEFAULT_TIME ) { $this->lock_duration = $lock_duration; } /** * Lock a document for the current user. * * @param int $document_id The document ID to lock * @return bool True if lock was successful, false otherwise */ public function lock( $document_id ) { try { $user_id = get_current_user_id(); if ( ! $user_id ) { return false; } $post = get_post( $document_id ); if ( ! $post ) { return false; } if ( $this->is_lock_expired( $document_id ) ) { $this->unlock( $document_id ); } $existing_lock = $this->get_lock_data( $document_id ); if ( $existing_lock['locked_by'] ) { return false; } update_post_meta( $document_id, self::LOCK_USER_META, $user_id ); update_post_meta( $document_id, self::LOCK_TIME_META, time() ); if ( ! function_exists( 'wp_set_post_lock' ) ) { require_once ABSPATH . 'wp-admin/includes/post.php'; } wp_set_post_lock( $document_id ); return true; } catch ( \Exception $e ) { error_log( 'Document lock error: ' . $e->getMessage() ); return false; } } /** * Unlock a document. * * @param int $document_id The document ID to unlock * @return bool True if unlock was successful, false otherwise */ public function unlock( $document_id ) { try { delete_post_meta( $document_id, self::LOCK_USER_META ); delete_post_meta( $document_id, self::LOCK_TIME_META ); delete_post_meta( $document_id, self::LOCK_EDIT_LOCK_META ); return true; } catch ( \Exception $e ) { error_log( 'Document unlock error: ' . $e->getMessage() ); return false; } } /** * Check if a document is currently locked. * * @param int $document_id The document ID to check * @return array Lock data with 'locked_by' (int|null), 'locked_at' (int|null) */ public function get_lock_data( $document_id ) { $locked_by = $this->get_document_lock_user( $document_id ); $locked_at = $this->get_document_lock_time( $document_id ); return [ 'locked_by' => $locked_by, 'locked_at' => $locked_at, ]; } /** * Check if a document lock has expired. * * @param int $document_id The document ID to check * @return bool True if lock exists and is expired, false if not locked or not expired */ public function is_lock_expired( $document_id ) { $lock_data = $this->get_lock_data( $document_id ); if ( ! $lock_data['locked_by'] ) { return false; } return $lock_data['locked_at'] && (int) $lock_data['locked_at'] + $this->lock_duration <= time(); } /** * Extend the lock for a document. * * @param int $document_id The document ID * @return bool True if extended successfully, false if not locked or locked by another user */ public function extend_lock( $document_id ) { $lock_data = $this->get_lock_data( $document_id ); if ( ! $lock_data['locked_by'] ) { return false; } $current_user_id = get_current_user_id(); if ( (int) $lock_data['locked_by'] !== (int) $current_user_id ) { return false; } update_post_meta( $document_id, self::LOCK_TIME_META, time() ); return true; } public function get_document_lock_user( $document_id ) { $lock_user_meta = get_post_meta( $document_id, self::LOCK_USER_META, true ); if ( $lock_user_meta ) { return (int) $lock_user_meta; } if ( ! function_exists( 'wp_check_post_lock' ) ) { require_once ABSPATH . 'wp-admin/includes/post.php'; } $wp_lock_user = wp_check_post_lock( $document_id ); return $wp_lock_user ? (int) $wp_lock_user : null; } /** * Get the lock time of a document. * * @param int $document_id The document ID to check * @return int|null Lock time, or null if not locked */ public function get_document_lock_time( $document_id ) { $lock_time = get_post_meta( $document_id, self::LOCK_TIME_META, true ); if ( $lock_time ) { return (int) $lock_time; } return null; } } components/module.php 0000644 00000015507 15252521350 0010737 0 ustar 00 <?php namespace Elementor\Modules\Components; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Modules\AtomicWidgets\Module as AtomicWidgetsModule; use Elementor\Plugin; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers_Registry; use Elementor\Modules\Components\Styles\Component_Styles; use Elementor\Modules\Components\Documents\Component as Component_Document; use Elementor\Modules\Components\Component_Lock_Manager; use Elementor\Modules\Components\PropTypes\Component_Instance_Prop_Type; use Elementor\Modules\Components\Transformers\Component_Instance_Transformer; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; use Elementor\Modules\Components\Transformers\Overridable_Transformer; use Elementor\Core\Base\Document; use Elementor\Modules\Components\PropTypes\Override_Prop_Type; use Elementor\Modules\Components\Transformers\Override_Transformer; use Elementor\Modules\Components\Widgets\Component_Instance; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const EXPERIMENT_NAME = 'e_components'; const PACKAGES = [ 'editor-components' ]; public function get_name() { return 'components'; } public function __construct() { parent::__construct(); if ( ! $this->is_experiment_active() ) { return; } $this->register_component_post_type(); add_filter( 'elementor/editor/v2/packages', fn ( $packages ) => $this->add_packages( $packages ) ); add_filter( 'elementor/atomic-widgets/props-schema', fn ( $schema ) => $this->modify_props_schema( $schema ) ); add_action( 'elementor/documents/register', fn ( $documents_manager ) => $this->register_document_type( $documents_manager ) ); add_action( 'elementor/document/before_save', fn( Document $document, array $data ) => $this->validate_circular_dependencies( $document, $data ), 10, 2 ); add_action( 'elementor/document/after_save', fn( Document $document, array $data ) => $this->set_component_overridable_props( $document, $data ), 10, 2 ); add_filter( 'elementor/global_classes/additional_post_types', fn( $post_types ) => array_merge( $post_types, [ Component_Document::TYPE ] ) ); add_filter( 'elementor/utils/find_element_recursive/inner_elements', fn( array $inner_elements, array $element_data ) => $this->get_inner_elements_for_search( $inner_elements, $element_data ), 10, 2 ); add_action( 'elementor/atomic-widgets/settings/transformers/register', fn ( $transformers ) => $this->register_settings_transformers( $transformers ) ); add_action( 'elementor/document/after_migrate', fn( Document $document, array $data ) => $this->after_component_migrate( $document, $data ), 10, 2 ); ( Component_Lock_Manager::get_instance()->register_hooks() ); ( new Component_Styles() )->register_hooks(); ( new Components_REST_API() )->register_hooks(); } public function is_experiment_active() { return Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ) && Plugin::$instance->experiments->is_feature_active( AtomicWidgetsModule::EXPERIMENT_NAME ); } public static function get_experimental_data() { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Components', 'elementor' ), 'description' => esc_html__( 'Enable components.', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_ACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_BETA, ]; } public function get_widgets() { return [ 'Component_Instance', ]; } private function add_packages( $packages ) { return array_merge( $packages, self::PACKAGES ); } private function modify_props_schema( array $schema ) { return Overridable_Schema_Extender::make()->get_extended_schema( $schema ); } private function register_component_post_type() { register_post_type( Component_Document::TYPE, [ 'label' => Component_Document::get_title(), 'labels' => Component_Document::get_labels(), 'public' => false, 'supports' => Component_Document::get_supported_features(), ] ); } private function register_document_type( $documents_manager ) { $documents_manager->register_document_type( Component_Document::TYPE, Component_Document::get_class_full_name() ); } private function validate_circular_dependencies( Document $document, array $data ) { if ( ! $document instanceof Component_Document ) { return; } if ( ! isset( $data['elements'] ) ) { return; } $component_id = $document->get_main_id(); $elements = $data['elements']; $result = Circular_Dependency_Validator::make()->validate( $component_id, $elements ); if ( ! $result['success'] ) { throw new \Exception( esc_html__( "Can't add this component - components that contain each other can't be nested.", 'elementor' ) ); } } private function set_component_overridable_props( Document $document, array $data ) { if ( ! isset( $data['settings'] ) ) { return; } if ( ( ! $document instanceof Component_Document ) || ( ! isset( $data['settings']['overridable_props'] ) ) ) { return; } if ( ! Components_Access_Controller::can_edit() ) { throw new \Exception( esc_html__( 'You do not have permission to edit component source.', 'elementor' ) ); } /* @var Component_Document $document */ $result = $document->update_overridable_props( $data['settings']['overridable_props'] ); if ( ! $result->is_valid() ) { throw new \Exception( esc_html( 'Settings validation failed for component overridable props: ' . $result->errors()->to_string() ) ); } } private function register_settings_transformers( Transformers_Registry $transformers ) { $transformers->register( Component_Instance_Prop_Type::get_key(), new Component_Instance_Transformer() ); $transformers->register( Overridable_Prop_Type::get_key(), new Overridable_Transformer() ); $transformers->register( Override_Prop_Type::get_key(), new Override_Transformer() ); } private function after_component_migrate( Document $document, array $data ) { if ( ! $document instanceof Component_Document ) { return; } $document->align_overridable_props_with_elements(); } private function get_inner_elements_for_search( array $inner_elements, array $element_data ): array { if ( ! $this->is_component_instance( $element_data ) ) { return $inner_elements; } $element_instance = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( ! $element_instance instanceof Component_Instance ) { return []; } return $element_instance->get_inner_elements_data_for_search(); } private function is_component_instance( array $element_data ): bool { return isset( $element_data['elType'], $element_data['widgetType'] ) && 'widget' === $element_data['elType'] && Component_Instance::get_element_type() === $element_data['widgetType']; } } components/non-atomic-widget-validator.php 0000644 00000005134 15252521350 0014755 0 ustar 00 <?php namespace Elementor\Modules\Components; use Elementor\Core\Utils\Collection; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Non_Atomic_Widget_Validator { const ERROR_CODE = 'non_atomic_element_in_component'; const WIDGET_EL_TYPE = 'widget'; public static function make(): Non_Atomic_Widget_Validator { return new self(); } public function validate( array $elements ): array { $non_atomic_elements = $this->find_non_atomic_elements( $elements ); if ( ! empty( $non_atomic_elements ) ) { return $this->build_error_response( $non_atomic_elements ); } return [ 'success' => true, 'messages' => [], ]; } public function validate_items( Collection $items ): array { foreach ( $items->all() as $item ) { $elements = $item['elements'] ?? []; $result = $this->validate( $elements ); if ( ! $result['success'] ) { return $result; } } return [ 'success' => true, 'messages' => [], ]; } private function find_non_atomic_elements( array $elements ): array { $non_atomic = []; foreach ( $elements as $element ) { $el_type = $element['elType'] ?? null; $widget_type = $element['widgetType'] ?? null; $element_type = $this->get_element_type( $el_type, $widget_type ); if ( $element_type && ! $this->is_element_atomic( $el_type, $widget_type ) ) { $non_atomic[] = $element_type; } if ( ! empty( $element['elements'] ) ) { $nested_non_atomic = $this->find_non_atomic_elements( $element['elements'] ); $non_atomic = array_merge( $non_atomic, $nested_non_atomic ); } } return array_unique( $non_atomic ); } private function get_element_type( ?string $el_type, ?string $widget_type ): ?string { return $widget_type ?? $el_type; } private function is_element_atomic( ?string $el_type, ?string $widget_type ): bool { if ( ! $el_type ) { return false; } $element_instance = Plugin::$instance->elements_manager->get_element( $el_type, $widget_type ); if ( ! $element_instance ) { return false; } return Utils::is_atomic( $element_instance ); } private function build_error_response( array $non_atomic_elements ): array { $message = sprintf( // translators: %s: Comma-separated list of non-atomic element types. esc_html__( 'Component contains non-supported elements: %s. Only atomic elements are allowed inside components.', 'elementor' ), implode( ', ', $non_atomic_elements ) ); return [ 'success' => false, 'code' => self::ERROR_CODE, 'messages' => [ $message ], 'non_atomic_elements' => $non_atomic_elements, ]; } } components/components-rest-api.php 0000644 00000052571 15252521350 0013363 0 ustar 00 <?php namespace Elementor\Modules\Components; use Elementor\Core\Base\Document; use Elementor\Core\Utils\Api\Error_Builder; use Elementor\Core\Utils\Api\Response_Builder; use Elementor\Core\Utils\Collection; use Elementor\Modules\Components\Documents\Component; use Elementor\Modules\Components\OverridableProps\Component_Overridable_Props_Parser; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Components_REST_API { const API_NAMESPACE = 'elementor/v1'; const API_BASE = 'components'; const LOCK_DOCUMENT_TYPE_NAME = 'components'; const STYLES_ROUTE = 'styles'; const MAX_COMPONENTS = 100; private $repository = null; public function register_hooks() { add_action( 'rest_api_init', fn() => $this->register_routes() ); } private function get_repository() { if ( ! $this->repository ) { $this->repository = new Components_Repository(); } return $this->repository; } /** * @return Component_Lock_Manager instance */ private function get_component_lock_manager() { return Component_Lock_Manager::get_instance(); } private function register_routes() { register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [ [ 'methods' => 'GET', 'callback' => fn() => $this->route_wrapper( fn() => $this->get_components() ), 'permission_callback' => fn() => current_user_can( 'edit_posts' ), ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/' . self::STYLES_ROUTE, [ [ 'methods' => 'GET', 'callback' => fn() => $this->route_wrapper( fn() => $this->get_styles() ), 'permission_callback' => fn() => current_user_can( 'edit_posts' ), ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [ [ 'methods' => 'POST', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->create_components( $request ) ), 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'status' => [ 'type' => 'string', 'enum' => [ Document::STATUS_PUBLISH, Document::STATUS_DRAFT, Document::STATUS_AUTOSAVE ], 'required' => true, ], 'items' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'uid' => [ 'type' => 'string', 'required' => true, ], 'title' => [ 'type' => 'string', 'required' => true, 'minLength' => 2, 'maxLength' => 200, ], 'elements' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'object', ], ], 'settings' => [ 'type' => 'object', 'required' => false, ], ], ], ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/create-validate', [ [ 'methods' => 'POST', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->create_validate_components( $request ) ), 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'items' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'uid' => [ 'type' => 'string', 'required' => true, ], 'title' => [ 'type' => 'string', 'required' => true, 'minLength' => 2, 'maxLength' => 200, ], 'elements' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'object', ], ], 'settings' => [ 'type' => 'object', 'required' => false, ], ], ], ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/overridable-props', [ [ 'methods' => 'GET', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->get_overridable_props( $request ) ), 'permission_callback' => fn() => current_user_can( 'edit_posts' ), 'args' => [ 'componentIds' => [ 'type' => 'array', 'items' => [ 'type' => 'integer', ], 'required' => true, 'description' => 'The component IDs to get overridable props for', ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/status', [ [ 'methods' => 'PUT', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->update_statuses( $request ) ), 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'status' => [ 'type' => 'string', 'required' => true, 'enum' => [ Document::STATUS_PUBLISH ], ], 'ids' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'number', 'required' => true, ], ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/lock', [ [ 'methods' => 'POST', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->lock_component( $request ) ), 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'componentId' => [ 'type' => 'number', 'required' => true, 'description' => 'The component ID to unlock', ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/unlock', [ [ 'methods' => 'POST', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->unlock_component( $request ) ), 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'componentId' => [ 'type' => 'number', 'required' => true, 'description' => 'The component ID to unlock', ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/lock-status', [ [ 'methods' => 'GET', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->get_lock_status( $request ) ), 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'componentId' => [ 'type' => 'string', 'required' => true, 'description' => 'The component ID to check lock status', ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/archive', [ [ 'methods' => 'POST', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->archive_components( $request ) ), 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'componentIds' => [ 'type' => 'array', 'items' => [ 'type' => 'number', 'required' => true, ], 'required' => true, 'description' => 'The component IDs to archive', ], 'status' => [ 'type' => 'string', 'enum' => [ Document::STATUS_PUBLISH, Document::STATUS_DRAFT, Document::STATUS_AUTOSAVE ], 'required' => true, ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/update-titles', [ [ 'methods' => 'POST', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->update_components_title( $request ) ), 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'components' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'componentId' => [ 'type' => 'number', 'required' => true, 'description' => 'The component ID to update title', ], 'title' => [ 'type' => 'string', 'required' => true, 'description' => 'The new title for the component', ], ], ], ], 'status' => [ 'type' => 'string', 'enum' => [ Document::STATUS_PUBLISH, Document::STATUS_DRAFT, Document::STATUS_AUTOSAVE ], 'required' => true, ], ], ], ] ); } private function get_components() { $components = $this->get_repository()->all(); $components_list = array_values( $components ->map( fn( $component ) => [ 'id' => $component['id'], 'name' => $component['title'], 'uid' => $component['uid'], 'isArchived' => $component['is_archived'] ?? false, ] ) ->all() ); return Response_Builder::make( $components_list )->build(); } private function get_styles() { $components = $this->get_repository()->all(); $styles = []; $components->each( function( $component ) use ( &$styles ) { $styles[ $component['id'] ] = $component['styles']; } ); return Response_Builder::make( $styles )->build(); } private function get_overridable_props( \WP_REST_Request $request ) { $component_ids = $request->get_param( 'componentIds' ); $data = []; $errors = []; foreach ( $component_ids as $component_id ) { $component_id = (int) $component_id; /** @var Component $document */ $document = $this->get_repository()->get( $component_id ); if ( ! $document ) { $errors[ $component_id ] = 'component_not_found'; continue; } // This is a fix for the case where overridable props in element settings where migrated // but the overridable props metadata were not aligned with the new origin values. // In version 4.0.1, we fixed this by running the align_overridable_props_with_elements method after the migration. $document_version = $document->get_elementor_version(); $overridable_props_migration_fix_version = '4.0.1'; $should_align_overridable_props = version_compare( $document_version, $overridable_props_migration_fix_version, '<=' ); if ( $should_align_overridable_props ) { $document->align_overridable_props_with_elements(); } $overridable = $document->get_json_meta( Component::OVERRIDABLE_PROPS_META_KEY ); $data[ $component_id ] = empty( $overridable ) ? null : $overridable; } return Response_Builder::make( $data ) ->set_meta( [ 'errors' => $errors ] ) ->build(); } private function create_components( \WP_REST_Request $request ) { if ( ! Components_Access_Controller::can_create() ) { return $this->get_insufficient_permissions_error( 'create' ); } $save_status = $request->get_param( 'status' ); $items = Collection::make( $request->get_param( 'items' ) ); $components = $this->get_repository()->all(); $result = Save_Components_Validator::make( $components )->validate( $items ); if ( ! $result['success'] ) { return Error_Builder::make( 'components_validation_failed' ) ->set_status( 422 ) ->set_message( 'Validation failed: ' . implode( ', ', $result['messages'] ) ) ->build(); } $circular_result = Circular_Dependency_Validator::make()->validate_new_components( $items ); if ( ! $circular_result['success'] ) { return Error_Builder::make( 'circular_dependency_detected' ) ->set_status( 422 ) ->set_message( __( "Can't add this component - components that contain each other can't be nested.", 'elementor' ) ) ->set_meta( [ 'caused_by' => $circular_result['messages'] ] ) ->build(); } $non_atomic_result = Non_Atomic_Widget_Validator::make()->validate_items( $items ); if ( ! $non_atomic_result['success'] ) { return Error_Builder::make( Non_Atomic_Widget_Validator::ERROR_CODE ) ->set_status( 422 ) ->set_message( __( 'Components require atomic elements only. Remove widgets to create this component.', 'elementor' ) ) ->set_meta( [ 'non_atomic_elements' => $non_atomic_result['non_atomic_elements'] ] ) ->build(); } $validation_errors = []; $created = $items->map_with_keys( function ( $item ) use ( $save_status, &$validation_errors ) { $title = sanitize_text_field( $item['title'] ); $content = $item['elements']; $uid = $item['uid']; try { $settings = isset( $item['settings'] ) ? $this->parse_settings( $item['settings'] ) : []; $status = Document::STATUS_AUTOSAVE === $save_status ? Document::STATUS_DRAFT : $save_status; $component_id = $this->get_repository()->create( $title, $content, $status, $uid, $settings ); return [ $uid => $component_id ]; } catch ( \Exception $e ) { $validation_errors[ $uid ] = $e->getMessage(); return [ $uid => null ]; } } ); if ( ! empty( $validation_errors ) ) { return Error_Builder::make( 'settings_validation_failed' ) ->set_status( 422 ) ->set_message( 'Settings validation failed: ' . json_encode( $validation_errors ) ) ->build(); } return Response_Builder::make( $created->all() ) ->set_status( 201 ) ->build(); } private function update_statuses( \WP_REST_Request $request ) { if ( ! Components_Access_Controller::can_publish() ) { return $this->get_insufficient_permissions_error( 'publish' ); } $result = Collection::make( $request->get_param( 'ids' ) ) ->reduce( function ( $result, int $component_id ) { $component = $this->get_repository()->get( $component_id ); if ( ! $component ) { $result['failed'][] = $component_id; return $result; } $publish_result = $this->get_repository()->publish_component( $component ); $result[ $publish_result ? 'success' : 'failed' ][] = $component_id; return $result; }, [ 'success' => [], 'failed' => [], ] ); return Response_Builder::make( $result )->build(); } private function lock_component( \WP_REST_Request $request ) { if ( ! Components_Access_Controller::can_lock() ) { return $this->get_insufficient_permissions_error( 'lock' ); } $component_id = $request->get_param( 'componentId' ); try { $success = $this->get_component_lock_manager()->lock( $component_id ); } catch ( \Exception $e ) { error_log( 'Components REST API lock_component error: ' . $e->getMessage() ); return Error_Builder::make( 'lock_failed' ) ->set_status( 500 ) ->set_message( __( 'Failed to lock component', 'elementor' ) ) ->build(); } if ( ! $success ) { return Error_Builder::make( 'lock_failed' ) ->set_status( 500 ) ->set_message( __( 'Failed to lock component', 'elementor' ) ) ->build(); } return Response_Builder::make( [ 'locked' => $success ] )->build(); } private function unlock_component( \WP_REST_Request $request ) { if ( ! Components_Access_Controller::can_lock() ) { return $this->get_insufficient_permissions_error( 'unlock' ); } $component_id = $request->get_param( 'componentId' ); try { $success = $this->get_component_lock_manager()->unlock( $component_id ); } catch ( \Exception $e ) { error_log( 'Components REST API unlock_component error: ' . $e->getMessage() ); return Error_Builder::make( 'unlock_failed' ) ->set_status( 500 ) ->set_message( __( 'Failed to unlock component', 'elementor' ) ) ->build(); } if ( ! $success ) { return Error_Builder::make( 'unlock_failed' ) ->set_status( 500 ) ->set_message( __( 'Failed to unlock component', 'elementor' ) ) ->build(); } return Response_Builder::make( [ 'unlocked' => $success ] )->build(); } private function get_lock_status( \WP_REST_Request $request ) { if ( ! Components_Access_Controller::can_lock() ) { return $this->get_insufficient_permissions_error( 'lock_status' ); } $component_id = (int) $request->get_param( 'componentId' ); try { $lock_manager = $this->get_component_lock_manager(); if ( $lock_manager->is_lock_expired( $component_id ) ) { $lock_manager->unlock( $component_id ); } $lock_data = $lock_manager->get_lock_data( $component_id ); $current_user_id = get_current_user_id(); // if current user is the lock user, return true if ( $lock_data['locked_by'] && $lock_data['locked_by'] === $current_user_id ) { return Response_Builder::make( [ 'is_current_user_allow_to_edit' => true, 'locked_by' => get_user_by( 'id', $lock_data['locked_by'] )->display_name, ] )->build(); } // if the user is not the lock user, return false if ( $lock_data['locked_by'] && $lock_data['locked_by'] !== $current_user_id ) { return Response_Builder::make( [ 'is_current_user_allow_to_edit' => false, 'locked_by' => get_user_by( 'id', $lock_data['locked_by'] )->display_name, ] )->build(); } // if the component is not locked, return true if ( ! $lock_data['locked_by'] ) { return Response_Builder::make( [ 'is_current_user_allow_to_edit' => true, 'locked_by' => null, ] )->build(); } } catch ( \Exception $e ) { error_log( 'Components REST API get_lock_status error: ' . $e->getMessage() ); return Error_Builder::make( 'get_lock_status_failed' ) ->set_status( 500 ) ->set_message( __( 'Failed to get lock status', 'elementor' ) ) ->build(); } } private function archive_components( \WP_REST_Request $request ) { if ( ! Components_Access_Controller::can_delete() ) { return $this->get_insufficient_permissions_error( 'delete' ); } $component_ids = $request->get_param( 'componentIds' ); $status = $request->get_param( 'status' ); try { $result = $this->get_repository()->archive( $component_ids, $status ); } catch ( \Exception $e ) { error_log( 'Components REST API archive_components error: ' . $e->getMessage() ); return Error_Builder::make( 'archive_failed' ) ->set_meta( [ 'error' => $e->getMessage() ] ) ->set_status( 500 ) ->set_message( __( 'Failed to archive components', 'elementor' ) ) ->build(); } return Response_Builder::make( $result )->build(); } private function update_components_title( \WP_REST_Request $request ) { if ( ! Components_Access_Controller::can_rename() ) { return $this->get_insufficient_permissions_error( 'rename' ); } $failed_ids = []; $success_ids = []; $components = $request->get_param( 'components' ); $status = $request->get_param( 'status' ); foreach ( $components as $component ) { $is_success = $this->get_repository()->update_title( $component['componentId'], $component['title'], $status ); if ( ! $is_success ) { $failed_ids[] = $component['componentId']; continue; } $success_ids[] = $component['componentId']; } return Response_Builder::make( [ 'failedIds' => $failed_ids, 'successIds' => $success_ids, ] )->build(); } private function create_validate_components( \WP_REST_Request $request ) { if ( ! Components_Access_Controller::can_create() ) { return $this->get_insufficient_permissions_error( 'create' ); } $items = Collection::make( $request->get_param( 'items' ) ); $components = $this->get_repository()->all(); $result = Save_Components_Validator::make( $components )->validate( $items ); if ( ! $result['success'] ) { return Error_Builder::make( 'components_validation_failed' ) ->set_status( 422 ) ->set_message( 'Validation failed: ' . implode( ', ', $result['messages'] ) ) ->build(); } $circular_result = Circular_Dependency_Validator::make()->validate_new_components( $items ); if ( ! $circular_result['success'] ) { return Error_Builder::make( 'circular_dependency_detected' ) ->set_status( 422 ) ->set_message( __( "Can't add this component - components that contain each other can't be nested.", 'elementor' ) ) ->set_meta( [ 'caused_by' => $circular_result['messages'] ] ) ->build(); } $non_atomic_result = Non_Atomic_Widget_Validator::make()->validate_items( $items ); if ( ! $non_atomic_result['success'] ) { return Error_Builder::make( Non_Atomic_Widget_Validator::ERROR_CODE ) ->set_status( 422 ) ->set_message( __( 'Components require atomic elements only. Remove widgets to create this component.', 'elementor' ) ) ->set_meta( [ 'non_atomic_elements' => $non_atomic_result['non_atomic_elements'] ] ) ->build(); } $validation_errors = $items->map_with_keys( function ( $item ) { try { if ( isset( $item['settings'] ) ) { $this->parse_settings( $item['settings'] ); } } catch ( \Exception $e ) { return [ $item['uid'] => $e->getMessage() ]; } return [ $item['uid'] => null ]; } ) ->filter( fn( $value ) => null !== $value ); if ( ! $validation_errors->is_empty() ) { return Error_Builder::make( 'settings_validation_failed' ) ->set_status( 422 ) ->set_message( 'Settings validation failed: ' . json_encode( $validation_errors->all() ) ) ->build(); } return Response_Builder::make() ->set_status( 200 ) ->build(); } private function parse_settings( array $settings ): array { $result = []; if ( empty( $settings ) ) { return $result; } if ( isset( $settings['overridable_props'] ) ) { $parser = Component_Overridable_Props_Parser::make(); $overridable_props_result = $parser->parse( $settings['overridable_props'] ); if ( ! $overridable_props_result->is_valid() ) { throw new \Exception( esc_html( 'Validation failed for overridable_props: ' . $overridable_props_result->errors()->to_string() ) ); } $result['overridable_props'] = $overridable_props_result->unwrap(); } return $result; } private function route_wrapper( callable $cb ) { try { $response = $cb(); } catch ( \Exception $e ) { return Error_Builder::make( 'unexpected_error' ) ->set_message( __( 'Something went wrong', 'elementor' ) ) ->build(); } return $response; } private function get_insufficient_permissions_error( string $action ) { return Error_Builder::make( 'insufficient_permissions' ) ->set_status( 403 ) ->set_message( __( 'You do not have permission to perform this action.', 'elementor' ) ) ->set_meta( [ 'action' => $action, 'tier' => Components_Access_Controller::get_access_tier(), ] ) ->build(); } } components/documents/component-overridable-prop.php 0000644 00000004444 15252521350 0016725 0 ustar 00 <?php namespace Elementor\Modules\Components\Documents; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Component_Overridable_Prop { /** @var string */ public $override_key; /** @var string */ public $element_id; /** @var string */ public $el_type; /** @var string */ public $widget_type; /** @var string */ public $prop_key; /** @var string */ public $label; /** @var array{ $$type: string, value: mixed } */ public $origin_value; /** @var string */ public $group_id; /** @var ?array{ $el_type: string, $widget_type: string, $prop_key: string } */ public $origin_prop_fields = null; public function __construct( array $overridable_prop ) { $this->override_key = $overridable_prop['overrideKey']; $this->element_id = $overridable_prop['elementId']; $this->el_type = $overridable_prop['elType']; $this->widget_type = $overridable_prop['widgetType']; $this->prop_key = $overridable_prop['propKey']; $this->label = $overridable_prop['label']; $this->origin_value = $overridable_prop['originValue']; $this->group_id = $overridable_prop['groupId'] ?? null; if ( isset( $overridable_prop['originPropFields'] ) ) { $this->origin_prop_fields = [ 'el_type' => $overridable_prop['originPropFields']['elType'], 'widget_type' => $overridable_prop['originPropFields']['widgetType'], 'prop_key' => $overridable_prop['originPropFields']['propKey'], 'element_id' => $overridable_prop['originPropFields']['elementId'], ]; } } public static function make( array $overridable_prop ): self { return new self( $overridable_prop ); } public function to_associative_array(): array { $result = [ 'overrideKey' => $this->override_key, 'elementId' => $this->element_id, 'elType' => $this->el_type, 'widgetType' => $this->widget_type, 'propKey' => $this->prop_key, 'label' => $this->label, 'originValue' => $this->origin_value, 'groupId' => $this->group_id, ]; if ( $this->origin_prop_fields ) { $result['originPropFields'] = [ 'elType' => $this->origin_prop_fields['el_type'], 'widgetType' => $this->origin_prop_fields['widget_type'], 'propKey' => $this->origin_prop_fields['prop_key'], 'elementId' => $this->origin_prop_fields['element_id'], ]; } return $result; } } components/documents/component-overridable-props.php 0000644 00000002355 15252521350 0017107 0 ustar 00 <?php namespace Elementor\Modules\Components\Documents; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Component_Overridable_Props { /** @var array{ [string]: Component_Overridable_Prop } */ public array $props; public array $groups; private function __construct( $overridable_props_meta ) { if ( is_string( $overridable_props_meta ) && ! empty( $overridable_props_meta ) ) { $overridable_props_meta = json_decode( $overridable_props_meta, true ); } if ( empty( $overridable_props_meta ) ) { $this->props = []; $this->groups = []; return; } $formatted_props = array_map( function( array $overridable_prop ) { return Component_Overridable_Prop::make( $overridable_prop ); }, $overridable_props_meta['props'] ?? [] ); $this->props = $formatted_props; $this->groups = $overridable_props_meta['groups'] ?? []; } public static function make( array $overridable_props_meta ): self { return new self( $overridable_props_meta ); } public function to_associative_array(): array { $props_map = []; foreach ( $this->props as $prop ) { $props_map[ $prop->override_key ] = $prop->to_associative_array(); } return [ 'props' => $props_map, 'groups' => $this->groups, ]; } } components/documents/component.php 0000644 00000014751 15252521350 0013455 0 ustar 00 <?php namespace Elementor\Modules\Components\Documents; use Elementor\Core\Base\Document; use Elementor\Core\Utils\Api\Parse_Result; use Elementor\Modules\Components\OverridableProps\Component_Overridable_Props_Parser; use Elementor\Modules\Components\PropTypes\Override_Prop_Type; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; use Elementor\Modules\Components\Widgets\Component_Instance; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Component extends Document { const TYPE = 'elementor_component'; const COMPONENT_UID_META_KEY = '_elementor_component_uid'; const OVERRIDABLE_PROPS_META_KEY = '_elementor_component_overridable_props'; const ARCHIVED_META_KEY = '_elementor_component_is_archived'; const ARCHIVED_AT_META_KEY = '_elementor_component_archived_at'; const COMPONENT_CUSTOM_META_KEYS = [ self::COMPONENT_UID_META_KEY, self::OVERRIDABLE_PROPS_META_KEY, self::ARCHIVED_META_KEY, self::ARCHIVED_AT_META_KEY, ]; public static function get_properties() { $properties = parent::get_properties(); $properties['cpt'] = [ self::TYPE ]; return $properties; } public static function get_type() { return self::TYPE; } public static function get_title() { return esc_html__( 'Component', 'elementor' ); } public static function get_plural_title() { return esc_html__( 'Components', 'elementor' ); } public static function get_labels(): array { $plural_label = static::get_plural_title(); $singular_label = static::get_title(); $labels = [ 'name' => $plural_label, 'singular_name' => $singular_label, ]; return $labels; } public static function get_supported_features(): array { return [ 'title', 'author', 'thumbnail', 'custom-fields', 'revisions', 'elementor', ]; } public function get_component_uid() { return $this->get_meta( self::COMPONENT_UID_META_KEY ); } public function get_overridable_props(): Component_Overridable_Props { $meta = $this->get_json_meta( self::OVERRIDABLE_PROPS_META_KEY ); return Component_Overridable_Props::make( $meta ?? [] ); } public function archive() { try { $this->update_json_meta( self::ARCHIVED_META_KEY, [ 'is_archived' => true, 'archived_at' => time(), ] ); } catch ( \Exception $e ) { throw new \Exception( 'Failed to archive component: ' . esc_html( $e->getMessage() ) ); } } public function get_is_archived(): bool { $archived_meta = $this->get_json_meta( self::ARCHIVED_META_KEY ); return $archived_meta['is_archived'] ?? false; } public function update_overridable_props( $data ): Parse_Result { $parser = Component_Overridable_Props_Parser::make(); $result = $parser->parse( $data ); if ( ! $result->is_valid() ) { return $result; } $sanitized_data = $result->unwrap(); $this->update_json_meta( self::OVERRIDABLE_PROPS_META_KEY, $sanitized_data ); return $result; } public function update_title( string $title ): bool { $sanitized_title = sanitize_text_field( $title ); if ( empty( $sanitized_title ) ) { return false; } return $this->update_post_field( 'post_title', $sanitized_title ); } public function update_status( string $status ): bool { if ( ! in_array( $status, [ Document::STATUS_PUBLISH, Document::STATUS_DRAFT, Document::STATUS_AUTOSAVE ], true ) ) { return false; } return $this->update_post_field( 'post_status', $status ); } private function update_post_field( string $field, $value ): bool { if ( is_string( $value ) ) { // NOTE: escape the json to support non-UTF-8 characters $value = wp_slash( $value ); } $result = wp_update_post( [ 'ID' => $this->post->ID, $field => $value, ] ); $success = ! is_wp_error( $result ) && $result > 0; if ( $success ) { $this->refresh_post(); } return $success; } public function print_elements_without_cache( array $elements_data ) { $this->do_print_elements( $elements_data ); } public function align_overridable_props_with_elements() { $elements_data = $this->get_elements_data(); // format elements data to flat map of overridable prop key -> new origin value $overridable_props_map = $this->get_elements_origin_values_map( $elements_data, [] ); if ( empty( $overridable_props_map ) ) { return; } $updated_overridable_props = $this->get_overridable_props(); foreach ( $updated_overridable_props->props as $prop ) { $new_origin_value = $overridable_props_map[ $prop->override_key ]; if ( isset( $new_origin_value ) ) { $prop->origin_value = $new_origin_value; } } $this->update_overridable_props( $updated_overridable_props->to_associative_array() ); } private function get_elements_origin_values_map( array $elements_data, array $overridable_props_map ) { foreach ( $elements_data as $element ) { if ( $this->is_component_instance( $element ) ) { $component_instance = $element['settings']['component_instance']['value']; $overrides = $component_instance['overrides']['value'] ?? []; if ( empty( $overrides ) ) { continue; } foreach ( $overrides as $item ) { if ( $this->is_overridable_prop( $item ) ) { $override_key = $item['value']['override_key']; $override = $item['value']['origin_value']; if ( ! $this->is_override_prop( $override ) ) { throw new \Exception( 'Invalid override value' ); } $overridable_props_map[ $override_key ] = $override['value']['override_value']; } } continue; } if ( ! empty( $element['settings'] ) ) { foreach ( $element['settings'] as $prop_key => $prop_value ) { if ( isset( $prop_value['$$type'] ) && Overridable_Prop_Type::get_key() === $prop_value['$$type'] ) { $override_key = $prop_value['value']['override_key']; $origin_value = $prop_value['value']['origin_value']; $overridable_props_map[ $override_key ] = $origin_value; } } } if ( is_array( $element['elements'] ) ) { $overridable_props_map = $this->get_elements_origin_values_map( $element['elements'], $overridable_props_map ); } } return $overridable_props_map; } private function is_overridable_prop( array $prop ): bool { return isset( $prop['$$type'] ) && Overridable_Prop_Type::get_key() === $prop['$$type']; } private function is_override_prop( array $prop ): bool { return isset( $prop['$$type'] ) && Override_Prop_Type::get_key() === $prop['$$type']; } private function is_component_instance( array $element ): bool { return 'widget' === $element['elType'] && Component_Instance::get_element_type() === $element['widgetType']; } } components/transformers/override-transformer.php 0000644 00000001005 15252521350 0016342 0 ustar 00 <?php namespace Elementor\Modules\Components\Transformers; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Override_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { return [ 'override_key' => $value['override_key'], 'override_value' => $value['override_value'], ]; } } components/transformers/component-instance-transformer.php 0000644 00000005510 15252521350 0020334 0 ustar 00 <?php namespace Elementor\Modules\Components\Transformers; use Elementor\Modules\AtomicWidgets\Elements\Base\Render_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; use Elementor\Plugin; use Elementor\Core\Base\Document as Component_Document; use Elementor\Modules\Components\Components_Repository; use Elementor\Modules\Components\Utils\Format_Component_Elements_Id; use Elementor\Modules\Components\Widgets\Component_Instance; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Component_Instance_Transformer extends Transformer_Base { private static array $rendering_stack = []; private static $repository; public static function reset_rendering_stack(): void { self::$rendering_stack = []; } public function transform( $value, Props_Resolver_Context $context ) { $component_id = $value['component_id']; if ( $this->is_circular_reference( $component_id ) ) { return ''; } $instance_element_id = Render_Context::get( Component_Instance::class )['instance_id'] ?? ''; self::$rendering_stack[] = $component_id; $content = $this->get_rendered_content( $component_id, $instance_element_id ); array_pop( self::$rendering_stack ); return $content; } private function is_circular_reference( int $component_id ): bool { return in_array( $component_id, self::$rendering_stack, true ); } private function get_rendered_content( int $component_id, ?string $instance_element_id ): string { $should_show_autosave = is_preview() || Plugin::$instance->editor->is_edit_mode(); $component = $this->get_repository()->get( $component_id, $should_show_autosave ); if ( ! $component || ! $this->should_render_content( $component ) ) { return ''; } Plugin::$instance->documents->switch_to_document( $component ); $data = $component->get_elements_data(); $data = apply_filters( 'elementor/frontend/builder_content_data', $data, $component_id ); $data = Format_Component_Elements_Id::format( $data, [ $instance_element_id ] ); $content = ''; if ( ! empty( $data ) ) { ob_start(); $component->print_elements_without_cache( $data ); $content = ob_get_clean(); $content = apply_filters( 'elementor/frontend/the_content', $content ); } Plugin::$instance->documents->restore_document(); return $content; } private function should_render_content( Component_Document $document ): bool { return ! $this->is_password_protected( $document ) && $document->is_built_with_elementor(); } private function is_password_protected( $document ) { return post_password_required( $document->get_post()->ID ); } private function get_repository(): Components_Repository { if ( ! self::$repository ) { self::$repository = new Components_Repository(); } return self::$repository; } } components/transformers/overridable-transformer.php 0000644 00000003240 15252521350 0017024 0 ustar 00 <?php namespace Elementor\Modules\Components\Transformers; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; use Elementor\Modules\AtomicWidgets\Elements\Base\Render_Context; use Elementor\Modules\Components\PropTypes\Override_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Overridable_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { [ 'override_key' => $override_key, 'origin_value' => $origin_value ] = $value; $result = $origin_value; $overrides = Render_Context::get( static::class )['overrides'] ?? []; if ( isset( $overrides[ $override_key ] ) ) { $matching_override_value = $overrides[ $override_key ]; if ( $this->is_origin_value_override( $origin_value ) ) { $result = $this->transform_overridable_override( $origin_value, $matching_override_value, $context ); } else { $result = $matching_override_value; } } return $result; } private function is_origin_value_override( array $origin_value ): bool { return isset( $origin_value['$$type'] ) && Override_Prop_Type::get_key() === $origin_value['$$type']; } private function transform_overridable_override( array $inner_override, array $outer_override_value, Props_Resolver_Context $context ): ?array { $override_transformer = new Override_Transformer(); $transformed_inner_override = $override_transformer->transform( $inner_override['value'], $context ); return [ 'override_key' => $transformed_inner_override['override_key'], 'override_value' => $outer_override_value, ]; } } components/overridable-props/overridable-groups-parser.php 0000644 00000013421 15252521350 0020207 0 ustar 00 <?php namespace Elementor\Modules\Components\OverridableProps; use Elementor\Core\Utils\Api\Parse_Result; use Elementor\Core\Utils\Collection; use Elementor\Modules\Components\Utils\Parsing_Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Overridable_Groups_Parser { public static function make(): self { return new static(); } public function parse( array $groups ): Parse_Result { $result = Parse_Result::make(); $structure_validation_result = $this->validate_structure( $groups ); if ( ! $structure_validation_result->is_valid() ) { return $structure_validation_result; } $parsed_groups = $this->parse_groups_items( $groups['items'] ); if ( ! $parsed_groups->is_valid() ) { $result->errors()->merge( $parsed_groups->errors() ); return $result; } $parsed_order = $this->parse_groups_order( $groups['order'] ); if ( ! $parsed_order->is_valid() ) { $result->errors()->merge( $parsed_order->errors() ); return $result; } $validation_result = $this->validate( $parsed_groups->unwrap(), $parsed_order->unwrap() ); if ( ! $validation_result->is_valid() ) { return $validation_result; } $sanitized_groups = $this->sanitize( $parsed_groups->unwrap(), $parsed_order->unwrap() ); return Parse_Result::make()->wrap( $sanitized_groups ); } private function validate_structure( array $groups ): Parse_Result { $result = Parse_Result::make(); $inner_fields = [ 'items', 'order' ]; foreach ( $inner_fields as $inner_field ) { if ( ! isset( $groups[ $inner_field ] ) ) { $result->errors()->add( "groups.$inner_field", 'missing' ); return $result; } if ( ! is_array( $groups[ $inner_field ] ) ) { $result->errors()->add( "groups.$inner_field", 'invalid_structure' ); return $result; } } foreach ( $groups['items'] as $group_id => $group ) { if ( ! is_array( $group ) ) { $result->errors()->add( "groups.items.$group_id", 'invalid_structure' ); continue; } $required_fields = [ 'id', 'label', 'props' ]; foreach ( $required_fields as $field ) { if ( ! isset( $group[ $field ] ) ) { $result->errors()->add( "groups.items.$group_id.$field", 'missing' ); } } if ( isset( $group['props'] ) && ! is_array( $group['props'] ) ) { $result->errors()->add( "groups.items.$group_id.props", 'invalid_structure' ); } } return $result; } private function validate( array $items, array $order ): Parse_Result { $result = Parse_Result::make(); $items_ids_collection = Collection::make( $items )->keys(); $order_collection = Collection::make( $order ); $excess_ids = $order_collection->diff( $items_ids_collection ); $missing_ids = $items_ids_collection->diff( $order_collection ); $excess_ids->each( fn( $id ) => $result->errors()->add( "groups.order.$id", 'excess' ) ); $missing_ids->each( fn( $id ) => $result->errors()->add( "groups.order.$id", 'missing' ) ); return $result; } private function parse_groups_items( array $items ): Parse_Result { $result = Parse_Result::make(); $validate_groups_items_result = $this->validate_groups_items( $items ); if ( ! $validate_groups_items_result->is_valid() ) { $result->errors()->merge( $validate_groups_items_result->errors() ); return $result; } return Parse_Result::make()->wrap( $this->sanitize_groups_items( $items ) ); } private function validate_groups_items( array $items ): Parse_Result { $result = Parse_Result::make(); $labels = []; foreach ( $items as $group_id => $group ) { if ( $group_id !== $group['id'] ) { $result->errors()->add( "groups.items.$group_id.id", 'mismatching_value' ); } $duplicate_props = Parsing_Utils::get_duplicates( $group['props'] ); if ( ! empty( $duplicate_props ) ) { $result->errors()->add( "groups.items.$group_id.props", 'duplicate_props: ' . implode( ', ', $duplicate_props ) ); } $labels[] = $group['label']; } $duplicate_labels = Parsing_Utils::get_duplicates( $labels ); if ( ! empty( $duplicate_labels ) ) { $result->errors()->add( 'groups.items', 'duplicate_labels: ' . implode( ', ', $duplicate_labels ) ); } return $result; } private function parse_groups_order( array $order ): Parse_Result { $result = Parse_Result::make(); $validate_groups_order_result = $this->validate_groups_order( $order ); if ( ! $validate_groups_order_result->is_valid() ) { return $validate_groups_order_result; } return Parse_Result::make()->wrap( $this->sanitize_groups_order( $order ) ); } private function validate_groups_order( array $order ): Parse_Result { $result = Parse_Result::make(); $order_collection = Collection::make( $order ); $non_string_items = $order_collection->some( fn( $item ) => ! is_string( $item ) ); if ( $non_string_items ) { $result->errors()->add( 'groups.order', 'non_string_items' ); return $result; } if ( Parsing_Utils::get_duplicates( $order ) ) { $result->errors()->add( 'groups.order', 'duplicate_ids' ); return $result; } return $result; } private function sanitize( array $items, array $order ): array { return [ 'items' => $items, 'order' => $order, ]; } private function sanitize_groups_items( array $items ): array { $sanitized_items = []; foreach ( $items as $group_id => $group ) { $sanitized_group_id = sanitize_key( $group_id ); $sanitized_items[ $sanitized_group_id ] = $this->sanitize_single_group( $group ); } return $sanitized_items; } private function sanitize_single_group( array $group ): array { return [ 'id' => sanitize_key( $group['id'] ), 'label' => sanitize_text_field( $group['label'] ), 'props' => array_map( 'sanitize_key', $group['props'] ), ]; } private function sanitize_groups_order( array $order ): array { return Collection::make( $order ) ->map( fn( $item ) => sanitize_key( $item ) ) ->values(); } } components/overridable-props/component-overridable-props-parser.php 0000644 00000011775 15252521350 0022045 0 ustar 00 <?php namespace Elementor\Modules\Components\OverridableProps; use Elementor\Core\Utils\Api\Parse_Result; use Elementor\Modules\Components\Utils\Parsing_Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Validates and sanitizes component overridable props object. * * Valid input example: * ``` * [ * 'props' => [ * 'prop1_UUID' => [ * 'overrideKey' => 'prop1_UUID', * 'label' => 'User Name', * 'elementId' => '90d25e3', * 'propKey' => 'title', * 'elType' => 'widget', * 'widgetType' => 'e-heading', * 'originValue' => [ * '$$type' => 'html', * 'value' => 'Jane Smith', * ], * 'groupId' => 'group1_UUID', * ], * ], * 'groups' => [ * 'items' => [ * 'group1_UUID' => [ * 'id' => 'group1_UUID', * 'label' => 'User Info', * 'props' => [ 'prop1_UUID' ], * ], * ], * 'order' => [ 'group1_UUID' ], * ], * ]; * ``` */ class Component_Overridable_Props_Parser { private Overridable_Props_Parser $props_parser; private Overridable_Groups_Parser $groups_parser; public function __construct( Overridable_Props_Parser $props_parser, Overridable_Groups_Parser $groups_parser ) { $this->props_parser = $props_parser; $this->groups_parser = $groups_parser; } public static function make(): self { return new static( Overridable_Props_Parser::make(), Overridable_Groups_Parser::make() ); } /** * @param array $data * * @return Parse_Result */ public function parse( array $data ): Parse_Result { $result = Parse_Result::make(); if ( empty( $data ) ) { return $result->wrap( [] ); } $inner_fields_structure_result = $this->validate_inner_fields_structure( $data ); if ( ! $inner_fields_structure_result->is_valid() ) { $result->errors()->merge( $inner_fields_structure_result->errors() ); return $result; } if ( empty( $data['props'] ) && empty( $data['groups'] ) ) { return $result->wrap( [] ); } $props_result = $this->props_parser->parse( $data['props'] ); if ( ! $props_result->is_valid() ) { $result->errors()->merge( $props_result->errors() ); return $result; } $groups_result = $this->groups_parser->parse( $data['groups'] ); if ( ! $groups_result->is_valid() ) { $result->errors()->merge( $groups_result->errors() ); return $result; } $parsed_props = $props_result->unwrap(); $parsed_groups = $groups_result->unwrap(); $validation_result = $this->validate( $parsed_props, $parsed_groups ); if ( ! $validation_result->is_valid() ) { $result->errors()->merge( $validation_result->errors() ); return $result; } return $this->sanitize( $parsed_props, $parsed_groups ); } private function validate_inner_fields_structure( array $data ): Parse_Result { $result = Parse_Result::make(); $inner_fields = [ 'props', 'groups' ]; foreach ( $inner_fields as $inner_field ) { if ( ! isset( $data[ $inner_field ] ) ) { $result->errors()->add( $inner_field, 'missing' ); return $result; } if ( ! is_array( $data[ $inner_field ] ) ) { $result->errors()->add( $inner_field, 'invalid_structure' ); return $result; } } return $result; } private function validate( array $props, array $groups ): Parse_Result { $result = Parse_Result::make(); $group_items = $groups['items']; $props_in_groups = []; foreach ( $group_items as $group_id => $group ) { foreach ( $group['props'] as $prop_id ) { if ( ! isset( $props[ $prop_id ] ) ) { $result->errors()->add( "groups.items.$group_id.props.$prop_id", 'prop_not_found_in_props' ); } else { $props_in_groups[ $prop_id ] = $group_id; } } } foreach ( $props as $prop_id => $prop ) { if ( ! isset( $props_in_groups[ $prop_id ] ) || $prop['groupId'] !== $props_in_groups[ $prop_id ] ) { $result->errors()->add( "props.$prop_id.groupId", 'mismatching_value_with_groups.items.props' ); } } $duplicate_labels_result = $this->check_duplicate_labels_within_groups( $group_items, $props ); if ( ! $duplicate_labels_result->is_valid() ) { $result->errors()->merge( $duplicate_labels_result->errors(), 'groups.items' ); } return $result; } private function sanitize( array $props, array $groups ): Parse_Result { return Parse_Result::make()->wrap( [ 'props' => $props, 'groups' => $groups, ] ); } private function check_duplicate_labels_within_groups( array $groups, array $props ): Parse_Result { $result = Parse_Result::make(); foreach ( $groups as $group_id => $group ) { $group_props = $group['props']; $labels = array_map( fn( $prop_id ) => $props[ $prop_id ]['label'], $group_props ); $duplicate_labels = Parsing_Utils::get_duplicates( $labels ); if ( ! empty( $duplicate_labels ) ) { $result->errors()->add( "$group_id.props", 'duplicate_labels: ' . implode( ', ', $duplicate_labels ) ); } } return $result; } } components/overridable-props/overridable-props-parser.php 0000644 00000004322 15252521350 0020033 0 ustar 00 <?php namespace Elementor\Modules\Components\OverridableProps; use Elementor\Core\Utils\Api\Parse_Result; use Elementor\Modules\Components\Utils\Parsing_Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Overridable_Props_Parser { private Overridable_Prop_Parser $prop_parser; public function __construct( Overridable_Prop_Parser $prop_parser ) { $this->prop_parser = $prop_parser; } public static function make(): self { return new static( Overridable_Prop_Parser::make() ); } public function parse( array $props ): Parse_Result { $parse_props_result = $this->parse_props( $props ); if ( ! $parse_props_result->is_valid() ) { return $parse_props_result; } $parsed_props = $parse_props_result->unwrap(); $validation_result = $this->validate( $parsed_props ); if ( ! $validation_result->is_valid() ) { return $validation_result; } return Parse_Result::make()->wrap( $parsed_props ); } private function parse_props( array $props ): Parse_Result { $result = Parse_Result::make(); $parsed_props = []; foreach ( $props as $prop_id => $prop ) { if ( ! is_array( $prop ) ) { $result->errors()->add( "props.$prop_id", 'invalid_structure' ); continue; } $prop_result = $this->prop_parser->parse( $prop ); if ( ! $prop_result->is_valid() ) { $result->errors()->merge( $prop_result->errors(), "props.$prop_id" ); continue; } $parsed_prop = $prop_result->unwrap(); $parsed_prop_id = sanitize_key( $prop_id ); if ( $parsed_prop_id != $parsed_prop['overrideKey'] ) { $result->errors()->add( "props.$parsed_prop_id", 'mismatching_override_key' ); continue; } $parsed_props[ $parsed_prop_id ] = $parsed_prop; } return $result->wrap( $parsed_props ); } private function validate( array $props ): Parse_Result { $result = Parse_Result::make(); $duplicate_prop_keys_for_same_element = Parsing_Utils::get_duplicates( array_map( fn( $prop ) => $prop['elementId'] . '.' . $prop['propKey'], $props ) ); if ( ! empty( $duplicate_prop_keys_for_same_element ) ) { $result->errors()->add( 'props', 'duplicate_prop_keys_for_same_element: ' . implode( ', ', $duplicate_prop_keys_for_same_element ) ); return $result; } return $result; } } components/overridable-props/overridable-prop-parser.php 0000644 00000007371 15252521350 0017657 0 ustar 00 <?php namespace Elementor\Modules\Components\OverridableProps; use Elementor\Modules\Components\PropTypes\Override_Prop_Type; use Elementor\Modules\Components\Utils\Parsing_Utils; use Elementor\Core\Utils\Api\Parse_Result; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Overridable_Prop_Parser { public static function make(): self { return new static(); } public function parse( array $prop ): Parse_Result { $validation_result = $this->validate( $prop ); if ( ! $validation_result->is_valid() ) { return $validation_result; } return $this->sanitize( $prop ); } private function validate( array $prop ): Parse_Result { $result = Parse_Result::make(); $required_fields = [ 'overrideKey', 'label', 'elementId', 'elType', 'widgetType', 'propKey', 'groupId', ]; foreach ( $required_fields as $field ) { if ( ! isset( $prop[ $field ] ) ) { $result->errors()->add( $field, 'missing_field' ); } } if ( ! $result->is_valid() ) { return $result; } $origin_value = $this->get_final_origin_value( $prop ); if ( ! empty( $origin_value ) ) { $origin_value_prop_type = $this->get_origin_prop_type( $prop ); if ( ! $origin_value_prop_type->validate( $origin_value ) ) { $result->errors()->add( 'originValue', 'invalid' ); return $result; } } return $result; } private function sanitize( array $prop ): Parse_Result { $result = Parse_Result::make(); $sanitized_origin_value = $this->get_sanitized_origin_value( $prop ); $sanitized_prop = [ 'overrideKey' => sanitize_key( $prop['overrideKey'] ), 'label' => sanitize_text_field( $prop['label'] ), 'elementId' => sanitize_key( $prop['elementId'] ), 'propKey' => sanitize_text_field( $prop['propKey'] ), 'widgetType' => sanitize_text_field( $prop['widgetType'] ), 'elType' => sanitize_text_field( $prop['elType'] ), 'originValue' => $sanitized_origin_value, 'groupId' => sanitize_key( $prop['groupId'] ), 'originPropFields' => isset( $prop['originPropFields'] ) ? [ 'elType' => sanitize_text_field( $prop['originPropFields']['elType'] ), 'widgetType' => sanitize_text_field( $prop['originPropFields']['widgetType'] ), 'propKey' => sanitize_text_field( $prop['originPropFields']['propKey'] ), 'elementId' => sanitize_key( $prop['originPropFields']['elementId'] ), ] : null, ]; return $result->wrap( $sanitized_prop ); } private function is_with_origin_prop_fields( array $prop ): bool { return ! empty( $prop['originPropFields'] ); } private function get_origin_prop_type( array $prop ) { if ( $this->is_with_origin_prop_fields( $prop ) ) { return $this->get_origin_prop_type( $prop['originPropFields'] ); } return Parsing_Utils::get_prop_type( $prop['elType'], $prop['widgetType'], $prop['propKey'], ); } private function get_final_origin_value( array $prop ) { if ( empty( $prop ) || empty( $prop['originValue'] ) ) { return null; } if ( isset( $prop['originValue']['$$type'] ) && Override_Prop_Type::get_key() === $prop['originValue']['$$type'] ) { return $prop['originValue']['value']['override_value']; } return $prop['originValue']; } private function get_sanitized_origin_value( array $prop ) { $origin_value = $this->get_final_origin_value( $prop ); $origin_prop_type = $this->get_origin_prop_type( $prop ); if ( ! empty( $origin_value ) ) { $sanitized_value = $origin_prop_type->sanitize( $origin_value ); if ( Override_Prop_Type::get_key() === $prop['originValue']['$$type'] ) { $raw_origin_value = $prop['originValue']; $raw_origin_value['value']['override_value'] = $sanitized_value; return $raw_origin_value; } return $sanitized_value; } return null; } } components/circular-dependency-validator.php 0000644 00000011020 15252521350 0015337 0 ustar 00 <?php namespace Elementor\Modules\Components; use Elementor\Core\Utils\Collection; use Elementor\Modules\Components\Documents\Component as Component_Document; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Circular_Dependency_Validator { const COMPONENT_WIDGET_TYPE = 'e-component'; const MAX_RECURSION_DEPTH = 50; private array $components_cache = []; public static function make(): Circular_Dependency_Validator { return new self(); } public function validate( $component_id, array $elements, array $unsaved_components = [] ): array { $inner_components_ids = $this->get_inner_component_ids( $elements ); if ( in_array( $component_id, $inner_components_ids, false ) ) { return $this->build_error_response( $component_id ); } foreach ( $inner_components_ids as $ref_id ) { if ( $this->is_component_eventually_contains( $ref_id, $component_id, $unsaved_components, [] ) ) { return $this->build_error_response( $component_id, $ref_id ); } } return [ 'success' => true, 'messages' => [], ]; } public function validate_new_components( Collection $items ): array { $unsaved_components = []; foreach ( $items->all() as $item ) { $unsaved_components[ $item['uid'] ] = $item['elements'] ?? []; } foreach ( $unsaved_components as $uid => $elements ) { $result = $this->validate( $uid, $elements, $unsaved_components ); if ( ! $result['success'] ) { return $result; } } return [ 'success' => true, 'messages' => [], ]; } private function is_component_eventually_contains( $component_id, $forbidden_id, array $unsaved_components, array $visited_path ): bool { if ( in_array( $component_id, $visited_path, false ) ) { return false; } if ( count( $visited_path ) >= self::MAX_RECURSION_DEPTH ) { return false; } $elements = $this->get_elements_for_component( $component_id, $unsaved_components ); if ( empty( $elements ) ) { return false; } $nested_ids = $this->get_inner_component_ids( $elements ); if ( in_array( $forbidden_id, $nested_ids, false ) ) { return true; } $visited_path[] = $component_id; foreach ( $nested_ids as $nested_id ) { if ( $this->is_component_eventually_contains( $nested_id, $forbidden_id, $unsaved_components, $visited_path ) ) { return true; } } return false; } private function get_elements_for_component( $component_id, array $unsaved_components ): array { if ( isset( $unsaved_components[ $component_id ] ) ) { return $unsaved_components[ $component_id ]; } return $this->get_component_elements( $component_id ); } private function get_component_elements( $component_id ): array { if ( ! is_int( $component_id ) ) { return []; } if ( isset( $this->components_cache[ $component_id ] ) ) { return $this->components_cache[ $component_id ]; } $doc = Plugin::$instance->documents->get( $component_id ); if ( ! $doc instanceof Component_Document ) { $this->components_cache[ $component_id ] = []; return []; } $elements = $doc->get_elements_data(); $this->components_cache[ $component_id ] = $elements; return $elements; } private function get_inner_component_ids( array $elements ): array { $ids = []; foreach ( $elements as $element ) { $widget_type = $element['widgetType'] ?? null; if ( self::COMPONENT_WIDGET_TYPE === $widget_type ) { $component_id = $this->extract_component_id_from_settings( $element['settings'] ?? [] ); if ( null !== $component_id ) { $ids[] = $component_id; } } if ( ! empty( $element['elements'] ) ) { $ids = array_merge( $ids, $this->get_inner_component_ids( $element['elements'] ) ); } } return array_unique( $ids, SORT_REGULAR ); } private function extract_component_id_from_settings( array $settings ) { return $settings['component_instance']['value']['component_id']['value'] ?? null; } private function build_error_response( $component_id, $via_component_id = null ): array { if ( null === $via_component_id ) { $message = sprintf( // translators: %s: Component ID that references itself. esc_html__( 'Circular dependency detected: Component "%s" references itself.', 'elementor' ), $component_id ); } else { $message = sprintf( // translators: %1$s: Component ID, %2$s: Component ID that creates the cycle. esc_html__( 'Circular dependency detected: Component "%1$s" would create a cycle via component "%2$s".', 'elementor' ), $component_id, $via_component_id ); } return [ 'success' => false, 'messages' => [ $message ], ]; } } components/save-components-validator.php 0000644 00000004546 15252521350 0014557 0 ustar 00 <?php namespace Elementor\Modules\Components; use Elementor\Core\Utils\Collection; use Elementor\Modules\Components\Documents\Component; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Save_Components_Validator { private Collection $components; public function __construct( Collection $components ) { $this->components = $components; } public static function make( Collection $components ) { return new static( $components ); } public function validate( Collection $data ) { $errors = Collection::make( [ $this->validate_count( $data ), $this->validate_duplicated_values( $data ), ] )->flatten(); if ( $errors->is_empty() ) { return [ 'success' => true, 'messages' => [], ]; } return [ 'success' => false, 'messages' => $errors->values(), ]; } private function validate_count( Collection $data ): array { $non_archived_components = $this->components->filter( fn ( $component ) => ! $component['is_archived'] ); $count = $non_archived_components->count() + $data->count(); if ( $count > Components_REST_API::MAX_COMPONENTS ) { return [ esc_html__( 'Maximum number of components exceeded.', 'elementor' ) ]; } return []; } private function validate_duplicated_values( Collection $data ): array { return $data ->map( function ( $component ) use ( $data ) { $errors = []; $title = $component['title']; $uid = $component['uid']; $is_title_exists = $this->components->some( fn ( $component ) => ! $component['is_archived'] && $component['title'] === $title ) || $data->filter( fn ( $component ) => ! $component['title'] === $title )->count() > 1; if ( $is_title_exists ) { $errors[] = [ sprintf( // translators: %s Component title. esc_html__( "Component title '%s' is duplicated.", 'elementor' ), $title ), ]; } $is_uid_exists = $this->components->some( fn ( $component ) => $component['uid'] === $uid ) || $data->filter( fn ( $component ) => $component['uid'] === $uid )->count() > 1; if ( $is_uid_exists ) { $errors[] = [ sprintf( // translators: %s Component uid. esc_html__( "Component uid '%s' is duplicated.", 'elementor' ), $uid ), ]; } return $errors; } ) ->flatten() ->flatten() ->unique() ->values(); } } components/component-lock-manager.php 0000644 00000007160 15252521350 0014006 0 ustar 00 <?php namespace Elementor\Modules\Components; use Elementor\Modules\Components\Documents\Component as Component_Document; use Elementor\Modules\Components\Document_Lock_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Component_Lock_Manager extends Document_Lock_Manager { const ONE_HOUR = 60 * 60; private static $instance = null; public function __construct() { parent::__construct( self::ONE_HOUR ); } public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } public function register_hooks() { add_filter( 'heartbeat_received', [ $this, 'heartbeat_received' ], 10, 2 ); } public function heartbeat_received( $response, $data ) { if ( ! isset( $data['elementor_post_lock']['post_ID'] ) ) { return $response; } $post_id = $data['elementor_post_lock']['post_ID']; if ( ! $this->is_component_post( $post_id ) ) { return $response; } $lock_data = $this->get_lock_data( $post_id ); $user_id = get_current_user_id(); if ( $user_id === (int) $lock_data['locked_by'] ) { $this->extend_lock( $post_id ); } return $response; } /** * Unlock a component. * * @param int $post_id The component ID to unlock * @return bool True if unlock was successful, false otherwise * @throws \Exception If post is not a component type. */ public function unlock( $post_id ) { if ( ! $this->is_component_post( $post_id ) ) { throw new \Exception( 'Post is not a component type' ); } $lock_data = $this->get_lock_data( $post_id ); $current_user_id = get_current_user_id(); if ( $lock_data['locked_by'] && (int) $lock_data['locked_by'] !== (int) $current_user_id ) { return false; } return parent::unlock( $post_id ); } /** * Lock a component. * * @param int $post_id The component ID to lock * @return bool|null True if lock was successful, null if locked by another user, false otherwise * @throws \Exception If post is not a component type. */ public function lock( $post_id ) { if ( ! $this->is_component_post( $post_id ) ) { throw new \Exception( 'Post is not a component type' ); } $lock_data = $this->get_lock_data( $post_id ); $is_expired = $this->is_lock_expired( $post_id ); if ( $is_expired ) { parent::unlock( $post_id ); } elseif ( $lock_data['locked_by'] ) { return null; } return parent::lock( $post_id ); } /** * Get lock data for a component. * * @param int $post_id The component ID * @return array Lock data with 'locked_by' (int|null), 'locked_at' (int|null) * @throws \Exception If post is not a component type. */ public function get_lock_data( $post_id ) { if ( ! $this->is_component_post( $post_id ) ) { throw new \Exception( 'Post is not a component type' ); } return parent::get_lock_data( $post_id ); } /** * Extend the lock for a component. * * @param int $post_id The component ID * @return bool|null True if extended successfully, null if not locked or locked by another user * @throws \Exception If post is not a component type. */ public function extend_lock( $post_id ) { if ( ! $this->is_component_post( $post_id ) ) { throw new \Exception( 'Post is not a component type' ); } $lock_data = $this->get_lock_data( $post_id ); if ( ! $lock_data['locked_by'] ) { return null; } $current_user_id = get_current_user_id(); if ( (int) $lock_data['locked_by'] !== (int) $current_user_id ) { return null; } return parent::extend_lock( $post_id ); } private function is_component_post( $post_id ) { return get_post_type( $post_id ) === Component_Document::TYPE; } } components/overridable-schema-extender.php 0000644 00000001637 15252521350 0015021 0 ustar 00 <?php namespace Elementor\Modules\Components; use Elementor\Modules\AtomicWidgets\PropTypes\Utils\Prop_Types_Schema_Extender; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type; use Elementor\Modules\GlobalClasses\Utils\Atomic_Elements_Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Overridable_Schema_Extender extends Prop_Types_Schema_Extender { public static function make(): self { return new static(); } protected function get_prop_types_to_add( Prop_Type $prop_type ): array { $is_ignore_overridable_applied = ! $prop_type->get_meta_item( Overridable_Prop_Type::META_KEY, true ); $is_classes_prop = Atomic_Elements_Utils::is_classes_prop( $prop_type ); if ( $is_ignore_overridable_applied || $is_classes_prop ) { return []; } return [ Overridable_Prop_Type::make()->set_origin_prop_type( $prop_type ) ]; } } admin-top-bar/module.php 0000644 00000007035 15252521350 0011201 0 ustar 00 <?php namespace Elementor\Modules\AdminTopBar; use Elementor\Core\Admin\Admin; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Plugin; use Elementor\Core\Base\App as BaseApp; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseApp { /** * @return bool */ public static function is_active() { return is_admin(); } /** * @return string */ public function get_name() { return 'admin-top-bar'; } private function render_admin_top_bar() { ?> <div id="e-admin-top-bar-root"> </div> <?php } /** * Enqueue admin scripts */ private function enqueue_scripts() { wp_enqueue_style( 'elementor-admin-top-bar-fonts', 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap', [], ELEMENTOR_VERSION ); wp_enqueue_style( 'elementor-admin-top-bar', $this->get_css_assets_url( 'admin-top-bar' ), [], ELEMENTOR_VERSION ); /** * Before admin top bar enqueue scripts. * * Fires before Elementor admin top bar scripts are enqueued. * * @since 3.19.0 */ do_action( 'elementor/admin_top_bar/before_enqueue_scripts', $this ); wp_enqueue_script( 'elementor-admin-top-bar', $this->get_js_assets_url( 'admin-top-bar' ), [ 'elementor-common', 'react', 'react-dom', 'tipsy', ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-admin-top-bar', 'elementor' ); $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_script( 'tipsy', ELEMENTOR_ASSETS_URL . 'lib/tipsy/tipsy' . $min_suffix . '.js', [ 'jquery', ], '1.0.0', true ); $this->print_config(); } private function add_frontend_settings() { $settings = []; $settings['is_administrator'] = current_user_can( 'manage_options' ); // TODO: Find a better way to add apps page url to the admin top bar. $settings['apps_url'] = admin_url( 'admin.php?page=elementor-apps' ); $settings['promotion'] = [ 'text' => __( 'Upgrade Now', 'elementor' ), 'url' => 'https://go.elementor.com/wp-dash-admin-top-bar-upgrade/', ]; $settings['promotion'] = Filtered_Promotions_Manager::get_filtered_promotion_data( $settings['promotion'], 'elementor/admin_top_bar/go_pro_promotion', 'url' ); $current_screen = get_current_screen(); /** @var \Elementor\Core\Common\Modules\Connect\Apps\Library $library */ $library = Plugin::$instance->common->get_component( 'connect' )->get_app( 'library' ); if ( $library ) { $settings = array_merge( $settings, [ 'is_user_connected' => $library->is_connected(), 'connect_url' => $library->get_admin_url( 'authorize', [ 'utm_source' => 'top-bar', 'utm_medium' => 'wp-dash', 'utm_campaign' => 'connect-account', 'utm_content' => $current_screen->id, 'source' => 'generic', ] ), ] ); } $this->set_settings( $settings ); do_action( 'elementor/admin-top-bar/init', $this ); } private function is_top_bar_active() { $current_screen = get_current_screen(); return apply_filters( 'elementor/admin-top-bar/is-active', Admin::is_elementor_admin_page( $current_screen ), $current_screen ); } /** * Module constructor. */ public function __construct() { parent::__construct(); add_action( 'current_screen', function () { if ( ! $this->is_top_bar_active() ) { return; } $this->add_frontend_settings(); add_action( 'in_admin_header', function () { $this->render_admin_top_bar(); } ); add_action( 'admin_enqueue_scripts', function () { $this->enqueue_scripts(); } ); } ); } } image-loading-optimization/module.php 0000644 00000025504 15252521350 0013771 0 ustar 00 <?php namespace Elementor\Modules\ImageLoadingOptimization; use Elementor\Core\Base\Module as BaseModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { /** * @var int Minimum square-pixels threshold. */ private $min_priority_img_pixels = 50000; /** * @var int The number of content media elements to not lazy-load. */ private $omit_threshold = 3; /** * @var array Keep a track of images for which loading optimization strategy were computed. */ private static $image_visited = []; /** * Get Module name. */ public function get_name() { return 'image-loading-optimization'; } /** * Constructor. */ public function __construct() { if ( ! static::is_optimized_image_loading_enabled() ) { return; } parent::__construct(); // Stop wp core logic. add_action( 'init', [ $this, 'stop_core_fetchpriority_high_logic' ] ); add_filter( 'wp_lazy_loading_enabled', '__return_false' ); // Run optimization logic on header. add_action( 'get_header', [ $this, 'set_buffer' ] ); // Ensure buffer is flushed (if any) before the content logic. add_filter( 'the_content', [ $this, 'flush_header_buffer' ], 0 ); // Run optimization logic on content. add_filter( 'wp_content_img_tag', [ $this, 'loading_optimization_image' ] ); } /** * Check whether the "Optimized Image Loading" settings is enabled. * * The 'optimized_image_loading' option can be enabled/disabled from the Elementor settings. * * @since 3.21.0 * @access private */ private static function is_optimized_image_loading_enabled(): bool { return '1' === get_option( 'elementor_optimized_image_loading', '1' ); } /** * Stop WordPress core fetchpriority logic by setting the wp_high_priority_element_flag flag to false. */ public function stop_core_fetchpriority_high_logic() { wp_high_priority_element_flag( false ); } /** * Set buffer to handle header and footer content. */ public function set_buffer() { ob_start( [ $this, 'handle_buffer_content' ] ); } /** * This function ensure that buffer if any is flushed before the content is called. * This function behaves more like an action than a filter. * * @param string $content the content. * @return string We simply return the content from parameter. */ public function flush_header_buffer( $content ) { $buffer_status = ob_get_status(); if ( ! empty( $buffer_status ) && 1 === $buffer_status['type'] && get_class( $this ) . '::handle_buffer_content' === $buffer_status['name'] ) { ob_end_flush(); } return $content; } /** * Callback to handle image optimization logic on buffered content. * * @param string $buffer Buffered content. * @return string Content with optimized images. */ public function handle_buffer_content( $buffer ) { return $this->filter_images( $buffer ); } /** * Check for image in the content provided and apply optimization logic on them. * * @param string $content Content to be analyzed. * @return string Content with optimized images. */ private function filter_images( $content ) { return preg_replace_callback( '/<img\s[^>]+>/', function ( $matches ) { return $this->loading_optimization_image( $matches[0] ); }, $content ); } /** * Apply loading optimization logic on the image. * * @param mixed $image Original image tag. * @return string Optimized image. */ public function loading_optimization_image( $image ) { if ( isset( self::$image_visited[ $image ] ) ) { return self::$image_visited[ $image ]; } $optimized_image = $this->add_loading_optimization_attrs( $image ); self::$image_visited[ $image ] = $optimized_image; return $optimized_image; } /** * Adds optimization attributes to an `img` HTML tag. * * @param string $image The HTML `img` tag where the attribute should be added. * @return string Converted `img` tag with optimization attributes added. */ private function add_loading_optimization_attrs( $image ) { $width = preg_match( '/ width=["\']([0-9]+)["\']/', $image, $match_width ) ? (int) $match_width[1] : null; $height = preg_match( '/ height=["\']([0-9]+)["\']/', $image, $match_height ) ? (int) $match_height[1] : null; $loading_val = preg_match( '/ loading=["\']([A-Za-z]+)["\']/', $image, $match_loading ) ? $match_loading[1] : null; $fetchpriority_val = preg_match( '/ fetchpriority=["\']([A-Za-z]+)["\']/', $image, $match_fetchpriority ) ? $match_fetchpriority[1] : null; // Images should have height and dimension width for the loading optimization attributes to be added. if ( ! str_contains( $image, ' width="' ) || ! str_contains( $image, ' height="' ) ) { return $image; } $optimization_attrs = $this->get_loading_optimization_attributes( [ 'width' => $width, 'height' => $height, 'loading' => $loading_val, 'fetchpriority' => $fetchpriority_val, ] ); if ( ! empty( $optimization_attrs['fetchpriority'] ) ) { $image = str_replace( '<img', '<img fetchpriority="' . esc_attr( $optimization_attrs['fetchpriority'] ) . '"', $image ); } if ( ! empty( $optimization_attrs['loading'] ) ) { $image = str_replace( '<img', '<img loading="' . esc_attr( $optimization_attrs['loading'] ) . '"', $image ); } return $image; } /** * Return loading Loading optimization attributes for a image with give attribute. * * @param array $attr Existing image attributes. * @return array Loading optimization attributes. */ private function get_loading_optimization_attributes( $attr ) { $loading_attrs = []; // For any resources, width and height must be provided, to avoid layout shifts. if ( ! isset( $attr['width'], $attr['height'] ) ) { return $loading_attrs; } /* * The key function logic starts here. */ $maybe_in_viewport = null; $increase_count = false; $maybe_increase_count = false; /* * Logic to handle a `loading` attribute that is already provided. * * Copied from `wp_get_loading_optimization_attributes()`. */ if ( isset( $attr['loading'] ) ) { /* * Interpret "lazy" as not in viewport. Any other value can be * interpreted as in viewport (realistically only "eager" or `false` * to force-omit the attribute are other potential values). */ if ( 'lazy' === $attr['loading'] ) { $maybe_in_viewport = false; } else { $maybe_in_viewport = true; } } // Logic to handle a `fetchpriority` attribute that is already provided. $has_fetchpriority_high_attr = ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ); /* * Handle cases where a `fetchpriority="high"` has already been set. * * Copied from `wp_get_loading_optimization_attributes()`. */ if ( $has_fetchpriority_high_attr ) { /* * If the image was already determined to not be in the viewport (e.g. * from an already provided `loading` attribute), trigger a warning. * Otherwise, the value can be interpreted as in viewport, since only * the most important in-viewport image should have `fetchpriority` set * to "high". */ if ( false === $maybe_in_viewport ) { _doing_it_wrong( __FUNCTION__, esc_html__( 'An image should not be lazy-loaded and marked as high priority at the same time.', 'elementor' ), '' ); /* * Set `fetchpriority` here for backward-compatibility as we should * not override what a developer decided, even though it seems * incorrect. */ $loading_attrs['fetchpriority'] = 'high'; } else { $maybe_in_viewport = true; } } if ( null === $maybe_in_viewport && ! is_admin() ) { $content_media_count = $this->increase_content_media_count( 0 ); $increase_count = true; if ( $content_media_count < $this->omit_threshold ) { $maybe_in_viewport = true; } else { $maybe_in_viewport = false; } } if ( $maybe_in_viewport ) { $loading_attrs = $this->maybe_add_fetchpriority_high_attr( $loading_attrs, $attr ); } else { $loading_attrs['loading'] = 'lazy'; } if ( $increase_count ) { $this->increase_content_media_count(); } elseif ( $maybe_increase_count ) { if ( $this->get_min_priority_img_pixels() <= $attr['width'] * $attr['height'] ) { $this->increase_content_media_count(); } } return $loading_attrs; } /** * Helper to get the minimum threshold for number of pixels an image needs to have to be considered "priority". * * @return int The minimum number of pixels (width * height). Default is 50000. */ private function get_min_priority_img_pixels() { /** * Filter the minimum pixel threshold used to determine if an image should have fetchpriority="high" applied. * * @see https://developer.wordpress.org/reference/hooks/wp_min_priority_img_pixels/ * * @param int $pixels The minimum number of pixels (with * height). * @return int The filtered value. */ return apply_filters( 'elementor/image-loading-optimization/min_priority_img_pixels', $this->min_priority_img_pixels ); } /** * Keeps a count of media image. * * @param int $amount Amount by which count must be increased. * @return int current image count. */ private function increase_content_media_count( $amount = 1 ) { static $content_media_count = 0; $content_media_count += $amount; return $content_media_count; } /** * Determines whether to add `fetchpriority='high'` to loading attributes. * * @param array $loading_attrs Array of the loading optimization attributes for the element. * @param array $attr Array of the attributes for the element. * @return array Updated loading optimization attributes for the element. */ private function maybe_add_fetchpriority_high_attr( $loading_attrs, $attr ) { if ( isset( $attr['fetchpriority'] ) ) { if ( 'high' === $attr['fetchpriority'] ) { $loading_attrs['fetchpriority'] = 'high'; $this->high_priority_element_flag( false ); } return $loading_attrs; } // Lazy-loading and `fetchpriority="high"` are mutually exclusive. if ( isset( $loading_attrs['loading'] ) && 'lazy' === $loading_attrs['loading'] ) { return $loading_attrs; } if ( ! $this->high_priority_element_flag() ) { return $loading_attrs; } if ( $this->get_min_priority_img_pixels() <= $attr['width'] * $attr['height'] ) { $loading_attrs['fetchpriority'] = 'high'; $this->high_priority_element_flag( false ); } return $loading_attrs; } /** * Accesses a flag that indicates if an element is a possible candidate for `fetchpriority='high'`. * * @param bool $value Optional. Used to change the static variable. Default null. * @return bool Returns true if high-priority element was marked already, otherwise false. */ private function high_priority_element_flag( $value = null ) { static $high_priority_element = true; if ( is_bool( $value ) ) { $high_priority_element = $value; } return $high_priority_element; } } web-cli/module.php 0000644 00000002165 15252521350 0010070 0 ustar 00 <?php namespace Elementor\Modules\WebCli; use Elementor\Core\Base\App; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends App { public function get_name() { return 'web-cli'; } public function __construct() { add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'elementor/frontend/after_register_scripts', [ $this, 'register_scripts' ] ); } public function register_scripts() { wp_register_script( 'elementor-web-cli', $this->get_js_assets_url( 'web-cli' ), [ 'elementor-vendors-redux', 'jquery', ], ELEMENTOR_VERSION, true ); $this->print_config( 'elementor-web-cli' ); } protected function get_init_settings() { return [ 'isDebug' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ), 'urls' => [ 'rest' => get_rest_url(), 'assets' => ELEMENTOR_ASSETS_URL, ], 'nonce' => wp_create_nonce( 'wp_rest' ), 'version' => ELEMENTOR_VERSION, ]; } } atomic-opt-in/opt-in-page.php 0000644 00000004443 15252521350 0012062 0 ustar 00 <?php namespace Elementor\Modules\AtomicOptIn; use Elementor\Plugin; use Elementor\Settings; use Elementor\User; use Elementor\Utils; class OptInPage { private Module $module; public function __construct( Module $module ) { $this->module = $module; } public function init() { if ( ! current_user_can( 'manage_options' ) ) { return; } $this->register_assets(); $this->add_settings_tab(); } private function register_assets() { $page_id = Settings::PAGE_ID; add_action( "elementor/admin/after_create_settings/{$page_id}", [ $this, 'enqueue_scripts' ] ); add_action( "elementor/admin/after_create_settings/{$page_id}", [ $this, 'enqueue_styles' ] ); } public function enqueue_styles() { wp_enqueue_style( Module::MODULE_NAME, $this->module->get_opt_in_css_assets_url( 'modules/editor-v4-opt-in/opt-in' ), [], ELEMENTOR_VERSION ); } public function enqueue_scripts() { $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_script( Module::MODULE_NAME, ELEMENTOR_ASSETS_URL . 'js/editor-v4-opt-in' . $min_suffix . '.js', [ 'react', 'react-dom', 'elementor-common', 'elementor-v2-ui', ], ELEMENTOR_VERSION, true ); wp_localize_script( Module::MODULE_NAME, 'elementorSettingsEditor4OptIn', $this->prepare_data() ); wp_set_script_translations( Module::MODULE_NAME, 'elementor' ); } private function prepare_data() { $create_new_post_type = User::is_current_user_can_edit_post_type( 'page' ) ? 'page' : 'post'; return [ 'features' => [ 'editor_v4' => $this->module->is_atomic_experiment_active(), ], 'urls' => [ 'start_building' => esc_url( Plugin::$instance->documents->get_create_new_post_url( $create_new_post_type ) ), ], ]; } private function add_settings_tab() { $page_id = Settings::PAGE_ID; add_action( "elementor/admin/after_create_settings/{$page_id}", function( Settings $settings ) { $this->add_new_tab_to( $settings ); }, 11 ); } private function add_new_tab_to( Settings $settings ) { $settings->add_tab( Module::MODULE_NAME, [ 'label' => esc_html__( 'Atomic Editor', 'elementor' ), 'sections' => [ 'opt-in' => [ 'callback' => function() { echo '<div id="page-editor-v4-opt-in"></div>'; }, 'fields' => [], ], ], ] ); } } atomic-opt-in/module.php 0000644 00000003004 15252521350 0011217 0 ustar 00 <?php namespace Elementor\Modules\AtomicOptIn; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Modules\AtomicWidgets\OptIn\Opt_In as Atomic_Widgets_Opt_In; use Elementor\Plugin; class Module extends BaseModule { const EXPERIMENT_NAME = 'e_opt_in_v4_page'; const MODULE_NAME = 'editor-v4-opt-in'; const WELCOME_POPOVER_DISPLAYED_OPTION = '_e_welcome_popover_displayed'; public function get_name() { return 'atomic-opt-in'; } public static function get_experimental_data(): array { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Editor v4 (Opt In Page)', 'elementor' ), 'description' => esc_html__( 'Enable the settings Opt In page', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_ACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_ALPHA, ]; } public function get_opt_in_css_assets_url( string $path ) { return $this->get_css_assets_url( $path ); } public function __construct() { ( new PanelChip() )->init(); if ( ! Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ) ) { return; } ( new Atomic_Widgets_Opt_In() )->init(); ( new OptInPage( $this ) )->init(); if ( ! $this->is_atomic_experiment_active() ) { return; } ( new WelcomeScreen() )->init(); } public function is_atomic_experiment_active(): bool { return Plugin::$instance->experiments->is_feature_active( Atomic_Widgets_Opt_In::EXPERIMENT_NAME ); } } atomic-opt-in/panel-chip.php 0000644 00000001201 15252521350 0011747 0 ustar 00 <?php namespace Elementor\Modules\AtomicOptIn; use Elementor\Utils; class PanelChip { public function init() { add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } public function enqueue_scripts() { $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_script( 'editor-v4-opt-in-alphachip', ELEMENTOR_ASSETS_URL . 'js/editor-v4-opt-in-alphachip' . $min_suffix . '.js', [ 'react', 'react-dom', 'elementor-common', 'elementor-v2-ui', ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'editor-v4-opt-in-alphachip', 'elementor' ); } } atomic-opt-in/welcome-screen.php 0000644 00000004733 15252521350 0012654 0 ustar 00 <?php namespace Elementor\Modules\AtomicOptIn; use Elementor\Core\Isolation\Elementor_Adapter; use Elementor\Core\Isolation\Elementor_Adapter_Interface; use Elementor\Core\Utils\Assets_Config_Provider; use Elementor\Modules\ElementorCounter\Module as Elementor_Counter; use Elementor\Core\Upgrade\Manager as Upgrade_Manager; use Elementor\Utils; class WelcomeScreen { const PACKAGE_NAME = 'v4-activation-modal'; private Elementor_Adapter_Interface $elementor_adapter; public function __construct() { $this->elementor_adapter = new Elementor_Adapter(); } public function init() { add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'maybe_enqueue_welcome_popover' ] ); } public function maybe_enqueue_welcome_popover(): void { if ( $this->is_first_or_second_editor_visit() ) { return; } if ( $this->has_welcome_popover_been_displayed() ) { return; } if ( Upgrade_Manager::is_new_installation() ) { return; } $this->register_package(); wp_enqueue_script( 'elementor-v2-' . self::PACKAGE_NAME ); wp_set_script_translations( 'elementor-v2-' . self::PACKAGE_NAME, 'elementor' ); $this->set_welcome_popover_as_displayed(); } private function is_first_or_second_editor_visit(): bool { if ( ! $this->elementor_adapter ) { return false; } $editor_visit_count = $this->elementor_adapter->get_count( Elementor_Counter::EDITOR_COUNTER_KEY ); return $editor_visit_count < 3; } private function has_welcome_popover_been_displayed(): bool { return get_user_meta( $this->get_current_user_id(), Module::WELCOME_POPOVER_DISPLAYED_OPTION, true ); } private function set_welcome_popover_as_displayed(): void { update_user_meta( $this->get_current_user_id(), Module::WELCOME_POPOVER_DISPLAYED_OPTION, true ); } private function register_package(): void { $min_suffix = Utils::is_script_debug() ? '' : '.min'; $package = self::PACKAGE_NAME; $assets_config_provider = ( new Assets_Config_Provider() ) ->set_path_resolver( function ( $name ) { return ELEMENTOR_ASSETS_PATH . "js/packages/{$name}/{$name}.asset.php"; } ); $config = $assets_config_provider->load( $package )->get( $package ); if ( ! $config ) { return; } wp_register_script( $config['handle'], ELEMENTOR_ASSETS_URL . "js/packages/{$package}/{$package}{$min_suffix}.js", $config['deps'], ELEMENTOR_VERSION, true ); } private function get_current_user_id(): int { $current_user = wp_get_current_user(); return $current_user->ID ?? 0; } } elements-color-picker/module.php 0000644 00000001723 15252521350 0012750 0 ustar 00 <?php namespace Elementor\Modules\ElementsColorPicker; use Elementor\Core\Experiments\Manager; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as Experiments_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { /** * Retrieve the module name. * * @return string */ public function get_name() { return 'elements-color-picker'; } /** * Enqueue the `Color-Thief` library to pick colors from images. * * @return void */ public function enqueue_scripts() { wp_enqueue_script( 'color-thief', $this->get_js_assets_url( 'color-thief', 'assets/lib/color-thief/', true ), [ 'elementor-editor' ], ELEMENTOR_VERSION, true ); } /** * Module constructor - Initialize the Eye-Dropper module. * * @return void */ public function __construct() { add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } } notifications/module.php 0000644 00000004330 15252521350 0011413 0 ustar 00 <?php namespace Elementor\Modules\Notifications; use Elementor\Core\Base\Module as BaseModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { public function get_name() { return 'notification-center'; } public function __construct() { parent::__construct(); add_action( 'elementor/admin_top_bar/before_enqueue_scripts', function() { if ( ! current_user_can( 'manage_options' ) ) { return; } wp_enqueue_script( 'e-admin-notifications', $this->get_js_assets_url( 'admin-notifications' ), [ 'elementor-v2-ui', 'elementor-v2-icons', 'elementor-v2-query', 'wp-i18n', ], ELEMENTOR_VERSION, true ); wp_localize_script( 'e-admin-notifications', 'elementorNotifications', $this->get_app_js_config() ); wp_set_script_translations( 'e-editor-notifications', 'elementor' ); }, 5 /* Before Elementor's admin enqueue scripts */ ); add_action( 'elementor/editor/v2/scripts/enqueue', [ $this, 'enqueue_editor_scripts' ] ); add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_editor_scripts' ] ); add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); } public function enqueue_editor_scripts() { $deps = [ 'elementor-editor', 'elementor-v2-ui', 'elementor-v2-icons', 'elementor-v2-query', 'elementor-v2-editor-app-bar', 'wp-i18n', ]; wp_enqueue_script( 'e-editor-notifications', $this->get_js_assets_url( 'editor-notifications' ), $deps, ELEMENTOR_VERSION, true ); wp_localize_script( 'e-editor-notifications', 'elementorNotifications', $this->get_app_js_config() ); wp_set_script_translations( 'e-editor-notifications', 'elementor' ); } private function get_app_js_config(): array { return [ 'is_unread' => Options::has_unread_notifications(), ]; } public function register_ajax_actions( $ajax ) { $ajax->register_ajax_action( 'notifications_get', [ $this, 'ajax_get_notifications' ] ); } public function ajax_get_notifications() { $notifications = API::get_notifications_by_conditions( true ); Options::mark_notification_read( $notifications ); return $notifications; } } notifications/options.php 0000644 00000003425 15252521350 0011625 0 ustar 00 <?php namespace Elementor\Modules\Notifications; class Options { public static function has_unread_notifications(): bool { $current_user = wp_get_current_user(); if ( ! $current_user ) { return false; } $unread_notifications = get_transient( "elementor_unread_notifications_{$current_user->ID}" ); if ( false === $unread_notifications ) { $notifications = API::get_notifications_by_conditions(); $notifications_ids = wp_list_pluck( $notifications, 'id' ); $unread_notifications = array_diff( $notifications_ids, static::get_notifications_dismissed() ); set_transient( "elementor_unread_notifications_{$current_user->ID}", $unread_notifications, HOUR_IN_SECONDS ); } return ! empty( $unread_notifications ); } public static function get_notifications_dismissed() { $current_user = wp_get_current_user(); if ( ! $current_user ) { return []; } $notifications_dismissed = get_user_meta( $current_user->ID, '_e_notifications_dismissed', true ); if ( ! is_array( $notifications_dismissed ) ) { $notifications_dismissed = []; } return $notifications_dismissed; } public static function mark_notification_read( $notifications ): bool { $current_user = wp_get_current_user(); if ( ! $current_user ) { return false; } $notifications_dismissed = static::get_notifications_dismissed(); foreach ( $notifications as $notification ) { if ( ! in_array( $notification['id'], $notifications_dismissed, true ) ) { $notifications_dismissed[] = $notification['id']; } } $notifications_dismissed = array_unique( $notifications_dismissed ); update_user_meta( $current_user->ID, '_e_notifications_dismissed', $notifications_dismissed ); delete_transient( "elementor_unread_notifications_{$current_user->ID}" ); return true; } } notifications/api.php 0000644 00000010350 15252521350 0010676 0 ustar 00 <?php namespace Elementor\Modules\Notifications; use Elementor\Includes\EditorAssetsAPI; use Elementor\User; class API { const NOTIFICATIONS_URL = 'https://assets.elementor.com/notifications/v1/notifications.json'; public static function get_notifications_by_conditions( $force_request = false ) { $notifications = static::get_notifications( $force_request ); $filtered_notifications = []; foreach ( $notifications as $notification ) { if ( empty( $notification['conditions'] ) ) { $filtered_notifications = static::add_to_array( $filtered_notifications, $notification ); continue; } if ( ! static::check_conditions( $notification['conditions'] ) ) { continue; } $filtered_notifications = static::add_to_array( $filtered_notifications, $notification ); } return $filtered_notifications; } private static function get_notifications( $force_request = false ) { $editor_assets_api = new EditorAssetsAPI( [ EditorAssetsAPI::ASSETS_DATA_URL => self::NOTIFICATIONS_URL, EditorAssetsAPI::ASSETS_DATA_TRANSIENT_KEY => '_elementor_notifications_data', EditorAssetsAPI::ASSETS_DATA_KEY => 'notifications', EditorAssetsAPI::ASSETS_DATA_EXPIRATION => '+12 hours', ] ); $notifications = $editor_assets_api->get_assets_data( $force_request ); $notifications = apply_filters( 'elementor/core/admin/notifications', $notifications ); return $notifications; } private static function add_to_array( $filtered_notifications, $notification ) { foreach ( $filtered_notifications as $filtered_notification ) { if ( $filtered_notification['id'] === $notification['id'] ) { return $filtered_notifications; } } $filtered_notifications[] = $notification; return $filtered_notifications; } private static function check_conditions( $groups ) { foreach ( $groups as $group ) { if ( static::check_group( $group ) ) { return true; } } return false; } private static function check_group( $group ) { $is_or_relation = ! empty( $group['relation'] ) && 'OR' === $group['relation']; unset( $group['relation'] ); $result = false; foreach ( $group as $condition ) { // Reset results for each condition. $result = false; switch ( $condition['type'] ) { case 'wordpress': // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText // include an unmodified $wp_version include ABSPATH . WPINC . '/version.php'; $result = version_compare( $wp_version, $condition['version'], $condition['operator'] ); break; case 'multisite': $result = is_multisite() === $condition['multisite']; break; case 'language': $in_array = in_array( get_locale(), $condition['languages'], true ); $result = 'in' === $condition['operator'] ? $in_array : ! $in_array; break; case 'plugin': if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $is_plugin_active = is_plugin_active( $condition['plugin'] ); if ( empty( $condition['operator'] ) ) { $condition['operator'] = '=='; } $result = '==' === $condition['operator'] ? $is_plugin_active : ! $is_plugin_active; break; case 'theme': $theme = wp_get_theme(); if ( wp_get_theme()->parent() ) { $theme = wp_get_theme()->parent(); } if ( $theme->get_template() === $condition['theme'] ) { $version = $theme->version; } else { $version = ''; } $result = version_compare( $version, $condition['version'], $condition['operator'] ); break; case 'introduction_meta': $result = User::get_introduction_meta( $condition['meta'] ); break; default: /** * Filters the notification condition, whether to check the group or not. * * The dynamic portion of the hook name, `$condition['type']`, refers to the condition type. * * @since 3.19.0 * * @param bool $result Whether to check the group. * @param array $condition Notification condition. */ $result = apply_filters( "elementor/notifications/condition/{$condition['type']}", $result, $condition ); break; } if ( ( $is_or_relation && $result ) || ( ! $is_or_relation && ! $result ) ) { return $result; } } return $result; } } admin-bar/module.php 0000644 00000010012 15252521350 0010366 0 ustar 00 <?php namespace Elementor\Modules\AdminBar; use Elementor\Core\Base\Document; use Elementor\Core\Base\App as BaseApp; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseApp { /** * @var Document[] */ private $documents = []; /** * @return bool */ public static function is_active() { return is_admin_bar_showing(); } /** * @return string */ public function get_name() { return 'admin-bar'; } /** * Collect the documents that was rendered in the current page. * * @param Document $document * @param $is_excerpt */ public function add_document_to_admin_bar( Document $document, $is_excerpt ) { if ( $is_excerpt || ! $document::get_property( 'show_on_admin_bar' ) || ! $document->is_editable_by_current_user() ) { return; } $this->documents[ $document->get_main_id() ] = $document; } /** * Scripts for module. */ public function enqueue_scripts() { if ( empty( $this->documents ) ) { return; } // Should load 'elementor-admin-bar' before 'admin-bar' wp_dequeue_script( 'admin-bar' ); wp_enqueue_script( 'elementor-admin-bar', $this->get_js_assets_url( 'elementor-admin-bar' ), [ 'elementor-frontend-modules' ], ELEMENTOR_VERSION, true ); // This is a core script of WordPress, it is not required to pass the 'ver' argument. // We should add dependencies to make sure that 'elementor-admin-bar' is loaded before 'admin-bar'. wp_enqueue_script( 'admin-bar', null, [ 'elementor-admin-bar' ], false, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters true ); $this->print_config( 'elementor-admin-bar' ); } /** * Creates admin bar menu items config. * * @return array */ public function get_init_settings() { $settings = []; if ( ! empty( $this->documents ) ) { $settings['elementor_edit_page'] = $this->get_edit_button_config(); } /** * Admin bar settings in the frontend. * * Register admin_bar config to parse later in the frontend and add to the admin bar with JS. * * @since 3.0.0 * * @param array $settings the admin_bar config */ $settings = apply_filters( 'elementor/frontend/admin_bar/settings', $settings ); return $settings; } /** * Creates the config for 'Edit with elementor' menu item. * * @return array */ private function get_edit_button_config() { $queried_object_id = get_queried_object_id(); $href = null; if ( is_singular() && isset( $this->documents[ $queried_object_id ] ) ) { $href = $this->documents[ $queried_object_id ]->get_edit_url(); unset( $this->documents[ $queried_object_id ] ); } return [ 'id' => 'elementor_edit_page', 'title' => esc_html__( 'Edit with Elementor', 'elementor' ), 'href' => $href, 'children' => array_map( function ( $document ) { return [ 'id' => "elementor_edit_doc_{$document->get_main_id()}", 'title' => $document->get_post()->post_title, 'sub_title' => $document::get_title(), 'href' => $document->get_edit_url(), ]; }, $this->documents ), ]; } public function add_clear_cache_in_admin_bar( $admin_bar_config ): array { if ( current_user_can( 'manage_options' ) ) { $clear_cache_url = add_query_arg( [ '_wpnonce' => wp_create_nonce( 'elementor_site_clear_cache' ), ], admin_url( 'admin-post.php?action=elementor_site_clear_cache' ), ); $admin_bar_config['elementor_edit_page']['children'][] = [ 'id' => 'elementor_site_clear_cache', 'title' => esc_html__( 'Clear Files & Data', 'elementor' ), 'sub_title' => esc_html__( 'Site', 'elementor' ), 'href' => $clear_cache_url, ]; } return $admin_bar_config; } /** * Module constructor. */ public function __construct() { add_action( 'elementor/frontend/before_get_builder_content', [ $this, 'add_document_to_admin_bar' ], 10, 2 ); add_action( 'wp_footer', [ $this, 'enqueue_scripts' ], 11 /* after third party scripts */ ); add_filter( 'elementor/frontend/admin_bar/settings', [ $this, 'add_clear_cache_in_admin_bar' ], 500 ); } } widget-creation/module.php 0000644 00000010171 15252521350 0011627 0 ustar 00 <?php namespace Elementor\Modules\WidgetCreation; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as ExperimentsManager; use Elementor\Core\Utils\Hints; use Elementor\Elements_Manager; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Module extends BaseModule { const MODULE_NAME = 'widget-creation'; const EXPERIMENT_NAME = 'e_widget_creation'; const PACKAGES = [ 'editor-widget-creation', ]; const ANGIE_CONSENT_OPTION = 'angie_external_scripts_consent'; public function get_name() { return self::MODULE_NAME; } public static function get_experimental_data(): array { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Widget Creation', 'elementor' ), 'description' => esc_html__( 'Promote widget creation with Angie plugin.', 'elementor' ), 'hidden' => true, 'default' => ExperimentsManager::STATE_ACTIVE, 'release_status' => ExperimentsManager::RELEASE_STATUS_ALPHA, ]; } public function __construct() { parent::__construct(); AngiePromotion::init(); add_filter( 'elementor/editor/v2/packages', fn( $packages ) => $this->add_packages( $packages ) ); add_action( 'elementor/elements/categories_registered', [ $this, 'maybe_register_custom_widgets_category_fallback' ], 100 ); add_action( 'rest_api_init', fn() => $this->register_consent_route() ); } private function register_consent_route(): void { register_rest_route( 'elementor/v1', '/angie/consent', [ 'methods' => 'POST', 'callback' => fn() => $this->handle_save_consent(), 'permission_callback' => fn() => current_user_can( 'manage_options' ), ] ); } private function handle_save_consent(): \WP_REST_Response { update_option( self::ANGIE_CONSENT_OPTION, 'yes' ); return new \WP_REST_Response( [ 'success' => true ], 200 ); } public function maybe_register_custom_widgets_category_fallback( Elements_Manager $elements_manager ): void { if ( ! Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ) ) { return; } if ( Hints::is_plugin_active( 'angie' ) ) { return; } $categories = $elements_manager->get_categories(); if ( isset( $categories[ Elements_Manager::CATEGORY_CUSTOM_WIDGETS ] ) ) { return; } $elements_manager->add_category( Elements_Manager::CATEGORY_CUSTOM_WIDGETS, [ 'title' => esc_html__( 'Custom Widget', 'elementor' ), 'icon' => 'eicon-ai', 'hideIfEmpty' => false, 'active' => true, ] ); add_action( 'elementor/editor/templates/panel/category', [ $this, 'render_custom_widgets_category_heading_cta' ] ); add_action( 'elementor/editor/templates/panel/category/content', [ $this, 'render_custom_widgets_category_empty_state' ] ); } public function render_custom_widgets_category_heading_cta(): void { if ( ! Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ) ) { return; } if ( ! current_user_can( 'manage_options' ) ) { return; } ?> <# if ( 'custom-widgets' === name ) { #> <button type="button" class="elementor-panel-custom-widgets__cta elementor-panel-custom-widgets__cta--heading"><?php echo esc_html__( 'Try for free', 'elementor' ); ?></button> <# } #> <?php } public function render_custom_widgets_category_empty_state(): void { if ( ! Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ) ) { return; } if ( $this->custom_widgets_category_has_widgets() ) { return; } ?> <# if ( 'custom-widgets' === name ) { #> <div class="elementor-panel-category-custom-widgets-empty"> <p class="elementor-panel-category-custom-widgets-empty__message"><?php echo esc_html__( 'Create custom widgets by describing what you need.', 'elementor' ); ?></p> </div> <# } #> <?php } private function custom_widgets_category_has_widgets(): bool { foreach ( Plugin::$instance->widgets_manager->get_widget_types() as $widget ) { if ( in_array( Elements_Manager::CATEGORY_CUSTOM_WIDGETS, $widget->get_categories(), true ) ) { return true; } } return false; } private function add_packages( array $packages ): array { return array_merge( $packages, self::PACKAGES ); } } widget-creation/angie-promotion.php 0000644 00000004205 15252521350 0013452 0 ustar 00 <?php namespace Elementor\Modules\WidgetCreation; use Elementor\Core\Upgrade\Manager as Upgrade_Manager; use Elementor\Core\Utils\Hints; use Elementor\Modules\ElementorCounter\Module as Elementor_Counter; use Elementor\Plugin; use Elementor\User; if ( ! defined( 'ABSPATH' ) ) { exit; } class AngiePromotion { const ANGIE_GUIDE_AUTO_SHOWN_OPTION = 'elementor_angie_guide_auto_shown'; public static function init() { add_filter( 'elementor/editor/localize_settings', function ( $settings ) { if ( ! self::should_display_promotion() ) { return $settings; } $settings = self::register_for_new_site( $settings ); $settings = self::register_for_existing_site( $settings ); return $settings; }, 10 ); } private static function should_display_promotion(): bool { return ! Hints::is_plugin_active( 'angie' ); } private static function register_for_new_site( array $settings ): array { if ( ! Upgrade_Manager::is_new_installation() ) { return $settings; } if ( 'yes' === get_option( self::ANGIE_GUIDE_AUTO_SHOWN_OPTION ) ) { return $settings; } $editor_visit_count = Elementor_Counter::instance()->get_count( Elementor_Counter::EDITOR_COUNTER_KEY ); if ( $editor_visit_count <= 2 ) { return $settings; } update_option( self::ANGIE_GUIDE_AUTO_SHOWN_OPTION, 'yes' ); $settings['angie']['autoShow'] = true; return $settings; } private static function register_for_existing_site( array $settings ): array { if ( Upgrade_Manager::is_new_installation() ) { return $settings; } if ( 'yes' === get_option( self::ANGIE_GUIDE_AUTO_SHOWN_OPTION ) ) { return $settings; } $is_container_active = Plugin::$instance->experiments->is_feature_active( 'container' ); if ( $is_container_active ) { $is_atomic_active = Plugin::$instance->experiments->is_feature_active( 'e_atomic_elements' ); $is_promo_dismissed = ! empty( User::get_introduction_meta( 'atomic_elements_promo' ) ); if ( ! $is_atomic_active && ! $is_promo_dismissed ) { return $settings; } } update_option( self::ANGIE_GUIDE_AUTO_SHOWN_OPTION, 'yes' ); $settings['angie']['autoShow'] = true; return $settings; } } link-in-bio/base/widget-link-in-bio-base.php 0000644 00000126157 15252521350 0014614 0 ustar 00 <?php namespace Elementor\Modules\LinkInBio\Base; use Elementor\Controls_Manager; use Elementor\Core\Base\Providers\Social_Network_Provider; use Elementor\Core\Base\Traits\Shared_Widget_Controls_Trait; use Elementor\Group_Control_Background; use Elementor\Group_Control_Typography; use Elementor\Modules\LinkInBio\Classes\Render\Core_Render; use Elementor\Plugin; use Elementor\Repeater; use Elementor\Utils; use Elementor\Widget_Base; abstract class Widget_Link_In_Bio_Base extends Widget_Base { use Shared_Widget_Controls_Trait; public function get_group_name(): string { return 'link-in-bio'; } public function get_style_depends(): array { $widget_name = $this->get_name(); $style_depends = Plugin::$instance->experiments->is_feature_active( 'e_font_icon_svg' ) ? parent::get_style_depends() : [ 'elementor-icons-fa-solid', 'elementor-icons-fa-brands', 'elementor-icons-fa-regular' ]; $style_depends[] = 'widget-link-in-bio-base'; if ( 'link-in-bio' !== $widget_name ) { $style_depends[] = "widget-{$widget_name}"; } return $style_depends; } public static function get_configuration() { return [ 'content' => [ 'identity_section' => [ 'identity_image_style' => [ 'default' => 'profile', ], 'has_heading_text' => false, 'has_profile_image_controls' => false, ], 'bio_section' => [ 'title' => [ 'default' => esc_html__( 'Kitchen Chronicles', 'elementor' ), ], 'description' => [ 'default' => esc_html__( 'Join me on my journey to a healthier lifestyle', 'elementor' ), ], 'has_about_field' => false, ], 'icon_section' => [ 'has_text' => false, 'platform' => [ 'group-1' => [ Social_Network_Provider::EMAIL, Social_Network_Provider::TELEPHONE, Social_Network_Provider::MESSENGER, Social_Network_Provider::WAZE, Social_Network_Provider::WHATSAPP, ], 'limit' => 5, ], 'default' => [ [ 'icon_platform' => Social_Network_Provider::FACEBOOK, ], [ 'icon_platform' => Social_Network_Provider::INSTAGRAM, ], [ 'icon_platform' => Social_Network_Provider::TIKTOK, ], ], ], 'cta_section' => [ 'cta_max' => 0, 'cta_has_image' => false, 'cta_repeater_defaults' => [ [ 'cta_link_text' => esc_html__( 'Get Healthy', 'elementor' ), ], [ 'cta_link_text' => esc_html__( 'Top 10 Recipes', 'elementor' ), ], [ 'cta_link_text' => esc_html__( 'Meal Prep', 'elementor' ), ], [ 'cta_link_text' => esc_html__( 'Healthy Living Resources', 'elementor' ), ], ], ], 'image_links_section' => false, ], 'style' => [ 'identity_section' => [ 'has_profile_image_shape' => true, 'profile_image_max' => 115, 'cover_image_max' => 1000, ], 'cta_section' => [ 'has_dividers' => false, 'has_image_border' => false, 'has_link_type' => [ 'default' => 'button', ], 'has_corners' => [ 'default' => 'rounded', 'options' => [ 'round' => esc_html__( 'Round', 'elementor' ), 'rounded' => esc_html__( 'Rounded', 'elementor' ), 'sharp' => esc_html__( 'Sharp', 'elementor' ), ], ], 'has_padding' => true, 'has_background_control' => true, 'has_cta_control_text' => false, 'has_border_control' => [ 'prefix' => 'cta_links', 'show_border_args' => [ 'condition' => [ 'cta_links_type' => 'button', ], ], 'border_width_args' => [ 'condition' => [ 'cta_links_type' => 'button', ], 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-ctas-border-width: {{SIZE}}{{UNIT}}', ], ], 'border_color_args' => [ 'condition' => [ 'cta_links_type' => 'button', ], 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-ctas-border-color: {{VALUE}}', ], ], ], ], 'border_section' => [ 'field_options' => false, 'overlay_field_options' => false, ], 'image_links_section' => false, ], ]; } public function get_description_position() { return 'top'; } public function get_icon(): string { return 'eicon-site-identity'; } public function get_categories(): array { return [ 'link-in-bio' ]; } public function get_keywords(): array { return [ 'buttons', 'bio', 'widget', 'link in bio' ]; } public function get_image_position_options(): array { return [ '' => esc_html__( 'Default', 'elementor' ), 'center center' => esc_html__( 'Center Center', 'elementor' ), 'center left' => esc_html__( 'Center Left', 'elementor' ), 'center right' => esc_html__( 'Center Right', 'elementor' ), 'top center' => esc_html__( 'Top Center', 'elementor' ), 'top left' => esc_html__( 'Top Left', 'elementor' ), 'top right' => esc_html__( 'Top Right', 'elementor' ), 'bottom center' => esc_html__( 'Bottom Center', 'elementor' ), 'bottom left' => esc_html__( 'Bottom Left', 'elementor' ), 'bottom right' => esc_html__( 'Bottom Right', 'elementor' ), ]; } protected function register_controls(): void { $this->add_content_tab(); $this->add_style_tab(); } protected function render(): void { $render_strategy = new Core_Render( $this ); $render_strategy->render(); } protected function add_image_links_controls() { $config = static::get_configuration(); if ( empty( $config['content']['image_links_section'] ) ) { return; } $this->start_controls_section( 'image_links_section', [ 'label' => esc_html__( 'Image Links', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); if ( ! empty( $config['content']['image_links_section']['images_max'] ) ) { $this->add_control( 'image_links_alert', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => sprintf( /* translators: %s: Maximum number of images allowed. */ esc_html__( 'Add up to %s Images', 'elementor' ), '<b>' . $config['content']['image_links_section']['images_max'] . '</b>' ), ] ); } $this->add_icons_per_row_control( 'image_links_per_row', [ '1' => '1', '2' => '2', '3' => '3', ], '2', esc_html__( 'Images Per Row', 'elementor' ), '--e-link-in-bio-image-links-columns', ); $repeater = new Repeater(); $repeater->add_control( 'image_links_image', [ 'label' => esc_html__( 'Choose Image', 'elementor' ), 'type' => Controls_Manager::MEDIA, 'label_block' => true, 'default' => [ 'url' => Utils::get_placeholder_image_src(), ], ] ); $repeater->add_control( 'image_links_url', [ 'label' => esc_html__( 'Link', 'elementor' ), 'type' => Controls_Manager::URL, 'dynamic' => [ 'active' => true, ], 'autocomplete' => true, 'label_block' => true, 'placeholder' => esc_html__( 'Paste URL or type', 'elementor' ), 'default' => [ 'is_external' => true, ], ], ); $this->add_control( 'image_links', [ 'type' => Controls_Manager::REPEATER, 'max_items' => $config['content']['image_links_section']['images_max'] ?? 0, 'fields' => $repeater->get_controls(), 'prevent_empty' => true, 'button_text' => esc_html__( 'Add item', 'elementor' ), 'default' => $config['content']['image_links_section']['images_repeater_defaults'] ?? [], ] ); $this->end_controls_section(); } protected function add_cta_controls() { $config = static::get_configuration(); if ( empty( $config['content']['cta_section'] ) ) { return; } $this->start_controls_section( 'cta_section', [ 'label' => esc_html__( 'CTA Link Buttons', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); if ( ! empty( $config['content']['cta_section']['cta_max'] ) ) { $this->add_control( 'cta_section_alert', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => sprintf( /* translators: %s: Maximum number of CTA links allowed. */ esc_html__( 'Add up to %s CTA links', 'elementor' ), '<b>' . $config['content']['cta_section']['cta_max'] . '</b>' ), ] ); } $repeater = new Repeater(); $repeater->add_control( 'cta_link_text', [ 'label' => esc_html__( 'Text', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'default' => esc_html__( 'CTA link', 'elementor' ), 'placeholder' => esc_html__( 'Enter link text', 'elementor' ), ], ); if ( $config['content']['cta_section']['cta_has_image'] ) { $repeater->add_control( 'cta_link_image', [ 'label' => esc_html__( 'Choose Image', 'elementor' ), 'type' => Controls_Manager::MEDIA, 'label_block' => true, 'default' => [ 'url' => Utils::get_placeholder_image_src(), ], ] ); } $repeater->add_control( 'cta_link_type', [ 'label' => esc_html__( 'Link Type', 'elementor' ), 'type' => Controls_Manager::SELECT, 'groups' => [ [ 'label' => '', 'options' => Social_Network_Provider::get_social_networks_text( [ Social_Network_Provider::URL, Social_Network_Provider::FILE_DOWNLOAD, ] ), ], [ 'label' => ' --', 'options' => Social_Network_Provider::get_social_networks_text( [ Social_Network_Provider::EMAIL, Social_Network_Provider::TELEPHONE, Social_Network_Provider::MESSENGER, Social_Network_Provider::WAZE, Social_Network_Provider::WHATSAPP, ] ), ], ], 'default' => Social_Network_Provider::URL, ], ); $repeater->add_control( 'cta_link_file', [ 'label' => esc_html__( 'Choose File', 'elementor' ), 'type' => Controls_Manager::MEDIA, 'label_block' => true, 'media_type' => [ 'application/pdf' ], 'condition' => [ 'cta_link_type' => [ Social_Network_Provider::FILE_DOWNLOAD, ], ], 'ai' => [ 'active' => false, ], ], ); $repeater->add_control( 'cta_link_url', [ 'label' => esc_html__( 'Link', 'elementor' ), 'type' => Controls_Manager::URL, 'dynamic' => [ 'active' => true, ], 'autocomplete' => true, 'label_block' => true, 'condition' => [ 'cta_link_type' => [ Social_Network_Provider::URL, ], ], 'placeholder' => esc_html__( 'Enter your link', 'elementor' ), 'default' => [ 'is_external' => true, ], ], ); $repeater->add_control( 'cta_link_mail', [ 'label' => esc_html__( 'Mail', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'condition' => [ 'cta_link_type' => [ Social_Network_Provider::EMAIL, ], ], 'placeholder' => esc_html__( 'Enter your email', 'elementor' ), ], ); $repeater->add_control( 'cta_link_mail_subject', [ 'label' => esc_html__( 'Subject', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'condition' => [ 'cta_link_type' => [ Social_Network_Provider::EMAIL, ], ], 'placeholder' => esc_html__( 'Subject', 'elementor' ), ], ); $repeater->add_control( 'cta_link_mail_body', [ 'label' => esc_html__( 'Message', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'condition' => [ 'cta_link_type' => [ Social_Network_Provider::EMAIL, ], ], 'placeholder' => esc_html__( 'Message', 'elementor' ), ], ); $repeater->add_control( 'cta_link_number', [ 'label' => esc_html__( 'Number', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'condition' => [ 'cta_link_type' => [ Social_Network_Provider::TELEPHONE, Social_Network_Provider::WHATSAPP, ], ], 'placeholder' => esc_html__( 'Enter your number', 'elementor' ), ], ); $repeater->add_control( 'cta_link_location', [ 'label' => esc_html__( 'Location', 'elementor' ), 'type' => Controls_Manager::URL, 'dynamic' => [ 'active' => true, ], 'default' => [ 'is_external' => true, ], 'label_block' => true, 'condition' => [ 'cta_link_type' => [ Social_Network_Provider::WAZE, ], ], 'placeholder' => esc_html__( 'Paste Waze link', 'elementor' ), ], ); $repeater->add_control( 'cta_link_username', [ 'label' => esc_html__( 'Username', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'condition' => [ 'cta_link_type' => [ Social_Network_Provider::MESSENGER, ], ], 'placeholder' => esc_html__( 'Enter your username', 'elementor' ), ], ); $this->add_control( 'cta_link', [ 'type' => Controls_Manager::REPEATER, 'max_items' => $config['content']['cta_section']['cta_max'] ?? 0, 'fields' => $repeater->get_controls(), 'title_field' => '{{{ cta_link_text }}}', 'button_text' => esc_html__( 'Add CTA Link', 'elementor' ), 'default' => $config['content']['cta_section']['cta_repeater_defaults'], ] ); $this->end_controls_section(); } protected function add_icons_controls(): void { $config = static::get_configuration(); $this->start_controls_section( 'icons_section', [ 'label' => esc_html__( 'Icons', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); if ( $config['content']['icon_section']['platform']['limit'] ) { $this->add_control( 'custom_panel_alert', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => sprintf( /* translators: %s: Maximum number of icons allowed. */ esc_html__( 'Add up to %s icons', 'elementor' ), '<b>' . $config['content']['icon_section']['platform']['limit'] . '</b>' ), ] ); } $repeater = new Repeater(); if ( $config['content']['icon_section']['has_text'] ) { $repeater->add_control( 'icon_text', [ 'label' => esc_html__( 'Text', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'placeholder' => esc_html__( 'Enter icon text', 'elementor' ), ], ); } $repeater->add_control( 'icon_platform', [ 'label' => esc_html__( 'Platform', 'elementor' ), 'type' => Controls_Manager::SELECT, 'groups' => [ [ 'label' => '', 'options' => Social_Network_Provider::get_social_networks_text( $config['content']['icon_section']['platform']['group-1'] ), ], [ 'label' => ' --', 'options' => Social_Network_Provider::get_social_networks_text( [ Social_Network_Provider::FACEBOOK, Social_Network_Provider::INSTAGRAM, Social_Network_Provider::LINKEDIN, Social_Network_Provider::PINTEREST, Social_Network_Provider::TIKTOK, Social_Network_Provider::TWITTER, Social_Network_Provider::YOUTUBE, ] ), ], [ 'label' => ' --', 'options' => Social_Network_Provider::get_social_networks_text( [ Social_Network_Provider::APPLEMUSIC, Social_Network_Provider::BEHANCE, Social_Network_Provider::DRIBBBLE, Social_Network_Provider::SPOTIFY, Social_Network_Provider::SOUNDCLOUD, Social_Network_Provider::VIMEO, ] ), ], ], 'default' => Social_Network_Provider::FACEBOOK, ], ); $repeater->add_control( 'icon_url', [ 'label' => esc_html__( 'Link', 'elementor' ), 'type' => Controls_Manager::URL, 'dynamic' => [ 'active' => true, ], 'autocomplete' => true, 'label_block' => true, 'placeholder' => esc_html__( 'Enter your link', 'elementor' ), 'default' => [ 'is_external' => true, ], 'condition' => [ 'icon_platform' => [ Social_Network_Provider::VIMEO, Social_Network_Provider::FACEBOOK, Social_Network_Provider::SOUNDCLOUD, Social_Network_Provider::SPOTIFY, Social_Network_Provider::INSTAGRAM, Social_Network_Provider::LINKEDIN, Social_Network_Provider::PINTEREST, Social_Network_Provider::TIKTOK, Social_Network_Provider::TWITTER, Social_Network_Provider::YOUTUBE, Social_Network_Provider::APPLEMUSIC, Social_Network_Provider::BEHANCE, Social_Network_Provider::DRIBBBLE, Social_Network_Provider::SPOTIFY, Social_Network_Provider::SOUNDCLOUD, Social_Network_Provider::URL, ], ], ], ); $repeater->add_control( 'icon_mail', [ 'label' => esc_html__( 'Email', 'elementor' ), 'type' => Controls_Manager::TEXT, 'placeholder' => esc_html__( 'Enter your email', 'elementor' ), 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'condition' => [ 'icon_platform' => [ Social_Network_Provider::EMAIL, ], ], 'ai' => [ 'active' => false, ], ] ); $repeater->add_control( 'icon_mail_subject', [ 'label' => esc_html__( 'Subject', 'elementor' ), 'type' => Controls_Manager::TEXT, 'placeholder' => esc_html__( 'Subject', 'elementor' ), 'label_block' => true, 'condition' => [ 'icon_platform' => [ Social_Network_Provider::EMAIL, ], ], ] ); $repeater->add_control( 'icon_mail_body', [ 'label' => esc_html__( 'Message', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'placeholder' => esc_html__( 'Message', 'elementor' ), 'label_block' => true, 'condition' => [ 'icon_platform' => [ Social_Network_Provider::EMAIL, ], ], ] ); $repeater->add_control( 'icon_number', [ 'label' => esc_html__( 'Number', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'placeholder' => esc_html__( '+', 'elementor' ), 'condition' => [ 'icon_platform' => [ Social_Network_Provider::TELEPHONE, Social_Network_Provider::WHATSAPP, ], ], 'ai' => [ 'active' => false, ], ], ); $repeater->add_control( 'icon_location', [ 'label' => esc_html__( 'Location', 'elementor' ), 'type' => Controls_Manager::URL, 'dynamic' => [ 'active' => true, ], 'default' => [ 'is_external' => true, ], 'label_block' => true, 'placeholder' => esc_html__( 'Paste Waze link', 'elementor' ), 'condition' => [ 'icon_platform' => [ Social_Network_Provider::WAZE, ], ], 'ai' => [ 'active' => false, ], ], ); $repeater->add_control( 'icon_username', [ 'label' => esc_html__( 'Username', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'placeholder' => esc_html__( 'Enter your username', 'elementor' ), 'condition' => [ 'icon_platform' => [ Social_Network_Provider::MESSENGER, ], ], ], ); $this->add_control( 'icon', [ 'max_items' => $config['content']['icon_section']['platform']['limit'], 'type' => Controls_Manager::REPEATER, 'fields' => $repeater->get_controls(), 'title_field' => $this->get_icon_title_field(), 'prevent_empty' => true, 'button_text' => esc_html__( 'Add Icon', 'elementor' ), 'default' => $config['content']['icon_section']['default'], ] ); $this->end_controls_section(); } protected function get_icon_title_field(): string { $platform_icons_js = json_encode( Social_Network_Provider::get_social_networks_icons() ); return <<<JS <# elementor.helpers.enqueueIconFonts( 'fa-solid' ); elementor.helpers.enqueueIconFonts( 'fa-brands' ); const mapping = {$platform_icons_js}; #> <i class='{{{ mapping[icon_platform] }}}' ></i> {{{ icon_platform }}} JS; } protected function add_style_tab(): void { $this->add_style_identity_controls(); $this->add_style_bio_controls(); $this->add_style_icons_controls(); $this->add_style_cta_section(); $this->add_style_image_links_controls(); $this->add_style_background_controls(); } protected function add_bio_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'bio_section', [ 'label' => esc_html__( 'Bio', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); $this->add_control( 'bio_heading', [ 'label' => esc_html__( 'Heading', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'dynamic' => [ 'active' => true, ], 'placeholder' => esc_html__( 'Heading', 'elementor' ), 'default' => esc_html__( 'Sara Parker', 'elementor' ), ] ); $this->add_html_tag_control( 'bio_heading_tag', 'h2' ); $this->add_control( 'bio_title', [ 'label' => esc_html__( 'Title or Tagline', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'dynamic' => [ 'active' => true, ], 'placeholder' => esc_html__( 'Title', 'elementor' ), 'default' => $config['content']['bio_section']['title']['default'], ] ); $this->add_html_tag_control( 'bio_title_tag', 'h3' ); if ( $config['content']['bio_section']['has_about_field'] ) { $this->add_control( 'bio_about', [ 'label' => esc_html__( 'About Heading', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'dynamic' => [ 'active' => true, ], 'placeholder' => esc_html__( 'About', 'elementor' ), 'default' => esc_html__( 'About Me', 'elementor' ), ] ); $this->add_html_tag_control( 'bio_about_tag', 'h3' ); } $this->add_control( 'bio_description', [ 'label' => esc_html__( 'Description', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'dynamic' => [ 'active' => true, ], 'placeholder' => esc_html__( 'Description', 'elementor' ), 'default' => $config['content']['bio_section']['description']['default'], ] ); $this->end_controls_section(); } protected function add_identity_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'identity_section', [ 'label' => esc_html__( 'Identity', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); if ( $config['content']['identity_section']['has_profile_image_controls'] ) { $this->add_control( 'identity_heading_cover', [ 'label' => esc_html__( 'Cover', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'identity_image_cover', [ 'label' => esc_html__( 'Choose Image', 'elementor' ), 'type' => Controls_Manager::MEDIA, 'default' => [ 'url' => Utils::get_placeholder_image_src(), ], ] ); $this->add_responsive_control( 'identity_image_cover_position', [ 'label' => esc_html__( 'Position', 'elementor' ), 'type' => Controls_Manager::SELECT, 'desktop_default' => 'center center', 'tablet_default' => 'center center', 'mobile_default' => 'center center', 'options' => $this->get_image_position_options(), 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-identity-image-cover-position: {{VALUE}}', ], 'condition' => [ 'identity_image_cover[url]!' => '', ], ] ); } if ( $config['content']['identity_section']['has_heading_text'] ) { $this->add_control( 'identity_heading', [ 'label' => $config['content']['identity_section']['has_heading_text'], 'type' => Controls_Manager::HEADING, ] ); } if ( $config['content']['identity_section']['identity_image_style'] ) { $this->add_control( 'identity_image_style', [ 'label' => esc_html__( 'Image style', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => $config['content']['identity_section']['identity_image_style']['default'], 'options' => [ 'profile' => esc_html__( 'Profile', 'elementor' ), 'cover' => esc_html__( 'Cover', 'elementor' ), ], ] ); } $this->add_control( 'identity_image', [ 'label' => esc_html__( 'Choose Image', 'elementor' ), 'type' => Controls_Manager::MEDIA, 'default' => [ 'url' => Utils::get_placeholder_image_src(), ], ] ); $this->add_responsive_control( 'identity_image_position', [ 'label' => esc_html__( 'Position', 'elementor' ), 'type' => Controls_Manager::SELECT, 'desktop_default' => 'center center', 'tablet_default' => 'center center', 'mobile_default' => 'center center', 'options' => $this->get_image_position_options(), 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-identity-image-profile-position: {{VALUE}}', ], 'condition' => [ 'identity_image[url]!' => '', ], ] ); $this->end_controls_section(); } protected function add_style_image_links_controls(): void { $config = static::get_configuration(); if ( empty( $config['style']['image_links_section'] ) ) { return; } $this->start_controls_section( 'image_links_section_style', [ 'label' => esc_html__( 'Image Links', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_responsive_control( 'image_links_height', [ 'label' => esc_html__( 'Image Height', 'elementor' ) . ' (px)', 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 300, 'step' => 1, ], ], 'default' => [ 'unit' => 'px', ], 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-image-links-height: {{SIZE}}{{UNIT}}', ], ] ); if ( $config['style']['image_links_section']['has_border_control'] ) { $this->add_borders_control( $config['style']['image_links_section']['has_border_control']['prefix'], $config['style']['image_links_section']['has_border_control']['show_border_args'], $config['style']['image_links_section']['has_border_control']['border_width_args'], $config['style']['image_links_section']['has_border_control']['border_color_args'], ); } $this->end_controls_section(); } protected function add_style_cta_section(): void { $config = static::get_configuration(); if ( empty( $config['style']['cta_section'] ) ) { return; } $this->start_controls_section( 'cta_links_section_style', [ 'label' => esc_html__( 'CTA Link Buttons', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); if ( $config['style']['cta_section']['has_cta_control_text'] ) { $this->add_control( 'cta_links_heading', [ 'label' => $config['style']['cta_section']['has_cta_control_text'], 'type' => Controls_Manager::HEADING, ] ); } if ( $config['style']['cta_section']['has_link_type'] ) { $this->add_control( 'cta_links_type', [ 'label' => esc_html__( 'Type', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => $config['style']['cta_section']['has_link_type']['default'], 'options' => [ 'button' => esc_html__( 'Button', 'elementor' ), 'link' => esc_html__( 'Link', 'elementor' ), ], ] ); } $this->add_control( 'cta_links_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-ctas-text-color: {{VALUE}}', '{{WRAPPER}} .e-link-in-bio__cta.is-type-link' => '--e-link-in-bio-ctas-text-color: {{VALUE}}', ], ] ); $condition_if_has_links = []; if ( $config['style']['cta_section']['has_link_type'] ) { $condition_if_has_links = [ 'cta_links_type' => 'button', ]; } if ( $config['style']['cta_section']['has_background_control'] ) { $this->add_control( 'cta_links_background_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'condition' => $condition_if_has_links, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-ctas-background-color: {{VALUE}}', ], ] ); } $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'cta_links_typography', 'selector' => '{{WRAPPER}} .e-link-in-bio__cta', ] ); if ( $config['style']['cta_section']['has_border_control'] ) { $this->add_borders_control( $config['style']['cta_section']['has_border_control']['prefix'], $config['style']['cta_section']['has_border_control']['show_border_args'], $config['style']['cta_section']['has_border_control']['border_width_args'], $config['style']['cta_section']['has_border_control']['border_color_args'], ); } if ( $config['style']['cta_section']['has_corners'] ) { $this->add_control( 'cta_links_corners', [ 'label' => esc_html__( 'Corners', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => $config['style']['cta_section']['has_corners']['default'], 'options' => $config['style']['cta_section']['has_corners']['options'], 'condition' => $condition_if_has_links, ] ); } if ( $config['style']['cta_section']['has_padding'] ) { $this->add_control( 'cta_links_hr', [ 'type' => Controls_Manager::DIVIDER, ] ); $this->add_responsive_control( 'cta_links_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem' ], 'default' => [ 'unit' => 'px', 'isLinked' => false, ], 'condition' => $condition_if_has_links, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-ctas-padding-block-end: {{BOTTOM}}{{UNIT}}; --e-link-in-bio-ctas-padding-block-start: {{TOP}}{{UNIT}}; --e-link-in-bio-ctas-padding-inline-end: {{RIGHT}}{{UNIT}}; --e-link-in-bio-ctas-padding-inline-start: {{LEFT}}{{UNIT}};', ], ] ); } if ( $config['style']['cta_section']['has_dividers'] ) { $this->add_control( 'cta_links_hr', [ 'type' => Controls_Manager::HEADING, 'label' => esc_html__( 'Dividers', 'elementor' ), 'separator' => 'before', ] ); $this->add_control( 'cta_links_divider_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio__cta' => 'border-bottom-color: {{VALUE}}', ], ] ); $this->add_control( 'cta_links_divider_width', [ 'label' => esc_html__( 'Weight', 'elementor' ) . ' (px)', 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 10, 'step' => 1, ], ], 'default' => [ 'unit' => 'px', ], 'selectors' => [ '{{WRAPPER}} .e-link-in-bio__cta' => 'border-bottom-width: {{SIZE}}{{UNIT}}', ], ] ); } $this->end_controls_section(); } protected function add_style_identity_controls(): void { $config = static::get_configuration(); $this->start_controls_section( 'identity_section_style', [ 'label' => esc_html__( 'Identity', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $condition = []; if ( $config['content']['identity_section']['identity_image_style'] ) { $condition = [ 'identity_image_style' => 'profile', ]; } $this->add_identity_image_profile_controls( $condition ); $condition = [ 'identity_image_style' => 'cover', ]; $this->add_identity_image_cover_control( $condition ); $this->end_controls_section(); } protected function add_content_tab(): void { $this->add_identity_section(); $this->add_bio_section(); $this->add_icons_controls(); $this->add_cta_controls(); $this->add_image_links_controls(); } protected function add_style_bio_controls(): void { $this->start_controls_section( 'bio_section_style', [ 'label' => esc_html__( 'Bio', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'bio_heading_heading', [ 'label' => esc_html__( 'Heading', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'bio_heading_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-heading-color: {{VALUE}}', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'bio_heading_typography', 'selector' => '{{WRAPPER}} .e-link-in-bio__heading', ] ); $this->add_control( 'bio_title_heading', [ 'label' => esc_html__( 'Title or Tagline', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'bio_title_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-title-color: {{VALUE}}', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'bio_title_typography', 'selector' => '{{WRAPPER}} .e-link-in-bio__title', ] ); $this->add_control( 'bio_description_heading', [ 'label' => esc_html__( 'Description', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'bio_description_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-description-color: {{VALUE}}', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'bio_description_typography', 'selector' => '{{WRAPPER}} .e-link-in-bio__description', ] ); $this->end_controls_section(); } protected function add_style_icons_controls(): void { $this->start_controls_section( 'icons_section_style', [ 'label' => esc_html__( 'Icons', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'icons_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-icon-color: {{VALUE}}', ], ] ); $this->add_control( 'icons_size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'small', 'options' => [ 'small' => esc_html__( 'Small', 'elementor' ), 'medium' => esc_html__( 'Medium', 'elementor' ), 'large' => esc_html__( 'Large', 'elementor' ), ], ] ); $this->end_controls_section(); } protected function add_style_background_controls(): void { $config = static::get_configuration(); // Defaults for background image and overlay $bg_section_image_field_option_defaults = [ 'background' => [ 'default' => 'classic', ], 'position' => [ 'default' => 'center center', ], 'size' => [ 'default' => 'cover', ], ]; // Background image $bg_image_field_options = $bg_section_image_field_option_defaults; if ( $config['style']['border_section']['field_options'] ) { $bg_image_field_options = array_merge( $bg_section_image_field_option_defaults, $config['style']['border_section']['field_options'] ); } // Background overlay $bg_overlay_image_field_options = $bg_section_image_field_option_defaults; if ( $config['style']['border_section']['overlay_field_options'] ) { $bg_overlay_image_field_options = array_merge( $bg_section_image_field_option_defaults, $config['style']['border_section']['overlay_field_options'] ); } $this->start_controls_section( 'background_border_section_style', [ 'label' => esc_html__( 'Box', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'background_border_background', [ 'label' => esc_html__( 'Background', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'background_border_background_group', 'types' => [ 'classic', 'gradient' ], 'selector' => '{{WRAPPER}} .e-link-in-bio__bg', 'fields_options' => $bg_image_field_options, ] ); $this->add_control( 'background_border_background_overlay', [ 'label' => esc_html__( 'Background Overlay', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'background_border_background_overlay_group', 'types' => [ 'classic', 'gradient' ], 'selector' => '{{WRAPPER}} .e-link-in-bio__bg-overlay', 'fields_options' => $bg_overlay_image_field_options, ] ); $this->add_responsive_control( 'background_overlay_opacity', [ 'label' => esc_html__( 'Opacity', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'max' => 1, 'min' => 0.10, 'step' => 0.01, ], ], 'default' => [ 'unit' => '%', 'size' => 0.5, ], 'condition' => [ 'background_border_background_overlay_group_background!' => '', ], 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--background-overlay-opacity: {{SIZE}};', ], ] ); $this->add_borders_control( 'background', [ 'selectors' => [], 'separator' => 'before', ], [ 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-border-width: {{SIZE}}{{UNIT}};', ], ], [ 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-border-color: {{VALUE}};', ], ] ); $this->add_control( 'background_dimensions', [ 'label' => esc_html__( 'Dimensions', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'advanced_layout_full_width_custom', [ 'label' => esc_html__( 'Full Width', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'default' => '', ] ); $this->add_responsive_control( 'advanced_layout_width', [ 'label' => esc_html__( 'Layout Width', 'elementor' ) . ' (px)', 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 500, 'step' => 1, ], ], 'default' => [ 'unit' => 'px', ], 'condition' => [ 'advanced_layout_full_width_custom' => '', ], 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-container-width: {{SIZE}}{{UNIT}};', ], ] ); $this->add_responsive_control( 'advanced_layout_content_width', [ 'label' => esc_html__( 'Content Width', 'elementor' ) . ' (px)', 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 400, 'step' => 1, ], ], 'default' => [ 'unit' => 'px', ], 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-content-width: {{SIZE}}{{UNIT}};', ], ] ); $this->add_control( 'advanced_layout_full_screen_height', [ 'label' => esc_html__( 'Full Screen Height', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'return_value' => 'yes', 'default' => '', 'condition' => [ 'advanced_layout_full_width_custom' => 'yes', ], ], ); $configured_breakpoints = $this->get_configured_breakpoints(); $this->add_control( 'advanced_layout_full_screen_height_controls', [ 'label' => esc_html__( 'Apply Full Screen Height on', 'elementor' ), 'type' => Controls_Manager::SELECT2, 'label_block' => true, 'multiple' => true, 'options' => $configured_breakpoints['devices_options'], 'default' => $configured_breakpoints['active_devices'], 'condition' => [ 'advanced_layout_full_width_custom' => 'yes', 'advanced_layout_full_screen_height' => 'yes', ], ] ); $this->end_controls_section(); } protected function add_identity_image_profile_controls( array $condition ): void { $config = static::get_configuration(); $this->add_responsive_control( 'identity_image_size', [ 'label' => esc_html__( 'Image Size', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => $config['style']['identity_section']['profile_image_max'] ?? 150, ], ], 'default' => [ 'unit' => 'px', ], 'condition' => $condition, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-identity-image-profile-width: {{SIZE}}{{UNIT}};', ], ] ); if ( $config['style']['identity_section']['has_profile_image_shape'] ) { $this->add_control( 'identity_image_shape', [ 'label' => esc_html__( 'Image Shape', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'circle', 'options' => [ 'circle' => esc_html__( 'Circle', 'elementor' ), 'square' => esc_html__( 'Square', 'elementor' ), ], 'condition' => $condition, ] ); } $this->add_borders_control( 'identity_image', [ 'condition' => $condition, ], [ 'condition' => $condition, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-identity-image-profile-border-width: {{SIZE}}{{UNIT}};', ], ], [ 'condition' => $condition, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-identity-image-profile-border-color: {{VALUE}};', ], ] ); } protected function add_identity_image_cover_control( array $condition ): void { $this->add_responsive_control( 'identity_image_height', [ 'label' => esc_html__( 'Image Height', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => $config['style']['identity_section']['cover_image_max'] ?? 1000, 'step' => 1, ], '%' => [ 'min' => 0, 'max' => 100, ], ], 'default' => [ 'unit' => 'px', ], 'condition' => $condition, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-identity-image-cover-height: {{SIZE}}{{UNIT}};', ], ] ); $this->add_borders_control( 'identity_image_bottom', [ 'condition' => $condition, 'label' => esc_html__( 'Bottom Border', 'elementor' ), ], [ 'condition' => $condition, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-identity-image-cover-border-bottom-width: {{SIZE}}{{UNIT}};', ], ], [ 'condition' => $condition, 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => '--e-link-in-bio-identity-image-cover-border-color: {{VALUE}};', ], ] ); } } link-in-bio/widgets/link-in-bio.php 0000644 00000001277 15252521350 0013152 0 ustar 00 <?php namespace Elementor\Modules\LinkInBio\Widgets; use Elementor\Modules\LinkInBio\Base\Widget_Link_In_Bio_Base; use Elementor\Modules\LinkInBio\Classes\Render\Core_Render; use Elementor\Modules\LinkInBio\Module as ConversionCenterModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Link in Bio widget. * * Elementor widget that displays an image, a bio, up to 4 CTA links and up to 5 icons. * * @since 3.23.0 */ class Link_In_Bio extends Widget_Link_In_Bio_Base { public function get_name(): string { return 'link-in-bio'; } public function get_title(): string { return esc_html__( 'Minimalist', 'elementor' ); } } link-in-bio/classes/render/render-base.php 0000644 00000053064 15252521350 0014500 0 ustar 00 <?php namespace Elementor\Modules\LinkInBio\Classes\Render; use Elementor\Core\Base\Providers\Social_Network_Provider; use Elementor\Core\Base\Traits\Shared_Widget_Controls_Trait; use Elementor\Icons_Manager; use Elementor\Modules\LinkInBio\Base\Widget_Link_In_Bio_Base; use Elementor\Utils; /** * Class Render_Base. * * This is the base class that will hold shared functionality that will be needed by all the various widget versions. * * @since 3.23.0 */ abstract class Render_Base { use Shared_Widget_Controls_Trait; protected Widget_Link_In_Bio_Base $widget; protected array $settings; abstract public function render(): void; public function __construct( Widget_Link_In_Bio_Base $widget ) { $this->widget = $widget; $this->settings = $widget->get_settings_for_display(); } protected function render_image_links(): void { $image_links_value_initial = $this->settings['image_links'] ?? []; $image_links_columns_value = $this->settings['image_links_per_row'] ?? 2; /** * If empty returns a sub-array with all empty values * Check for this here to avoid rendering container when empty */ $image_links_value = $this->clean_array( $image_links_value_initial ); $has_image_links = ! empty( $image_links_value ); if ( ! $has_image_links ) { return; } $image_links_classnames = 'e-link-in-bio__image-links'; if ( ! empty( $image_links_columns_value ) ) { $image_links_classnames .= ' has-' . $image_links_columns_value . '-columns'; } $this->widget->add_render_attribute( 'image-links', [ 'class' => $image_links_classnames, ] ); ?> <div <?php echo $this->widget->get_render_attribute_string( 'image-links' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php foreach ( $image_links_value as $key => $image_link ) { $formatted_link = $image_link['image_links_url']['url'] ?? ''; $image_link_image = $image_link['image_links_image'] ?? []; // Manage Link class variations $image_link_classnames = 'e-link-in-bio__image-links-link'; // Manage Link attributes $url_attrs = [ 'class' => $image_link_classnames, 'href' => esc_url( $formatted_link ), ]; $url_combined_attrs = $this->get_link_attributes( $image_link['image_links_url'], $url_attrs ); foreach ( $url_combined_attrs as $attr_key => $attr_value ) { $this->widget->add_render_attribute( 'image-links-link' . $key, [ $attr_key => $attr_value, ] ); } ?> <a <?php echo $this->widget->get_render_attribute_string( 'image-links-link' . $key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php if ( ! empty( $image_link_image['id'] ) ) { echo wp_get_attachment_image( $image_link_image['id'], 'thumbnail', false, [ 'class' => 'e-link-in-bio__image-links-img', ] ); } else { $this->widget->add_render_attribute( 'image-links-img-' . $key, [ 'alt' => '', 'class' => 'e-link-in-bio__image-links-img', 'src' => esc_url( $image_link_image['url'] ), ] ); ?> <img <?php echo $this->widget->get_render_attribute_string( 'image-links-img-' . $key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> /> <?php } ?> </a> <?php } ?> </div> <?php } protected function render_ctas(): void { $ctas_props_corners = $this->settings['cta_links_corners'] ?? 'rounded'; $ctas_props_show_border = $this->settings['cta_links_show_border'] ?? false; $ctas_props_type = $this->settings['cta_links_type'] ?? 'button'; $ctas_value_initial = $this->settings['cta_link'] ?? []; /** * $this->settings['cta_link'] if empty returns a sub-array with all empty values * Check for this here to avoid rendering container when empty */ $ctas_value = $this->clean_array( $ctas_value_initial ); $has_ctas = ! empty( $ctas_value ); if ( ! $has_ctas ) { return; } $this->widget->add_render_attribute( 'ctas', [ 'class' => 'e-link-in-bio__ctas has-type-' . $ctas_props_type, ] ); ?> <div <?php echo $this->widget->get_render_attribute_string( 'ctas' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php foreach ( $ctas_value as $key => $cta ) { $formatted_link = $this->get_formatted_link_based_on_type_for_cta( $cta ); $cta_image = $cta['cta_link_image'] ?? []; $cta_has_image = ! empty( $cta_image ) && ( ! empty( $cta_image['url'] || ! empty( $cta_image['id'] ) ) ) && 'button' === $ctas_props_type; // Manage Link class variations $ctas_classnames = 'e-link-in-bio__cta is-type-' . $ctas_props_type; if ( 'button' === $ctas_props_type && $ctas_props_show_border ) { $ctas_classnames .= ' has-border'; } if ( $cta_has_image ) { $ctas_classnames .= ' has-image'; } if ( 'button' === $ctas_props_type ) { $ctas_classnames .= ' has-corners-' . $ctas_props_corners; } // Manage Link attributes $url_attrs = [ 'class' => $ctas_classnames, 'href' => esc_url( $formatted_link ), ]; if ( Social_Network_Provider::FILE_DOWNLOAD === $cta['cta_link_type'] || Social_Network_Provider::VCF === $cta['cta_link_type'] ) { $url_attrs['download'] = 'download'; } $cta_url = $cta['cta_link_url']; if ( Social_Network_Provider::WAZE == $cta['cta_link_type'] ) { $cta_url = $cta['cta_link_location']; } $url_combined_attrs = $this->get_link_attributes( $cta_url, $url_attrs ); foreach ( $url_combined_attrs as $attr_key => $attr_value ) { $this->widget->add_render_attribute( 'cta-' . $key, [ $attr_key => $attr_value, ] ); } ?> <a <?php echo $this->widget->get_render_attribute_string( 'cta-' . $key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php if ( $cta_has_image ) : ?> <span class="e-link-in-bio__cta-image"> <?php if ( ! empty( $cta_image['id'] ) ) { echo wp_get_attachment_image( $cta_image['id'], 'thumbnail', false, [ 'class' => 'e-link-in-bio__cta-image-element', ] ); } else { $this->widget->add_render_attribute( 'cta-link-image' . $key, [ 'alt' => '', 'class' => 'e-link-in-bio__cta-image-element', 'src' => esc_url( $cta_image['url'] ), ] ); ?> <img <?php echo $this->widget->get_render_attribute_string( 'cta-link-image' . $key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> /> <?php } ?> </span> <?php endif; ?> <span class="e-link-in-bio__cta-text"> <?php echo esc_html( $cta['cta_link_text'] ); ?> </span> </a> <?php } ?> </div> <?php } protected function render_icons(): void { $icons_props_show_border = $this->settings['icons_border_show_border'] ?? false; $icons_props_size = $this->settings['icons_size'] ?? 'small'; $icons_value = $this->settings['icon'] ?? []; $has_icons = ! empty( $icons_value ); if ( ! $has_icons ) { return; } $this->widget->add_render_attribute( 'icons', [ 'class' => 'e-link-in-bio__icons has-size-' . $icons_props_size, ] ); ?> <div <?php echo $this->widget->get_render_attribute_string( 'icons' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php foreach ( $icons_value as $key => $icon ) { $formatted_link = $this->get_formatted_link_for_icon( $icon ); $icon_class_names = 'e-link-in-bio__icon is-size-' . $icons_props_size; if ( $icons_props_show_border ) { $icon_class_names .= ' has-border'; } $this->widget->add_render_attribute( 'icon-' . $key, [ 'class' => $icon_class_names, ] ); // Manage Link attributes $url_attrs = [ 'aria-label' => esc_attr( $icon['icon_platform'] ), 'class' => 'e-link-in-bio__icon-link', 'href' => esc_url( $formatted_link ), ]; $icon_url = $icon['icon_url']; if ( Social_Network_Provider::WAZE == $icon['icon_platform'] ) { $icon_url = $icon['icon_location']; } $url_combined_attrs = $this->get_link_attributes( $icon_url, $url_attrs ); foreach ( $url_combined_attrs as $attr_key => $attr_value ) { $this->widget->add_render_attribute( 'icon-link-' . $key, [ $attr_key => $attr_value, ] ); } ?> <div <?php echo $this->widget->get_render_attribute_string( 'icon-' . $key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <a <?php echo $this->widget->get_render_attribute_string( 'icon-link-' . $key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <span class="e-link-in-bio__icon-svg"> <?php $mapping = Social_Network_Provider::get_icon_mapping( $icon['icon_platform'] ); $icon_lib = explode( ' ', $mapping )[0]; $library = 'fab' === $icon_lib ? 'fa-brands' : 'fa-solid'; Icons_Manager::render_icon( [ 'library' => $library, 'value' => $mapping, ], [ 'aria-hidden' => 'true' ] ); ?> </span> <?php if ( ! empty( $icon['icon_text'] ) ) : ?> <span class="e-link-in-bio__icon-label"> <?php echo esc_html( $icon['icon_text'] ); ?> </span> <?php endif; ?> </a> </div> <?php } ?> </div> <?php } protected function render_bio(): void { $bio_heading_props_tag = $this->settings['bio_heading_tag'] ?? 'h2'; $bio_heading_value = $this->settings['bio_heading'] ?? ''; $bio_title_props_tag = $this->settings['bio_title_tag'] ?? 'h2'; $bio_title_value = $this->settings['bio_title'] ?? ''; if ( 'top' === $this->widget->get_description_position() ) { $bio_about_heading_props_tag = $this->settings['bio_about_tag'] ?? 'h3'; $bio_about_heading_value = $this->settings['bio_about'] ?? ''; $bio_description_value = $this->settings['bio_description'] ?? ''; } $has_bio_about_heading = ! empty( $bio_about_heading_value ); $has_bio_description = ! empty( $bio_description_value ); $has_bio_heading = ! empty( $bio_heading_value ); $has_bio_title = ! empty( $bio_title_value ); if ( $has_bio_heading || $has_bio_title || $has_bio_about_heading || $has_bio_description ) { ?> <div class="e-link-in-bio__bio"> <?php if ( $has_bio_heading ) { $this->widget->add_render_attribute( 'heading', 'class', 'e-link-in-bio__heading' ); $bio_heading_output = sprintf( '<%1$s %2$s>%3$s</%1$s>', Utils::validate_html_tag( $bio_heading_props_tag ), $this->widget->get_render_attribute_string( 'heading' ), esc_html( $bio_heading_value ) ); // Escaped above Utils::print_unescaped_internal_string( $bio_heading_output ); } ?> <?php if ( $has_bio_title ) { $this->widget->add_render_attribute( 'title', 'class', 'e-link-in-bio__title' ); $bio_title_output = sprintf( '<%1$s %2$s>%3$s</%1$s>', Utils::validate_html_tag( $bio_title_props_tag ), $this->widget->get_render_attribute_string( 'title' ), esc_html( $bio_title_value ) ); // Escaped above Utils::print_unescaped_internal_string( $bio_title_output ); } ?> <?php if ( $has_bio_about_heading ) { $this->widget->add_render_attribute( 'about-heading', 'class', 'e-link-in-bio__about-heading' ); $bio_about_heading_output = sprintf( '<%1$s %2$s>%3$s</%1$s>', Utils::validate_html_tag( $bio_about_heading_props_tag ), $this->widget->get_render_attribute_string( 'about-heading' ), esc_html( $bio_about_heading_value ) ); // Escaped above Utils::print_unescaped_internal_string( $bio_about_heading_output ); } ?> <?php if ( $has_bio_description ) { $this->widget->add_render_attribute( 'description', 'class', 'e-link-in-bio__description' ); $bio_description_output = sprintf( '<p %1$s>%2$s</p>', $this->widget->get_render_attribute_string( 'description' ), esc_html( $bio_description_value ) ); // Escaped above Utils::print_unescaped_internal_string( $bio_description_output ); } ?> </div> <?php } } protected function render_footer_bio(): void { if ( 'bottom' !== $this->widget->get_description_position() ) { return; } $bio_about_heading_props_tag = $this->settings['bio_about_tag'] ?? 'h3'; $bio_about_heading_value = $this->settings['bio_about'] ?? ''; $bio_description_value = $this->settings['bio_description'] ?? ''; $has_bio_description = ! empty( $bio_description_value ); $has_bio_about_heading = ! empty( $bio_about_heading_value ); if ( $has_bio_about_heading || $has_bio_description ) { ?> <div class="e-link-in-bio__bio e-link-in-bio__bio--footer"> <?php if ( $has_bio_about_heading ) { $this->widget->add_render_attribute( 'about-heading', 'class', 'e-link-in-bio__about-heading' ); $bio_about_heading_output = sprintf( '<%1$s %2$s>%3$s</%1$s>', Utils::validate_html_tag( $bio_about_heading_props_tag ), $this->widget->get_render_attribute_string( 'about-heading' ), esc_html( $bio_about_heading_value ) ); // Escaped above Utils::print_unescaped_internal_string( $bio_about_heading_output ); } ?> <?php if ( $has_bio_description ) { $this->widget->add_render_attribute( 'description', 'class', 'e-link-in-bio__description' ); $bio_description_output = sprintf( '<p %1$s>%2$s</p>', $this->widget->get_render_attribute_string( 'description' ), esc_html( $bio_description_value ) ); // Escaped above Utils::print_unescaped_internal_string( $bio_description_output ); } ?> </div> <?php } } protected function render_identity_image(): void { /** * Get base data for potential images * Note order is important - secondary must render before primary */ $output_images = [ 'secondary_image' => [ 'props' => [], 'should_render' => false, 'value' => $this->settings['identity_image_cover'] ?? [], ], 'primary_image' => [ 'props' => [], 'should_render' => false, 'value' => $this->settings['identity_image'] ?? [], ], ]; $output_images['primary_image']['should_render'] = ! empty( $output_images['primary_image']['value'] ) && ( ! empty( $output_images['primary_image']['value']['url'] || ! empty( $output_images['primary_image']['value']['id'] ) ) ); $output_images['secondary_image']['should_render'] = ! empty( $output_images['secondary_image']['value'] ) && ( ! empty( $output_images['secondary_image']['value']['url'] || ! empty( $output_images['secondary_image']['value']['id'] ) ) ); if ( ! $output_images['primary_image']['should_render'] && ! $output_images['secondary_image']['should_render'] ) { return; } $output_images = $this->set_primary_image_properties( $output_images ); $output_images = $this->set_secondary_image_properties( $output_images ); ?> <div class="e-link-in-bio__identity"> <?php foreach ( $output_images as $image_key => $image ) : if ( $image['should_render'] ) : $this->widget->add_render_attribute( 'identity_image_' . $image_key, [ 'class' => $this->get_image_classnames( $image ), ] ); ?> <div <?php echo $this->widget->get_render_attribute_string( 'identity_image_' . $image_key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php if ( ! empty( $image['value']['id'] ) ) { echo wp_get_attachment_image( $image['value']['id'], 'medium', false, [ 'class' => 'e-link-in-bio__identity-image-element', ] ); } else { $this->widget->add_render_attribute( 'identity_image_src' . $image_key, [ 'alt' => '', 'class' => 'e-link-in-bio__identity-image-element', 'src' => esc_url( $image['value']['url'] ), ] ); ?> <img <?php echo $this->widget->get_render_attribute_string( 'identity_image_src' . $image_key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> /> <?php } ?> <?php if ( ! empty( $image['props']['has_shape_divider'] ) ) { $this->print_shape_divider(); } ?> </div> <?php endif; endforeach; ?> </div> <?php } protected function get_image_classnames( array $image ): string { $image_classnames = 'e-link-in-bio__identity-image e-link-in-bio__identity-image-' . $image['props']['style']; if ( ! empty( $image['props']['show_border'] ) || ! empty( $image['props']['show_bottom_border'] ) ) { $image_classnames .= ' has-border'; } if ( ! empty( $image['props']['shape'] ) && 'profile' === $image['props']['style'] ) { $image_classnames .= ' has-style-' . $image['props']['shape']; } if ( ! empty( $image['props']['has_shape_divider'] ) ) { $image_classnames .= ' has-shape-divider'; } return $image_classnames; } protected function get_formatted_link_based_on_type_for_cta( array $cta ): string { $formatted_link = $cta['cta_link_url']['url'] ?? ''; // Ensure we clear the default link value if the matching type value is empty switch ( $cta['cta_link_type'] ) { case Social_Network_Provider::EMAIL: $formatted_link = Social_Network_Provider::build_email_link( $cta, 'cta_link' ); break; case Social_Network_Provider::TELEPHONE: $formatted_link = ! empty( $cta['cta_link_number'] ) ? 'tel:' . $cta['cta_link_number'] : ''; break; case Social_Network_Provider::MESSENGER: $formatted_link = ! empty( $cta['cta_link_username'] ) ? Social_Network_Provider::build_messenger_link( $cta['cta_link_username'] ) : ''; break; case Social_Network_Provider::WAZE: $formatted_link = ! empty( $cta['cta_link_location']['url'] ) ? $cta['cta_link_location']['url'] : ''; break; case Social_Network_Provider::WHATSAPP: $formatted_link = ! empty( $cta['cta_link_number'] ) ? 'https://wa.me/' . $cta['cta_link_number'] : ''; break; case Social_Network_Provider::FILE_DOWNLOAD: $formatted_link = ! empty( $cta['cta_link_file']['url'] ) ? $cta['cta_link_file']['url'] : ''; break; case Social_Network_Provider::VCF: $formatted_link = ! empty( $cta['cta_link_file']['url'] ) ? $cta['cta_link_file']['url'] : ''; break; default: break; } return $formatted_link; } protected function get_formatted_link_for_icon( array $icon ): string { $formatted_link = $icon['icon_url']['url'] ?? ''; // Ensure we clear the default link value if the matching type value is empty switch ( $icon['icon_platform'] ) { case Social_Network_Provider::EMAIL: $formatted_link = Social_Network_Provider::build_email_link( $icon, 'icon' ); break; case Social_Network_Provider::TELEPHONE: $formatted_link = ! empty( $icon['icon_number'] ) ? 'tel:' . $icon['icon_number'] : ''; break; case Social_Network_Provider::MESSENGER: $formatted_link = ! empty( $icon['icon_username'] ) ? Social_Network_Provider::build_messenger_link( $icon['icon_username'] ) : ''; break; case Social_Network_Provider::WAZE: $formatted_link = ! empty( $icon['icon_location']['url'] ) ? $icon['icon_location']['url'] : ''; break; case Social_Network_Provider::WHATSAPP: $formatted_link = ! empty( $icon['icon_number'] ) ? 'https://wa.me/' . $icon['icon_number'] : ''; break; default: break; } return $formatted_link; } protected function build_layout_render_attribute(): void { $layout_props_full_height = $this->settings['advanced_layout_full_screen_height'] ?? ''; $layout_props_full_height_controls = $this->settings['advanced_layout_full_screen_height_controls'] ?? ''; $layout_props_full_width = $this->settings['advanced_layout_full_width_custom'] ?? ''; $layout_props_show_border = $this->settings['background_show_border'] ?? ''; $custom_classes = $this->settings['advanced_custom_css_classes'] ?? ''; $layout_classnames = 'e-link-in-bio e-' . $this->widget->get_name(); if ( 'yes' === $layout_props_show_border ) { $layout_classnames .= ' has-border'; } if ( 'yes' === $layout_props_full_width ) { $layout_classnames .= ' is-full-width'; } if ( 'yes' === $layout_props_full_height ) { $layout_classnames .= ' is-full-height'; } if ( ! empty( $layout_props_full_height_controls ) ) { foreach ( $layout_props_full_height_controls as $breakpoint ) { $layout_classnames .= ' is-full-height-' . $breakpoint; } } if ( $custom_classes ) { $layout_classnames .= ' ' . $custom_classes; } $attrs = [ 'class' => $layout_classnames, ]; if ( ! empty( $this->settings['advanced_custom_css_id'] ) ) { $attrs['id'] = $this->settings['advanced_custom_css_id']; } $this->widget->add_render_attribute( 'layout', $attrs ); } private function set_primary_image_properties( array $output_images ): array { if ( $output_images['primary_image']['should_render'] ) { $output_images['primary_image']['props']['shape'] = $this->settings['identity_image_shape'] ?? 'circle'; $output_images['primary_image']['props']['style'] = $this->settings['identity_image_style'] ?? 'profile'; $output_images['primary_image']['props']['show_border'] = $this->settings['identity_image_show_border'] ?? false; $output_images['primary_image']['props']['show_bottom_border'] = $this->settings['identity_image_bottom_show_border'] ?? false; } return $output_images; } private function set_secondary_image_properties( array $output_images ): array { if ( $output_images['secondary_image']['should_render'] ) { $output_images['secondary_image']['props']['style'] = 'cover'; $output_images['secondary_image']['props']['show_bottom_border'] = $this->settings['identity_image_bottom_show_border'] ?? false; if ( ! empty( $this->settings['identity_section_style_cover_divider_bottom'] ) ) { $output_images['secondary_image']['props']['has_shape_divider'] = true; // Remove border if a shaped divider is applied $output_images['secondary_image']['props']['show_bottom_border'] = false; } $output_images['primary_image']['props']['style'] = 'profile'; } return $output_images; } } link-in-bio/classes/render/core-render.php 0000644 00000001502 15252521350 0014504 0 ustar 00 <?php namespace Elementor\Modules\LinkInBio\Classes\Render; /** * Class Core_Render. * * This class handles the rendering of the Link In Bio widget for the core version. * * @since 3.23.0 */ class Core_Render extends Render_Base { public function render(): void { $this->build_layout_render_attribute(); ?> <div <?php echo $this->widget->get_render_attribute_string( 'layout' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <div class="e-link-in-bio__content"> <?php $this->render_identity_image(); $this->render_bio(); $this->render_icons(); $this->render_image_links(); $this->render_ctas(); $this->render_footer_bio(); ?> </div> <div class="e-link-in-bio__bg"> <div class="e-link-in-bio__bg-overlay"></div> </div> </div> <?php } } link-in-bio/module.php 0000644 00000004044 15252521350 0010654 0 ustar 00 <?php namespace Elementor\Modules\LinkInBio; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const WIDGET_HAS_CUSTOM_BREAKPOINTS = true; public function get_name(): string { return 'link-in-bio'; } public function get_widgets(): array { return [ 'Link_In_Bio', ]; } public function __construct() { parent::__construct(); add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] ); } /** * Register styles. * * At build time, Elementor compiles `/modules/link-in-bio/assets/scss/widgets/*.scss` * to `/assets/css/widget-*.min.css`. * * @return void */ public function register_styles() { $direction_suffix = is_rtl() ? '-rtl' : ''; $widget_styles = $this->get_widgets_style_list(); $has_custom_breakpoints = Plugin::$instance->breakpoints->has_custom_breakpoints(); foreach ( $widget_styles as $widget_style_name => $widget_has_responsive_style ) { $should_load_responsive_css = $widget_has_responsive_style ? $has_custom_breakpoints : false; wp_register_style( $widget_style_name, $this->get_frontend_file_url( "{$widget_style_name}{$direction_suffix}.min.css", $should_load_responsive_css ), [ 'elementor-frontend' ], $should_load_responsive_css ? null : ELEMENTOR_VERSION ); } } private function get_widgets_style_list(): array { return [ 'widget-link-in-bio' => self::WIDGET_HAS_CUSTOM_BREAKPOINTS, // TODO: Remove in v3.27.0 [ED-15717] 'widget-link-in-bio-base' => self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-link-in-bio-var-2' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-link-in-bio-var-3' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-link-in-bio-var-4' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-link-in-bio-var-5' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-link-in-bio-var-7' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, ]; } } generator-tag/module.php 0000644 00000004460 15252521350 0011305 0 ustar 00 <?php namespace Elementor\Modules\GeneratorTag; use Elementor\Plugin; use Elementor\Settings; use Elementor\Core\Base\Module as BaseModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { public function get_name() { return 'generator-tag'; } public function __construct() { parent::__construct(); add_action( 'wp_head', [ $this, 'render_generator_tag' ] ); add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_settings' ], 100 ); } public function render_generator_tag() { if ( '1' === get_option( 'elementor_meta_generator_tag' ) ) { return; } $generator_content = $this->get_generator_content(); echo '<meta name="generator" content="' . esc_attr( $generator_content ) . '">' . PHP_EOL; } private function get_generator_content(): string { $active_features = $this->get_active_features(); $settings = $this->get_generator_tag_settings(); $tags = [ 'Elementor ' . ELEMENTOR_VERSION, ]; if ( ! empty( $active_features ) ) { $tags[] = 'features: ' . implode( ', ', $active_features ); } if ( ! empty( $settings ) ) { $tags[] = 'settings: ' . implode( ', ', $settings ); } return implode( '; ', $tags ); } private function get_active_features(): array { $active_features = []; foreach ( Plugin::$instance->experiments->get_active_features() as $feature_slug => $feature ) { if ( isset( $feature['generator_tag'] ) && $feature['generator_tag'] ) { $active_features[] = $feature_slug; } } return $active_features; } private function get_generator_tag_settings(): array { return apply_filters( 'elementor/generator_tag/settings', [] ); } public function register_admin_settings( Settings $settings ) { $settings->add_field( Settings::TAB_ADVANCED, Settings::TAB_ADVANCED, 'meta_generator_tag', [ 'label' => esc_html__( 'Generator Tag', 'elementor' ), 'field_args' => [ 'type' => 'select', 'std' => '', 'options' => [ '' => esc_html__( 'Enable', 'elementor' ), '1' => esc_html__( 'Disable', 'elementor' ), ], 'desc' => esc_html__( 'A generator tag is a meta element that indicates the attributes used to create a webpage. It is used for analytical purposes.', 'elementor' ), ], ] ); } } interactions/interactions-frontend-handler.php 0000644 00000007363 15252521350 0015722 0 ustar 00 <?php namespace Elementor\Modules\Interactions; use Elementor\Plugin; use Elementor\Modules\Interactions\Cache\Interactions_Postmeta; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Handles frontend-specific interaction logic including: * - Collecting interactions from document elements during render * - Outputting interaction data as JSON in the page footer * * This class is responsible for the frontend rendering pipeline of interactions, * working with the Interactions_Collector for data storage and Adapter for data transformation. */ class Interactions_Frontend_Handler { /** * @var callable|null */ private $config_provider; public function __construct( $config_provider = null ) { $this->config_provider = is_callable( $config_provider ) ? $config_provider : null; } /** * Collect interactions from document elements during frontend render. * * This method is hooked to 'elementor/frontend/builder_content_data' filter * to capture interactions from all documents (header, footer, post content) * as they are rendered. * * @param array $elements_data The document's elements data. * @param int $post_id The document's post ID. * @return array The unmodified elements data (pass-through filter). */ public function collect_document_interactions( $elements_data, $post_id ) { // Only collect on frontend, not in editor if ( Plugin::$instance->editor->is_edit_mode() ) { return $elements_data; } if ( empty( $elements_data ) || ! is_array( $elements_data ) ) { return $elements_data; } $interactions_postmeta = new Interactions_Postmeta(); $cached_rows = $interactions_postmeta->load_content( $post_id ); if ( empty( $cached_rows ) ) { $cached_rows = $interactions_postmeta->process_content( $post_id, [ 'elements' => $elements_data, ] ); } $collector = Interactions_Collector::instance(); foreach ( $cached_rows as $element_id => $interactions ) { $collector->register( $element_id, $interactions ); } return $elements_data; } /** * Output collected interaction data as a JSON script tag in the footer. * * This method is hooked to 'wp_footer' to output all collected interactions * as a centralized JSON data block that the frontend JavaScript can consume. */ public function print_interactions_data() { // Only output on frontend, not in editor if ( Plugin::$instance->editor->is_edit_mode() ) { return; } $elements_with_interactions = $this->elements_with_interactions(); if ( empty( $elements_with_interactions ) ) { return; } $this->enqueue_interactions_assets(); // Output as JSON script tag $json_data = wp_json_encode( $elements_with_interactions, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON data is already encoded echo '<script type="application/json" id="' . Module::SCRIPT_ID_INTERACTIONS_DATA . '">' . $json_data . '</script>'; } private function elements_with_interactions() { $all_interactions = Interactions_Collector::instance()->get_all(); $elements_with_interactions = []; foreach ( $all_interactions as $element_id => $interactions ) { $elements_with_interactions[] = [ 'elementId' => $element_id, 'dataId' => $element_id, 'interactions' => $interactions, ]; } return $elements_with_interactions; } private function get_interactions_config() { return $this->config_provider ? call_user_func( $this->config_provider ) : []; } private function enqueue_interactions_assets() { wp_enqueue_script( Module::HANDLE_MOTION_JS ); wp_enqueue_script( Module::HANDLE_FRONTEND ); wp_localize_script( Module::HANDLE_FRONTEND, Module::JS_CONFIG_OBJECT, $this->get_interactions_config() ); } } interactions/module.php 0000644 00000013315 15252521350 0011247 0 ustar 00 <?php namespace Elementor\Modules\Interactions; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Base\Document; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Modules\AtomicWidgets\Module as AtomicWidgetsModule; use Elementor\Modules\Interactions\Cache\Interactions_Postmeta; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const MODULE_NAME = 'e-interactions'; const EXPERIMENT_NAME = 'e_interactions'; const HANDLE_MOTION_JS = 'motion-js'; const HANDLE_SHARED_UTILS = 'elementor-interactions-shared-utils'; const HANDLE_FRONTEND = 'elementor-interactions'; const HANDLE_EDITOR = 'elementor-editor-interactions'; const JS_CONFIG_OBJECT = 'ElementorInteractionsConfig'; const SCRIPT_ID_INTERACTIONS_DATA = 'elementor-interactions-data'; public function get_name() { return self::MODULE_NAME; } private $preset_animations; private function get_presets() { if ( ! $this->preset_animations ) { $this->preset_animations = new Presets(); } return $this->preset_animations; } private $frontend_handler; private function get_frontend_handler() { if ( ! $this->frontend_handler ) { $this->frontend_handler = new Interactions_Frontend_Handler( fn () => $this->get_config() ); } return $this->frontend_handler; } public static function get_experimental_data() { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Interactions', 'elementor' ), 'description' => esc_html__( 'Enable element interactions.', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_ACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_DEV, ]; } public function is_experiment_active() { return Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ) && Plugin::$instance->experiments->is_feature_active( AtomicWidgetsModule::EXPERIMENT_NAME ); } public function __construct() { parent::__construct(); if ( ! $this->is_experiment_active() ) { return; } $this->register_hooks(); } private function register_hooks() { add_action( 'elementor/frontend/after_register_scripts', fn () => $this->register_frontend_scripts() ); add_action( 'elementor/preview/enqueue_scripts', fn () => $this->enqueue_preview_scripts() ); add_action( 'elementor/editor/before_enqueue_scripts', fn () => $this->enqueue_editor_scripts() ); add_action( 'elementor/editor/after_enqueue_scripts', fn () => $this->enqueue_editor_scripts() ); add_filter( 'elementor/document/save/data', [ $this, 'handle_interactions' ], 10, 2 ); add_action( 'elementor/document/after_save', [ $this, 'handle_interactions_cache' ], 10, 2 ); // Collect interactions from documents before they render (header, footer, post content) add_filter( 'elementor/frontend/builder_content_data', [ $this->get_frontend_handler(), 'collect_document_interactions', ], 10, 2 ); // Output centralized interaction data in footer add_action( 'wp_footer', [ $this->get_frontend_handler(), 'print_interactions_data' ], 1 ); } /** * Sanitize and validate data before saving the document. * * @throws \Exception When validation fails. * @return array */ public function handle_interactions( $data, $document ) { $validation = new Validation(); $document_after_sanitization = $validation->sanitize( $data ); $validation->validate(); $parser = new Parser( $document->get_main_id() ); return $parser->assign_interaction_ids( $document_after_sanitization ); } public function handle_interactions_cache( Document $document, $data ) { $postmeta = new Interactions_Postmeta(); $postmeta->process_content( $document->get_main_id(), $data ); } public function get_config() { return [ 'constants' => $this->get_presets()->defaults(), 'breakpoints' => $this->get_active_breakpoints(), ]; } private function get_active_breakpoints() { $breakpoints_config = Plugin::$instance->breakpoints->get_breakpoints_config(); $active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints(); $breakpoints = []; foreach ( array_keys( $active_breakpoints ) as $breakpoint_label ) { $breakpoints[ $breakpoint_label ] = $breakpoints_config[ $breakpoint_label ]; } return $breakpoints; } private function register_frontend_scripts() { $suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min'; wp_register_script( self::HANDLE_MOTION_JS, ELEMENTOR_ASSETS_URL . 'lib/motion/motion' . $suffix . '.js', [], '11.13.5', true ); wp_register_script( self::HANDLE_SHARED_UTILS, $this->get_js_assets_url( 'interactions-shared-utils' ), [ self::HANDLE_MOTION_JS ], '1.0.0', true ); wp_register_script( self::HANDLE_FRONTEND, $this->get_js_assets_url( 'interactions' ), [ self::HANDLE_MOTION_JS, self::HANDLE_SHARED_UTILS ], '1.0.0', true ); wp_register_script( self::HANDLE_EDITOR, $this->get_js_assets_url( 'editor-interactions' ), [ self::HANDLE_MOTION_JS, self::HANDLE_SHARED_UTILS ], '1.0.0', true ); } public function enqueue_editor_scripts() { wp_add_inline_script( 'elementor-common', 'window.' . self::JS_CONFIG_OBJECT . ' = ' . wp_json_encode( $this->get_config() ) . ';', 'before' ); } public function enqueue_preview_scripts() { wp_enqueue_script( self::HANDLE_SHARED_UTILS ); // Ensure motion-js and editor-interactions handler are available in preview iframe wp_enqueue_script( self::HANDLE_MOTION_JS ); wp_enqueue_script( self::HANDLE_EDITOR ); wp_localize_script( self::HANDLE_EDITOR, self::JS_CONFIG_OBJECT, $this->get_config() ); } } interactions/props/custom-effect-prop-type.php 0000644 00000000705 15252521350 0015625 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Custom_Effect_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'custom-effect'; } protected function define_shape(): array { return [ 'keyframes' => Keyframes_Prop_Type::make()->required(), ]; } } interactions/props/time-size-prop-type.php 0000644 00000000700 15252521350 0014762 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Time_Size_Prop_Type extends Size_Prop_Type { public static function make() { return parent::make()->units( Size_Constants::time() )->default_unit( Size_Constants::UNIT_MILLI_SECOND ); } } interactions/props/excluded-breakpoints-prop-type.php 0000644 00000001077 15252521350 0017200 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Excluded_Breakpoints_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'excluded-breakpoints'; } protected function define_item_type(): Prop_Type { return String_Prop_Type::make(); } } interactions/props/interaction-item-prop-type.php 0000644 00000002102 15252521350 0016325 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\Interactions\Presets; use Elementor\Modules\Interactions\Utils\Prop_Shape_Filter_For_Pro; if ( ! defined( 'ABSPATH' ) ) { exit; } class Interaction_Item_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'interaction-item'; } protected function define_shape(): array { return [ 'interaction_id' => String_Prop_Type::make()->description( 'The interaction id to use for the animation' ), 'trigger' => String_Prop_Type::make()->meta( 'enum', Presets::triggers_options() )->meta( 'pro', Presets::ADDITIONAL_TRIGGERS )->description( 'The trigger to use for the animation' ), 'animation' => Animation_Preset_Prop_Type::make()->description( 'The animation to use for the interaction' ), 'breakpoints' => Interaction_Breakpoints_Prop_Type::make()->description( 'The breakpoints to use for the animation' ), ]; } } interactions/props/interaction-breakpoints-prop-type.php 0000644 00000001002 15252521350 0017706 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Interaction_Breakpoints_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'interaction-breakpoints'; } protected function define_shape(): array { return [ 'excluded' => Excluded_Breakpoints_Prop_Type::make()->description( 'The excluded breakpoints' ), ]; } } interactions/props/timing-config-prop-type.php 0000644 00000001120 15252521350 0015603 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Timing_Config_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'timing-config'; } protected function define_shape(): array { return [ 'duration' => Time_Size_Prop_Type::make()->description( 'The duration to use for the animation' ), 'delay' => Time_Size_Prop_Type::make()->description( 'The delay to use for the animation' ), ]; } } interactions/props/animation-config-prop-type.php 0000644 00000003425 15252521350 0016305 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\Interactions\Presets; if ( ! defined( 'ABSPATH' ) ) { exit; } class Animation_Config_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'config-v2'; } protected function define_shape(): array { return [ 'replay' => Boolean_Prop_Type::make()->meta( 'pro', true )->description( 'Whether to replay the animation' ), 'easing' => String_Prop_Type::make()->meta( 'enum', Presets::easing_options() )->default( Presets::DEFAULT_EASING )->meta( 'pro', Presets::ADDITIONAL_EASING )->description( 'The easing function to use for the animation' ), 'relativeTo' => String_Prop_Type::make()->meta( 'pro', true )->description( 'The container scope used by scroll-based interactions' ), 'repeat' => String_Prop_Type::make()->meta( 'enum', Presets::REPEAT_OPTIONS )->default( Presets::DEFAULT_REPEAT )->meta( 'pro', true )->description( 'Repeat mode for interactions that can run multiple times' ), 'times' => Number_Prop_Type::make()->meta( 'pro', true )->description( 'Total number of times to play when repeat mode is "times"' ), 'start' => Size_Prop_Type::make()->units( '%' )->default_unit( '%' )->meta( 'pro', true )->description( 'The start to use for the animation' ), 'end' => Size_Prop_Type::make()->units( '%' )->default_unit( '%' )->meta( 'pro', true )->description( 'The end to use for the animation' ), ]; } } interactions/props/keyframe-stop-settings-prop-type.php 0000644 00000002575 15252521350 0017514 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Move_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Rotate_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Scale_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Skew_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Keyframe_Stop_Settings_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'keyframe-stop-settings'; } protected function define_shape(): array { return [ 'opacity' => Size_Prop_Type::make(), 'move' => Transform_Move_Prop_Type::make(), 'rotate' => Transform_Rotate_Prop_Type::make(), 'scale' => Transform_Scale_Prop_Type::make(), 'skew' => Transform_Skew_Prop_Type::make(), ]; } protected function validate_value( $value ): bool { if ( ! is_array( $value ) ) { return false; } $allowed_keys = array_keys( $this->get_shape() ); $value_keys = array_keys( $value ); if ( array_diff( $value_keys, $allowed_keys ) !== [] ) { return false; } return parent::validate_value( $value ); } } interactions/props/animation-preset-prop-type.php 0000644 00000002430 15252521350 0016335 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\Interactions\Presets; use Elementor\Modules\Interactions\Utils\Prop_Shape_Filter_For_Pro; if ( ! defined( 'ABSPATH' ) ) { exit; } class Animation_Preset_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'animation-preset-props'; } protected function define_shape(): array { return [ 'effect' => String_Prop_Type::make()->meta( 'enum', Presets::effects_options() )->meta( 'pro', Presets::ADDITIONAL_EFFECTS )->description( 'The effect to use for the animation' ), 'type' => String_Prop_Type::make()->meta( 'enum', Presets::TYPES )->description( 'The type to use for the animation' ), 'direction' => String_Prop_Type::make()->meta( 'enum', Presets::DIRECTIONS )->description( 'The direction to use for the animation' ), 'timing_config' => Timing_Config_Prop_Type::make()->description( 'The timing config to use for the animation' ), 'config' => Animation_Config_Prop_Type::make()->description( 'The config to use for the animation' ), 'custom_effect' => Custom_Effect_Prop_Type::make()->meta( 'pro', true ), ]; } } interactions/props/keyframes-prop-type.php 0000644 00000001266 15252521350 0015052 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Keyframes_Prop_Type extends Array_Prop_Type { public static function get_key(): string { return 'keyframes'; } protected function define_item_type(): Prop_Type { return Keyframe_Stop_Prop_Type::make(); } protected function validate_value( $value ): bool { $is_empty_array = empty( $value ) && is_array( $value ); if ( $is_empty_array ) { return false; } return parent::validate_value( $value ); } } interactions/props/keyframe-stop-prop-type.php 0000644 00000001270 15252521350 0015645 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Props; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Keyframe_Stop_Prop_Type extends Object_Prop_Type { public static function get_key(): string { return 'keyframe-stop'; } protected function define_shape(): array { return [ 'stop' => Size_Prop_Type::make() ->default_unit( Size_Constants::UNIT_PERCENT ) ->required(), 'settings' => Keyframe_Stop_Settings_Prop_Type::make() ->required(), ]; } } interactions/validators/custom-effect-value.php 0000644 00000001523 15252521350 0016006 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Validators; use Elementor\Modules\AtomicWidgets\Parsers\Props_Parser; use Elementor\Modules\Interactions\Props\Custom_Effect_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * TODO: At least a value validator interface to enforce is_valid fxn for consistency */ class Custom_Effect_Value { public static function is_valid( array $animation_value ): bool { $effect_value = $animation_value['effect']['value'] ?? null; if ( 'custom' !== $effect_value ) { return true; } if ( ! isset( $animation_value['custom_effect'] ) ) { return false; } $props_parser = Props_Parser::make( [ 'custom_effect' => Custom_Effect_Prop_Type::make(), ] ); $result = $props_parser->parse( [ 'custom_effect' => $animation_value['custom_effect'] ] ); return $result->is_valid(); } } interactions/validators/string-value.php 0000644 00000001172 15252521350 0014550 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Validators; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class String_Value { public static function is_valid( $prop_value, $allowed_values = null ) { if ( ! is_array( $prop_value ) ) { return false; } if ( ! isset( $prop_value['$$type'] ) || 'string' !== $prop_value['$$type'] ) { return false; } if ( ! isset( $prop_value['value'] ) || ! is_string( $prop_value['value'] ) ) { return false; } if ( null !== $allowed_values && ! in_array( $prop_value['value'], $allowed_values, true ) ) { return false; } return true; } } interactions/validators/breakpoints-value.php 0000644 00000003164 15252521350 0015566 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Validators; use Elementor\Modules\Interactions\Validators\String_Value as StringValueValidator; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Breakpoints_Value { public static function is_valid( $breakpoints_prop_value ) { if ( ! is_array( $breakpoints_prop_value ) ) { return false; } if ( ! isset( $breakpoints_prop_value['$$type'] ) || 'interaction-breakpoints' !== $breakpoints_prop_value['$$type'] ) { return false; } if ( ! isset( $breakpoints_prop_value['value'] ) || ! is_array( $breakpoints_prop_value['value'] ) ) { return false; } return self::validate_value( $breakpoints_prop_value['value'] ); } private static function validate_value( $value ) { if ( ! is_array( $value ) ) { return false; } if ( ! isset( $value['excluded'] ) || ! is_array( $value['excluded'] ) ) { return false; } return self::validate_excluded( $value['excluded'] ); } private static function validate_excluded( $excluded ) { if ( ! is_array( $excluded ) ) { return false; } if ( ! isset( $excluded['$$type'] ) || 'excluded-breakpoints' !== $excluded['$$type'] ) { return false; } if ( ! isset( $excluded['value'] ) || ! is_array( $excluded['value'] ) ) { return false; } return self::validate_excluded_value( $excluded['value'] ); } private static function validate_excluded_value( $value ) { if ( ! is_array( $value ) ) { return false; } foreach ( $value as $breakpoint_value ) { if ( ! StringValueValidator::is_valid( $breakpoint_value ) ) { return false; } } return true; } } interactions/validators/trigger-value.php 0000644 00000000771 15252521350 0014711 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Validators; use Elementor\Modules\Interactions\Validators\String_Value as StringValueValidator; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Trigger_Value { private const VALID_TRIGGERS = [ 'load', 'scrollIn', 'scrollOut', 'scrollOn', 'hover', 'click', ]; public static function is_valid( $trigger_prop_value ) { return StringValueValidator::is_valid( $trigger_prop_value, static::VALID_TRIGGERS ); } } interactions/cache/elements-interactions.php 0000644 00000003130 15252521350 0015333 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Cache; if ( ! defined( 'ABSPATH' ) ) { exit; } class Elements_Interactions { private $map; public function __construct() { $this->map = []; } public function all() { return $this->map; } public function parse_from( array $payload ) { if ( ! isset( $payload['elements'] ) || ! is_array( $payload['elements'] ) ) { return; } $elements = $payload['elements']; if ( empty( $elements ) ) { return; } foreach ( $elements as $element ) { $element_id = $this->extract_element_id( $element ); $interactions = $this->extract_interactions( $element ); if ( $element_id && $interactions ) { $this->map[ $element_id ] = $interactions; } $this->parse_from( $element ); } } private function extract_element_id( array $element ) { if ( ! isset( $element['id'] ) || empty( $element['id'] ) ) { return null; } return $element['id']; } private function extract_interactions( $element ) { if ( ! isset( $element['interactions'] ) ) { return null; } $interactions_value = $this->decode_interactions( $element['interactions'] ); if ( ! is_array( $interactions_value ) ) { return null; } if ( ! isset( $interactions_value['items'] ) || ! is_array( $interactions_value['items'] ) ) { return null; } return $interactions_value['items']; } private function decode_interactions( $source ) { if ( is_string( $source ) ) { $decoded = json_decode( $source, true ); if ( JSON_ERROR_NONE === json_last_error() && is_array( $decoded ) ) { return $decoded; } } return $source; } } interactions/cache/interactions-postmeta.php 0000644 00000002420 15252521350 0015354 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Cache; use Elementor\Core\Base\Document; if ( ! defined( 'ABSPATH' ) ) { exit; } class Interactions_Postmeta { const META_KEY = 'elementor-interactions-cache'; public function load_content( $post_id ) { return get_post_meta( $post_id, self::META_KEY, true ); } public function process_content( $post_id, $data ) { if ( $this->skip_processing( $data ) ) { return; } $elements_interactions = $this->extract_elements_interactions( $data ); $this->save_postmeta( $post_id, $elements_interactions ); return $elements_interactions; } private function save_postmeta( $post_id, array $interactions ) { if ( empty( $interactions ) ) { delete_post_meta( $post_id, self::META_KEY ); return; } update_post_meta( $post_id, self::META_KEY, $interactions ); } private function skip_processing( array $data ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return true; } if ( isset( $data['settings']['post_status'] ) && Document::STATUS_AUTOSAVE === $data['settings']['post_status'] ) { return true; } return false; } private function extract_elements_interactions( array $data ) { $parser = new Elements_Interactions(); $parser->parse_from( $data ); return $parser->all(); } } interactions/interactions-collector.php 0000644 00000003202 15252521350 0014442 0 ustar 00 <?php namespace Elementor\Modules\Interactions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Collects interaction data from all rendered documents and provides centralized access. */ class Interactions_Collector { /** * @var Interactions_Collector */ private static $instance = null; /** * @var array Stores interaction data keyed by element ID * Format: [ 'element_id' => [ interaction_items... ] ] */ private $interactions_data = []; /** * Get singleton instance. * * @return Interactions_Collector */ public static function instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Register interaction data for an element. * * @param string $element_id The element ID (data-id attribute value) * @param array $interactions The full interactions array from the element */ public function register( $element_id, $interactions ) { if ( empty( $element_id ) || empty( $interactions ) ) { return; } $this->interactions_data[ $element_id ] = $interactions; } /** * Get all collected interaction data. * * @return array Format: [ 'element_id' => interactions_array ] */ public function get_all() { return $this->interactions_data; } /** * Get interaction data for a specific element. * * @param string $element_id The element ID * @return array|null */ public function get( $element_id ) { return $this->interactions_data[ $element_id ] ?? null; } /** * Reset collected data (useful for testing or page reloads). */ public function reset() { $this->interactions_data = []; } } interactions/presets.php 0000644 00000003341 15252521350 0011445 0 ustar 00 <?php namespace Elementor\Modules\Interactions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Presets { const DEFAULT_DURATION = 600; const DEFAULT_DELAY = 0; const DEFAULT_SLIDE_DISTANCE = 100; const DEFAULT_SCALE_START = 0; const DEFAULT_RELATIVE_TO = 'viewport'; const DEFAULT_END = 15; const DEFAULT_START = 85; const BASE_TRIGGERS = [ 'load', 'scrollIn' ]; const ADDITIONAL_TRIGGERS = [ 'scrollOut', 'scrollOn', 'hover', 'click' ]; const DEFAULT_EASING = 'easeIn'; const BASE_EFFECTS = [ 'fade', 'slide', 'scale' ]; const ADDITIONAL_EFFECTS = [ 'custom' ]; const TYPES = [ 'in', 'out' ]; const DIRECTIONS = [ 'left', 'right', 'top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right', '' ]; const BASE_EASING = [ 'easeIn' ]; const ADDITIONAL_EASING = [ 'easeOut', 'easeInOut', 'backIn', 'backInOut', 'backOut', 'linear' ]; const DEFAULT_REPEAT = ''; const REPEAT_OPTIONS = [ 'loop', 'times', '' ]; public static function easing_options() { return array_merge( self::BASE_EASING, self::ADDITIONAL_EASING ); } public static function effects_options() { return array_merge( self::BASE_EFFECTS, self::ADDITIONAL_EFFECTS ); } public static function triggers_options() { return array_merge( self::BASE_TRIGGERS, self::ADDITIONAL_TRIGGERS ); } public function defaults() { return [ 'defaultDuration' => self::DEFAULT_DURATION, 'defaultDelay' => self::DEFAULT_DELAY, 'slideDistance' => self::DEFAULT_SLIDE_DISTANCE, 'scaleStart' => self::DEFAULT_SCALE_START, 'defaultEasing' => self::DEFAULT_EASING, 'relativeTo' => self::DEFAULT_RELATIVE_TO, 'repeat' => self::DEFAULT_REPEAT, 'start' => self::DEFAULT_START, 'end' => self::DEFAULT_END, ]; } } interactions/validation.php 0000644 00000025560 15252521350 0012121 0 ustar 00 <?php namespace Elementor\Modules\Interactions; use Elementor\Modules\Interactions\Validators\Breakpoints_Value as BreakpointsValueValidator; use Elementor\Modules\Interactions\Validators\Custom_Effect_Value; use Elementor\Modules\Interactions\Validators\String_Value as StringValueValidator; use Elementor\Modules\Interactions\Validators\Trigger_Value as TriggerValueValidator; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Validation { private $elements_to_interactions_counter = []; private $max_number_of_interactions = 5; private const VALID_EFFECTS = [ 'fade', 'slide', 'scale', 'custom' ]; private const VALID_TYPES = [ 'in', 'out' ]; private const VALID_DIRECTIONS = [ '', 'left', 'right', 'top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right' ]; private const VALID_REPEAT_MODES = [ '', 'loop', 'times' ]; public function sanitize( $document ) { return $this->sanitize_document_data( $document ); } public function validate() { foreach ( $this->elements_to_interactions_counter as $element_id => $number_of_interactions ) { if ( $number_of_interactions > $this->max_number_of_interactions ) { throw new \Exception( sprintf( // translators: %1$s: element ID, %2$d: maximum number of interactions allowed. esc_html__( 'Element %1$s has more than %2$d interactions', 'elementor' ), esc_html( $element_id ), esc_html( $this->max_number_of_interactions ) ) ); } } return true; } private function sanitize_document_data( $data ) { if ( isset( $data['elements'] ) && is_array( $data['elements'] ) ) { $data['elements'] = $this->sanitize_elements_interactions( $data['elements'] ); } return $data; } private function sanitize_elements_interactions( $elements ) { if ( ! is_array( $elements ) ) { return $elements; } foreach ( $elements as &$element ) { if ( isset( $element['interactions'] ) ) { $element['interactions'] = $this->sanitize_interactions( $element['interactions'], $element['id'] ); } if ( isset( $element['elements'] ) && is_array( $element['elements'] ) ) { $element['elements'] = $this->sanitize_elements_interactions( $element['elements'] ); } } return $elements; } private function decode_interactions( $interactions ) { if ( is_array( $interactions ) ) { if ( isset( $interactions['items']['$$type'] ) && 'array' === $interactions['items']['$$type'] ) { return isset( $interactions['items']['value'] ) ? $interactions['items']['value'] : []; } return isset( $interactions['items'] ) ? $interactions['items'] : []; } if ( is_string( $interactions ) ) { $decoded = json_decode( $interactions, true ); if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) { if ( isset( $decoded['items']['$$type'] ) && 'array' === $decoded['items']['$$type'] ) { return isset( $decoded['items']['value'] ) ? $decoded['items']['value'] : []; } return isset( $decoded['items'] ) ? $decoded['items'] : []; } } return []; } private function increment_interactions_counter_for( $element_id ) { if ( ! array_key_exists( $element_id, $this->elements_to_interactions_counter ) ) { $this->elements_to_interactions_counter[ $element_id ] = 0; } ++$this->elements_to_interactions_counter[ $element_id ]; return $this; } private function sanitize_interactions( $interactions, $element_id ) { $sanitized = [ 'items' => [], 'version' => 1, ]; $list_of_interactions = $this->decode_interactions( $interactions ); foreach ( $list_of_interactions as $interaction ) { if ( $this->is_valid_interaction_item( $interaction ) ) { $sanitized['items'][] = $interaction; $this->increment_interactions_counter_for( $element_id ); } } if ( empty( $sanitized['items'] ) ) { return []; } return wp_json_encode( $sanitized ); } private function is_valid_interaction_item( $item ) { if ( ! is_array( $item ) ) { return false; } // Validate PropType format: { $$type: 'interaction-item', value: { ... } } if ( ! isset( $item['$$type'] ) || 'interaction-item' !== $item['$$type'] ) { return false; } if ( ! isset( $item['value'] ) || ! is_array( $item['value'] ) ) { return false; } $value = $item['value']; // Validate required fields exist if ( isset( $value['interaction_id'] ) && ! $this->is_valid_string_prop( $value, 'interaction_id' ) ) { return false; } if ( ! $this->is_valid_trigger_prop( $value ) ) { return false; } if ( ! $this->is_valid_animation_prop( $value ) ) { return false; } if ( ! $this->is_valid_breakpoints_prop( $value ) ) { return false; } return true; } private function is_valid_trigger_prop( $data ) { if ( ! array_key_exists( 'trigger', $data ) ) { return false; } return TriggerValueValidator::is_valid( $data['trigger'] ); } private function is_valid_breakpoints_prop( $data ) { if ( array_key_exists( 'breakpoints', $data ) ) { return BreakpointsValueValidator::is_valid( $data['breakpoints'] ); } return true; } private function is_valid_string_prop( $data, $key, $allowed_values = null ) { if ( ! isset( $data[ $key ] ) ) { return false; } return StringValueValidator::is_valid( $data[ $key ], $allowed_values ); } private function is_valid_boolean_prop( $data, $key ) { if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) { return false; } $prop = $data[ $key ]; if ( ! isset( $prop['$$type'] ) || 'boolean' !== $prop['$$type'] ) { return false; } if ( ! isset( $prop['value'] ) || ! is_bool( $prop['value'] ) ) { return false; } return true; } private function is_valid_number_prop( $data, $key ) { if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) { return false; } $prop = $data[ $key ]; if ( ! isset( $prop['$$type'] ) || 'number' !== $prop['$$type'] ) { return false; } if ( ! isset( $prop['value'] ) || ! is_numeric( $prop['value'] ) ) { return false; } return true; } private function is_valid_number_prop_in_range( $data, $key, $min = null, $max = null ) { if ( ! $this->is_valid_number_prop( $data, $key ) ) { return false; } $value = (float) $data[ $key ]['value']; if ( null !== $min && $value < $min ) { return false; } if ( null !== $max && $value > $max ) { return false; } return true; } private function is_valid_config_prop( $data ) { if ( ! isset( $data['config'] ) || ! is_array( $data['config'] ) ) { return false; } $config_value = $data['config']['value']; if ( isset( $config_value['replay'] ) && ! $this->is_valid_boolean_prop( $config_value, 'replay' ) ) { return false; } if ( isset( $config_value['easing'] ) && ! $this->is_valid_string_prop( $config_value, 'easing' ) ) { return false; } if ( isset( $config_value['relativeTo'] ) && ! $this->is_valid_string_prop( $config_value, 'relativeTo' ) ) { return false; } if ( isset( $config_value['repeat'] ) && ! $this->is_valid_string_prop( $config_value, 'repeat', self::VALID_REPEAT_MODES ) ) { return false; } if ( isset( $config_value['times'] ) && ! $this->is_valid_number_prop_in_range( $config_value, 'times', 1 ) ) { return false; } if ( isset( $config_value['start'] ) && ! $this->is_valid_size_prop_in_range( $config_value, 'start', 0, 100 ) ) { return false; } if ( isset( $config_value['end'] ) && ! $this->is_valid_size_prop_in_range( $config_value, 'end', 0, 100 ) ) { return false; } return true; } private function is_valid_animation_prop( $data ) { if ( ! isset( $data['animation'] ) || ! is_array( $data['animation'] ) ) { return false; } $animation = $data['animation']; if ( ! isset( $animation['$$type'] ) || 'animation-preset-props' !== $animation['$$type'] ) { return false; } if ( ! isset( $animation['value'] ) || ! is_array( $animation['value'] ) ) { return false; } $animation_value = $animation['value']; // Validate effect if ( ! $this->is_valid_string_prop( $animation_value, 'effect', self::VALID_EFFECTS ) ) { return false; } // Validate type if ( ! $this->is_valid_string_prop( $animation_value, 'type', self::VALID_TYPES ) ) { return false; } // Validate direction (can be empty string) if ( ! $this->is_valid_string_prop( $animation_value, 'direction', self::VALID_DIRECTIONS ) ) { return false; } // Validate timing_config if ( ! $this->is_valid_timing_config( $animation_value ) ) { return false; } if ( isset( $animation_value['config'] ) && ! $this->is_valid_config_prop( $animation_value ) ) { return false; } if ( ! Custom_Effect_Value::is_valid( $animation_value ) ) { return false; } return true; } private function is_valid_timing_config( $data ) { if ( ! isset( $data['timing_config'] ) || ! is_array( $data['timing_config'] ) ) { return false; } $timing = $data['timing_config']; if ( ! isset( $timing['$$type'] ) || 'timing-config' !== $timing['$$type'] ) { return false; } if ( ! isset( $timing['value'] ) || ! is_array( $timing['value'] ) ) { return false; } $timing_value = $timing['value']; // Validate duration (accepts both 'number' and 'size' formats) if ( ! $this->is_valid_timing_value( $timing_value, 'duration', 0 ) ) { return false; } // Validate delay (accepts both 'number' and 'size' formats) if ( ! $this->is_valid_timing_value( $timing_value, 'delay', 0 ) ) { return false; } return true; } /** * Validate timing value that can be either 'number' or 'size' type. * - number format: {$$type: 'number', value: 123} * - size format: {$$type: 'size', value: {size: 123, unit: 'ms'}} */ private function is_valid_timing_value( $data, $key, $min = null, $max = null ) { if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) { return false; } $prop = $data[ $key ]; if ( ! isset( $prop['$$type'] ) ) { return false; } // Accept 'number' format if ( 'number' === $prop['$$type'] ) { return $this->is_valid_number_prop_in_range( $data, $key, $min, $max ); } // Accept 'size' format if ( 'size' === $prop['$$type'] ) { return $this->is_valid_size_prop_in_range( $data, $key, $min, $max ); } return false; } /** * Validate size prop: {$$type: 'size', value: {size: X, unit: 'ms'}} */ private function is_valid_size_prop_in_range( $data, $key, $min = null, $max = null ) { if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) { return false; } $prop = $data[ $key ]; if ( ! isset( $prop['$$type'] ) || 'size' !== $prop['$$type'] ) { return false; } if ( ! isset( $prop['value'] ) || ! is_array( $prop['value'] ) ) { return false; } if ( ! isset( $prop['value']['size'] ) || ! is_numeric( $prop['value']['size'] ) ) { return false; } $value = (float) $prop['value']['size']; if ( null !== $min && $value < $min ) { return false; } if ( null !== $max && $value > $max ) { return false; } return true; } } interactions/parser.php 0000644 00000005333 15252521350 0011257 0 ustar 00 <?php namespace Elementor\Modules\Interactions; use Elementor\Modules\AtomicWidgets\Utils\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Parser { protected $post_id; protected $ids_lookup = []; public function __construct( $post_id ) { $this->post_id = $post_id; } public function assign_interaction_ids( $data ) { if ( isset( $data['elements'] ) && is_array( $data['elements'] ) ) { $data['elements'] = $this->process_interactions_for( $data['elements'] ); } return $data; } private function process_interactions_for( $elements ) { if ( ! is_array( $elements ) ) { return $elements; } foreach ( $elements as &$element ) { if ( isset( $element['interactions'] ) ) { $element['interactions'] = $this->maybe_assign_interaction_ids( $element['interactions'], $element['id'] ); } if ( isset( $element['elements'] ) && is_array( $element['elements'] ) ) { $element['elements'] = $this->process_interactions_for( $element['elements'] ); } } return $elements; } private function maybe_assign_interaction_ids( $interactions_json, $element_id ) { $interactions = $this->decode_interactions( $interactions_json ); if ( ! isset( $interactions['items'] ) ) { return []; } foreach ( $interactions['items'] as &$interaction ) { if ( ! isset( $interaction['$$type'] ) || 'interaction-item' !== $interaction['$$type'] ) { continue; } $existing_id = null; if ( isset( $interaction['value']['interaction_id']['value'] ) ) { $existing_id = $interaction['value']['interaction_id']['value']; } if ( $existing_id && $this->is_temp_id( $existing_id ) ) { $interaction['value']['interaction_id'] = [ '$$type' => 'string', 'value' => $this->get_next_interaction_id( $element_id ), ]; } elseif ( $existing_id ) { $this->ids_lookup[] = $existing_id; } else { $interaction['value']['interaction_id'] = [ '$$type' => 'string', 'value' => $this->get_next_interaction_id( $element_id ), ]; } } return wp_json_encode( $interactions ); } private function is_temp_id( $id ) { return is_string( $id ) && strpos( $id, 'temp-' ) === 0; } private function decode_interactions( $interactions ) { if ( is_array( $interactions ) ) { return $interactions; } if ( is_string( $interactions ) ) { $decoded = json_decode( $interactions, true ); if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) { return $decoded; } } return [ 'items' => [], 'version' => 1, ]; } protected function get_next_interaction_id( $prefix ) { $next_id = Utils::generate_id( "{$this->post_id}-{$prefix}-", $this->ids_lookup ); $this->ids_lookup[] = $next_id; return $next_id; } } interactions/schema/interactions-schema.php 0000644 00000001060 15252521350 0015154 0 ustar 00 <?php namespace Elementor\Modules\Interactions\Schema; use Elementor\Modules\Interactions\Props\Interaction_Item_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Interactions_Schema { public static function get() { return apply_filters( 'elementor/atomic-widgets/interactions/schema', static::get_interactions_schema() ); } public static function get_interactions_schema(): array { return [ 'version' => 1, 'items' => [ Interaction_Item_Prop_Type::make()->description( 'Interaction item' ) ], ]; } } performance-lab/module.php 0000644 00000003442 15252521350 0011602 0 ustar 00 <?php namespace Elementor\Modules\PerformanceLab; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const PERFORMANCE_LAB_FUNCTION_NAME = 'webp_uploads_img_tag_update_mime_type'; const PERFORMANCE_LAB_OPTION_NAME = 'site-health/webp-support'; public function get_name() { return 'performance-lab'; } private function is_performance_lab_is_active() { if ( function_exists( self::PERFORMANCE_LAB_FUNCTION_NAME ) ) { $perflab_modules_settings = get_option( self::PERFORMANCE_LAB_OPTION_NAME, [] ); if ( isset( $perflab_modules_settings ) && isset( $perflab_modules_settings[ self::PERFORMANCE_LAB_OPTION_NAME ] ) && '1' === $perflab_modules_settings[ self::PERFORMANCE_LAB_OPTION_NAME ]['enabled'] ) { return true; } } return false; } private function performance_lab_get_webp_src( $attachment_id, $size, $url ) { $image_object = wp_get_attachment_image_src( $attachment_id, $size ); $image_src = call_user_func( self::PERFORMANCE_LAB_FUNCTION_NAME, $image_object[0], 'webp', $attachment_id ); if ( ! empty( $image_src ) ) { return $image_src; } return $url; } private function replace_css_with_webp( $value, $css_property, $matches ) { if ( 0 === strpos( $css_property, 'background-image' ) && '{{URL}}' === $matches[0] ) { $value['url'] = $this->performance_lab_get_webp_src( $value['id'], 'full', $value['url'] ); } return $value; } public function __construct() { parent::__construct(); if ( $this->is_performance_lab_is_active() ) { add_filter( 'elementor/files/css/property', function( $value, $css_property, $matches ) { return $this->replace_css_with_webp( $value, $css_property, $matches ); }, 10, 3 ); } } } favorites/favorites-type.php 0000644 00000001446 15252521350 0012245 0 ustar 00 <?php namespace Elementor\Modules\Favorites; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use Elementor\Core\Utils\Collection; use Elementor\Core\Utils\Static_Collection; abstract class Favorites_Type extends Static_Collection { public function __construct( array $items = [] ) { parent::__construct( $items, true ); } /** * Get the name of the type. * * @return mixed */ abstract public function get_name(); /** * Prepare favorites before taking any action. * * @param Collection|array|string $favorites * * @return array */ public function prepare( $favorites ) { if ( $favorites instanceof Collection ) { $favorites = $favorites->values(); } if ( ! is_array( $favorites ) ) { return [ $favorites ]; } return $favorites; } } favorites/module.php 0000644 00000011723 15252521350 0010550 0 ustar 00 <?php namespace Elementor\Modules\Favorites; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager; use Elementor\Modules\Favorites\Types\Widgets; use Elementor\Plugin; use http\Exception\InvalidArgumentException; use WP_Error; class Module extends BaseModule { /** * List of registered favorites type. * * @var Favorites_Type[] */ protected $types = []; const OPTION_NAME = 'elementor_editor_user_favorites'; /** * The name of the merge action. * * @var string */ const ACTION_MERGE = 'merge'; /** * The name of the delete action. * * @var string */ const ACTION_DELETE = 'delete'; /** * Favorites module constructor. */ public function __construct() { // Register default types $this->register( Widgets::class ); $this->populate(); Plugin::instance()->data_manager_v2->register_controller( new Controller() ); add_filter( 'elementor/tracker/send_tracking_data_params', [ $this, 'add_tracking_data' ] ); } /** * Add usage data related to favorites. * * @param $params * * @return array */ public function add_tracking_data( $params ) { $params['usages']['favorites'] = $this->get(); return $params; } public function get_name() { return 'favorites'; } /** * Get user favorites by type. * * @param string[]|string $type * * @return array */ public function get( $type = null ) { if ( null === $type ) { $type = array_keys( $this->types ); } if ( is_array( $type ) ) { return array_intersect_key( $this->combined(), array_flip( (array) $type ) ); } return $this->type_instance( $type ) ->values(); } /** * Merge new user favorites to a type. * * @param string $type * @param array|string $favorites * @param bool $store * * @return array|bool */ public function merge( $type, $favorites, $store = true ) { return $this->update( $type, $favorites, static::ACTION_MERGE, $store ); } /** * Delete existing favorites from a type. * * @param string $type * @param array|string $favorites * @param bool $store * * @return array|int */ public function delete( $type, $favorites, $store = true ) { return $this->update( $type, $favorites, static::ACTION_DELETE, $store ); } /** * Update favorites on a type by merging or deleting from it. * * @param $type * @param $favorites * @param $action * @param bool $store * * @return array|boolean */ public function update( $type, $favorites, $action, $store = true ) { $type_instance = $this->type_instance( $type ); $favorites = $type_instance->prepare( $favorites ); switch ( $action ) { case static::ACTION_MERGE: $type_instance->merge( $favorites ); break; case static::ACTION_DELETE: $type_instance->filter( function( $value ) use ( $favorites ) { return ! in_array( $value, $favorites, true ); } ); break; default: $this->action_doesnt_exists( $action ); } if ( $store && ! $this->store() ) { return false; } return $type_instance->values(); } /** * Get registered favorites type instance. * * @param string $type * * @return Favorites_Type */ public function type_instance( $type ) { return $this->types[ $type ]; } /** * Register a new type class. * * @param string $class_name */ public function register( $class_name ) { $type_instance = new $class_name(); $this->types[ $type_instance->get_name() ] = $type_instance; } /** * Returns all available types keys. * * @return string[] */ public function available() { return array_keys( $this->types ); } /** * Combine favorites from all types into a single array. * * @return array */ protected function combined() { $all = []; foreach ( $this->types as $type ) { $favorites = $type->values(); if ( ! empty( $favorites ) ) { $all[ $type->get_name() ] = $favorites; } } return $all; } /** * Populate all type classes with the stored data. */ protected function populate() { $combined = $this->retrieve(); foreach ( $this->types as $key => $type ) { if ( isset( $combined[ $key ] ) ) { $type->merge( $combined[ $key ] ); } } } /** * Retrieve stored user favorites types. * * @return mixed|false */ protected function retrieve() { return get_user_option( static::OPTION_NAME ); } /** * Update all changes to user favorites type. * * @return int|bool */ protected function store() { return update_user_option( get_current_user_id(), static::OPTION_NAME, $this->combined() ); } /** * Throw action doesn't exist exception. * * @param string $action * * @throws \InvalidArgumentException If favorite action fails or validation errors occur. */ public function action_doesnt_exists( $action ) { throw new \InvalidArgumentException( sprintf( "Action '%s' to apply on favorites doesn't exists", esc_html( $action ) ) ); } } favorites/controller.php 0000644 00000003720 15252521350 0011444 0 ustar 00 <?php namespace Elementor\Modules\Favorites; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use Elementor\Data\V2\Base\Controller as Controller_Base; use Elementor\Plugin; class Controller extends Controller_Base { public function get_name() { return 'favorites'; } public function create_item( $request ) { $module = $this->get_module(); $type = $request->get_param( 'id' ); $favorite = $request->get_param( 'favorite' ); $module->update( $type, $favorite, $module::ACTION_MERGE ); return $module->get( $type ); } public function delete_item( $request ) { $module = $this->get_module(); $type = $request->get_param( 'id' ); $favorite = $request->get_param( 'favorite' ); $module->update( $type, $favorite, $module::ACTION_DELETE ); return $module->get( $type ); } public function create_item_permissions_check( $request ) { return current_user_can( 'edit_posts' ); } public function delete_item_permissions_check( $request ) { return $this->create_item_permissions_check( $request ); } /** * Get the favorites module instance. * * @return Module */ protected function get_module() { return Plugin::instance()->modules_manager->get_modules( 'favorites' ); } public function register_endpoints() { $this->index_endpoint->register_item_route( \WP_REST_Server::CREATABLE, [ 'id_arg_type_regex' => '[\w]+', 'id' => [ 'description' => 'Type of favorites.', 'type' => 'string', 'required' => true, ], 'favorite' => [ 'description' => 'The favorite slug to create.', 'type' => 'string', 'required' => true, ], ] ); $this->index_endpoint->register_item_route( \WP_REST_Server::DELETABLE, [ 'id_arg_type_regex' => '[\w]+', 'id' => [ 'description' => 'Type of favorites.', 'type' => 'string', 'required' => true, ], 'favorite' => [ 'description' => 'The favorite slug to delete.', 'type' => 'string', 'required' => true, ], ] ); } } favorites/types/widgets.php 0000644 00000002767 15252521350 0012105 0 ustar 00 <?php namespace Elementor\Modules\Favorites\Types; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use Elementor\Modules\Favorites\Favorites_Type; use Elementor\Plugin; class Widgets extends Favorites_Type { const CATEGORY_SLUG = 'favorites'; /** * Widgets favorites type constructor. */ public function __construct( array $items = [] ) { parent::__construct( $items ); add_action( 'elementor/document/before_get_config', [ $this, 'update_widget_categories' ], 10, 1 ); } public function get_name() { return 'widgets'; } public function prepare( $favorites ) { return array_intersect( parent::prepare( $favorites ), $this->get_available() ); } /** * Get all available widgets. * * @return string[] */ public function get_available() { return array_merge( array_keys( Plugin::instance()->widgets_manager->get_widget_types() ), array_keys( Plugin::instance()->elements_manager->get_element_types() ) ); } /** * Update the categories of a widget inside a filter. * * @param $document */ public function update_widget_categories( $document ) { foreach ( $this->values() as $favorite ) { $widget = Plugin::$instance->widgets_manager->get_widget_types( $favorite ); // If it's not a widget, maybe it's an element. if ( ! $widget ) { $widget = Plugin::$instance->elements_manager->get_element_types( $favorite ); } if ( $widget ) { $widget->set_config( 'categories', [ static::CATEGORY_SLUG ] ); } } } } styleguide/module.php 0000644 00000006664 15252521350 0010734 0 ustar 00 <?php namespace Elementor\Modules\Styleguide; use Elementor\Core\Base\Module as Base_Module; use Elementor\Plugin; use Elementor\Modules\Styleguide\Controls\Switcher; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends Base_Module { const ASSETS_HANDLE = 'elementor-styleguide'; const ASSETS_SRC = 'styleguide'; /** * Initialize the Container-Converter module. * * @return void */ public function __construct() { parent::__construct(); add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_main_scripts' ] ); add_action( 'elementor/preview/enqueue_styles', [ $this, 'enqueue_styles' ] ); add_action( 'elementor/frontend/after_register_scripts', function () { $is_preview = Plugin::$instance->preview->is_preview(); if ( ! $is_preview ) { return; } $this->enqueue_app_initiator( $is_preview ); } ); add_action( 'elementor/controls/register', [ $this, 'register_controls' ] ); add_action( 'elementor/element/after_section_start', [ $this, 'add_styleguide_enable_controls' ], 10, 3 ); } /** * Retrieve the module name. * * @return string */ public function get_name() { return 'styleguide'; } /** * Enqueue scripts. * * @return void */ public function enqueue_main_scripts() { wp_enqueue_script( static::ASSETS_HANDLE, $this->get_js_assets_url( static::ASSETS_SRC ), [ 'elementor-editor' ], ELEMENTOR_VERSION, true ); $kit_id = Plugin::$instance->kits_manager->get_active_id(); wp_localize_script( static::ASSETS_HANDLE, 'elementorStyleguideConfig', [ 'activeKitId' => $kit_id, ] ); wp_set_script_translations( static::ASSETS_HANDLE, 'elementor' ); } public function enqueue_app_initiator( $is_preview = false ) { $dependencies = [ 'wp-i18n', 'react', 'react-dom', ]; if ( ! $is_preview ) { $dependencies[] = static::ASSETS_HANDLE; } wp_enqueue_script( static::ASSETS_HANDLE . '-app-initiator', $this->get_js_assets_url( static::ASSETS_SRC . '-app-initiator' ), $dependencies, ELEMENTOR_VERSION, true ); wp_set_script_translations( static::ASSETS_HANDLE . '-app-initiator', 'elementor' ); } public function enqueue_styles() { wp_enqueue_style( static::ASSETS_HANDLE, $this->get_css_assets_url( 'modules/styleguide/editor' ), [], ELEMENTOR_VERSION ); } public function register_controls() { $controls_manager = Plugin::$instance->controls_manager; $controls_manager->register( new Switcher() ); } /** * Add the Enable Styleguide Preview controls to Global Colors and Global Fonts. * * @param $element * @param string $section_id * @param array $args */ public function add_styleguide_enable_controls( $element, $section_id, $args ) { if ( 'kit' !== $element->get_name() || ! in_array( $section_id, [ 'section_global_colors', 'section_text_style' ] ) ) { return; } $control_name = str_replace( 'global-', '', $args['tab'] ) . '_enable_styleguide_preview'; $element->add_control( $control_name, [ 'label' => esc_html__( 'Show global settings', 'elementor' ), 'type' => Switcher::CONTROL_TYPE, 'description' => esc_html__( 'Temporarily overlay the canvas with the style guide to preview your changes to global colors and fonts.', 'elementor' ), 'separator' => 'after', 'label_off' => esc_html__( 'No', 'elementor' ), 'label_on' => esc_html__( 'Yes', 'elementor' ), 'on_change_command' => true, ] ); } } styleguide/controls/switcher.php 0000644 00000000763 15252521350 0013134 0 ustar 00 <?php namespace Elementor\Modules\Styleguide\Controls; use Elementor\Control_Switcher; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Switcher extends Control_Switcher { const CONTROL_TYPE = 'global-style-switcher'; /** * Get control type. * * Retrieve the control type, in this case `global-style-switcher`. * * @since 3.13.0 * @access public * * @return string Control type. */ public function get_type() { return self::CONTROL_TYPE; } } cloud-library/render-mode-preview.php 0000644 00000004261 15252521350 0013710 0 ustar 00 <?php namespace Elementor\Modules\CloudLibrary; use Elementor\Core\Frontend\RenderModes\Render_Mode_Base; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Render_Mode_Preview extends Render_Mode_Base { const ENQUEUE_SCRIPTS_PRIORITY = 1000; const MODE = 'cloud-template-preview'; protected int $template_id; public function __construct( $template_id ) { $this->template_id = $template_id; $this->document = $this->create_document(); Plugin::$instance->db->switch_to_post( $this->document->get_main_id() ); Plugin::$instance->documents->switch_to_document( $this->document ); add_filter( 'template_include', [ $this, 'filter_template' ] ); add_action( 'wp_footer', [ $this, 'cleanup' ], 999 ); parent::__construct( $this->document->get_main_id() ); } public static function get_name() { return self::MODE; } public function prepare_render() { parent::prepare_render(); show_admin_bar( false ); } public function filter_template() { return ELEMENTOR_PATH . 'modules/page-templates/templates/canvas.php'; } public function cleanup() { if ( $this->document && $this->document->get_main_id() ) { wp_delete_post( $this->document->get_main_id(), true ); } } public function enqueue_scripts() { $suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min'; wp_enqueue_script( 'cloud-library-screenshot', ELEMENTOR_ASSETS_URL . "/js/cloud-library-screenshot{$suffix}.js", [], ELEMENTOR_VERSION, true ); $config = [ 'selector' => '.elementor-' . $this->document->get_main_id(), 'home_url' => home_url(), 'post_id' => $this->document->get_main_id(), 'template_id' => $this->template_id, ]; wp_add_inline_script( 'cloud-library-screenshot', 'var ElementorScreenshotConfig = ' . wp_json_encode( $config ) . ';' ); } private function create_document() { if ( ! Plugin::$instance->common ) { Plugin::$instance->init_common(); } $document = Plugin::$instance->templates_manager->get_source( 'cloud' )->create_document_for_preview( $this->template_id ); if ( is_wp_error( $document ) ) { wp_die(); } return $document; } } cloud-library/module.php 0000644 00000011265 15252521350 0011317 0 ustar 00 <?php namespace Elementor\Modules\CloudLibrary; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\Connect\Module as ConnectModule; use Elementor\Core\Documents_Manager; use Elementor\Core\Frontend\Render_Mode_Manager; use Elementor\Modules\CloudLibrary\Connect\Cloud_Library; use Elementor\Core\Common\Modules\Connect\Apps\Library; use Elementor\Core\Experiments\Manager as ExperimentsManager; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { /** * @var callable */ protected $print_preview_callback; public function get_name(): string { return 'cloud-library'; } public function __construct() { parent::__construct(); $this->register_app(); add_action( 'elementor/init', function () { $this->set_cloud_library_settings(); }, 12 /** After the initiation of the connect cloud library */ ); add_filter( 'elementor/editor/localize_settings', function ( $settings ) { return $this->localize_settings( $settings ); }, 11 /** After Elementor Core */ ); add_filter( 'elementor/render_mode/module', function( $module_name ) { $render_mode_manager = \Elementor\Plugin::$instance->frontend->render_mode_manager; if ( $render_mode_manager ) { $current_render_mode = $render_mode_manager->get_current(); if ( $current_render_mode instanceof \Elementor\Modules\CloudLibrary\Render_Mode_Preview ) { return 'cloud-library'; } } return $module_name; }, 12); } public function localize_settings( $settings ) { if ( isset( $settings['i18n'] ) ) { $settings['i18n']['folder'] = esc_html__( 'Folder', 'elementor' ); } $settings['library']['doc_types'] = $this->get_document_types(); return $settings; } private function register_app() { add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) { $connect_module->register_app( 'cloud-library', Cloud_Library::get_class_name() ); } ); add_action( 'elementor/frontend/render_mode/register', [ $this, 'register_render_mode' ] ); add_action( 'elementor/documents/register', function ( Documents_Manager $documents_manager ) { $documents_manager->register_document_type( Documents\Cloud_Template_Preview::TYPE, Documents\Cloud_Template_Preview::get_class_full_name() ); }); } /** * @param Render_Mode_Manager $manager * * @throws \Exception If render mode registration fails. */ public function register_render_mode( Render_Mode_Manager $manager ) { $manager->register_render_mode( Render_Mode_Preview::class ); } private function set_cloud_library_settings() { if ( ! Plugin::$instance->common ) { return; } /** @var ConnectModule $connect */ $connect = Plugin::$instance->common->get_component( 'connect' ); /** @var Library $library */ $library = $connect->get_app( 'library' ); if ( ! $library ) { return; } Plugin::$instance->app->set_settings( 'cloud-library', [ 'library_connect_url' => esc_url( $library->get_admin_url( 'authorize', [ 'utm_source' => 'template-library', 'utm_medium' => 'wp-dash', 'utm_campaign' => 'library-connect', 'utm_content' => 'cloud-library', 'source' => 'cloud-library', ] ) ), 'library_connect_title_copy' => esc_html__( 'Connect to your Elementor account', 'elementor' ), 'library_connect_sub_title_copy' => esc_html__( 'Then you can find all your templates in one convenient library.', 'elementor' ), 'library_connect_button_copy' => esc_html__( 'Connect', 'elementor' ), ] ); } private function get_document_types() { $document_types = Plugin::$instance->documents->get_document_types( [ 'show_in_library' => true, ] ); $data = []; foreach ( $document_types as $name => $document_type ) { $data[ $name ] = $document_type::get_title(); } return $data; } public function print_content() { if ( ! $this->print_preview_callback ) { $this->print_preview_callback = [ $this, 'print_thumbnail_preview_callback' ]; } call_user_func( $this->print_preview_callback ); } private function print_thumbnail_preview_callback() { $doc = Plugin::$instance->documents->get_current(); if ( ! $doc ) { $render_mode = Plugin::$instance->frontend->render_mode_manager->get_current(); if ( $render_mode instanceof Render_Mode_Preview ) { $doc = $render_mode->get_document(); } } if ( ! $doc ) { echo '<div class="elementor-alert elementor-alert-danger">' . esc_html__( 'Document not found for preview.', 'elementor' ) . '</div>'; return; } Plugin::$instance->documents->switch_to_document( $doc ); $content = $doc->get_content( true ); echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } cloud-library/documents/cloud-template-preview.php 0000644 00000002311 15252521350 0016421 0 ustar 00 <?php namespace Elementor\Modules\CloudLibrary\Documents; use Elementor\Core\Base\Document; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor preview library document. * * @since 3.29.0 */ class Cloud_Template_Preview extends Document { const TYPE = 'cloud-template-preview'; public static function get_properties() { $properties = parent::get_properties(); $properties['admin_tab_group'] = ''; $properties['has_elements'] = true; $properties['is_editable'] = true; $properties['show_in_library'] = false; $properties['show_on_admin_bar'] = false; $properties['show_in_finder'] = false; $properties['register_type'] = true; $properties['support_conditions'] = false; $properties['support_page_layout'] = false; return $properties; } public static function get_type(): string { return self::TYPE; } public static function get_title(): string { return esc_html__( 'Cloud Template Preview', 'elementor' ); } public static function get_plural_title(): string { return esc_html__( 'Cloud Template Previews', 'elementor' ); } public function get_content( $with_css = false ) { return do_shortcode( parent::get_content( $with_css ) ); } } cloud-library/connect/cloud-library.php 0000644 00000022301 15252521350 0014224 0 ustar 00 <?php namespace Elementor\Modules\CloudLibrary\Connect; use Elementor\Core\Common\Modules\Connect\Apps\Library; use Elementor\Core\Utils\Exceptions; use Elementor\Modules\CloudLibrary\Render_Mode_Preview; use Elementor\TemplateLibrary\Source_Cloud; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Cloud_Library extends Library { public function get_title(): string { return esc_html__( 'Cloud Library', 'elementor' ); } protected function get_api_url(): string { return 'https://cloud-library.prod.builder.elementor.red/api/v1/cloud-library'; } public function get_resources( $args = [] ): array { $templates = []; $endpoint = 'resources'; $query_string = http_build_query( [ 'limit' => isset( $args['limit'] ) ? (int) $args['limit'] : null, 'offset' => isset( $args['offset'] ) ? (int) $args['offset'] : null, 'search' => isset( $args['search'] ) ? $args['search'] : null, 'parentId' => isset( $args['parentId'] ) ? $args['parentId'] : null, 'templateType' => isset( $args['templateType'] ) ? $args['templateType'] : null, 'orderBy' => isset( $args['orderby'] ) ? $args['orderby'] : null, 'order' => isset( $args['order'] ) ? strtoupper( $args['order'] ) : null, ] ); $endpoint .= '?' . $query_string; $cloud_templates = $this->http_request( 'GET', $endpoint, $args, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); if ( is_wp_error( $cloud_templates ) || ! is_array( $cloud_templates['data'] ) ) { return $templates; } foreach ( $cloud_templates['data'] as $cloud_template ) { $templates[] = $this->prepare_template( $cloud_template ); } return [ 'templates' => $templates, 'total' => $cloud_templates['total'], ]; } /** * @return array|\WP_Error */ public function get_resource( array $args ) { return $this->http_request( 'GET', 'resources/' . $args['id'], $args, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); } protected function prepare_template( array $template_data ): array { $template = [ 'template_id' => $template_data['id'], 'source' => 'cloud', 'type' => $template_data['templateType'], 'subType' => $template_data['type'], 'title' => $template_data['title'], 'status' => $template_data['status'], 'author' => $template_data['authorEmail'], 'human_date' => date_i18n( get_option( 'date_format' ), strtotime( $template_data['createdAt'] ) ), 'export_link' => $this->get_export_link( $template_data['id'] ), 'hasPageSettings' => $template_data['hasPageSettings'], 'parentId' => $template_data['parentId'], 'preview_url' => esc_url_raw( $template_data['previewUrl'] ?? '' ), 'generate_preview_url' => esc_url_raw( $this->generate_preview_url( $template_data ) ?? '' ), ]; if ( ! empty( $template_data['content'] ) ) { $template['content'] = $template_data['content']; } return $template; } private function generate_preview_url( $template_data ): ?string { if ( ! empty( $template_data['previewUrl'] ) || Source_Cloud::FOLDER_RESOURCE_TYPE === $template_data['type'] || empty( $template_data['id'] ) ) { return null; } $template_id = $template_data['id']; $query_args = [ 'render_mode_nonce' => wp_create_nonce( 'render_mode_' . $template_id ), 'template_id' => $template_id, 'render_mode' => Render_Mode_Preview::MODE, ]; return set_url_scheme( add_query_arg( $query_args, site_url() ) ); } private function get_export_link( $template_id ) { return add_query_arg( [ 'action' => 'elementor_library_direct_actions', 'library_action' => 'export_template', 'source' => 'cloud', '_nonce' => wp_create_nonce( 'elementor_ajax' ), 'template_id' => $template_id, ], admin_url( 'admin-ajax.php' ) ); } public function post_resource( $data ): array { $resource = [ 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => wp_json_encode( $data ), ]; return $this->http_request( 'POST', 'resources', $resource, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); } public function post_bulk_resources( $data ): array { $resource = [ 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => wp_json_encode( $data ), 'timeout' => 120, ]; return $this->http_request( 'POST', 'resources/bulk', $resource, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); } public function delete_resource( $template_id ): bool { $request = $this->http_request( 'DELETE', 'resources/' . $template_id ); if ( isset( $request->errors[204] ) && 'No Content' === $request->errors[204][0] ) { return true; } if ( is_wp_error( $request ) ) { return false; } return true; } public function update_resource( array $template_data ) { $endpoint = 'resources/' . $template_data['id']; $request = $this->http_request( 'PATCH', $endpoint, [ 'body' => $template_data ], [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); if ( is_wp_error( $request ) ) { return false; } return true; } public function update_resource_preview( $template_id, $file_data ) { $endpoint = 'resources/' . $template_id . '/preview'; $boundary = wp_generate_password( 24, false ); $headers = [ 'Content-Type' => 'multipart/form-data; boundary=' . $boundary, ]; $body = $this->generate_multipart_payload( $file_data, $boundary, $template_id . '_preview.png' ); $payload = [ 'headers' => $headers, 'body' => $body, ]; $response = $this->http_request( 'PATCH', $endpoint, $payload, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, 'timeout' => 120, ]); if ( is_wp_error( $response ) || empty( $response['preview_url'] ) ) { $error_message = esc_html__( 'Failed to save preview.', 'elementor' ); throw new \Exception( $error_message, Exceptions::INTERNAL_SERVER_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return $response['preview_url']; } public function mark_preview_as_failed( $template_id, $error ) { $endpoint = 'resources/' . $template_id . '/preview'; $payload = [ 'body' => [ 'error' => $error, ], ]; $response = $this->http_request( 'PATCH', $endpoint, $payload, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ]); if ( is_wp_error( $response ) ) { $error_message = esc_html__( 'Failed to mark preview as failed.', 'elementor' ); throw new \Exception( $error_message, Exceptions::INTERNAL_SERVER_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return $response; } /** * @param $file_data * @param $boundary * @param $file_name * @return string */ private function generate_multipart_payload( $file_data, $boundary, $file_name ): string { $payload = ''; // Append the file $payload .= "--{$boundary}\r\n"; $payload .= 'Content-Disposition: form-data; name="file"; filename="' . esc_attr( $file_name ) . "\"\r\n"; $payload .= "Content-Type: image/png\r\n\r\n"; $payload .= $file_data . "\r\n"; $payload .= "--{$boundary}--\r\n"; return $payload; } public function bulk_delete_resources( $template_ids ) { $endpoint = 'resources/bulk'; $endpoint .= '?ids=' . implode( ',', $template_ids ); $response = $this->http_request( 'DELETE', $endpoint, [], [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); if ( isset( $response->errors[204] ) ) { return true; } if ( is_wp_error( $response ) ) { return $response; } return true; } public function bulk_undo_delete_resources( $template_ids ) { $endpoint = 'resources/bulk-delete/undo'; $body = wp_json_encode( [ 'ids' => $template_ids ] ); $request = [ 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => $body, ]; $response = $this->http_request( 'POST', $endpoint, $request, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); if ( is_wp_error( $response ) ) { return $response; } return true; } public function get_bulk_resources_with_content( $args = [] ): array { $templates = []; $endpoint = 'resources/bulk'; $query_string = http_build_query( [ 'ids' => implode( ',', $args['from_template_id'] ), ] ); $endpoint .= '?' . $query_string; $cloud_templates = $this->http_request( 'GET', $endpoint, $args, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); if ( is_wp_error( $cloud_templates ) || ! is_array( $cloud_templates ) ) { return $templates; } foreach ( $cloud_templates as $cloud_template ) { $templates[] = $this->prepare_template( $cloud_template ); } return $templates; } public function bulk_move_templates( array $template_data ) { $endpoint = 'resources/move'; $args = [ 'body' => wp_json_encode( $template_data ), 'headers' => [ 'Content-Type' => 'application/json' ], ]; $request = $this->http_request( 'PATCH', $endpoint, $args, [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); if ( is_wp_error( $request ) ) { return false; } return true; } /** * @return array|\WP_Error */ public function get_quota() { if ( ! $this->is_connected() ) { return new \WP_Error( 'not_connected', esc_html__( 'Not connected', 'elementor' ) ); } return $this->http_request( 'GET', 'quota', [], [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, ] ); } protected function init() {} } wp-cli/library.php 0000644 00000015474 15252521350 0010127 0 ustar 00 <?php namespace Elementor\Modules\WpCli; use Elementor\Api; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Page Builder cli tools. */ class Library extends \WP_CLI_Command { /** * Sync Elementor Library. * * [--network] * Sync library in all the sites in the network. * * [--force] * Force sync even if it's looks like that the library is already up to date. * * ## EXAMPLES * * 1. wp elementor library sync * - This will sync the library with Elementor cloud library. * * 2. wp elementor library sync --force * - This will sync the library with Elementor cloud even if it's looks like that the library is already up to date. * * 3. wp elementor library sync --network * - This will sync the library with Elementor cloud library for each site in the network if needed. * * @since 2.8.0 * @access public */ public function sync( $args, $assoc_args ) { $network = isset( $assoc_args['network'] ) && is_multisite(); if ( $network ) { $blog_ids = get_sites( [ 'fields' => 'ids', 'number' => 0, ] ); foreach ( $blog_ids as $blog_id ) { switch_to_blog( $blog_id ); \WP_CLI::line( 'Site #' . $blog_id . ' - ' . get_option( 'blogname' ) ); $this->do_sync( isset( $assoc_args['force'] ) ); \WP_CLI::success( 'Done! - ' . get_option( 'home' ) ); restore_current_blog(); } } else { $this->do_sync( isset( $assoc_args['force'] ) ); \WP_CLI::success( 'Done!' ); } } /** * Import template files to the Library. * * [--returnType] * Forms of output. Possible values are 'ids', 'info'. * if this parameter won't be specified, the import info will be output. * * ## EXAMPLES * * 1. wp elementor library import <file-path> * - This will import a file or a zip of multiple files to the library. * - file-path can be a path or url. * * 2. wp elementor library import <file-path> --returnType=info,ids * * @param $args * @param $assoc_args * * @since 2.8.0 * @access public */ public function import( $args, $assoc_args ) { if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please set file path.' ); } $file = $args[0]; $imported_items_ids = []; $return_type = \WP_CLI\Utils\get_flag_value( $assoc_args, 'returnType', 'info' ); /** @var Source_Local $source */ $source = Plugin::$instance->templates_manager->get_source( 'local' ); if ( filter_var( $file, FILTER_VALIDATE_URL ) ) { $tmp_path = download_url( $file ); if ( is_wp_error( $tmp_path ) ) { \WP_CLI::error( $tmp_path->get_error_message() ); } $file = $tmp_path; } $imported_items = $source->import_template( basename( $file ), $file ); if ( is_wp_error( $imported_items ) ) { \WP_CLI::error( $imported_items->get_error_message() ); } foreach ( $imported_items as $item ) { $imported_items_ids[] = $item['template_id']; } $imported_items_ids = implode( ',', $imported_items_ids ); if ( 'ids' === $return_type ) { \WP_CLI::line( $imported_items_ids ); } else { \WP_CLI::success( count( $imported_items ) . ' item(s) has been imported.' ); } if ( isset( $tmp_path ) ) { // Remove the temporary file, now that we're done with it. Plugin::$instance->uploads_manager->remove_file_or_dir( $file ); } } /** * Import all template files from a directory. * * ## EXAMPLES * * 1. wp elementor library import-dir <file-path> * - This will import all JSON files from <file-path> * * @param $args * * @since 3.4.7 * @access public * @alias import-dir */ public function import_dir( $args ) { if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please set dir path.' ); } $dir = $args[0]; if ( ! file_exists( $dir ) ) { \WP_CLI::error( "Dir `{$dir}` not found." ); } $files = glob( $dir . '/*.json' ); if ( empty( $files ) ) { \WP_CLI::error( 'Files not found.' ); } /** @var Source_Local $source */ $source = Plugin::$instance->templates_manager->get_source( 'local' ); $succeed = []; $errors = []; foreach ( $files as $file ) { $basename = basename( $file ); if ( ! file_exists( $file ) ) { $errors[ $basename ] = $file . ' file not found.'; continue; } $imported_items = $source->import_template( $basename, $file ); if ( is_wp_error( $imported_items ) ) { $errors[ $basename ] = $imported_items->get_error_message(); } else { $succeed[ $basename ] = true; } } $succeed_message = count( $succeed ) . ' item(s) has been imported.'; if ( ! empty( $errors ) ) { $error_message = var_export( $errors, 1 ); if ( ! empty( $succeed ) ) { $error_message = $succeed_message . ' ' . count( $errors ) . ' has errors: ' . $error_message; } \WP_CLI::error( $error_message ); } \WP_CLI::success( $succeed_message ); } /** * Connect site to Elementor Library. * (Network is not supported) * * --user * The user to connect <id|login|email> * * --token * A connect token from Elementor Account Dashboard. * * ## EXAMPLES * * 1. wp elementor library connect --user=admin --token=<connect-cli-token> * - This will connect the admin to Elementor library. * * @param $args * @param $assoc_args * * @since 2.8.0 * @access public */ public function connect( $args, $assoc_args ) { if ( ! get_current_user_id() ) { \WP_CLI::error( 'Please set user to connect (--user=<id|login|email>).' ); } if ( empty( $assoc_args['token'] ) ) { \WP_CLI::error( 'Please set connect token.' ); } $_REQUEST['mode'] = 'cli'; $_REQUEST['token'] = $assoc_args['token']; $app = $this->get_library_app(); $app->set_auth_mode( 'cli' ); $app->action_authorize(); $app->action_get_token(); } /** * Disconnect site from Elementor Library. * * --user * The user to disconnect <id|login|email> * * ## EXAMPLES * * 1. wp elementor library disconnect --user=admin * - This will disconnect the admin from Elementor library. * * @param $args * @param $assoc_args * * @since 2.8.0 * @access public */ public function disconnect() { if ( ! get_current_user_id() ) { \WP_CLI::error( 'Please set user to connect (--user=<id|login|email>).' ); } $_REQUEST['mode'] = 'cli'; $this->get_library_app()->action_disconnect(); } private function do_sync() { $data = Api::get_library_data( true ); if ( empty( $data ) ) { \WP_CLI::error( 'Cannot sync library.' ); } } /** * @return \Elementor\Core\Common\Modules\Connect\Apps\Library */ private function get_library_app() { $connect = Plugin::$instance->common->get_component( 'connect' ); $app = $connect->get_app( 'library' ); // Before init. if ( ! $app ) { $connect->init(); $app = $connect->get_app( 'library' ); } return $app; } } wp-cli/cli-logger.php 0000644 00000001127 15252521350 0010475 0 ustar 00 <?php namespace Elementor\Modules\WpCli; use Elementor\Core\Logger\Loggers\Db; use Elementor\Core\Logger\Items\Log_Item_Interface; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Cli_Logger extends Db { public function save_log( Log_Item_Interface $item ) { $message = $item->format( 'raw' ); switch ( $item->type ) { case self::LEVEL_WARNING: \WP_CLI::warning( $message ); break; case self::LEVEL_ERROR: \WP_CLI::error( $message, false ); break; default: \WP_CLI::log( $message ); break; } parent::save_log( $item ); } } wp-cli/update.php 0000644 00000005055 15252521350 0007737 0 ustar 00 <?php namespace Elementor\Modules\WpCli; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Page Builder cli tools. */ class Update extends \WP_CLI_Command { /** * Update the DB after plugin upgrade. * * [--network] * Update DB in all the sites in the network. * * [--force] * Force update even if it's looks like that update is in progress. * * * ## EXAMPLES * * 1. wp elementor update db * - This will Upgrade the DB if needed. * * 2. wp elementor update db --force * - This will Upgrade the DB even if another process is running. * * 3. wp elementor update db --network * - This will Upgrade the DB for each site in the network if needed. * * @since 2.4.0 * @access public * * @param $args * @param $assoc_args */ public function db( $args, $assoc_args ) { $network = ! empty( $assoc_args['network'] ) && is_multisite(); if ( $network ) { $blog_ids = get_sites( [ 'fields' => 'ids', 'number' => 0, ] ); foreach ( $blog_ids as $blog_id ) { switch_to_blog( $blog_id ); \WP_CLI::line( 'Site #' . $blog_id . ' - ' . get_option( 'blogname' ) ); $this->do_db_upgrade( $assoc_args ); \WP_CLI::success( 'Done! - ' . get_option( 'home' ) ); restore_current_blog(); } } else { $this->do_db_upgrade( $assoc_args ); } } protected function get_update_db_manager_class() { return '\Elementor\Core\Upgrade\Manager'; } protected function do_db_upgrade( $assoc_args ) { $manager_class = $this->get_update_db_manager_class(); /** @var \Elementor\Core\Upgrade\Manager $manager */ $manager = new $manager_class(); $updater = $manager->get_task_runner(); if ( $updater->is_process_locked() && empty( $assoc_args['force'] ) ) { \WP_CLI::warning( 'Oops! Process is already running. Use --force to force run.' ); return; } if ( ! $manager->should_upgrade() ) { \WP_CLI::success( 'The DB is already updated!' ); return; } $callbacks = $manager->get_upgrade_callbacks(); $did_tasks = false; if ( ! empty( $callbacks ) ) { Plugin::$instance->logger->get_logger()->info( 'Update DB has been started', [ 'meta' => [ 'plugin' => $manager->get_plugin_label(), 'from' => $manager->get_current_version(), 'to' => $manager->get_new_version(), ], ] ); $updater->handle_immediately( $callbacks ); $did_tasks = true; } $manager->on_runner_complete( $did_tasks ); \WP_CLI::success( count( $callbacks ) . ' updates(s) has been applied.' ); } } wp-cli/module.php 0000644 00000002452 15252521350 0007740 0 ustar 00 <?php namespace Elementor\Modules\WpCli; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Logger\Manager as Logger; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { /** * Get module name. * * @since 2.0.0 * @access public * * @return string Module name. */ public function get_name() { return 'wp-cli'; } /** * @since 2.1.0 * @access public * @static */ public static function is_active() { return defined( 'WP_CLI' ) && WP_CLI; } /** * @param Logger $logger * @access public */ public function register_cli_logger( $logger ) { $logger->register_logger( 'cli', 'Elementor\Modules\WpCli\Cli_Logger' ); $logger->set_default_logger( 'cli' ); } public function init_common() { Plugin::$instance->init_common(); } /** * * @since 2.1.0 * @access public */ public function __construct() { add_action( 'cli_init', [ $this, 'init_common' ] ); add_action( 'elementor/loggers/register', [ $this, 'register_cli_logger' ] ); \WP_CLI::add_command( 'elementor', '\Elementor\Modules\WpCli\Command' ); \WP_CLI::add_command( 'elementor update', '\Elementor\Modules\WpCli\Update' ); \WP_CLI::add_command( 'elementor library', '\Elementor\Modules\WpCli\Library' ); } } wp-cli/command.php 0000644 00000011140 15252521350 0010063 0 ustar 00 <?php namespace Elementor\Modules\WpCli; use Elementor\Api; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Page Builder cli tools. */ class Command extends \WP_CLI_Command { /** * Flush the Elementor Page Builder CSS Cache. * * [--network] * Flush CSS Cache for all the sites in the network. * * [--regenerate] * Re-create the CSS files. Otherwise they will be created by a page visit. * * ## EXAMPLES * * 1. wp elementor flush-css * - This will flush the CSS files for elementor page builder. * * 2. wp elementor flush-css --network * - This will flush the CSS files for elementor page builder for all the sites in the network. * * 3. wp elementor flush-css --regenerate * - This will flush the CSS files for elementor page builder and re-create the new CSS files. * * @since 2.1.0 * @access public * @alias flush-css */ public function flush_css( $args, $assoc_args ) { $network = ! empty( $assoc_args['network'] ) && is_multisite(); $should_regenerate = ! empty( $assoc_args['regenerate'] ); if ( $network ) { $blog_ids = get_sites( [ 'fields' => 'ids', 'number' => 0, ] ); foreach ( $blog_ids as $blog_id ) { switch_to_blog( $blog_id ); $this->handle_flush( $should_regenerate, 'Flushed the Elementor CSS Cache for site - ' . get_option( 'home' ) ); restore_current_blog(); } } else { $this->handle_flush( $should_regenerate, 'Flushed the Elementor CSS Cache' ); } } private function handle_flush( bool $should_regenerate, string $success_message ): void { Plugin::$instance->files_manager->clear_cache(); if ( $should_regenerate ) { Plugin::$instance->files_manager->generate_css(); } \WP_CLI::success( $success_message ); } /** * Print system info powered by Elementor * * ## EXAMPLES * * 1. wp elementor system-info * - This will print the System Info in JSON format * * @since 3.0.11 * @access public * @alias system-info */ public function system_info() { echo wp_json_encode( \Elementor\Tracker::get_tracking_data() ); } /** * Replace old URLs with new URLs in all Elementor pages. * * [--force] * Suppress error messages. instead, return "0 database rows affected.". * * ## EXAMPLES * * 1. wp elementor replace-urls <old> <new> * - This will replace all <old> URLs with the <new> URL. * * 2. wp elementor replace-urls <old> <new> --force * - This will replace all <old> URLs with the <new> URL without throw errors. * * @access public * @alias replace-urls */ public function replace_urls( $args, $assoc_args ) { if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please set the `old` URL' ); } if ( empty( $args[1] ) ) { \WP_CLI::error( 'Please set the `new` URL' ); } try { $results = Utils::replace_urls( $args[0], $args[1] ); \WP_CLI::success( $results ); } catch ( \Exception $e ) { if ( isset( $assoc_args['force'] ) ) { \WP_CLI::success( '0 database rows affected.' ); } else { \WP_CLI::error( $e->getMessage() ); } } } /** * Sync Elementor Library. * * ## EXAMPLES * * 1. wp elementor sync-library * - This will sync the library with Elementor cloud library. * * @since 2.1.0 * @access public * @alias sync-library */ public function sync_library( $args, $assoc_args ) { // TODO: // \WP_CLI::warning( 'command is deprecated since 2.8.0 Please use: wp elementor library sync' ); $data = Api::get_library_data( true ); if ( empty( $data ) ) { \WP_CLI::error( 'Cannot sync library.' ); } \WP_CLI::success( 'Library has been synced.' ); } /** * Import template files to the Library. * * ## EXAMPLES * * 1. wp elementor import-library <file-path> * - This will import a file or a zip of multiple files to the library. * * @since 2.1.0 * @access public * @alias import-library */ public function import_library( $args, $assoc_args ) { // TODO: // \WP_CLI::warning( 'command is deprecated since 2.8.0 Please use: wp elementor library import' ); if ( empty( $args[0] ) ) { \WP_CLI::error( 'Please set file path.' ); } /** @var Source_Local $source */ $source = Plugin::$instance->templates_manager->get_source( 'local' ); $imported_items = $source->import_template( basename( $args[0] ), $args[0] ); if ( is_wp_error( $imported_items ) ) { \WP_CLI::error( $imported_items->get_error_message() ); } \WP_CLI::success( count( $imported_items ) . ' item(s) has been imported.' ); } } pro-free-trial-popup/module.php 0000644 00000013233 15252521350 0012535 0 ustar 00 <?php /** * Pro Free Trial Popup Module * * @package Elementor\Modules\ProFreeTrialPopup * @since 3.32.0 */ namespace Elementor\Modules\ProFreeTrialPopup; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Core\Utils\Ab_Test; use Elementor\Core\Isolation\Elementor_Adapter; use Elementor\Core\Isolation\Elementor_Adapter_Interface; use Elementor\Includes\EditorAssetsAPI; use Elementor\Modules\ElementorCounter\Module as Elementor_Counter; use Elementor\Utils; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const EXPERIMENT_NAME = 'e_pro_free_trial_popup'; const MODULE_NAME = 'pro-free-trial-popup'; const POPUP_DISPLAYED_OPTION = '_e_pro_free_trial_popup_displayed'; const AB_TEST_NAME = 'pro_free_trial_popup'; const REQUIRED_VISIT_COUNT = 4; const EXTERNAL_DATA_URL = 'https://assets.elementor.com/pro-free-trial-popup/v1/pro-free-trial-popup.json'; const ACTIVE = 'active'; private Elementor_Adapter_Interface $elementor_adapter; private array $external_data; public function __construct() { parent::__construct(); if ( ! current_user_can( 'manage_options' ) ) { return; } if ( ! Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ) ) { return; } if ( Utils::has_pro() ) { return; } $this->external_data = $this->get_external_data(); if ( ! EditorAssetsAPI::has_valid_nested_array( $this->external_data, [ self::MODULE_NAME, 0 ] ) ) { return false; } $this->elementor_adapter = new Elementor_Adapter(); add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'maybe_enqueue_popup' ] ); } public function get_name() { return self::MODULE_NAME; } public static function get_experimental_data(): array { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Pro Free Trial Popup', 'elementor' ), 'description' => esc_html__( 'Show Pro free trial popup on 4th editor visit', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_INACTIVE, 'new_site' => [ 'default_active' => true, 'minimum_installation_version' => '3.32.0', ], ]; } /** * Check if popup should be enqueued and enqueue if needed */ public function maybe_enqueue_popup(): void { if ( ! $this->should_show_popup() ) { return; } $this->enqueue_scripts(); $this->set_popup_as_displayed(); } /** * Determine if popup should be shown * * @return bool True if popup should be shown */ private function should_show_popup(): bool { if ( ! $this->is_feature_enabled() ) { return false; } if ( $this->is_before_fourth_visit() ) { return false; } if ( $this->has_popup_been_displayed() ) { return false; } $result = Ab_Test::should_show_feature( self::AB_TEST_NAME ); return $result; } /** * Check if feature is enabled via external JSON * * @return bool True if feature is enabled */ private function is_feature_enabled(): bool { $popup_data = $this->extract_popup_data( $this->external_data ); $status = $popup_data['status'] ?? ''; return ! empty( $status ) && self::ACTIVE === $status; } /** * Get external JSON data * * @return array External data or empty array on failure */ private function get_external_data(): array { $editor_assets_api = new EditorAssetsAPI( $this->get_api_config() ); return $editor_assets_api->get_assets_data(); } private function get_api_config(): array { return [ EditorAssetsAPI::ASSETS_DATA_URL => self::EXTERNAL_DATA_URL, EditorAssetsAPI::ASSETS_DATA_TRANSIENT_KEY => '_elementor_pro_free_trial_data', EditorAssetsAPI::ASSETS_DATA_KEY => self::MODULE_NAME, ]; } /** * Check if current visit is before the 4th visit * * @return bool True if before 4th visit */ private function is_before_fourth_visit(): bool { if ( ! $this->elementor_adapter ) { return true; } $editor_visit_count = $this->elementor_adapter->get_count( Elementor_Counter::EDITOR_COUNTER_KEY ); return $editor_visit_count < self::REQUIRED_VISIT_COUNT; } /** * Check if popup has already been displayed to this user * * @return bool True if already displayed */ private function has_popup_been_displayed(): bool { return (bool) get_user_meta( $this->get_current_user_id(), self::POPUP_DISPLAYED_OPTION, true ); } /** * Mark popup as displayed for current user */ private function set_popup_as_displayed(): void { $user_id = $this->get_current_user_id(); update_user_meta( $user_id, self::POPUP_DISPLAYED_OPTION, true ); } /** * Enqueue popup scripts */ private function enqueue_scripts(): void { $min_suffix = Utils::is_script_debug() ? '' : '.min'; $script_url = ELEMENTOR_ASSETS_URL . 'js/pro-free-trial-popup' . $min_suffix . '.js'; wp_enqueue_script( self::MODULE_NAME, $script_url, [ 'react', 'react-dom', 'elementor-common', 'elementor-v2-ui', ], ELEMENTOR_VERSION, true ); $popup_data = $this->extract_popup_data( $this->external_data ); wp_localize_script( self::MODULE_NAME, 'elementorProFreeTrialData', $popup_data ); wp_set_script_translations( self::MODULE_NAME, 'elementor' ); } /** * Extract popup data from external data * * @param array $external_data The full external data array * @return array Popup data or empty array if not found */ private function extract_popup_data( array $external_data ): array { return $external_data[ self::MODULE_NAME ][0]; } /** * Get current user ID * * @return int Current user ID */ private function get_current_user_id(): int { $current_user = wp_get_current_user(); return $current_user->ID ?? 0; } } page-templates/module.php 0000644 00000025535 15252521350 0011464 0 ustar 00 <?php namespace Elementor\Modules\PageTemplates; use Elementor\Controls_Manager; use Elementor\Core\Base\Document; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Kits\Documents\Kit; use Elementor\Plugin; use Elementor\Utils; use Elementor\Core\DocumentTypes\PageBase; use Elementor\Modules\Library\Documents\Page as LibraryPageDocument; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor page templates module. * * Elementor page templates module handler class is responsible for registering * and managing Elementor page templates modules. * * @since 2.0.0 */ class Module extends BaseModule { /** * The of the theme. */ const TEMPLATE_THEME = 'elementor_theme'; /** * Elementor Canvas template name. */ const TEMPLATE_CANVAS = 'elementor_canvas'; /** * Elementor Header & Footer template name. */ const TEMPLATE_HEADER_FOOTER = 'elementor_header_footer'; /** * Print callback. * * Holds the page template callback content. * * @since 2.0.0 * @access protected * * @var callable */ protected $print_callback; /** * Get module name. * * Retrieve the page templates module name. * * @since 2.0.0 * @access public * * @return string Module name. */ public function get_name() { return 'page-templates'; } /** * Template include. * * Update the path for the Elementor Canvas template. * * Fired by `template_include` filter. * * @since 2.0.0 * @access public * * @param string $template The path of the template to include. * * @return string The path of the template to include. */ public function template_include( $template ) { if ( is_singular() ) { $document = Plugin::$instance->documents->get_doc_for_frontend( get_the_ID() ); if ( $document && $document::get_property( 'support_wp_page_templates' ) ) { $page_template = $document->get_meta( '_wp_page_template' ); $template_path = $this->get_template_path( $page_template ); if ( self::TEMPLATE_THEME !== $page_template && ! $template_path && $document->is_built_with_elementor() ) { $kit_default_template = Plugin::$instance->kits_manager->get_current_settings( 'default_page_template' ); $template_path = $this->get_template_path( $kit_default_template ); } if ( $template_path ) { $template = $template_path; Plugin::$instance->inspector->add_log( 'Page Template', Plugin::$instance->inspector->parse_template_path( $template ), $document->get_edit_url() ); } } } return $template; } /** * Add WordPress templates. * * Adds Elementor templates to all the post types that support * Elementor. * * Fired by `init` action. * * @since 2.0.0 * @access public */ public function add_wp_templates_support() { $post_types = get_post_types_by_support( 'elementor' ); foreach ( $post_types as $post_type ) { add_filter( "theme_{$post_type}_templates", [ $this, 'add_page_templates' ], 10, 4 ); } } /** * Add page templates. * * Add the Elementor page templates to the theme templates. * * Fired by `theme_{$post_type}_templates` filter. * * @since 2.0.0 * @access public * @static * * @param array $page_templates Array of page templates. Keys are filenames, checks are translated names. * @param \WP_Theme $wp_theme * @param \WP_Post $post * * @return array Page templates. */ public function add_page_templates( $page_templates, $wp_theme, $post ) { if ( $post ) { // FIX ME: Gutenberg not send $post as WP_Post object, just the post ID. $post_id = ! empty( $post->ID ) ? $post->ID : $post; $document = Plugin::$instance->documents->get( $post_id ); if ( $document && ! $document::get_property( 'support_wp_page_templates' ) ) { return $page_templates; } } $page_templates = [ self::TEMPLATE_CANVAS => esc_html__( 'Elementor Canvas', 'elementor' ), self::TEMPLATE_HEADER_FOOTER => esc_html__( 'Elementor Full Width', 'elementor' ), self::TEMPLATE_THEME => esc_html__( 'Theme', 'elementor' ), ] + $page_templates; return $page_templates; } /** * Set print callback. * * Set the page template callback. * * @since 2.0.0 * @access public * * @param callable $callback */ public function set_print_callback( $callback ) { $this->print_callback = $callback; } /** * Print callback. * * Prints the page template content using WordPress loop. * * @since 2.0.0 * @access public */ public function print_callback() { while ( have_posts() ) : the_post(); the_content(); endwhile; } /** * Print content. * * Prints the page template content. * * @since 2.0.0 * @access public */ public function print_content() { if ( ! $this->print_callback ) { $this->print_callback = [ $this, 'print_callback' ]; } call_user_func( $this->print_callback ); } /** * Get page template path. * * Retrieve the path for any given page template. * * @since 2.0.0 * @access public * * @param string $page_template The page template name. * * @return string Page template path. */ public function get_template_path( $page_template ) { $template_path = ''; switch ( $page_template ) { case self::TEMPLATE_CANVAS: $template_path = __DIR__ . '/templates/canvas.php'; break; case self::TEMPLATE_HEADER_FOOTER: $template_path = __DIR__ . '/templates/header-footer.php'; break; } return $template_path; } /** * Register template control. * * Adds custom controls to any given document. * * Fired by `update_post_metadata` action. * * @since 2.0.0 * @access public * * @param Document $document The document instance. */ public function action_register_template_control( $document ) { if ( ( $document instanceof PageBase || $document instanceof LibraryPageDocument ) && $document::get_property( 'support_page_layout' ) ) { $this->register_template_control( $document ); } } /** * Register template control. * * Adds custom controls to any given document. * * @since 2.0.0 * @access public * * @param Document $document The document instance. * @param string $control_id Optional. The control ID. Default is `template`. */ public function register_template_control( $document, $control_id = 'template' ) { if ( ! Utils::is_cpt_custom_templates_supported() ) { return; } require_once ABSPATH . '/wp-admin/includes/template.php'; $document->start_injection( [ 'of' => 'post_status', 'fallback' => [ 'of' => 'post_title', ], ] ); $control_options = [ 'options' => array_flip( get_page_templates( null, $document->get_main_post()->post_type ) ), ]; $this->add_template_controls( $document, $control_id, $control_options ); $document->end_injection(); } /** * The $options variable is an array of $control_options to overwrite the default. */ public function add_template_controls( Document $document, $control_id, $control_options ) { // Default Control Options $default_control_options = [ 'label' => esc_html__( 'Page Layout', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'default', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), ], ]; $control_options = array_replace_recursive( $default_control_options, $control_options ); $document->add_control( $control_id, $control_options ); $document->add_control( $control_id . '_default_description', [ 'type' => Controls_Manager::RAW_HTML, 'raw' => '<b>' . esc_html__( 'The default page template as defined in Elementor Panel → Hamburger Menu → Site Settings.', 'elementor' ) . '</b>', 'content_classes' => 'elementor-descriptor', 'condition' => [ $control_id => 'default', ], ] ); $document->add_control( $control_id . '_theme_description', [ 'type' => Controls_Manager::RAW_HTML, 'raw' => '<b>' . esc_html__( 'Default Page Template from your theme.', 'elementor' ) . '</b>', 'content_classes' => 'elementor-descriptor', 'condition' => [ $control_id => self::TEMPLATE_THEME, ], ] ); $document->add_control( $control_id . '_canvas_description', [ 'type' => Controls_Manager::RAW_HTML, 'raw' => '<b>' . esc_html__( 'No header, no footer, just Elementor', 'elementor' ) . '</b>', 'content_classes' => 'elementor-descriptor', 'condition' => [ $control_id => self::TEMPLATE_CANVAS, ], ] ); $document->add_control( $control_id . '_header_footer_description', [ 'type' => Controls_Manager::RAW_HTML, 'raw' => '<b>' . esc_html__( 'This template includes the header, full-width content and footer', 'elementor' ) . '</b>', 'content_classes' => 'elementor-descriptor', 'condition' => [ $control_id => self::TEMPLATE_HEADER_FOOTER, ], ] ); if ( $document instanceof Kit ) { $document->add_control( 'reload_preview_description', [ 'type' => Controls_Manager::RAW_HTML, 'raw' => esc_html__( 'Changes will be reflected in the preview only after the page reloads.', 'elementor' ), 'content_classes' => 'elementor-descriptor', ] ); } } /** * Filter metadata update. * * Filters whether to update metadata of a specific type. * * Elementor don't allow WordPress to update the parent page template * during `wp_update_post`. * * Fired by `update_{$meta_type}_metadata` filter. * * @since 2.0.0 * @access public * * @param bool $check Whether to allow updating metadata for the given type. * @param int $object_id Object ID. * @param string $meta_key Meta key. * * @return bool Whether to allow updating metadata of a specific type. */ public function filter_update_meta( $check, $object_id, $meta_key ) { if ( '_wp_page_template' === $meta_key && Plugin::$instance->common ) { /** @var \Elementor\Core\Common\Modules\Ajax\Module $ajax */ $ajax = Plugin::$instance->common->get_component( 'ajax' ); $ajax_data = $ajax->get_current_action_data(); $is_autosave_action = $ajax_data && 'save_builder' === $ajax_data['action'] && Document::STATUS_AUTOSAVE === $ajax_data['data']['status']; // Don't allow WP to update the parent page template. // (during `wp_update_post` from page-settings or save_plain_text). if ( $is_autosave_action && ! wp_is_post_autosave( $object_id ) && Document::STATUS_DRAFT !== get_post_status( $object_id ) ) { $check = false; } } return $check; } /** * Page templates module constructor. * * Initializing Elementor page templates module. * * @since 2.0.0 * @access public */ public function __construct() { add_action( 'init', [ $this, 'add_wp_templates_support' ] ); add_filter( 'template_include', [ $this, 'template_include' ], 11 /* After Plugins/WooCommerce */ ); add_action( 'elementor/documents/register_controls', [ $this, 'action_register_template_control' ] ); add_filter( 'update_post_metadata', [ $this, 'filter_update_meta' ], 10, 3 ); } } page-templates/templates/canvas.php 0000644 00000002561 15252521350 0013442 0 ustar 00 <?php use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } \Elementor\Plugin::$instance->frontend->add_body_class( 'elementor-template-canvas' ); ?> <!DOCTYPE html> <html <?php language_attributes(); ?>> <head> <meta charset="<?php bloginfo( 'charset' ); ?>"> <?php if ( ! current_theme_supports( 'title-tag' ) ) : ?> <title><?php echo wp_get_document_title(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></title> <?php endif; ?> <?php wp_head(); ?> <?php // Keep the following line after `wp_head()` call, to ensure it's not overridden by another templates. Utils::print_unescaped_internal_string( Utils::get_meta_viewport( 'canvas' ) ); ?> </head> <body <?php body_class(); ?>> <?php wp_body_open(); /** * Before canvas page template content. * * Fires before the content of Elementor canvas page template. * * @since 1.0.0 */ do_action( 'elementor/page_templates/canvas/before_content' ); $module = apply_filters( 'elementor/render_mode/module', 'page-templates' ); \Elementor\Plugin::$instance->modules_manager->get_modules( $module )->print_content(); /** * After canvas page template content. * * Fires after the content of Elementor canvas page template. * * @since 1.0.0 */ do_action( 'elementor/page_templates/canvas/after_content' ); wp_footer(); ?> </body> </html> page-templates/templates/header-footer.php 0000644 00000001332 15252521350 0014706 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } \Elementor\Plugin::$instance->frontend->add_body_class( 'elementor-template-full-width' ); get_header(); /** * Before Header-Footer page template content. * * Fires before the content of Elementor Header-Footer page template. * * @since 2.0.0 */ do_action( 'elementor/page_templates/header-footer/before_content' ); \Elementor\Plugin::$instance->modules_manager->get_modules( 'page-templates' )->print_content(); /** * After Header-Footer page template content. * * Fires after the content of Elementor Header-Footer page template. * * @since 2.0.0 */ do_action( 'elementor/page_templates/header-footer/after_content' ); get_footer(); ai/module.php 0000644 00000127046 15252521350 0007145 0 ustar 00 <?php namespace Elementor\Modules\Ai; use Elementor\Controls_Manager; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Common\Modules\Connect\Module as ConnectModule; use Elementor\Element_Base; use Elementor\Modules\Ai\Feature_Intro\Product_Image_Unification_Intro; use Elementor\Plugin; use Elementor\Core\Utils\Collection; use Elementor\Modules\Ai\Connect\Ai; use Elementor\User; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const HISTORY_TYPE_ALL = 'all'; const HISTORY_TYPE_TEXT = 'text'; const HISTORY_TYPE_CODE = 'code'; const HISTORY_TYPE_IMAGE = 'images'; const HISTORY_TYPE_BLOCK = 'blocks'; const VALID_HISTORY_TYPES = [ self::HISTORY_TYPE_ALL, self::HISTORY_TYPE_TEXT, self::HISTORY_TYPE_CODE, self::HISTORY_TYPE_IMAGE, self::HISTORY_TYPE_BLOCK, ]; const MIN_PAGES_FOR_CREATE_WITH_AI_BANNER = 10; public function get_name() { return 'ai'; } public function __construct() { parent::__construct(); ( new SitePlannerConnect\Module() ); if ( is_admin() ) { ( new Preferences() )->register(); add_action( 'elementor/import-export/import-kit/runner/after-run', [ $this, 'handle_kit_install' ] ); } if ( ! $this->is_ai_enabled() ) { return; } add_filter( 'elementor/core/admin/homescreen', [ $this, 'add_create_with_ai_banner_to_homescreen' ] ); add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) { $connect_module->register_app( 'ai', Ai::get_class_name() ); } ); add_action( 'elementor/ajax/register_actions', function( $ajax ) { $handlers = [ 'ai_get_user_information' => [ $this, 'ajax_ai_get_user_information' ], 'ai_get_remote_config' => [ $this, 'ajax_ai_get_remote_config' ], 'ai_get_remote_frontend_config' => [ $this, 'ajax_ai_get_remote_frontend_config' ], 'ai_get_completion_text' => [ $this, 'ajax_ai_get_completion_text' ], 'ai_get_excerpt' => [ $this, 'ajax_ai_get_excerpt' ], 'ai_get_featured_image' => [ $this, 'ajax_ai_get_featured_image' ], 'ai_get_edit_text' => [ $this, 'ajax_ai_get_edit_text' ], 'ai_get_custom_code' => [ $this, 'ajax_ai_get_custom_code' ], 'ai_get_custom_css' => [ $this, 'ajax_ai_get_custom_css' ], 'ai_set_get_started' => [ $this, 'ajax_ai_set_get_started' ], 'ai_set_status_feedback' => [ $this, 'ajax_ai_set_status_feedback' ], 'ai_get_image_prompt_enhancer' => [ $this, 'ajax_ai_get_image_prompt_enhancer' ], 'ai_get_text_to_image' => [ $this, 'ajax_ai_get_text_to_image' ], 'ai_get_image_to_image' => [ $this, 'ajax_ai_get_image_to_image' ], 'ai_get_image_to_image_mask' => [ $this, 'ajax_ai_get_image_to_image_mask' ], 'ai_get_image_to_image_mask_cleanup' => [ $this, 'ajax_ai_get_image_to_image_mask_cleanup' ], 'ai_get_image_to_image_outpainting' => [ $this, 'ajax_ai_get_image_to_image_outpainting' ], 'ai_get_image_to_image_upscale' => [ $this, 'ajax_ai_get_image_to_image_upscale' ], 'ai_get_image_to_image_remove_background' => [ $this, 'ajax_ai_get_image_to_image_remove_background' ], 'ai_get_image_to_image_replace_background' => [ $this, 'ajax_ai_get_image_to_image_replace_background' ], 'ai_upload_image' => [ $this, 'ajax_ai_upload_image' ], 'ai_generate_layout' => [ $this, 'ajax_ai_generate_layout' ], 'ai_get_layout_prompt_enhancer' => [ $this, 'ajax_ai_get_layout_prompt_enhancer' ], 'ai_get_history' => [ $this, 'ajax_ai_get_history' ], 'ai_delete_history_item' => [ $this, 'ajax_ai_delete_history_item' ], 'ai_toggle_favorite_history_item' => [ $this, 'ajax_ai_toggle_favorite_history_item' ], 'ai_get_product_image_unification' => [ $this, 'ajax_ai_get_product_image_unification' ], 'ai_get_animation' => [ $this, 'ajax_ai_get_animation' ], 'ai_get_image_to_image_isolate_objects' => [ $this, 'ajax_ai_get_product_image_unification' ], ]; foreach ( $handlers as $tag => $callback ) { $ajax->register_ajax_action( $tag, $callback ); } } ); add_action( 'elementor/editor/before_enqueue_scripts', function() { $this->enqueue_main_script(); $this->enqueue_layout_script(); } ); add_action( 'elementor/editor/after_enqueue_styles', function() { wp_enqueue_style( 'elementor-ai-editor', $this->get_css_assets_url( 'modules/ai/editor' ), [], ELEMENTOR_VERSION ); } ); add_action( 'elementor/preview/enqueue_styles', function() { wp_enqueue_style( 'elementor-ai-layout-preview', $this->get_css_assets_url( 'modules/ai/layout-preview' ), [], ELEMENTOR_VERSION ); } ); if ( is_admin() ) { add_action( 'wp_enqueue_media', [ $this, 'enqueue_ai_media_library' ] ); add_action( 'admin_head', [ $this, 'enqueue_ai_media_library_upload_screen' ] ); if ( current_user_can( 'edit_products' ) || current_user_can( 'publish_products' ) ) { add_action( 'admin_init', [ $this, 'enqueue_ai_products_page_scripts' ] ); add_action( 'current_screen', [ $this, 'enqueue_ai_single_product_page_scripts' ] ); add_action( 'wp_ajax_elementor-ai-get-product-images', [ $this, 'get_product_images_ajax' ] ); add_action( 'wp_ajax_elementor-ai-set-product-images', [ $this, 'set_product_images_ajax' ] ); Product_Image_Unification_Intro::add_hooks(); } } add_action( 'enqueue_block_editor_assets', function() { wp_enqueue_script( 'elementor-ai-gutenberg', $this->get_js_assets_url( 'ai-gutenberg' ), [ 'jquery', 'elementor-v2-ui', 'elementor-v2-icons', 'wp-blocks', 'wp-element', 'wp-editor', 'wp-data', 'wp-components', 'wp-compose', 'wp-i18n', 'wp-hooks', 'elementor-ai-media-library', ], ELEMENTOR_VERSION, true ); wp_localize_script( 'elementor-ai-gutenberg', 'ElementorAiConfig', [ 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ), 'connect_url' => $this->get_ai_connect_url(), ] ); wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' ); }); add_filter( 'elementor/document/save/data', function ( $data ) { return $this->remove_temporary_containers( $data ); } ); add_action( 'elementor/element/common/section_effects/after_section_start', [ $this, 'register_ai_motion_effect_control' ], 10, 1 ); add_action( 'elementor/element/container/section_effects/after_section_start', [ $this, 'register_ai_motion_effect_control' ], 10, 1 ); add_action( 'elementor/element/common/_section_transform/after_section_end', [ $this, 'register_ai_hover_effect_control' ], 10, 1 ); add_action( 'elementor/element/container/_section_transform/after_section_end', [ $this, 'register_ai_hover_effect_control' ], 10, 1 ); } public function is_ai_enabled() { if ( ! Plugin::$instance->experiments->is_feature_active( 'container' ) ) { return false; } return Preferences::is_ai_enabled( get_current_user_id() ); } public function handle_kit_install( $imported_data ) { if ( ! $this->is_ai_enabled() ) { return; } if ( ! isset( $imported_data['status'] ) || 'success' !== $imported_data['status'] ) { return; } if ( ! isset( $imported_data['runner'] ) || 'site-settings' !== $imported_data['runner'] ) { return; } if ( ! isset( $imported_data['configData']['lastImportedSession']['instance_data']['site_settings']['settings']['ai'] ) ) { return; } $is_connected = $this->get_ai_app()->is_connected() && User::get_introduction_meta( 'ai_get_started' ); if ( ! $is_connected ) { return; } $last_imported_session = $imported_data['configData']['lastImportedSession']; $imported_ai_data = $last_imported_session['instance_data']['site_settings']['settings']['ai']; $this->get_ai_app()->send_event( [ 'name' => 'kit_installed', 'data' => $imported_ai_data, 'client' => [ 'name' => 'elementor', 'version' => ELEMENTOR_VERSION, 'session_id' => $last_imported_session['session_id'], ], ] ); } public function register_ai_hover_effect_control( Element_Base $element ) { if ( ! $element->get_controls( 'ai_hover_animation' ) ) { $element->add_control( 'ai_hover_animation', [ 'tabs_wrapper' => '_tabs_positioning', 'inner_tab' => '_tab_positioning_hover', 'label' => esc_html__( 'Animate With AI', 'elementor' ), 'type' => Controls_Manager::RAW_HTML, 'raw' => ' <style> .elementor-control-ai_hover_animation .elementor-control-content { display: flex; flex-direction: row; justify-content: space-between; align-items: center; } .elementor-control-ai_hover_animation .elementor-control-raw-html { display: none; } </style>', 'render_type' => 'none', 'ai' => [ 'active' => true, 'type' => 'hover_animation', ], ], [ 'position' => [ 'of' => '_transform_rotate_popover_hover', 'type' => 'control', 'at' => 'before', ], ] ); } } public function register_ai_motion_effect_control( $element ) { if ( Utils::has_pro() && ! $element->get_controls( 'ai_animation' ) ) { $element->add_control( 'ai_animation', [ 'label' => esc_html__( 'Animate With AI', 'elementor' ), 'type' => Controls_Manager::RAW_HTML, 'raw' => ' <style> .elementor-control-ai_animation .elementor-control-content { display: flex; flex-direction: row; justify-content: space-between; align-items: center; } .elementor-control-ai_animation .elementor-control-raw-html { display: none; } </style>', 'render_type' => 'none', 'ai' => [ 'active' => true, 'type' => 'animation', ], ] ); } } private function get_current_screen() { $is_wc = class_exists( 'WooCommerce' ) && post_type_exists( 'product' ); if ( ! $is_wc ) { return 'other'; } $is_products_page = isset( $_GET['post_type'] ) && 'product' === $_GET['post_type']; if ( $is_products_page ) { return 'wc-products'; } $screen = get_current_screen(); $is_single_product_page = isset( $screen->post_type ) && ( 'product' === $screen->post_type && 'post' === $screen->base ); if ( $is_single_product_page ) { return 'wc-single-product'; } return 'other'; } public function enqueue_ai_products_page_scripts() { if ( 'wc-products' !== $this->get_current_screen() ) { return; } $this->add_wc_scripts(); } public function enqueue_ai_single_product_page_scripts() { if ( 'wc-single-product' !== $this->get_current_screen() ) { return; } $this->add_wc_scripts(); } private function add_products_bulk_action( $bulk_actions ) { $bulk_actions['elementor-ai-unify-product-images'] = __( 'Unify with Elementor AI', 'elementor' ); return $bulk_actions; } public function get_product_images_ajax() { check_ajax_referer( 'elementor-ai-unify-product-images_nonce', 'nonce' ); $post_ids = isset( $_POST['post_ids'] ) ? array_map( 'intval', $_POST['post_ids'] ) : []; $is_galley_only = isset( $_POST['is_galley_only'] ) && sanitize_text_field( wp_unslash( $_POST['is_galley_only'] ) ); $image_ids = []; foreach ( $post_ids as $post_id ) { if ( $is_galley_only ) { $product = wc_get_product( $post_id ); $gallery_image_ids = $product->get_gallery_image_ids(); foreach ( $gallery_image_ids as $image_id ) { $image_ids[] = [ 'productId' => $post_id, 'id' => $image_id, 'image_url' => wp_get_attachment_url( $image_id ), ]; } continue; } $image_id = get_post_thumbnail_id( $post_id ); if ( ! $image_id ) { $product = wc_get_product( $post_id ); $gallery_image_ids = $product->get_gallery_image_ids(); if ( ! empty( $gallery_image_ids ) ) { $image_id = $gallery_image_ids[0]; } } $image_ids[] = [ 'productId' => $post_id, 'id' => $image_id ? $image_id : 'No Image', 'image_url' => $image_id ? wp_get_attachment_url( $image_id ) : 'No Image', ]; } wp_send_json_success( [ 'product_images' => array_slice( $image_ids, 0, 10 ) ] ); wp_die(); } private function get_attachment_id_by_url( $url ) { $attachments = get_posts( [ 'post_type' => 'attachment', 'meta_query' => [ [ 'key' => '_wp_attached_file', 'value' => basename( $url ), 'compare' => 'LIKE', ], ], 'fields' => 'ids', 'numberposts' => 1, ] ); return ! empty( $attachments ) ? $attachments[0] : null; } public function set_product_images_ajax() { check_ajax_referer( 'elementor-ai-unify-product-images_nonce', 'nonce' ); $product_id = isset( $_POST['productId'] ) ? sanitize_text_field( wp_unslash( $_POST['productId'] ) ) : ''; $image_url = isset( $_POST['image_url'] ) ? sanitize_text_field( wp_unslash( $_POST['image_url'] ) ) : ''; $image_to_add = isset( $_POST['image_to_add'] ) ? intval( wp_unslash( $_POST['image_to_add'] ) ) : null; $image_to_remove = isset( $_POST['image_to_remove'] ) ? intval( wp_unslash( $_POST['image_to_remove'] ) ) : null; $is_product_gallery = isset( $_POST['is_product_gallery'] ) && sanitize_text_field( wp_unslash( $_POST['is_product_gallery'] ) ) === 'true'; if ( ! $product_id || ! $image_url ) { throw new \Exception( 'Product ID and Image URL are required' ); } $product = wc_get_product( $product_id ); if ( ! $product ) { throw new \Exception( 'Product not found' ); } $attachment_id = $this->get_attachment_id_by_url( $image_url ); if ( is_wp_error( $attachment_id ) ) { throw new \Exception( 'Image upload failed' ); } if ( $is_product_gallery ) { $this->update_product_gallery( $product, $image_to_remove, $image_to_add ); } else { $product->set_image_id( $attachment_id ); $product->save(); } wp_send_json_success( [ 'message' => __( 'Image added successfully', 'elementor' ), ] ); } public function enqueue_ai_media_library_upload_screen() { $screen = get_current_screen(); if ( ! $screen || 'upload' !== $screen->id ) { return; } $this->enqueue_ai_media_library(); } public function enqueue_ai_media_library() { wp_enqueue_script( 'elementor-ai-media-library', $this->get_js_assets_url( 'ai-media-library' ), [ 'jquery', 'elementor-v2-ui', 'elementor-v2-icons', 'media-grid', ], ELEMENTOR_VERSION, true ); wp_localize_script( 'elementor-ai-media-library', 'ElementorAiConfig', [ 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ), 'connect_url' => $this->get_ai_connect_url(), ] ); wp_set_script_translations( 'elementor-ai-media-library', 'elementor' ); } private function enqueue_main_script() { wp_enqueue_script( 'elementor-ai', $this->get_js_assets_url( 'ai' ), [ 'react', 'react-dom', 'backbone-marionette', 'elementor-web-cli', 'wp-date', 'elementor-common', 'elementor-editor-modules', 'elementor-editor-document', 'elementor-v2-ui', 'elementor-v2-icons', ], ELEMENTOR_VERSION, true ); $config = [ 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ), 'connect_url' => $this->get_ai_connect_url(), ]; wp_localize_script( 'elementor-ai', 'ElementorAiConfig', $config ); wp_set_script_translations( 'elementor-ai', 'elementor' ); } private function enqueue_layout_script() { wp_enqueue_script( 'elementor-ai-layout', $this->get_js_assets_url( 'ai-layout' ), [ 'react', 'react-dom', 'backbone-marionette', 'elementor-common', 'elementor-web-cli', 'elementor-editor-modules', 'elementor-ai', 'elementor-v2-ui', 'elementor-v2-icons', ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'elementor-ai-layout', 'elementor' ); } private function remove_temporary_containers( $data ) { if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) { return $data; } // If for some reason the document has been saved during an AI Layout session, // ensure that the temporary containers are removed from the data. $data['elements'] = array_filter( $data['elements'], function( $element ) { $is_preview_container = strpos( $element['id'], 'e-ai-preview-container' ) === 0; $is_screenshot_container = strpos( $element['id'], 'e-ai-screenshot-container' ) === 0; return ! $is_preview_container && ! $is_screenshot_container; } ); return $data; } private function get_ai_connect_url() { $app = $this->get_ai_app(); return $app->get_admin_url( 'authorize', [ 'utm_source' => 'ai-popup', 'utm_campaign' => 'connect-account', 'utm_medium' => 'wp-dash', 'source' => 'generic', ] ); } public function ajax_ai_get_user_information( $data ) { $app = $this->get_ai_app(); if ( ! $app->is_connected() ) { return [ 'is_connected' => false, 'connect_url' => $this->get_ai_connect_url(), ]; } $user_usage = wp_parse_args( $app->get_usage(), [ 'hasAiSubscription' => false, 'usedQuota' => 0, 'quota' => 100, ] ); return [ 'is_connected' => true, 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ), 'usage' => $user_usage, ]; } public function ajax_ai_get_remote_config() { $app = $this->get_ai_app(); if ( ! $app->is_connected() ) { return []; } return $app->get_remote_config(); } public function ajax_ai_get_remote_frontend_config( $data ) { $callback = function () use ( $data ) { return $this->get_ai_app()->get_remote_frontend_config( $data ); }; return Utils::get_cached_callback( $callback, 'ai_remote_frontend_config-' . get_current_user_id(), HOUR_IN_SECONDS ); } public function verify_upload_permissions( $data ) { $referer = wp_get_referer(); if ( str_contains( $referer, 'wp-admin/upload.php' ) && current_user_can( 'upload_files' ) ) { return; } $this->verify_permissions( $data['editor_post_id'] ); } private function verify_permissions( $editor_post_id ) { $document = Plugin::$instance->documents->get( $editor_post_id ); if ( ! $document ) { throw new \Exception( 'Document not found' ); } if ( $document->is_built_with_elementor() ) { if ( ! $document->is_editable_by_current_user() ) { throw new \Exception( 'Access denied' ); } } elseif ( ! current_user_can( 'edit_post', $editor_post_id ) ) { throw new \Exception( 'Access denied' ); } } public function ajax_ai_get_image_prompt_enhancer( $data ) { $this->verify_upload_permissions( $data ); $app = $this->get_ai_app(); if ( empty( $data['prompt'] ) ) { throw new \Exception( 'Missing prompt' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_image_prompt_enhanced( $data['prompt'], [], $request_ids ); $this->throw_on_error( $result ); return [ 'text' => $result['text'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_completion_text( $data ) { $this->verify_permissions( $data['editor_post_id'] ); $app = $this->get_ai_app(); if ( empty( $data['payload']['prompt'] ) ) { throw new \Exception( 'Missing prompt' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_completion_text( $data['payload']['prompt'], $context, $request_ids ); $this->throw_on_error( $result ); return [ 'text' => $result['text'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_excerpt( $data ): array { $app = $this->get_ai_app(); if ( empty( $data['payload']['content'] ) ) { throw new \Exception( 'Missing content' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'Not connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_excerpt( $data['payload']['content'], $context, $request_ids ); $this->throw_on_error( $result ); return [ 'text' => $result['text'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_featured_image( $data ): array { $this->verify_upload_permissions( $data ); if ( empty( $data['payload']['prompt'] ) ) { throw new \Exception( 'Missing prompt' ); } $app = $this->get_ai_app(); if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_featured_image( $data, $context, $request_ids ); $this->throw_on_error( $result ); return [ 'images' => $result['images'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } private function get_ai_app(): Ai { return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' ); } private function get_request_context( $data ) { if ( empty( $data['context'] ) ) { return []; } return $data['context']; } private function get_request_ids( $data ) { if ( empty( $data['requestIds'] ) ) { return new \stdClass(); } return $data['requestIds']; } public function ajax_ai_get_edit_text( $data ) { $this->verify_permissions( $data['editor_post_id'] ); $app = $this->get_ai_app(); if ( empty( $data['payload']['input'] ) ) { throw new \Exception( 'Missing input' ); } if ( empty( $data['payload']['instruction'] ) ) { throw new \Exception( 'Missing instruction' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_edit_text( $data, $context, $request_ids ); $this->throw_on_error( $result ); return [ 'text' => $result['text'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_custom_code( $data ) { $app = $this->get_ai_app(); if ( empty( $data['payload']['prompt'] ) ) { throw new \Exception( 'Missing prompt' ); } if ( empty( $data['payload']['language'] ) ) { throw new \Exception( 'Missing language' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_custom_code( $data, $context, $request_ids ); $this->throw_on_error( $result ); return [ 'text' => $result['text'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_custom_css( $data ) { $this->verify_permissions( $data['editor_post_id'] ); $app = $this->get_ai_app(); if ( empty( $data['payload']['prompt'] ) ) { throw new \Exception( 'Missing prompt' ); } if ( empty( $data['payload']['html_markup'] ) ) { $data['html_markup'] = ''; } if ( empty( $data['payload']['element_id'] ) ) { throw new \Exception( 'Missing element_id' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_custom_css( $data, $context, $request_ids ); $this->throw_on_error( $result ); return [ 'text' => $result['text'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_set_get_started( $data ) { $app = $this->get_ai_app(); User::set_introduction_viewed( [ 'introductionKey' => 'ai_get_started', ] ); return $app->set_get_started(); } public function ajax_ai_set_status_feedback( $data ) { if ( empty( $data['response_id'] ) ) { throw new \Exception( 'Missing response_id' ); } $app = $this->get_ai_app(); if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $app->set_status_feedback( $data['response_id'] ); return []; } public function ajax_ai_get_text_to_image( $data ) { $this->verify_upload_permissions( $data ); if ( empty( $data['payload']['prompt'] ) ) { throw new \Exception( 'Missing prompt' ); } $app = $this->get_ai_app(); if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_text_to_image( $data, $context, $request_ids ); $this->throw_on_error( $result ); return [ 'images' => $result['images'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_image_to_image( $data ) { $this->verify_upload_permissions( $data ); $app = $this->get_ai_app(); if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) { throw new \Exception( 'Missing Image' ); } if ( empty( $data['payload']['settings'] ) ) { throw new \Exception( 'Missing prompt settings' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_image_to_image( [ 'prompt' => $data['payload']['prompt'], 'promptSettings' => $data['payload']['settings'], 'attachment_id' => $data['payload']['image']['id'], ], $context, $request_ids ); $this->throw_on_error( $result ); return [ 'images' => $result['images'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_image_to_image_upscale( $data ) { $this->verify_upload_permissions( $data ); $app = $this->get_ai_app(); if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) { throw new \Exception( 'Missing Image' ); } if ( empty( $data['payload']['promptSettings'] ) ) { throw new \Exception( 'Missing prompt settings' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_image_to_image_upscale( [ 'promptSettings' => $data['payload']['promptSettings'], 'attachment_id' => $data['payload']['image']['id'], ], $context, $request_ids ); $this->throw_on_error( $result ); return [ 'images' => $result['images'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_image_to_image_replace_background( $data ) { $this->verify_upload_permissions( $data ); $app = $this->get_ai_app(); if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) { throw new \Exception( 'Missing Image' ); } if ( empty( $data['payload']['prompt'] ) ) { throw new \Exception( 'Prompt Missing' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_image_to_image_replace_background( [ 'attachment_id' => $data['payload']['image']['id'], 'prompt' => $data['payload']['prompt'], ], $context, $request_ids ); $this->throw_on_error( $result ); return [ 'images' => $result['images'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_image_to_image_remove_background( $data ) { $this->verify_upload_permissions( $data ); $app = $this->get_ai_app(); if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) { throw new \Exception( 'Missing Image' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_image_to_image_remove_background( [ 'attachment_id' => $data['payload']['image']['id'], ], $context, $request_ids ); $this->throw_on_error( $result ); return [ 'images' => $result['images'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_image_to_image_mask( $data ) { $this->verify_upload_permissions( $data ); $app = $this->get_ai_app(); if ( empty( $data['payload']['prompt'] ) ) { throw new \Exception( 'Missing prompt' ); } if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) { throw new \Exception( 'Missing Image' ); } if ( empty( $data['payload']['settings'] ) ) { throw new \Exception( 'Missing prompt settings' ); } if ( empty( $data['payload']['mask'] ) ) { throw new \Exception( 'Missing Mask' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_image_to_image_mask( [ 'prompt' => $data['payload']['prompt'], 'attachment_id' => $data['payload']['image']['id'], 'mask' => $data['payload']['mask'], ], $context, $request_ids ); $this->throw_on_error( $result ); return [ 'images' => $result['images'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_image_to_image_mask_cleanup( $data ) { $this->verify_upload_permissions( $data ); $app = $this->get_ai_app(); if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) { throw new \Exception( 'Missing Image' ); } if ( empty( $data['payload']['settings'] ) ) { throw new \Exception( 'Missing prompt settings' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } if ( empty( $data['payload']['mask'] ) ) { throw new \Exception( 'Missing Mask' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_image_to_image_mask_cleanup( [ 'attachment_id' => $data['payload']['image']['id'], 'mask' => $data['payload']['mask'], ], $context, $request_ids ); $this->throw_on_error( $result ); return [ 'images' => $result['images'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_image_to_image_outpainting( $data ) { $this->verify_upload_permissions( $data ); $app = $this->get_ai_app(); if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } if ( empty( $data['payload']['mask'] ) ) { throw new \Exception( 'Missing Expended Image' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_image_to_image_out_painting( [ 'size' => $data['payload']['size'], 'position' => $data['payload']['position'], 'mask' => $data['payload']['mask'], 'image_base64' => $data['payload']['image_base64'], ], $context, $request_ids ); $this->throw_on_error( $result ); return [ 'images' => $result['images'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_upload_image( $data ) { $this->verify_upload_permissions( $data ); if ( empty( $data['image'] ) ) { throw new \Exception( 'Missing image data' ); } $image = $data['image']; if ( empty( $image['image_url'] ) ) { throw new \Exception( 'Missing image_url' ); } $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] ); if ( is_wp_error( $image_data ) ) { throw new \Exception( esc_html( $image_data->get_error_message() ) ); } if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) { $app = $this->get_ai_app(); $app->set_used_gallery_image( $image['id'] ); } return [ 'image' => array_merge( $image_data, $data ), ]; } public function ajax_ai_generate_layout( $data ) { $this->verify_permissions( $data['editor_post_id'] ); $app = $this->get_ai_app(); if ( empty( $data['prompt'] ) && empty( $data['attachments'] ) ) { throw new \Exception( 'Missing prompt / attachments' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $result = $app->generate_layout( $data, $this->prepare_generate_layout_context( $data ) ); if ( is_wp_error( $result ) ) { $message = $result->get_error_message(); if ( is_array( $message ) ) { $message = implode( ', ', $message ); throw new \Exception( esc_html( $message ) ); } $this->throw_on_error( $result ); } $elements = $result['text']['elements'] ?? []; $base_template_id = $result['baseTemplateId'] ?? null; $template_type = $result['templateType'] ?? null; if ( empty( $elements ) || ! is_array( $elements ) ) { throw new \Exception( 'unknown_error' ); } if ( 1 === count( $elements ) ) { $template = $elements[0]; } else { $template = [ 'elType' => 'container', 'elements' => $elements, 'settings' => [ 'content_width' => 'full', 'flex_gap' => [ 'column' => '0', 'row' => '0', 'unit' => 'px', ], 'padding' => [ 'unit' => 'px', 'top' => '0', 'right' => '0', 'bottom' => '0', 'left' => '0', 'isLinked' => true, ], ], ]; } return [ 'all' => [], 'text' => $template, 'response_id' => $result['responseId'], 'usage' => $result['usage'], 'base_template_id' => $base_template_id, 'template_type' => $template_type, ]; } public function ajax_ai_get_layout_prompt_enhancer( $data ) { $this->verify_permissions( $data['editor_post_id'] ); $app = $this->get_ai_app(); if ( empty( $data['prompt'] ) ) { throw new \Exception( 'Missing prompt' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $result = $app->get_layout_prompt_enhanced( $data['prompt'], $data['enhance_type'], $this->prepare_generate_layout_context( $data ) ); $this->throw_on_error( $result ); return [ 'text' => $result['text'] ?? $data['prompt'], 'response_id' => $result['responseId'] ?? '', 'usage' => $result['usage'] ?? '', ]; } private function prepare_generate_layout_context( $data ) { $request_context = $this->get_request_context( $data ); $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return $request_context; } $kits_data = Collection::make( $kit->get_data()['settings'] ?? [] ); $colors = $kits_data ->filter( function ( $_, $key ) { return in_array( $key, [ 'system_colors', 'custom_colors' ], true ); } ) ->flatten() ->filter( function ( $val ) { return ! empty( $val['_id'] ); } ) ->map( function ( $val ) { return [ 'id' => $val['_id'], 'label' => $val['title'] ?? null, 'value' => $val['color'] ?? null, ]; } ); $typography = $kits_data ->filter( function ( $_, $key ) { return in_array( $key, [ 'system_typography', 'custom_typography' ], true ); } ) ->flatten() ->filter( function ( $val ) { return ! empty( $val['_id'] ); } ) ->map( function ( $val ) { $font_size = null; if ( isset( $val['typography_font_size']['unit'], $val['typography_font_size']['size'] ) ) { $prop = $val['typography_font_size']; $font_size = 'custom' === $prop['unit'] ? $prop['size'] : $prop['size'] . $prop['unit']; } return [ 'id' => $val['_id'], 'label' => $val['title'] ?? null, 'value' => [ 'family' => $val['typography_font_family'] ?? null, 'weight' => $val['typography_font_weight'] ?? null, 'style' => $val['typography_font_style'] ?? null, 'size' => $font_size, ], ]; } ); $request_context['globals'] = [ 'colors' => $colors->all(), 'typography' => $typography->all(), ]; return $request_context; } private function upload_image( $image_url, $image_title, $parent_post_id = 0 ) { if ( ! current_user_can( 'upload_files' ) ) { throw new \Exception( 'Not Allowed to Upload images' ); } $uploads_manager = new \Elementor\Core\Files\Uploads_Manager(); if ( $uploads_manager::are_unfiltered_uploads_enabled() ) { Plugin::$instance->uploads_manager->set_elementor_upload_state( true ); add_filter( 'wp_handle_sideload_prefilter', [ Plugin::$instance->uploads_manager, 'handle_elementor_upload' ] ); add_filter( 'image_sideload_extensions', function( $extensions ) { $extensions[] = 'svg'; return $extensions; }); } $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' ); if ( is_wp_error( $attachment_id ) ) { return new \WP_Error( 'upload_error', $attachment_id->get_error_message() ); } if ( ! empty( $attachment_id['error'] ) ) { return new \WP_Error( 'upload_error', $attachment_id['error'] ); } return [ 'id' => $attachment_id, 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ), 'alt' => esc_attr( $image_title ), 'source' => 'library', ]; } public function ajax_ai_get_history( $data ): array { $type = $data['type'] ?? self::HISTORY_TYPE_ALL; if ( ! in_array( $type, self::VALID_HISTORY_TYPES, true ) ) { throw new \Exception( 'Invalid history type' ); } $page = sanitize_text_field( $data['page'] ?? 1 ); $limit = sanitize_text_field( $data['limit'] ?? 10 ); $app = $this->get_ai_app(); if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $result = $app->get_history_by_type( $type, $page, $limit, $context ); if ( is_wp_error( $result ) ) { throw new \Exception( esc_html( $result->get_error_message() ) ); } return $result; } public function ajax_ai_delete_history_item( $data ): array { if ( empty( $data['id'] ) || ! wp_is_uuid( $data['id'] ) ) { throw new \Exception( 'Missing id parameter' ); } $app = $this->get_ai_app(); if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $result = $app->delete_history_item( $data['id'], $context ); if ( is_wp_error( $result ) ) { throw new \Exception( esc_html( $result->get_error_message() ) ); } return []; } public function ajax_ai_toggle_favorite_history_item( $data ): array { if ( empty( $data['id'] ) || ! wp_is_uuid( $data['id'] ) ) { throw new \Exception( 'Missing id parameter' ); } $app = $this->get_ai_app(); if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $result = $app->toggle_favorite_history_item( $data['id'], $context ); if ( is_wp_error( $result ) ) { throw new \Exception( esc_html( $result->get_error_message() ) ); } return []; } public function ajax_ai_get_product_image_unification( $data ): array { if ( ! empty( $data['payload']['postId'] ) ) { $data['editor_post_id'] = $data['payload']['postId']; } $this->verify_upload_permissions( $data ); $app = $this->get_ai_app(); if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) { throw new \Exception( 'Missing Image' ); } if ( empty( $data['payload']['settings'] ) ) { throw new \Exception( 'Missing prompt settings' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_unify_product_images( [ 'promptSettings' => $data['payload']['settings'], 'attachment_id' => $data['payload']['image']['id'], 'featureIdentifier' => $data['payload']['featureIdentifier'] ?? '', ], $context, $request_ids ); $this->throw_on_error( $result ); return [ 'images' => $result['images'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } public function ajax_ai_get_animation( $data ): array { $this->verify_upload_permissions( $data ); $app = $this->get_ai_app(); if ( empty( $data['payload']['prompt'] ) ) { throw new \Exception( 'Missing prompt' ); } if ( empty( $data['payload']['motionEffectType'] ) ) { throw new \Exception( 'Missing animation type' ); } if ( ! $app->is_connected() ) { throw new \Exception( 'not_connected' ); } $context = $this->get_request_context( $data ); $request_ids = $this->get_request_ids( $data['payload'] ); $result = $app->get_animation( $data, $context, $request_ids ); $this->throw_on_error( $result ); return [ 'text' => $result['text'], 'response_id' => $result['responseId'], 'usage' => $result['usage'], ]; } /** * @param mixed $result */ private function throw_on_error( $result ): void { if ( is_wp_error( $result ) ) { wp_send_json_error( [ 'message' => esc_html( $result->get_error_message() ), 'extra_data' => $result->get_error_data(), ] ); } } /** * @return void */ public function add_wc_scripts(): void { wp_enqueue_script( 'elementor-ai-unify-product-images', $this->get_js_assets_url( 'ai-unify-product-images' ), [ 'jquery', 'elementor-v2-ui', 'elementor-v2-icons', 'wp-components', 'elementor-common', ], ELEMENTOR_VERSION, true ); wp_localize_script( 'elementor-ai-unify-product-images', 'UnifyProductImagesConfig', [ 'get_product_images_url' => admin_url( 'admin-ajax.php' ), 'set_product_images_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'elementor-ai-unify-product-images_nonce' ), 'placeholder' => ELEMENTOR_ASSETS_URL . 'images/app/ai/product-image-unification-example.gif?' . ELEMENTOR_VERSION, 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ), 'connect_url' => $this->get_ai_connect_url(), ] ); add_filter( 'bulk_actions-edit-product', function ( $data ) { return $this->add_products_bulk_action( $data ); }); wp_set_script_translations( 'elementor-ai-unify-product-images', 'elementor' ); } /** * @param $product * @param int|null $image_to_remove * @param int|null $image_to_add * @return void */ private function update_product_gallery( $product, ?int $image_to_remove, ?int $image_to_add ): void { $gallery_image_ids = $product->get_gallery_image_ids(); $index = array_search( $image_to_remove, $gallery_image_ids, true ); if ( false !== $index ) { unset( $gallery_image_ids[ $index ] ); } if ( ! in_array( $image_to_add, $gallery_image_ids, true ) ) { $gallery_image_ids[] = $image_to_add; } $product->set_gallery_image_ids( $gallery_image_ids ); $product->save(); } private function should_display_create_with_ai_banner() { $elementor_pages = new \WP_Query( [ 'post_type' => 'page', 'post_status' => 'publish', 'fields' => 'ids', 'posts_per_page' => self::MIN_PAGES_FOR_CREATE_WITH_AI_BANNER + 1, ] ); if ( $elementor_pages->post_count > self::MIN_PAGES_FOR_CREATE_WITH_AI_BANNER ) { return false; } if ( Utils::is_custom_kit_applied() ) { return false; } return true; } private function get_create_with_ai_banner_data() { return [ 'title' => 'Create and launch your site faster with AI', 'description' => 'Share your vision with our AI Chat and watch as it becomes a brief, sitemap, and wireframes in minutes:', 'input_placeholder' => 'Start describing the site you want to create...', 'button_title' => 'Create with AI', 'button_cta_url' => 'http://planner.elementor.com/chat.html', 'background_image' => ELEMENTOR_ASSETS_URL . 'images/app/ai/ai-site-creator-homepage-bg.svg', 'utm_source' => 'editor-home', 'utm_medium' => 'wp-dash', 'utm_campaign' => 'generate-with-ai', ]; } public function add_create_with_ai_banner_to_homescreen( $home_screen_data ) { if ( $this->should_display_create_with_ai_banner() ) { $home_screen_data['create_with_ai'] = $this->get_create_with_ai_banner_data(); } else { $home_screen_data['create_with_ai'] = null; } return $home_screen_data; } } ai/feature-intro/product-image-unification-intro.php 0000644 00000004202 15252521350 0016627 0 ustar 00 <?php namespace Elementor\Modules\Ai\Feature_Intro; use Elementor\Core\Upgrade\Manager as Upgrade_Manager; use Elementor\User; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Product_Image_Unification_Intro { const RELEASE_VERSION = '3.26.0'; const CURRENT_POINTER_SLUG = 'e-ai-product-image-unification'; public static function add_hooks() { add_action( 'admin_print_footer_scripts', [ __CLASS__, 'product_image_unification_intro_script' ] ); } public static function product_image_unification_intro_script() { if ( static::is_dismissed() ) { return; } $screen = get_current_screen(); if ( ! isset( $screen->post_type ) || 'product' !== $screen->post_type ) { return; } wp_enqueue_script( 'wp-pointer' ); wp_enqueue_style( 'wp-pointer' ); $pointer_content = '<h3>' . esc_html__( 'New! Unify pack-shots with Elementor AI', 'elementor' ) . '</h3>'; $pointer_content .= '<p>' . esc_html__( 'Now you can process images in bulk and standardized the background and ratio - no manual editing required!', 'elementor' ) . '</p>'; $pointer_content .= sprintf( '<p><button style="padding: 0; border: 0"><a class="button button-primary" href="%s" target="_blank">%s</a></button></p>', esc_js( 'https://go.elementor.com/wp-dash-unify-images-learn-more/' ), esc_html__( 'Learn more', 'elementor' ) ); ?> <script> jQuery( document ).ready( function( $ ) { setTimeout( function () { $( '#bulk-action-selector-top' ).pointer( { content: '<?php echo $pointer_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>', position: { edge: <?php echo is_rtl() ? "'right'" : "'left'"; ?>, align: 'center' }, pointerWidth: 360, close: function () { elementorCommon.ajax.addRequest( 'introduction_viewed', { data: { introductionKey: '<?php echo esc_attr( static::CURRENT_POINTER_SLUG ); ?>', }, } ); } } ).pointer( 'open' ); }, 10 ); } ); </script> <?php } private static function is_dismissed() { return User::get_introduction_meta( static::CURRENT_POINTER_SLUG ); } } ai/connect/ai.php 0000644 00000054543 15252521350 0007703 0 ustar 00 <?php namespace Elementor\Modules\Ai\Connect; use Elementor\Core\Common\Modules\Connect\Apps\Library; use Elementor\Modules\Ai\Module; use Elementor\Utils as ElementorUtils; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Ai extends Library { const API_URL = 'https://my.elementor.com/api/v2/ai/'; const STYLE_PRESET = 'style_preset'; const IMAGE_TYPE = 'image_type'; const IMAGE_STRENGTH = 'image_strength'; const ASPECT_RATIO = 'ratio'; const IMAGE_RESOLUTION = 'image_resolution'; const IMAGE_BACKGROUND_COLOR = 'background_color'; const PROMPT = 'prompt'; public function get_title() { return esc_html__( 'AI', 'elementor' ); } protected function get_api_url() { return static::API_URL . '/'; } public function get_usage() { return $this->ai_request( 'POST', 'status/check', [ 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ] ); } public function get_remote_config() { return $this->ai_request( 'GET', 'remote-config/config', [ 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ] ); } public function get_remote_frontend_config( $data ) { return $this->ai_request( 'POST', 'remote-config/frontend-config', [ 'client_name' => $data['payload']['client_name'], 'client_version' => $data['payload']['client_version'], 'client_session_id' => $data['payload']['client_session_id'], 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } /** * @param array $event_data { * @type string $name * @type array $data * @type array $client { * @type string $name * @type string $version * @type string $session_id * } * } */ public function send_event( array $event_data ): void { $this->ai_request( 'POST', 'client-events/events', [ 'payload' => $event_data, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } /** * Get file upload get_file_payload * * @param $filename * @param $file_type * @param $file_path * @param $boundary * * @return string */ private function get_file_payload( $filename, $file_type, $file_path, $boundary ) { $name = $filename ?? basename( $file_path ); $mine_type = 'image' === $file_type ? image_type_to_mime_type( exif_imagetype( $file_path ) ) : $file_type; $payload = ''; // Upload the file $payload .= '--' . $boundary; $payload .= "\r\n"; $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"; filename="' . esc_attr( $name ) . '"' . "\r\n"; $payload .= 'Content-Type: ' . $mine_type . "\r\n"; $payload .= "\r\n"; $payload .= file_get_contents( $file_path ); $payload .= "\r\n"; return $payload; } private function get_upload_request_body( $body, $file, $boundary, $file_name = '' ) { $payload = ''; // add all body fields as standard POST fields: foreach ( $body as $name => $value ) { $payload .= '--' . $boundary; $payload .= "\r\n"; $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"' . "\r\n\r\n"; $payload .= $value; $payload .= "\r\n"; } if ( is_array( $file ) ) { foreach ( $file as $key => $file_data ) { $payload .= $this->get_file_payload( $file_data['name'], $file_data['type'], $file_data['path'], $boundary ); } } else { $image_mime = image_type_to_mime_type( exif_imagetype( $file ) ); // @todo: add validation for supported image types if ( empty( $file_name ) ) { $file_name = basename( $file ); } $payload .= $this->get_file_payload( $file_name, $image_mime, $file, $boundary ); } $payload .= '--' . $boundary . '--'; return $payload; } private function ai_request( $method, $endpoint, $body, $file = false, $file_name = '', $format = 'default' ) { $headers = [ 'x-elementor-ai-version' => '2', ]; if ( $file ) { $boundary = wp_generate_password( 24, false ); $body = $this->get_upload_request_body( $body, $file, $boundary, $file_name ); // add content type header $headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary; } elseif ( 'json' === $format ) { $headers['Content-Type'] = 'application/json'; $body = wp_json_encode( $body ); } return $this->http_request( $method, $endpoint, [ 'timeout' => 100, 'headers' => $headers, 'body' => $body, ], [ 'return_type' => static::HTTP_RETURN_TYPE_ARRAY, 'with_error_data' => true, ] ); } public function set_get_started() { return $this->ai_request( 'POST', 'status/get-started', [ 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ] ); } public function set_status_feedback( $response_id ) { return $this->ai_request( 'POST', 'status/feedback/' . $response_id, [ 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ] ); } public function set_used_gallery_image( $image_id ) { return $this->ai_request( 'POST', 'status/used-gallery-image/' . $image_id, [ 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ] ); } public function get_completion_text( $prompt, $context, $request_ids ) { return $this->ai_request( 'POST', 'text/completion', [ 'prompt' => $prompt, 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } public function get_excerpt( $prompt, $context, $request_ids ) { $excerpt_length = apply_filters( 'excerpt_length', 55 ); return $this->ai_request( 'POST', 'text/get-excerpt', [ 'content' => $prompt, 'maxLength' => $excerpt_length, 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } /** * Get Image Prompt Enhanced get_image_prompt_enhanced * * @param $prompt * * @return mixed|\WP_Error */ public function get_image_prompt_enhanced( $prompt, $context, $request_ids ) { return $this->ai_request( 'POST', 'text/enhance-image-prompt', [ 'prompt' => $prompt, 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ] ); } public function get_edit_text( $data, $context, $request_ids ) { return $this->ai_request( 'POST', 'text/edit', [ 'input' => $data['payload']['input'], 'instruction' => $data['payload']['instruction'], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } public function get_custom_code( $data, $context, $request_ids ) { return $this->ai_request( 'POST', 'text/custom-code', [ 'prompt' => $data['payload']['prompt'], 'language' => $data['payload']['language'], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } public function get_custom_css( $data, $context, $request_ids ) { return $this->ai_request( 'POST', 'text/custom-css', [ 'prompt' => $data['payload']['prompt'], 'html_markup' => $data['payload']['html_markup'], 'element_id' => $data['payload']['element_id'], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } /** * Get text to image get_text_to_image * * @param $prompt * @param $prompt_settings * * @return mixed|\WP_Error */ public function get_text_to_image( $data, $context, $request_ids ) { return $this->ai_request( 'POST', 'image/text-to-image', [ self::PROMPT => $data['payload']['prompt'], self::IMAGE_TYPE => $data['payload']['settings'][ self::IMAGE_TYPE ] . '/' . $data['payload']['settings'][ self::STYLE_PRESET ], self::ASPECT_RATIO => $data['payload']['settings'][ self::ASPECT_RATIO ], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } /** * Get_Featured_Image get_featured_image * * @param $data * @param $context * @param $request_ids * @return mixed|\WP_Error */ public function get_featured_image( $data, $context, $request_ids ) { return $this->ai_request( 'POST', 'image/text-to-image/featured-image', [ self::PROMPT => $data['payload']['prompt'], self::IMAGE_TYPE => $data['payload']['settings'][ self::IMAGE_TYPE ] . '/' . $data['payload']['settings'][ self::STYLE_PRESET ], self::ASPECT_RATIO => $data['payload']['settings'][ self::ASPECT_RATIO ], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } /** * Get Image To Image get_image_to_image * * @param $image_data * @param $context * @param $request_ids * @return mixed|\WP_Error * @throws \Exception If image file not found. */ public function get_image_to_image( $image_data, $context, $request_ids ) { $image_file = get_attached_file( $image_data['attachment_id'] ); if ( ! $image_file ) { throw new \Exception( 'Image file not found' ); } $result = $this->ai_request( 'POST', 'image/image-to-image', [ self::PROMPT => $image_data[ self::PROMPT ], self::IMAGE_TYPE => $image_data['promptSettings'][ self::IMAGE_TYPE ] . '/' . $image_data['promptSettings'][ self::STYLE_PRESET ], self::IMAGE_STRENGTH => $image_data['promptSettings'][ self::IMAGE_STRENGTH ], self::ASPECT_RATIO => $image_data['promptSettings'][ self::ASPECT_RATIO ], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], $image_file, 'image' ); return $result; } private function resizeImageIfNeeded( $original_url ) { try { $max_file_size = 4194304; $current_size = filesize( $original_url ); if ( $current_size <= $max_file_size ) { return $original_url; } $image_editor = wp_get_image_editor( $original_url ); if ( is_wp_error( $image_editor ) ) { return $original_url; } $dimensions = $image_editor->get_size(); $original_width = $dimensions['width']; $original_height = $dimensions['height']; $scaling_factor = sqrt( $max_file_size / $current_size ); $new_width = (int) ( $original_width * $scaling_factor ); $new_height = (int) ( $original_height * $scaling_factor ); $image_editor->resize( $new_width, $new_height, true ); $file_extension = pathinfo( $original_url, PATHINFO_EXTENSION ); $temp_image = tempnam( sys_get_temp_dir(), 'resized_' ) . '.' . $file_extension; $image_editor->save( $temp_image ); return $temp_image; } catch ( \Exception $e ) { return $original_url; } } public function get_unify_product_images( $image_data, $context, $request_ids ) { $image_file = get_attached_file( $image_data['attachment_id'] ); if ( ! $image_file ) { throw new \Exception( 'Image file not found' ); } $final_path = $this->resizeImageIfNeeded( $image_file ); $result = $this->ai_request( 'POST', 'image/image-to-image/unify-product-images', [ 'aspectRatio' => $image_data['promptSettings'][ self::ASPECT_RATIO ], 'backgroundColor' => $image_data['promptSettings'][ self::IMAGE_BACKGROUND_COLOR ], 'featureIdentifier' => $image_data['featureIdentifier'], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], $final_path, 'image' ); if ( $image_file !== $final_path ) { unlink( $final_path ); } return $result; } /** * Get Image To Image Upscale get_image_to_image_upscale * * @param $image_data * @param $context * @param $request_ids * @return mixed|\WP_Error * @throws \Exception If image file not found. */ public function get_image_to_image_upscale( $image_data, $context, $request_ids ) { $image_file = get_attached_file( $image_data['attachment_id'] ); if ( ! $image_file ) { throw new \Exception( 'Image file not found' ); } $result = $this->ai_request( 'POST', 'image/image-to-image/upscale', [ self::IMAGE_RESOLUTION => $image_data['promptSettings']['upscale_to'], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], $image_file, 'image' ); return $result; } /** * Get Image To Image Remove Background get_image_to_image_remove_background * * @param $image_data * @param $context * @param $request_ids * @return mixed|\WP_Error * @throws \Exception If image file not found. */ public function get_image_to_image_remove_background( $image_data, $context, $request_ids ) { $image_file = get_attached_file( $image_data['attachment_id'] ); if ( ! $image_file ) { throw new \Exception( 'Image file not found' ); } $result = $this->ai_request( 'POST', 'image/image-to-image/remove-background', [ 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], $image_file, 'image' ); return $result; } /** * Get Image To Image Remove Text get_image_to_image_remove_text * * @param $image_data * @param $context * @param $request_ids * @return mixed|\WP_Error * @throws \Exception If image file not found. */ public function get_image_to_image_replace_background( $image_data, $context, $request_ids ) { $image_file = get_attached_file( $image_data['attachment_id'] ); if ( ! $image_file ) { throw new \Exception( 'Image file not found' ); } $result = $this->ai_request( 'POST', 'image/image-to-image/replace-background', [ self::PROMPT => $image_data[ self::PROMPT ], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], $image_file, 'image' ); return $result; } /** * Store Temp File store_temp_file * used to store a temp file for the AI request and deletes it once the request is done * * @param $file_content * @param $file_ext * * @return string */ private function store_temp_file( $file_content, $file_ext = '' ) { $temp_file = str_replace( '.tmp', '', wp_tempnam() . $file_ext ); file_put_contents( $temp_file, $file_content ); // make sure the temp file is deleted on shutdown register_shutdown_function( function () use ( $temp_file ) { if ( file_exists( $temp_file ) ) { unlink( $temp_file ); } } ); return $temp_file; } /** * Get Image To Image Out Painting get_image_to_image_out_painting * * @param $image_data * @param $context * @param $request_ids * @return mixed|\WP_Error * @throws \Exception If image file not found. */ public function get_image_to_image_out_painting( $image_data, $context, $request_ids ) { $img_content = str_replace( ' ', '+', $image_data['mask'] ); $img_content = substr( $img_content, strpos( $img_content, ',' ) + 1 ); $img_content = base64_decode( $img_content ); $mask_file = $this->store_temp_file( $img_content, '.png' ); if ( ! $mask_file ) { throw new \Exception( 'Expended Image file not found' ); } $result = $this->ai_request( 'POST', 'image/image-to-image/outpainting', [ 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), 'size' => wp_json_encode( $image_data['size'] ), 'position' => wp_json_encode( $image_data['position'] ), 'image_base64' => $image_data['image_base64'], $image_data['image'], ], [ [ 'name' => 'image', 'type' => 'image', 'path' => $mask_file, ], ] ); return $result; } /** * Get Image To Image Mask get_image_to_image_mask * * @param $image_data * @param $context * @param $request_ids * @return mixed|\WP_Error * @throws \Exception If image file not found. */ public function get_image_to_image_mask( $image_data, $context, $request_ids ) { $image_file = get_attached_file( $image_data['attachment_id'] ); $mask_file = $this->store_temp_file( $image_data['mask'], '.svg' ); if ( ! $image_file ) { throw new \Exception( 'Image file not found' ); } if ( ! $mask_file ) { throw new \Exception( 'Mask file not found' ); } $result = $this->ai_request( 'POST', 'image/image-to-image/inpainting', [ self::PROMPT => $image_data[ self::PROMPT ], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), 'image_base64' => $image_data['image_base64'], ], [ [ 'name' => 'image', 'type' => 'image', 'path' => $image_file, ], [ 'name' => 'mask_image', 'type' => 'image/svg+xml', 'path' => $mask_file, ], ] ); return $result; } public function get_image_to_image_mask_cleanup( $image_data, $context, $request_ids ) { $image_file = get_attached_file( $image_data['attachment_id'] ); $mask_file = $this->store_temp_file( $image_data['mask'], '.svg' ); if ( ! $image_file ) { throw new \Exception( 'Image file not found' ); } if ( ! $mask_file ) { throw new \Exception( 'Mask file not found' ); } $result = $this->ai_request( 'POST', 'image/image-to-image/cleanup', [ self::PROMPT => $image_data[ self::PROMPT ], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), 'image_base64' => $image_data['image_base64'], ], [ [ 'name' => 'image', 'type' => 'image', 'path' => $image_file, ], [ 'name' => 'mask_image', 'type' => 'image/svg+xml', 'path' => $mask_file, ], ] ); return $result; } public function generate_layout( $data, $context ) { $endpoint = 'generate/layout'; $body = [ 'prompt' => $data['prompt'], 'variationType' => (int) $data['variationType'], 'ids' => $data['ids'], ]; if ( ! empty( $data['prevGeneratedIds'] ) ) { $body['generatedBaseTemplatesIds'] = $data['prevGeneratedIds']; } if ( ! empty( $data['attachments'] ) ) { $attachment = $data['attachments'][0]; switch ( $attachment['type'] ) { case 'json': $endpoint = 'generate/generate-json-variation'; $body['json'] = [ 'type' => 'elementor', 'elements' => [ $attachment['content'] ], 'label' => $attachment['label'], 'source' => $attachment['source'], ]; break; case 'url': $endpoint = 'generate/html-to-elementor'; $html = wp_json_encode( $attachment['content'] ); $body['html'] = $html; $body['htmlFetchedUrl'] = $attachment['label']; break; } } $context['currentContext'] = $data['currentContext']; $context['features'] = [ 'supportedFeatures' => [ 'Taxonomy' ], ]; if ( ElementorUtils::has_pro() ) { $context['features']['subscriptions'] = [ 'Pro' ]; } if ( Plugin::instance()->experiments->get_active_features()['nested-elements'] ) { $context['features']['supportedFeatures'][] = 'Nested'; } if ( Plugin::instance()->experiments->get_active_features()['mega-menu'] ) { $context['features']['supportedFeatures'][] = 'MegaMenu'; } if ( class_exists( 'WC' ) ) { $context['features']['supportedFeatures'][] = 'WooCommerce'; } $metadata = [ 'context' => $context, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), 'config' => [ 'generate' => [ 'all' => true, ], ], ]; $body = array_merge( $body, $metadata ); // Temp hack for platforms that filters the http_request_args, and it breaks JSON requests. remove_all_filters( 'http_request_args' ); return $this->ai_request( 'POST', $endpoint, $body, false, '', 'json' ); } public function get_layout_prompt_enhanced( $prompt, $enhance_type, $context ) { return $this->ai_request( 'POST', 'generate/enhance-prompt', [ 'prompt' => $prompt, 'enhance_type' => $enhance_type, 'context' => wp_json_encode( $context ), 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } public function get_history_by_type( $type, $page, $limit, $context = [] ) { $endpoint = Module::HISTORY_TYPE_ALL === $type ? 'history' : add_query_arg( [ 'page' => $page, 'limit' => $limit, ], "history/{$type}" ); return $this->ai_request( 'POST', $endpoint, [ 'context' => wp_json_encode( $context ), 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ] ); } public function delete_history_item( $id, $context = [] ) { return $this->ai_request( 'DELETE', 'history/' . $id, [ 'context' => wp_json_encode( $context ), 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ] ); } public function toggle_favorite_history_item( $id, $context = [] ) { $sanitized_id = str_replace( '%', '%%', $id ); return $this->ai_request( 'POST', sprintf( 'history/%s/favorite', $sanitized_id ), [ 'context' => wp_json_encode( $context ), 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ] ); } public function get_animation( $data, $context, $request_ids ) { return $this->ai_request( 'POST', 'text/get-motion-effect', [ 'prompt' => $data['payload']['prompt'], 'motionEffectType' => $data['payload']['motionEffectType'], 'context' => wp_json_encode( $context ), 'ids' => $request_ids, 'api_version' => ELEMENTOR_VERSION, 'site_lang' => get_bloginfo( 'language' ), ], false, '', 'json' ); } protected function init() {} } ai/preferences.php 0000644 00000005566 15252521350 0010163 0 ustar 00 <?php namespace Elementor\Modules\Ai; use Elementor\User; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Preferences { const ENABLE_AI = 'elementor_enable_ai'; /** * Register actions and hooks. * * @return void */ public function register() { add_action( 'personal_options', function ( \WP_User $user ) { $this->add_personal_options_settings( $user ); } ); add_action( 'personal_options_update', function ( $user_id ) { $this->update_personal_options_settings( $user_id ); } ); add_action( 'edit_user_profile_update', function ( $user_id ) { $this->update_personal_options_settings( $user_id ); } ); } /** * Determine if AI features are enabled for a user. * * @param int $user_id - User ID. * * @return bool */ public static function is_ai_enabled( $user_id ) { return (bool) User::get_user_option_with_default( static::ENABLE_AI, $user_id, true ); } /** * Add settings to the "Personal Options". * * @param \WP_User $user - User object. * * @return void */ protected function add_personal_options_settings( \WP_User $user ) { if ( ! $this->has_permissions_to_edit_user( $user->ID ) ) { return; } $ai_value = User::get_user_option_with_default( static::ENABLE_AI, $user->ID, '1' ); ?> <tr> <th style="padding:0px"> <h2><?php echo esc_html__( 'Elementor - AI', 'elementor' ); ?></h2> </th> </tr> <tr> <th> <label for="<?php echo esc_attr( static::ENABLE_AI ); ?>"> <?php echo esc_html__( 'Status', 'elementor' ); ?> </label> </th> <td> <label for="<?php echo esc_attr( static::ENABLE_AI ); ?>"> <input name="<?php echo esc_attr( static::ENABLE_AI ); ?>" id="<?php echo esc_attr( static::ENABLE_AI ); ?>" type="checkbox" value="1"<?php checked( '1', $ai_value ); ?> /> <?php echo esc_html__( 'Enable Elementor AI functionality', 'elementor' ); ?> </label> </td> </tr> <?php } /** * Save the settings in the "Personal Options". * * @param int $user_id - User ID. * * @return void */ protected function update_personal_options_settings( $user_id ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce already verified in `wp_verify_nonce`. $wpnonce = Utils::get_super_global_value( $_POST, '_wpnonce' ); if ( ! wp_verify_nonce( $wpnonce, 'update-user_' . $user_id ) ) { return; } if ( ! $this->has_permissions_to_edit_user( $user_id ) ) { return; } $ai_value = empty( $_POST[ static::ENABLE_AI ] ) ? '0' : '1'; update_user_option( $user_id, static::ENABLE_AI, sanitize_text_field( $ai_value ) ); } /** * Determine if the current user has permission to view/change preferences of a user. * * @param int $user_id * * @return bool */ protected function has_permissions_to_edit_user( $user_id ) { return current_user_can( 'edit_user', $user_id ); } } ai/site-planner-connect/view.php 0000644 00000020270 15252521350 0012651 0 ustar 00 <?php namespace Elementor\Modules\Ai\SitePlannerConnect; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&display=swap" rel="stylesheet"><?php // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet ?> <style> #wpwrap { display: none; } .site-planner-consent { position: fixed; top: 0; left: 0; z-index: 99999; /* above admin top bar */ width: 100%; background-color: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; } .site-planner-consent-title { color: #0C0D0E; text-align: center; /* typography/h4 */ font-family: Roboto, sans-serif; font-size: 32px; font-style: normal; font-weight: 700; line-height: 123.5%; letter-spacing: 0.25px; } .site-planner-consent-description { width: 393px; color: #69727D; text-align: center; /* typography/body1 */ font-family: Roboto, sans-serif; font-size: 16px; font-style: normal; font-weight: 400; line-height: 150%; /* 24px */ letter-spacing: 0.15px; } .site-planner-consent-connect-names { display: flex; flex-direction: row; justify-content: space-between; width: 500px; } .site-planner-consent-connect-names div { width: 50%; text-align: center; } .site-planner-consent button { cursor: pointer; border: none; display: flex; width: 387px; padding: 8px 22px; flex-direction: column; justify-content: center; align-items: center; border-radius: 4px; background: #F0ABFC; color: #0C0D0E; font-feature-settings: 'liga' off, 'clig' off; /* components/button/button-large */ font-family: Roboto, sans-serif; font-size: 16px; font-style: normal; font-weight: 500; line-height: 26px; /* 162.5% */ letter-spacing: 0.46px; } .site-planner-consent .generating-results { display: none; padding: 8px 16px; margin: 0 32px; } .site-planner-consent .generating-results.error { display: block; background: rgb(253, 236, 236); } </style> <div class="site-planner-consent"> <h1 class="site-planner-consent-title"> %title% </h1> <div style="height: 20px"></div> <p class="site-planner-consent-description"> %description% </p> <div style="height: 40px"></div> <svg width="287" height="40" viewBox="0 0 287 40" fill="none" xmlns="http://www.w3.org/2000/svg"> <line x1="16.5" y1="22.5" x2="271.5" y2="22.5" stroke="#69727D" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="2 4"/> <circle cx="145.623" cy="22" r="11.5" fill="white" stroke="#69727D"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M147.977 19.6467C148.172 19.842 148.172 20.1586 147.977 20.3538L143.977 24.3538C143.782 24.5491 143.465 24.5491 143.27 24.3538C143.074 24.1586 143.074 23.842 143.27 23.6467L147.27 19.6467C147.465 19.4515 147.782 19.4515 147.977 19.6467Z" fill="#69727D"/> <path d="M149.691 18.1948L149.402 17.9058C148.377 16.8804 146.714 16.8804 145.689 17.9058L145.002 18.5922C144.807 18.7875 144.491 18.7875 144.295 18.5922C144.1 18.397 144.1 18.0804 144.295 17.8851L144.982 17.1987C146.398 15.7827 148.693 15.7827 150.109 17.1987L150.398 17.4877C151.814 18.9036 151.814 21.1993 150.398 22.6153L149.712 23.3017C149.517 23.497 149.2 23.497 149.005 23.3017C148.81 23.1065 148.81 22.7899 149.005 22.5946L149.691 21.9082C150.717 20.8828 150.717 19.2202 149.691 18.1948Z" fill="#69727D"/> <path d="M141.529 22.0658C140.503 23.0912 140.503 24.7538 141.529 25.7792L141.818 26.0682C142.843 27.0936 144.506 27.0936 145.531 26.0682L146.218 25.3818C146.413 25.1865 146.73 25.1865 146.925 25.3818C147.12 25.577 147.12 25.8936 146.925 26.0889L146.238 26.7753C144.822 28.1913 142.527 28.1913 141.111 26.7753L140.822 26.4863C139.406 25.0704 139.406 22.7747 140.822 21.3587L141.508 20.6723C141.703 20.477 142.02 20.477 142.215 20.6723C142.411 20.8675 142.411 21.1841 142.215 21.3794L141.529 22.0658Z" fill="#69727D"/> <rect x="247" width="40" height="40" rx="20" fill="#F3F3F4"/> <g clip-path="url(#clip0_7635_41076)"> <path fill-rule="evenodd" clip-rule="evenodd" d="M257.022 26.6668C255.704 24.6934 255 22.3734 255 20C255 16.8174 256.264 13.7652 258.515 11.5147C260.765 9.26428 263.817 8 267 8C269.373 8 271.693 8.70379 273.667 10.0224C275.64 11.3409 277.178 13.2151 278.087 15.4078C278.995 17.6005 279.232 20.0133 278.769 22.3411C278.306 24.6688 277.164 26.807 275.485 28.4853C273.807 30.1635 271.669 31.3064 269.341 31.7694C267.013 32.2324 264.601 31.9948 262.408 31.0865C260.215 30.1783 258.341 28.6402 257.022 26.6668ZM264 14.9996H262.001V24.9999H264V14.9996ZM271.999 14.9996H266V16.9993H271.999V14.9996ZM271.999 18.999H266V20.9987H271.999V18.999ZM271.999 23.0002H266V24.9999H271.999V23.0002Z" fill="#0C0D0E"/> </g> <rect width="40" height="40" rx="20" fill="#F3F3F4"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M20.0004 10.0156C14.4944 10.0156 10.0156 14.494 10.0156 19.9996C10.0156 25.5053 14.4944 29.9844 20.0004 29.9844C25.5056 29.9844 29.9844 25.5053 29.9844 19.9996C29.9844 14.4947 25.5056 10.0156 20.0004 10.0156ZM11.1616 19.9996C11.1616 18.7184 11.4367 17.5017 11.927 16.4031L16.1431 27.9539C13.1948 26.5215 11.1616 23.4984 11.1616 19.9996ZM20.0004 28.8387C19.1327 28.8387 18.2954 28.7106 17.5032 28.4785L20.1549 20.7731L22.8725 28.2154C22.8898 28.2589 22.9115 28.2992 22.9353 28.3372C22.0167 28.6607 21.0292 28.8387 20.0004 28.8387ZM21.218 15.856C21.7501 15.8279 22.2293 15.7715 22.2293 15.7715C22.7058 15.7153 22.65 15.0158 22.1733 15.0438C22.1733 15.0438 20.7415 15.156 19.8176 15.156C18.9495 15.156 17.4894 15.0438 17.4894 15.0438C17.0133 15.0158 16.9579 15.744 17.4336 15.7715C17.4336 15.7715 17.8845 15.8277 18.3602 15.856L19.7373 19.6286L17.8034 25.4297L14.5851 15.8564C15.1178 15.8283 15.5968 15.7721 15.5968 15.7721C16.0725 15.7159 16.0169 15.016 15.54 15.0445C15.54 15.0445 14.1088 15.1564 13.1843 15.1564C13.0178 15.1564 12.823 15.1521 12.6157 15.1457C14.1954 12.7459 16.9123 11.1617 20.0004 11.1617C22.3018 11.1617 24.3964 12.0416 25.9689 13.4816C25.9302 13.4797 25.8937 13.4748 25.854 13.4748C24.9861 13.4748 24.3695 14.2309 24.3695 15.0434C24.3695 15.7715 24.789 16.388 25.2377 17.1159C25.5741 17.7051 25.9662 18.4613 25.9662 19.5537C25.9662 20.3102 25.6758 21.1882 25.2936 22.4107L24.4121 25.3566L21.218 15.856ZM24.4435 27.6389L27.1431 19.8337C27.6481 18.573 27.8152 17.5647 27.8152 16.6679C27.8152 16.343 27.7937 16.0404 27.7557 15.7591C28.4466 17.018 28.8391 18.4629 28.8386 19.9998C28.8386 23.2602 27.0708 26.1068 24.4435 27.6389Z" fill="#0C0D0E"/> <defs> <clipPath id="clip0_7635_41076"> <rect width="24" height="24" fill="white" transform="translate(255 8)"/> </clipPath> </defs> </svg> <div class="site-planner-consent-connect-names"> <div>%domain%</div> <div>%app_name%</div> </div> <div style="height: 40px"></div> <button class="site-planner-consent-button" onclick="sendPassword()"> %cta% </button> <div style="height: 40px"></div> <div class="generating-results"></div> </div> <script> const generatingResults = document.querySelector(".generating-results"); const hideAdminUi = () => { document.body.append(document.querySelector(".site-planner-consent")) } const sendPassword = () => { generatingResults.classList.remove("error"); fetch(`${ wpApiSettings.root}wp/v2/users/me/application-passwords`, { method: "POST", headers: { "Content-Type": "application/json", "X-WP-Nonce": wpApiSettings.nonce, }, body: JSON.stringify({ name: "Site Planner Connect" }) }) .then(response => response.json()) .then(data => { window.opener.postMessage({ type: "app_password", details: { userLogin: data.user_login, appPassword: data.password, uuid: data.uuid, created: data.created } }, '%safe_origin%'); window.close(); }) .catch(error => { console.error("Error:", error); generatingResults.classList.add("error"); generatingResults.innerText = "Error generating password: " + error; }); } hideAdminUi(); </script> ai/site-planner-connect/module.php 0000644 00000003425 15252521350 0013167 0 ustar 00 <?php namespace Elementor\Modules\Ai\SitePlannerConnect; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module { const NOT_TRANSLATED_APP_NAME = 'Site Planner'; const PLANNER_ORIGIN = 'https://planner.elementor.com'; const HIDDEN_PAGE_SLUG = ''; public function __construct() { add_action( 'rest_api_init', [ $this, 'on_rest_init' ] ); add_action( 'admin_menu', [ $this, 'register_menu_page' ], 100 ); add_filter( 'rest_prepare_application_password', function ( $response, $item, $request ) { if ( '/wp/v2/users/me/application-passwords' === $request->get_route() && is_user_logged_in() ) { $user = wp_get_current_user(); $response->data['user_login'] = $user->user_login; } return $response; }, 10, 3 ); } public function on_rest_init(): void { ( new Wp_Rest_Api() )->register(); } public function register_menu_page() { add_submenu_page( self::HIDDEN_PAGE_SLUG, 'App Password Generator', 'App Password', 'manage_options', 'e-site-planner-password-generator', [ $this, 'render_menu_page' ] ); } public function render_menu_page() { ob_start(); require_once __DIR__ . '/view.php'; $content = ob_get_clean(); $vars = [ '%app_name%' => self::NOT_TRANSLATED_APP_NAME, '%safe_origin%' => esc_url( self::PLANNER_ORIGIN ), '%domain%' => isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '', '%title%' => esc_html__( 'Connect to Site Planner', 'elementor' ), '%description%' => esc_html__( 'To connect your site to Site Planner, you need to generate an app password.', 'elementor' ), '%cta%' => esc_html__( 'Approve & Connect', 'elementor' ), ]; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo strtr( $content, $vars ); } } ai/site-planner-connect/wp-rest-api.php 0000644 00000001353 15252521350 0014050 0 ustar 00 <?php namespace Elementor\Modules\Ai\SitePlannerConnect; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Just a simple rest api to validate new Site Planner Connect feature exists. */ class Wp_Rest_Api { public function register(): void { register_rest_route('elementor-ai/v1', 'permissions', [ [ 'methods' => \WP_REST_Server::READABLE, 'permission_callback' => function () { return current_user_can( 'manage_options' ); }, 'callback' => function () { try { wp_send_json_success( [ 'site_planner_connect' => true, ] ); } catch ( \Exception $e ) { wp_send_json_error( [ 'message' => $e->getMessage(), ] ); } }, ], ] ); } } dev-tools/module.php 0000644 00000002763 15252521350 0010466 0 ustar 00 <?php namespace Elementor\Modules\DevTools; use Elementor\Core\Base\App; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Fix issue with 'Potentially polymorphic call. The code may be inoperable depending on the actual class instance passed as the argument.'. * Its tells to the editor that instance() return right module. instead of base module. * * @method Module instance() */ class Module extends App { /** * @var Deprecation */ public $deprecation; public function __construct() { $this->deprecation = new Deprecation( ELEMENTOR_VERSION ); add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'elementor/frontend/after_register_scripts', [ $this, 'register_scripts' ] ); add_action( 'elementor/common/after_register_scripts', [ $this, 'register_scripts' ] ); } public function get_name() { return 'dev-tools'; } public function register_scripts() { wp_register_script( 'elementor-dev-tools', $this->get_js_assets_url( 'dev-tools' ), [], ELEMENTOR_VERSION, true ); $this->print_config( 'elementor-dev-tools' ); } protected function get_init_settings() { return [ 'isDebug' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ), 'urls' => [ 'assets' => ELEMENTOR_ASSETS_URL, ], 'deprecation' => $this->deprecation->get_settings(), ]; } } dev-tools/deprecation.php 0000644 00000023266 15252521350 0011477 0 ustar 00 <?php namespace Elementor\Modules\DevTools; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Deprecation { const SOFT_VERSIONS_COUNT = 4; const HARD_VERSIONS_COUNT = 8; private $current_version = null; private $soft_deprecated_notices = []; public function __construct( $current_version ) { $this->current_version = $current_version; } public function get_settings() { return [ 'soft_notices' => $this->soft_deprecated_notices, 'soft_version_count' => self::SOFT_VERSIONS_COUNT, 'hard_version_count' => self::HARD_VERSIONS_COUNT, 'current_version' => ELEMENTOR_VERSION, ]; } /** * Get total of major. * * Since `get_total_major` cannot determine how much really versions between 2.9.0 and 3.3.0 if there is 2.10.0 version for example, * versions with major2 more then 9 will be added to total. * * @since 3.1.0 * * @param array $parsed_version * * @return int */ public function get_total_major( $parsed_version ) { $major1 = $parsed_version['major1']; $major2 = $parsed_version['major2']; $major2 = $major2 > 9 ? 9 : $major2; $minor = 0; $total = intval( "{$major1}{$major2}{$minor}" ); if ( $total > 99 ) { $total = $total / 10; } else { $total = intval( $total / 10 ); } if ( $parsed_version['major2'] > 9 ) { $total += $parsed_version['major2'] - 9; } return $total; } /** * Get next version. * * @since 3.1.0 * * @param string $version * @param int $count * * @return string|false */ public function get_next_version( $version, $count = 1 ) { $version = $this->parse_version( $version ); if ( ! $version ) { return false; } $version['total'] = $this->get_total_major( $version ) + $count; $total = $version['total']; if ( $total > 9 ) { $version['major1'] = intval( $total / 10 ); $version['major2'] = $total % 10; } else { $version['major1'] = 0; $version['major2'] = $total; } $version['minor'] = 0; return $this->implode_version( $version ); } /** * Implode parsed version to string version. * * @since 3.1.0 * * @param array $parsed_version * * @return string */ public function implode_version( $parsed_version ) { $major1 = $parsed_version['major1']; $major2 = $parsed_version['major2']; $minor = $parsed_version['minor']; return "{$major1}.{$major2}.{$minor}"; } /** * Parse to an informative array. * * @since 3.1.0 * * @param string $version * * @return array|false */ public function parse_version( $version ) { $version_explode = explode( '.', $version ); $version_explode_count = count( $version_explode ); if ( $version_explode_count < 3 || $version_explode_count > 4 ) { trigger_error( 'Invalid Semantic Version string provided' ); return false; } list( $major1, $major2, $minor ) = $version_explode; $result = [ 'major1' => intval( $major1 ), 'major2' => intval( $major2 ), 'minor' => intval( $minor ), ]; if ( $version_explode_count > 3 ) { $result['build'] = $version_explode[3]; } return $result; } /** * Compare two versions, result is equal to diff of major versions. * Notice: If you want to compare between 2.9.0 and 3.3.0, and there is also a 2.10.0 version, you cannot get the right comparison * Since $this->deprecation->get_total_major cannot determine how much really versions between 2.9.0 and 3.3.0. * * @since 3.1.0 * * @param {string} $version1 * @param {string} $version2 * * @return int|false */ public function compare_version( $version1, $version2 ) { $version1 = self::parse_version( $version1 ); $version2 = self::parse_version( $version2 ); if ( $version1 && $version2 ) { $versions = [ &$version1, &$version2 ]; foreach ( $versions as &$version ) { $version['total'] = self::get_total_major( $version ); } return $version1['total'] - $version2['total']; } return false; } /** * Check Deprecation * * Checks whether the given entity is valid. If valid, this method checks whether the deprecation * should be soft (browser console notice) or hard (use WordPress' native deprecation methods). * * @since 3.1.0 * * @param string $entity - The Deprecated entity (the function/hook itself) * @param string $version * @param string $replacement Optional * @param string $base_version Optional. Default is `null` * * @return bool * @throws \Exception Invalid deprecation. */ private function check_deprecation( $entity, $version, $replacement, $base_version = null ) { if ( null === $base_version ) { $base_version = $this->current_version; } $diff = $this->compare_version( $base_version, $version ); if ( false === $diff ) { throw new \Exception( 'Invalid deprecation diff.' ); } $print_deprecated = false; if ( defined( 'WP_DEBUG' ) && WP_DEBUG && $diff <= self::SOFT_VERSIONS_COUNT ) { // Soft deprecated. if ( ! isset( $this->soft_deprecated_notices[ $entity ] ) ) { $this->soft_deprecated_notices[ $entity ] = [ $version, $replacement, ]; } if ( Utils::is_elementor_debug() ) { $print_deprecated = true; } } return $print_deprecated; } /** * Deprecated Function * * Handles the deprecation process for functions. * * @since 3.1.0 * * @param string $function_name * @param string $version * @param string $replacement Optional. Default is '' * @param string $base_version Optional. Default is `null` * @throws \Exception Deprecation error. */ public function deprecated_function( $function_name, $version, $replacement = '', $base_version = null ) { $print_deprecated = $this->check_deprecation( $function_name, $version, $replacement, $base_version ); if ( $print_deprecated ) { // PHPCS - We need to echo special characters because they can exist in function calls. _deprecated_function( $function_name, esc_html( $version ), $replacement ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } /** * Deprecated Hook * * Handles the deprecation process for hooks. * * @param string $hook * @param string $version * @param string $replacement Optional. Default is '' * @param string $base_version Optional. Default is `null` * @throws \Exception Deprecation error. * @since 3.1.0 */ public function deprecated_hook( $hook, $version, $replacement = '', $base_version = null ) { $print_deprecated = $this->check_deprecation( $hook, $version, $replacement, $base_version ); if ( $print_deprecated ) { _deprecated_hook( esc_html( $hook ), esc_html( $version ), esc_html( $replacement ) ); } } /** * Deprecated Argument * * Handles the deprecation process for function arguments. * * @since 3.1.0 * * @param string $argument * @param string $version * @param string $replacement * @param string $message * @throws \Exception Deprecation error. */ public function deprecated_argument( $argument, $version, $replacement = '', $message = '' ) { $print_deprecated = $this->check_deprecation( $argument, $version, $replacement ); if ( $print_deprecated ) { $message = empty( $message ) ? '' : ' ' . $message; // These arguments are escaped because they are printed later, and are not escaped when printed. $error_message_args = [ esc_html( $argument ), esc_html( $version ) ]; if ( $replacement ) { /* translators: 1: Function argument, 2: Elementor version number, 3: Replacement argument name. */ $translation_string = esc_html__( 'The %1$s argument is deprecated since version %2$s! Use %3$s instead.', 'elementor' ); $error_message_args[] = $replacement; } else { /* translators: 1: Function argument, 2: Elementor version number. */ $translation_string = esc_html__( 'The %1$s argument is deprecated since version %2$s!', 'elementor' ); } trigger_error( vsprintf( // PHPCS - $translation_string is already escaped above. $translation_string, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped // PHPCS - $error_message_args is an array. $error_message_args // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ) . esc_html( $message ), E_USER_DEPRECATED ); } } /** * Do Deprecated Action * * A method used to run deprecated actions through Elementor's deprecation process. * * @param string $hook * @param array $args * @param string $version * @param string $replacement * @param null|string $base_version * * @throws \Exception Deprecation error. * @since 3.1.0 */ public function do_deprecated_action( $hook, $args, $version, $replacement = '', $base_version = null ) { if ( ! has_action( $hook ) ) { return; } $this->deprecated_hook( $hook, $version, $replacement, $base_version ); do_action_ref_array( $hook, $args ); } /** * Apply Deprecated Filter * * A method used to run deprecated filters through Elementor's deprecation process. * * @param string $hook * @param array $args * @param string $version * @param string $replacement * @param null|string $base_version * * @return mixed * @throws \Exception Deprecation error. * @since 3.2.0 */ public function apply_deprecated_filter( $hook, $args, $version, $replacement = '', $base_version = null ) { if ( ! has_action( $hook ) ) { // `$args` should be an array, but in order to keep BC, we need to support non-array values. if ( is_array( $args ) ) { return $args[0] ?? null; } return $args; } // BC - See the comment above. if ( ! is_array( $args ) ) { $args = [ $args ]; } // Avoid associative arrays. $args = array_values( $args ); $this->deprecated_hook( $hook, $version, $replacement, $base_version ); return apply_filters_ref_array( $hook, $args ); } } site-navigation/rest-fields/page-user-can.php 0000644 00000001647 15252521350 0015236 0 ustar 00 <?php namespace Elementor\Modules\SiteNavigation\Rest_Fields; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Page_User_Can { public function register_rest_field() { if ( ! isset( $_GET['_fields'] ) ) { return; } $fields = sanitize_text_field( wp_unslash( $_GET['_fields'] ) ); $array_fields = explode( ',', $fields ); if ( ! in_array( 'user_can', $array_fields ) ) { return; } register_rest_field( 'page', 'user_can', [ 'get_callback' => [ $this, 'get_callback' ], 'schema' => [ 'description' => __( 'Whether the current user can edit or delete this post', 'elementor' ), 'type' => 'array', ], ] ); } public function get_callback( $post ) { $can_edit = current_user_can( 'edit_post', $post['id'] ); $can_delete = current_user_can( 'delete_post', $post['id'] ); return [ 'edit' => $can_edit, 'delete' => $can_delete, ]; } } site-navigation/module.php 0000644 00000004256 15252521350 0011652 0 ustar 00 <?php namespace Elementor\Modules\SiteNavigation; use Elementor\Core\Base\Module as Module_Base; use Elementor\Core\Experiments\Exceptions\Dependency_Exception; use Elementor\Core\Experiments\Manager as Experiments_Manager; use Elementor\Modules\SiteNavigation\Data\Controller; use Elementor\Modules\SiteNavigation\Rest_Fields\Page_User_Can; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends Module_Base { const PAGES_PANEL_EXPERIMENT_NAME = 'pages_panel'; const PACKAGES = [ 'editor-site-navigation', ]; /** * Initialize the Site navigation module. * * @return void * @throws \Exception If the experiment registration fails. */ public function __construct() { Plugin::$instance->data_manager_v2->register_controller( new Controller() ); $this->register_pages_panel_experiment(); add_filter( 'elementor/editor/v2/packages', fn( $packages ) => $this->add_packages( $packages ) ); add_filter( 'elementor/editor/v2/scripts/env', function( $env ) { $env['@elementor/editor-site-navigation'] = [ 'is_pages_panel_active' => Plugin::$instance->experiments->is_feature_active( self::PAGES_PANEL_EXPERIMENT_NAME ), ]; return $env; } ); if ( Plugin::$instance->experiments->is_feature_active( self::PAGES_PANEL_EXPERIMENT_NAME ) ) { $this->register_rest_fields(); } } /** * Retrieve the module name. * * @return string */ public function get_name() { return 'site-navigation'; } /** * Register Experiment * * @since 3.16.0 * * @return void */ private function register_pages_panel_experiment() { Plugin::$instance->experiments->add_feature( [ 'name' => self::PAGES_PANEL_EXPERIMENT_NAME, 'title' => esc_html__( 'Pages Panel', 'elementor' ), 'release_status' => Experiments_Manager::RELEASE_STATUS_ALPHA, 'default' => Experiments_Manager::STATE_INACTIVE, 'hidden' => true, ] ); } private function register_rest_fields() { add_action( 'rest_api_init', function() { ( new Page_User_Can() )->register_rest_field(); } ); } private function add_packages( $packages ) { return array_merge( $packages, self::PACKAGES ); } } site-navigation/data/controller.php 0000644 00000002761 15252521350 0013460 0 ustar 00 <?php namespace Elementor\Modules\SiteNavigation\Data; use Elementor\Plugin; use Elementor\Data\V2\Base\Controller as Base_Controller; use Elementor\Modules\SiteNavigation\Data\Endpoints\Add_New_Post; use Elementor\Modules\SiteNavigation\Data\Endpoints\Duplicate_Post; use Elementor\Modules\SiteNavigation\Data\Endpoints\Homepage; use Elementor\Modules\SiteNavigation\Data\Endpoints\Recent_Posts; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Controller extends Base_Controller { public function get_name() { return 'site-navigation'; } public function get_items_permissions_check( $request ) { return current_user_can( 'edit_posts' ); } public function create_items_permissions_check( $request ): bool { // Permissions check is located in the endpoint return true; } public function get_item_permissions_check( $request ) { return $this->get_items_permissions_check( $request ); } public function create_item_permissions_check( $request ): bool { return $this->create_items_permissions_check( $request ); } public function register_endpoints() { $this->register_endpoint( new Recent_Posts( $this ) ); $this->register_endpoint( new Add_New_Post( $this ) ); if ( Plugin::$instance->experiments->is_feature_active( 'pages_panel' ) ) { $this->register_endpoint( new Duplicate_Post( $this ) ); $this->register_endpoint( new Homepage( $this ) ); } } protected function register_index_endpoint() { // Bypass, currently does not required. } } site-navigation/data/endpoints/homepage.php 0000644 00000001214 15252521350 0015055 0 ustar 00 <?php namespace Elementor\Modules\SiteNavigation\Data\Endpoints; use Elementor\Data\V2\Base\Endpoint; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Homepage extends Endpoint { public function get_permission_callback( $request ) { return current_user_can( 'edit_posts' ); } public function get_name() { return 'homepage'; } public function get_format() { return 'site-navigation/homepage'; } public function get_items( $request ) { $homepage_id = get_option( 'page_on_front' ); $show_on_front = get_option( 'show_on_front' ); return 'page' === $show_on_front ? intval( $homepage_id ) : 0; } } site-navigation/data/endpoints/duplicate-post.php 0000644 00000007467 15252521350 0016245 0 ustar 00 <?php namespace Elementor\Modules\SiteNavigation\Data\Endpoints; use Elementor\Data\V2\Base\Endpoint; use Elementor\Plugin; use Elementor\User; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Duplicate_Post extends Endpoint { protected function register() { $args = [ 'post_id' => [ 'description' => 'Post id to duplicate', 'type' => 'integer', 'required' => true, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ], 'title' => [ 'description' => 'Post title', 'type' => 'string', 'required' => false, 'sanitize_callback' => function ( $value ) { return sanitize_text_field( $value ); }, 'validate_callback' => 'rest_validate_request_arg', ], ]; $this->register_items_route( \WP_REST_Server::CREATABLE, $args ); } public function get_name() { return 'duplicate-post'; } public function get_format() { return 'site-navigation/duplicate-post'; } public function create_items( $request ) { $post_id = $request->get_param( 'post_id' ); $post_title = $request->get_param( 'title' ); $post = get_post( $post_id ); if ( ! User::is_current_user_can_edit( $post_id ) ) { $sanitized_post_type = esc_html( str_replace( '%', '%%', $post->post_type ) ); return new \WP_Error( 401, sprintf( 'User dont have capability to create page of type - %s.', $sanitized_post_type ), [ 'status' => 401 ] ); } if ( ! $post ) { return new \WP_Error( 500, 'Post not found' ); } $new_post_id = $this->duplicate_post( $post, $post_title ); if ( is_wp_error( $new_post_id ) ) { return new \WP_Error( 500, 'Error while duplicating post.' ); } // Duplicate all post meta $this->duplicate_post_meta( $post_id, $new_post_id ); // Duplicate all taxonomies $this->duplicate_post_taxonomies( $post_id, $new_post_id ); return [ 'post_id' => $new_post_id, ]; } /** * Duplicate post * * @param $post * * @return int|\WP_Error */ private function duplicate_post( $post, $post_title ) { $post_status = 'draft'; $current_user = wp_get_current_user(); $new_post_author = $current_user->ID; $args = [ 'comment_status' => $post->comment_status, 'ping_status' => $post->ping_status, 'post_author' => $new_post_author, 'post_content' => $post->post_content, 'post_excerpt' => $post->post_excerpt, 'post_parent' => $post->post_parent, 'post_password' => $post->post_password, 'post_status' => $post_status, 'post_title' => $post_title, 'post_type' => $post->post_type, 'to_ping' => $post->to_ping, 'menu_order' => $post->menu_order, ]; return wp_insert_post( $args ); } /** * Duplicate the associated post meta to the new post ID. * * @param int $post_id * @param int $new_post_id */ private function duplicate_post_meta( int $post_id, int $new_post_id ) { $post_meta = get_post_meta( $post_id ); if ( empty( $post_meta ) || ! is_array( $post_meta ) ) { return; } foreach ( $post_meta as $key => $values ) { if ( '_wp_old_slug' === $key ) { // Ignore this meta key continue; } foreach ( $values as $value ) { $value = maybe_unserialize( $value ); add_post_meta( $new_post_id, $key, wp_slash( $value ) ); } } } /** * Duplicate_post_taxonomies * * @param int $post_id * @param int $new_post_id */ private function duplicate_post_taxonomies( $post_id, $new_post_id ) { $taxonomies = array_map( 'sanitize_text_field', get_object_taxonomies( get_post_type( $post_id ) ) ); if ( empty( $taxonomies ) || ! is_array( $taxonomies ) ) { return; } foreach ( $taxonomies as $taxonomy ) { $post_terms = wp_get_object_terms( $post_id, $taxonomy, [ 'fields' => 'slugs' ] ); if ( ! is_wp_error( $post_terms ) ) { wp_set_object_terms( $new_post_id, $post_terms, $taxonomy, false ); } } } } site-navigation/data/endpoints/add-new-post.php 0000644 00000004576 15252521350 0015610 0 ustar 00 <?php namespace Elementor\Modules\SiteNavigation\Data\Endpoints; use Elementor\Data\V2\Base\Endpoint; use Elementor\Plugin; use Elementor\User; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Add_New_Post extends Endpoint { protected function register() { $args = [ 'post_type' => [ 'description' => 'Post type to create', 'type' => 'string', 'required' => false, 'default' => 'post', 'sanitize_callback' => function ( $value ) { return sanitize_text_field( $value ); }, 'validate_callback' => 'rest_validate_request_arg', ], ]; $this->register_items_route( \WP_REST_Server::CREATABLE, $args ); } public function get_name() { return 'add-new-post'; } public function get_format() { return 'site-navigation/add-new-post'; } public function create_items( $request ) { $post_type = $request->get_param( 'post_type' ); if ( ! $this->validate_post_type( $post_type ) ) { $sanitized_post_type = esc_html( str_replace( '%', '%%', $post_type ) ); return new \WP_Error( 400, sprintf( 'Post type %s does not exist.', $sanitized_post_type ), [ 'status' => 400 ] ); } if ( ! User::is_current_user_can_edit_post_type( $post_type ) ) { $sanitized_post_type = esc_html( str_replace( '%', '%%', $post_type ) ); return new \WP_Error( 401, sprintf( 'User dont have capability to create page of type - %s.', $sanitized_post_type ), [ 'status' => 401 ] ); } // Temporary solution for the fact that documents creation not using the actual registered post types. $post_type = $this->map_post_type( $post_type ); $document = Plugin::$instance->documents->create( $post_type ); if ( is_wp_error( $document ) ) { $sanitized_post_type = esc_html( str_replace( '%', '%%', $post_type ) ); return new \WP_Error( 500, sprintf( 'Error while creating %s.', $sanitized_post_type ) ); } return [ 'id' => $document->get_main_id(), 'edit_url' => $document->get_edit_url(), ]; } private function validate_post_type( $post_type ): bool { $post_types = get_post_types(); return in_array( $post_type, $post_types ); } /** * Map post type to Elementor document type. * * @param $post_type * * @return string */ private function map_post_type( $post_type ): string { $post_type_map = [ 'page' => 'wp-page', 'post' => 'wp-post', ]; return $post_type_map[ $post_type ] ?? $post_type; } } site-navigation/data/endpoints/recent-posts.php 0000644 00000004762 15252521350 0015731 0 ustar 00 <?php namespace Elementor\Modules\SiteNavigation\Data\Endpoints; use Elementor\Core\Base\Document; use Elementor\Core\Kits\Documents\Kit; use Elementor\Data\V2\Base\Endpoint; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils; use WP_REST_Server; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Recent_Posts extends Endpoint { public function register_items_route( $methods = WP_REST_Server::READABLE, $args = [] ) { $args = [ 'posts_per_page' => [ 'description' => 'Number of posts to return', 'type' => 'integer', 'required' => true, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ], 'post_type' => [ 'description' => 'Post types to retrieve', 'type' => 'array', 'required' => false, 'default' => [ 'page', 'post', Source_Local::CPT ], 'sanitize_callback' => 'rest_sanitize_array', 'validate_callback' => 'rest_validate_request_arg', ], 'post__not_in' => [ 'description' => 'Post id`s to exclude', 'type' => 'array', 'required' => [], 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ], ]; parent::register_items_route( $methods, $args ); } public function get_name() { return 'recent-posts'; } public function get_format() { return 'site-navigation/recent-posts'; } public function get_items( $request ) { $args = [ 'posts_per_page' => $request->get_param( 'posts_per_page' ), 'post_type' => $request->get_param( 'post_type' ), 'fields' => 'ids', 'meta_query' => [ [ 'key' => Document::TYPE_META_KEY, 'value' => Kit::get_type(), // Exclude kits. 'compare' => '!=', ], ], ]; $exclude = $request->get_param( 'post__not_in' ); if ( ! empty( $exclude ) ) { $args['post__not_in'] = $exclude; } $recently_edited_query = Utils::get_recently_edited_posts_query( $args ); $recent = []; foreach ( $recently_edited_query->posts as $id ) { $document = Plugin::$instance->documents->get( $id ); $recent[] = [ 'id' => $id, 'title' => get_the_title( $id ), 'edit_url' => $document->get_edit_url(), 'date_modified' => get_post_timestamp( $id, 'modified' ), 'type' => [ 'post_type' => get_post_type( $id ), 'doc_type' => $document->get_name(), 'label' => $document->get_title(), ], 'user_can' => [ 'edit' => current_user_can( 'edit_post', $id ), ], ]; } return $recent; } } floating-buttons/base/widget-contact-button-base.php 0000644 00000246175 15252521350 0016642 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Base; use Elementor\Controls_Manager; use Elementor\Core\Base\Providers\Social_Network_Provider; use Elementor\Core\Base\Traits\Shared_Widget_Controls_Trait; use Elementor\Group_Control_Box_Shadow; use Elementor\Group_Control_Typography; use Elementor\Modules\FloatingButtons\Classes\Render\Contact_Buttons_Core_Render; use Elementor\Modules\FloatingButtons\Documents\Floating_Buttons; use Elementor\Plugin; use Elementor\Repeater; use Elementor\Utils; use Elementor\Widget_Base; abstract class Widget_Contact_Button_Base extends Widget_Base { use Shared_Widget_Controls_Trait; const TAB_ADVANCED = 'advanced-tab-floating-buttons'; public function show_in_panel(): bool { return false; } public function get_group_name(): string { return 'floating-buttons'; } public function get_style_depends(): array { $widget_name = $this->get_name(); $style_depends = Plugin::$instance->experiments->is_feature_active( 'e_font_icon_svg' ) ? parent::get_style_depends() : [ 'elementor-icons-fa-solid', 'elementor-icons-fa-brands', 'elementor-icons-fa-regular' ]; $style_depends[] = 'widget-contact-buttons-base'; if ( 'contact-buttons' !== $widget_name ) { $style_depends[] = "widget-{$widget_name}"; } return $style_depends; } public function has_widget_inner_wrapper(): bool { return ! Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' ); } public function hide_on_search(): bool { return true; } protected function get_initial_config(): array { return array_merge( parent::get_initial_config(), [ 'commonMerged' => true, ] ); } public static function get_configuration() { return [ 'content' => [ 'chat_button_section' => [ 'section_name' => esc_html__( 'Chat Button', 'elementor' ), 'has_platform' => true, 'has_icon' => false, 'icon_default' => [ 'value' => 'far fa-comment-dots', 'library' => 'fa-regular', ], 'icons_recommended' => [ 'fa-regular' => [ 'comment', 'comment-dots', 'comment-alt', ], 'fa-solid' => [ 'ellipsis-v', ], ], 'has_notification_dot' => true, 'has_notification_dot_default_enabled' => true, 'has_active_tab' => false, 'has_display_text' => false, 'display_text_label' => esc_html__( 'Call now', 'elementor' ), 'has_display_text_select' => true, 'platform' => [ 'group' => [ Social_Network_Provider::EMAIL, Social_Network_Provider::SMS, Social_Network_Provider::WHATSAPP, Social_Network_Provider::SKYPE, Social_Network_Provider::MESSENGER, Social_Network_Provider::VIBER, ], 'default' => Social_Network_Provider::WHATSAPP, ], 'chat_aria_label' => Floating_Buttons::get_title(), 'defaults' => [ 'mail' => null, 'mail_subject' => null, 'mail_body' => null, 'number' => null, 'username' => null, 'location' => [ 'is_external' => true, ], 'url' => [ 'is_external' => true, ], ], 'has_accessible_name' => true, ], 'top_bar_section' => [ 'section_name' => esc_html__( 'Top Bar', 'elementor' ), 'has_image' => true, 'has_active_dot' => true, 'has_subtitle' => true, 'title' => [ 'label' => esc_html__( 'Name', 'elementor' ), 'default' => esc_html__( 'Rob Jones', 'elementor' ), 'placeholder' => esc_html__( 'Type your name here', 'elementor' ), 'dynamic' => false, 'ai' => false, 'label_block' => false, ], 'subtitle' => [ 'label' => esc_html__( 'Title', 'elementor' ), 'default' => esc_html__( 'Store Manager', 'elementor' ), 'placeholder' => esc_html__( 'Type your title here', 'elementor' ), 'dynamic' => false, 'ai' => false, 'label_block' => false, ], ], 'message_bubble_section' => [ 'has_typing_animation' => true, ], 'contact_section' => [ 'section_name' => esc_html__( 'Contact Buttons', 'elementor' ), 'has_cta_text' => true, 'repeater' => [ 'has_tooltip' => false, 'tooltip_label' => esc_html__( 'Text', 'elementor' ), 'tooltip_default' => esc_html__( 'Tooltip', 'elementor' ), 'tooltip_placeholder' => esc_html__( 'Enter icon text', 'elementor' ), 'has_title' => false, 'has_description' => false, ], 'platform' => [ 'group-1' => [ Social_Network_Provider::EMAIL, Social_Network_Provider::SMS, Social_Network_Provider::WHATSAPP, Social_Network_Provider::SKYPE, Social_Network_Provider::MESSENGER, Social_Network_Provider::VIBER, ], 'limit' => 5, 'min_items' => 0, ], 'default' => [ [ 'contact_icon_platform' => Social_Network_Provider::WHATSAPP, ], [ 'contact_icon_platform' => Social_Network_Provider::EMAIL, ], [ 'contact_icon_platform' => Social_Network_Provider::SMS, ], [ 'contact_icon_platform' => Social_Network_Provider::VIBER, ], [ 'contact_icon_platform' => Social_Network_Provider::MESSENGER, ], ], 'has_accessible_name' => true, ], 'send_button_section' => [ 'section_name' => esc_html__( 'Send Button', 'elementor' ), 'has_link' => false, 'text' => [ 'default' => esc_html__( 'Click to start chat', 'elementor' ), ], ], ], 'style' => [ 'has_platform_colors' => true, 'chat_button_section' => [ 'has_entrance_animation' => true, 'has_box_shadow' => true, 'has_drop_shadow' => false, 'has_padding' => false, 'has_button_size' => true, 'button_size_default' => 'small', 'has_typography' => false, 'has_icon_position' => false, 'has_icon_spacing' => false, 'has_tabs' => true, 'has_platform_color_controls' => false, 'hover_animation_type' => 'default', 'icon_color_label' => esc_html__( 'Icon Color', 'elementor' ), ], 'top_bar_section' => [ 'has_title_heading' => true, 'title_heading_label' => esc_html__( 'Name', 'elementor' ), 'subtitle_heading_label' => esc_html__( 'Title', 'elementor' ), 'has_style_close_button' => true, 'has_close_button_heading' => false, 'has_background' => true, 'has_background_heading' => false, ], 'message_bubble_section' => [ 'has_chat_background' => true, ], 'contact_section' => [ 'has_buttons_heading' => true, 'buttons_heading_label' => esc_html__( 'Buttons', 'elementor' ), 'has_buttons_size' => true, 'has_box_shadow' => false, 'has_buttons_spacing' => false, 'has_hover_animation' => true, 'has_chat_box_animation' => false, 'has_icon_bg_color' => true, 'has_button_bar' => false, 'has_tabs' => true, 'has_text_color' => false, 'has_bg_color' => false, 'has_padding' => false, 'has_button_corners' => false, 'has_typography' => false, 'icon_color_label' => esc_html__( 'Icon Color', 'elementor' ), 'has_hover_transition_duration' => false, ], 'send_button_section' => [ 'has_platform_colors' => true, 'has_icon_color' => true, 'has_background_color' => true, 'has_text_color' => false, 'has_typography' => true, 'typography_selector' => '{{WRAPPER}} .e-contact-buttons__send-cta', ], 'chat_box_section' => [ 'section_name' => esc_html__( 'Chat Box', 'elementor' ), 'has_width' => true, 'has_padding' => false, ], ], 'advanced' => [ 'has_layout_position' => true, 'horizontal_position_default' => 'end', 'has_mobile_full_width' => false, 'has_vertical_offset' => true, 'has_horizontal_offset' => true, ], ]; } public function get_icon(): string { return 'eicon-commenting-o'; } public function get_categories(): array { return [ 'general' ]; } protected function register_controls(): void { $this->add_content_tab(); $this->add_style_tab(); $this->add_advanced_tab(); } private function social_media_controls(): void { $config = static::get_configuration(); $this->add_control( 'chat_button_mail', [ 'label' => esc_html__( 'Email', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'label_block' => true, 'placeholder' => esc_html__( '@', 'elementor' ), 'default' => $config['content']['chat_button_section']['defaults']['mail'], 'condition' => [ 'chat_button_platform' => Social_Network_Provider::EMAIL, ], ], ); $this->add_control( 'chat_button_mail_subject', [ 'label' => esc_html__( 'Subject', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'default' => $config['content']['chat_button_section']['defaults']['mail_subject'], 'condition' => [ 'chat_button_platform' => Social_Network_Provider::EMAIL, ], ], ); $this->add_control( 'chat_button_mail_body', [ 'label' => esc_html__( 'Message', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'default' => $config['content']['chat_button_section']['defaults']['mail_body'], 'condition' => [ 'chat_button_platform' => Social_Network_Provider::EMAIL, ], ] ); $this->add_control( 'chat_button_number', [ 'label' => esc_html__( 'Number', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'label_block' => true, 'placeholder' => esc_html__( '+', 'elementor' ), 'default' => $config['content']['chat_button_section']['defaults']['number'], 'condition' => [ 'chat_button_platform' => [ Social_Network_Provider::SMS, Social_Network_Provider::WHATSAPP, Social_Network_Provider::VIBER, Social_Network_Provider::TELEPHONE, ], ], ], ); $this->add_control( 'chat_button_username', [ 'label' => esc_html__( 'Username', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'label_block' => true, 'default' => $config['content']['chat_button_section']['defaults']['username'], 'condition' => [ 'chat_button_platform' => [ Social_Network_Provider::SKYPE, Social_Network_Provider::MESSENGER, ], ], ], ); $this->add_control( 'chat_button_viber_action', [ 'label' => esc_html__( 'Action', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'chat', 'options' => [ 'chat' => 'Chat', 'contact' => 'Contact', ], 'condition' => [ 'chat_button_platform' => Social_Network_Provider::VIBER, ], ] ); $this->add_control( 'chat_button_waze', [ 'label' => esc_html__( 'Location', 'elementor' ), 'type' => Controls_Manager::URL, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'label_block' => true, 'placeholder' => esc_html__( 'Paste Waze link', 'elementor' ), 'default' => $config['content']['chat_button_section']['defaults']['location'], 'condition' => [ 'chat_button_platform' => [ Social_Network_Provider::WAZE, ], ], ], ); $this->add_control( 'chat_button_url', [ 'label' => esc_html__( 'Link', 'elementor' ), 'type' => Controls_Manager::URL, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'autocomplete' => true, 'label_block' => true, 'default' => $config['content']['chat_button_section']['defaults']['url'], 'condition' => [ 'chat_button_platform' => [ Social_Network_Provider::URL, ], ], ], ); } private function get_display_text_condition( $condition ) { $config = static::get_configuration(); if ( true == $config['content']['chat_button_section']['has_display_text_select'] ) { return $condition; } return null; } protected function add_chat_button_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'chat_button_section', [ 'label' => $config['content']['chat_button_section']['section_name'], 'tab' => Controls_Manager::TAB_CONTENT, ] ); if ( $config['content']['chat_button_section']['has_accessible_name'] ) { $this->add_control( 'chat_aria_label', [ 'label' => esc_html__( 'Accessible name', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => $config['content']['chat_button_section']['chat_aria_label'], 'placeholder' => esc_html__( 'Add accessible name', 'elementor' ), 'dynamic' => [ 'active' => true, ], ], ); } if ( $config['content']['chat_button_section']['has_platform'] ) { $this->add_control( 'chat_button_platform', [ 'label' => esc_html__( 'Platform', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => $config['content']['chat_button_section']['platform']['default'], 'options' => Social_Network_Provider::get_social_networks_text( $config['content']['chat_button_section']['platform']['group'] ), ] ); $this->social_media_controls(); } if ( $config['content']['chat_button_section']['has_icon'] ) { $this->add_control( 'chat_button_icon', [ 'label' => esc_html__( 'Icon', 'elementor' ), 'type' => Controls_Manager::ICONS, 'default' => $config['content']['chat_button_section']['icon_default'], 'recommended' => $config['content']['chat_button_section']['icons_recommended'], ] ); } if ( $config['content']['chat_button_section']['has_notification_dot'] ) { $notification_dot_return_value = 'yes'; $notification_dot_default = $notification_dot_return_value; // Only clear if explicitly passed if ( false === $config['content']['chat_button_section']['has_notification_dot_default_enabled'] ) { $notification_dot_default = ''; } $this->add_control( 'chat_button_show_dot', [ 'label' => esc_html__( 'Notification Dot', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'elementor' ), 'label_off' => esc_html__( 'Hide', 'elementor' ), 'return_value' => $notification_dot_return_value, 'default' => $notification_dot_default, ] ); } if ( $config['content']['chat_button_section']['has_display_text'] ) { if ( $config['content']['chat_button_section']['has_display_text_select'] ) { $this->add_control( 'chat_button_display_text_select', [ 'label' => esc_html__( 'Display Text', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'details', 'options' => [ 'details' => esc_html__( 'Contact Details', 'elementor' ), 'cta' => esc_html__( 'Call to Action', 'elementor' ), ], ] ); } $this->add_control( 'chat_button_display_text', [ 'label' => esc_html__( 'Call to Action Text', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'placeholder' => esc_html__( 'Enter the text', 'elementor' ), 'default' => $config['content']['chat_button_section']['display_text_label'], 'condition' => $this->get_display_text_condition([ 'chat_button_display_text_select' => 'cta', ] ), ], ); } $this->end_controls_section(); } protected function add_top_bar_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'top_bar_section', [ 'label' => $config['content']['top_bar_section']['section_name'], 'tab' => Controls_Manager::TAB_CONTENT, ] ); $this->add_control( 'top_bar_title', [ 'label' => $config['content']['top_bar_section']['title']['label'], 'type' => Controls_Manager::TEXT, 'default' => $config['content']['top_bar_section']['title']['default'], 'placeholder' => $config['content']['top_bar_section']['title']['placeholder'], 'dynamic' => [ 'active' => $config['content']['top_bar_section']['title']['dynamic'], ], 'ai' => [ 'active' => $config['content']['top_bar_section']['title']['ai'], ], 'label_block' => $config['content']['top_bar_section']['title']['label_block'], ] ); if ( $config['content']['top_bar_section']['has_subtitle'] ) { $this->add_control( 'top_bar_subtitle', [ 'label' => $config['content']['top_bar_section']['subtitle']['label'], 'type' => Controls_Manager::TEXT, 'default' => $config['content']['top_bar_section']['subtitle']['default'], 'placeholder' => $config['content']['top_bar_section']['subtitle']['placeholder'], $config['content']['top_bar_section']['subtitle']['dynamic'], 'ai' => [ 'active' => $config['content']['top_bar_section']['subtitle']['ai'], ], 'label_block' => $config['content']['top_bar_section']['title']['label_block'], ] ); } if ( $config['content']['top_bar_section']['has_image'] ) { $this->add_control( 'top_bar_image', [ 'label' => esc_html__( 'Profile Image', 'elementor' ), 'type' => Controls_Manager::MEDIA, 'default' => [ 'url' => Utils::get_placeholder_image_src(), ], ] ); } if ( $config['content']['top_bar_section']['has_active_dot'] ) { $this->add_control( 'top_bar_show_dot', [ 'label' => esc_html__( 'Active Dot', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'elementor' ), 'label_off' => esc_html__( 'Hide', 'elementor' ), 'return_value' => 'yes', 'default' => 'yes', ] ); } $this->end_controls_section(); } protected function add_message_bubble_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'message_bubble_section', [ 'label' => esc_html__( 'Message Bubble', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); $this->add_control( 'message_bubble_name', [ 'label' => esc_html__( 'Name', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => esc_html__( 'Rob', 'elementor' ), 'placeholder' => esc_html__( 'Type your name here', 'elementor' ), ] ); $this->add_control( 'message_bubble_body', [ 'label' => esc_html__( 'Message', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'default' => esc_html__( 'Hey, how can I help you today?', 'elementor' ), 'placeholder' => esc_html__( 'Message', 'elementor' ), ], ); $this->add_control( 'chat_button_time_format', [ 'label' => esc_html__( 'Time format', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => '12h', 'options' => [ '12h' => esc_html__( '2:20 PM', 'elementor' ), '24h' => esc_html__( '14:20', 'elementor' ), ], ] ); if ( $config['content']['message_bubble_section']['has_typing_animation'] ) { $this->add_control( 'chat_button_show_animation', [ 'label' => esc_html__( 'Typing Animation', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'elementor' ), 'label_off' => esc_html__( 'Hide', 'elementor' ), 'return_value' => 'yes', 'default' => 'yes', ] ); } $this->end_controls_section(); } protected function add_contact_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'contact_section', [ 'label' => $config['content']['contact_section']['section_name'], 'tab' => Controls_Manager::TAB_CONTENT, ] ); if ( $config['content']['contact_section']['has_accessible_name'] ) { $this->add_control( 'contact_aria_label', [ 'label' => esc_html__( 'Accessible name', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => $config['content']['chat_button_section']['chat_aria_label'], 'placeholder' => esc_html__( 'Add accessible name', 'elementor' ), 'dynamic' => [ 'active' => true, ], ], ); } if ( $config['content']['contact_section']['has_cta_text'] ) { $this->add_control( 'contact_cta_text', [ 'label' => esc_html__( 'Call to Action Text', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => esc_html__( 'Start conversation:', 'elementor' ), 'placeholder' => esc_html__( 'Type your text here', 'elementor' ), 'label_block' => true, ] ); } if ( $config['content']['contact_section']['platform']['limit'] ) { if ( $config['content']['contact_section']['platform']['min_items'] ) { $this->add_control( 'contact_custom_panel_alert', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => sprintf( /* translators: 1: Minimum items, 2: Items limit. */ esc_html__( 'Add between %1$s to %2$s contact buttons', 'elementor' ), '<b>' . $config['content']['contact_section']['platform']['min_items'] . '</b>', '<b>' . $config['content']['contact_section']['platform']['limit'] . '</b>' ), ] ); } else { $this->add_control( 'contact_custom_panel_alert', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => sprintf( /* translators: %s: Items limit. */ esc_html__( 'Add up to %s contact buttons', 'elementor' ), '<b>' . $config['content']['contact_section']['platform']['limit'] . '</b>' ), ] ); } } $repeater = new Repeater(); $repeater->add_control( 'contact_icon_platform', [ 'label' => esc_html__( 'Platform', 'elementor' ), 'type' => Controls_Manager::SELECT, 'options' => Social_Network_Provider::get_social_networks_text( $config['content']['contact_section']['platform']['group-1'] ), 'default' => Social_Network_Provider::WHATSAPP, ], ); if ( $config['content']['contact_section']['repeater']['has_tooltip'] ) { $repeater->add_control( 'contact_tooltip', [ 'label' => $config['content']['contact_section']['repeater']['tooltip_label'], 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'default' => $config['content']['contact_section']['repeater']['tooltip_default'], 'placeholder' => $config['content']['contact_section']['repeater']['tooltip_placeholder'], ], ); } if ( $config['content']['contact_section']['repeater']['has_title'] ) { $repeater->add_control( 'contact_title', [ 'label' => 'Title', 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'default' => 'Title', 'placeholder' => esc_html__( 'Enter title', 'elementor' ), ], ); } if ( $config['content']['contact_section']['repeater']['has_description'] ) { $repeater->add_control( 'contact_description', [ 'label' => 'Description', 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'default' => 'Description', 'placeholder' => esc_html__( 'Enter description', 'elementor' ), ], ); } $repeater->add_control( 'contact_icon_mail', [ 'label' => esc_html__( 'Email', 'elementor' ), 'type' => Controls_Manager::TEXT, 'placeholder' => esc_html__( 'Enter your email', 'elementor' ), 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'label_block' => true, 'condition' => [ 'contact_icon_platform' => [ Social_Network_Provider::EMAIL, ], ], ], ); $repeater->add_control( 'contact_icon_mail_subject', [ 'label' => esc_html__( 'Subject', 'elementor' ), 'type' => Controls_Manager::TEXT, 'placeholder' => esc_html__( 'Subject', 'elementor' ), 'label_block' => true, 'condition' => [ 'contact_icon_platform' => [ Social_Network_Provider::EMAIL, ], ], ] ); $repeater->add_control( 'contact_icon_mail_body', [ 'label' => esc_html__( 'Message', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'placeholder' => esc_html__( 'Message', 'elementor' ), 'label_block' => true, 'condition' => [ 'contact_icon_platform' => [ Social_Network_Provider::EMAIL, ], ], ] ); $repeater->add_control( 'contact_icon_number', [ 'label' => esc_html__( 'Number', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'label_block' => true, 'placeholder' => esc_html__( '+', 'elementor' ), 'condition' => [ 'contact_icon_platform' => [ Social_Network_Provider::SMS, Social_Network_Provider::WHATSAPP, Social_Network_Provider::VIBER, Social_Network_Provider::TELEPHONE, ], ], ], ); $repeater->add_control( 'contact_icon_username', [ 'label' => esc_html__( 'Username', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'label_block' => true, 'placeholder' => esc_html__( 'Enter your username', 'elementor' ), 'condition' => [ 'contact_icon_platform' => [ Social_Network_Provider::MESSENGER, Social_Network_Provider::SKYPE, ], ], ], ); $repeater->add_control( 'contact_icon_url', [ 'label' => esc_html__( 'Link', 'elementor' ), 'type' => Controls_Manager::URL, 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'autocomplete' => true, 'label_block' => true, 'condition' => [ 'contact_icon_platform' => [ Social_Network_Provider::URL, ], ], 'default' => [ 'is_external' => true, ], 'placeholder' => esc_html__( 'Paste URL or type', 'elementor' ), ], ); $repeater->add_control( 'contact_icon_waze', [ 'label' => esc_html__( 'Location', 'elementor' ), 'type' => Controls_Manager::URL, 'default' => [ 'is_external' => true, ], 'dynamic' => [ 'active' => true, ], 'label_block' => true, 'placeholder' => esc_html__( 'Paste Waze link', 'elementor' ), 'condition' => [ 'contact_icon_platform' => [ Social_Network_Provider::WAZE, ], ], 'ai' => [ 'active' => false, ], ], ); $repeater->add_control( 'contact_icon_viber_action', [ 'label' => esc_html__( 'Action', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'chat', 'dynamic' => [ 'active' => true, ], 'options' => [ 'chat' => 'Chat', 'contact' => 'Contact', ], 'condition' => [ 'contact_icon_platform' => Social_Network_Provider::VIBER, ], ] ); $this->add_control( 'contact_repeater', [ 'max_items' => $config['content']['contact_section']['platform']['limit'], 'min_items' => $config['content']['contact_section']['platform']['min_items'], 'type' => Controls_Manager::REPEATER, 'fields' => $repeater->get_controls(), 'title_field' => $this->get_icon_title_field(), 'prevent_empty' => true, 'button_text' => esc_html__( 'Add Item', 'elementor' ), 'default' => $config['content']['contact_section']['default'], ] ); $this->end_controls_section(); } protected function get_icon_title_field(): string { $platform_icons_js = json_encode( Social_Network_Provider::get_social_networks_icons() ); $platform_text_js = json_encode( Social_Network_Provider::get_social_networks_text() ); return <<<JS <# elementor.helpers.enqueueIconFonts( 'fa-solid' ); elementor.helpers.enqueueIconFonts( 'fa-brands' ); const mapping = {$platform_icons_js}; const text_mapping = {$platform_text_js}; #> <i class='{{{ mapping[contact_icon_platform] }}}' ></i> {{{ text_mapping[contact_icon_platform] }}} JS; } protected function add_send_button_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'send_button_section', [ 'label' => $config['content']['send_button_section']['section_name'], 'tab' => Controls_Manager::TAB_CONTENT, ] ); $this->add_control( 'send_button_text', [ 'label' => esc_html__( 'Text', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => $config['content']['send_button_section']['text']['default'], 'placeholder' => esc_html__( 'Type your text here', 'elementor' ), 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], ] ); if ( $config['content']['send_button_section']['has_link'] ) { $this->add_control( 'send_button_url', [ 'label' => esc_html__( 'Link', 'elementor' ), 'type' => Controls_Manager::URL, 'default' => [ 'is_external' => true, ], 'dynamic' => [ 'active' => true, ], 'ai' => [ 'active' => false, ], 'autocomplete' => true, 'label_block' => true, ], ); } $this->end_controls_section(); } protected function add_content_tab(): void { $this->add_chat_button_section(); $this->add_top_bar_section(); $this->add_message_bubble_section(); $this->add_send_button_section(); } private function get_platform_color_condition( $condition ) { $config = static::get_configuration(); if ( true == $config['style']['has_platform_colors'] ) { return $condition; } return null; } protected function add_style_chat_button_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'style_chat_button', [ 'label' => $config['content']['chat_button_section']['section_name'], 'tab' => Controls_Manager::TAB_STYLE, ] ); if ( $config['style']['chat_button_section']['has_button_size'] ) { $this->add_control( 'style_chat_button_size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => $config['style']['chat_button_section']['button_size_default'], 'options' => [ 'small' => esc_html__( 'Small', 'elementor' ), 'medium' => esc_html__( 'Medium', 'elementor' ), 'large' => esc_html__( 'Large', 'elementor' ), ], ] ); } if ( $config['style']['chat_button_section']['has_icon_position'] ) { $this->add_responsive_control( 'style_chat_button_horizontal_position', [ 'label' => esc_html__( 'Icon Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Left', 'elementor' ), 'icon' => 'eicon-h-align-left', ], 'end' => [ 'title' => esc_html__( 'Right', 'elementor' ), 'icon' => 'eicon-h-align-right', ], ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons__chat-button svg' => 'order: {{VALUE}};', ], 'selectors_dictionary' => [ 'start' => '-1', 'end' => '2', ], 'default' => 'start', 'mobile_default' => 'start', 'toggle' => true, ] ); } if ( $config['style']['chat_button_section']['has_icon_spacing'] ) { $this->add_responsive_control( 'style_chat_button_spacing', [ 'label' => esc_html__( 'Icon Spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'min' => 10, 'max' => 100, ], 'px' => [ 'min' => 0, 'max' => 100, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-chat-button-gap: {{SIZE}}{{UNIT}}', ], 'separator' => 'before', ] ); } if ( $config['style']['chat_button_section']['has_typography'] ) { $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_top_bar_title_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__chat-button', ] ); } if ( $config['style']['chat_button_section']['has_tabs'] ) { $this->start_controls_tabs( 'style_button_color_tabs' ); $this->start_controls_tab( 'style_button_color_tabs_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); if ( $config['style']['has_platform_colors'] ) { $this->add_control( 'style_button_color_select', [ 'label' => esc_html__( 'Colors', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'default', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), 'custom' => esc_html__( 'Custom', 'elementor' ), ], ] ); } $this->add_control( 'style_button_color_icon', [ 'label' => $config['style']['chat_button_section']['icon_color_label'], 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-button-icon: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_button_color_select' => 'custom', ] ), ] ); $this->add_control( 'style_button_color_background', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-button-bg: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_button_color_select' => 'custom', ] ), ] ); $this->end_controls_tab(); $this->start_controls_tab( 'style_button_color_tabs_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); if ( $config['style']['has_platform_colors'] ) { $this->add_control( 'style_button_color_select_hover', [ 'label' => esc_html__( 'Colors', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'default', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), 'custom' => esc_html__( 'Custom', 'elementor' ), ], ] ); } $this->add_control( 'style_button_color_icon_hover', [ 'label' => $config['style']['chat_button_section']['icon_color_label'], 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-button-icon-hover: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_button_color_select_hover' => 'custom', ] ), ] ); $this->add_control( 'style_button_color_background_hover', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-button-bg-hover: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_button_color_select_hover' => 'custom', ] ), ] ); if ( 'default' == $config['style']['chat_button_section']['hover_animation_type'] ) { $this->add_hover_animation_control( 'style_button_color_hover_animation', ); } $this->end_controls_tab(); if ( $config['content']['chat_button_section']['has_active_tab'] ) { $this->start_controls_tab( 'style_button_color_tabs_active', [ 'label' => esc_html__( 'Active', 'elementor' ), ] ); $this->add_control( 'style_button_color_icon_active', [ 'label' => esc_html__( 'Icon Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-active-button-color: {{VALUE}}', ], ] ); $this->add_control( 'style_button_color_background_active', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-active-button-bg: {{VALUE}}', ], ] ); $this->end_controls_tab(); } $this->end_controls_tabs(); } if ( $config['style']['chat_button_section']['has_platform_color_controls'] ) { $this->add_control( 'style_platform_control_select', [ 'label' => esc_html__( 'Colors', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'default', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), 'custom' => esc_html__( 'Custom', 'elementor' ), ], 'separator' => 'before', ] ); $this->add_control( 'style_button_color_icon', [ 'label' => $config['style']['chat_button_section']['icon_color_label'], 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-button-icon: {{VALUE}}', ], 'condition' => [ 'style_platform_control_select' => 'custom', ], ] ); $this->add_control( 'style_button_color_background', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-button-bg: {{VALUE}}', ], 'condition' => [ 'style_platform_control_select' => 'custom', ], ] ); } if ( $config['style']['chat_button_section']['has_entrance_animation'] ) { $this->add_responsive_control( 'style_chat_button_animation', [ 'label' => esc_html__( 'Entrance Animation', 'elementor' ), 'type' => Controls_Manager::ANIMATION, 'frontend_available' => true, 'separator' => 'before', ] ); $this->add_control( 'style_chat_button_animation_duration', [ 'label' => esc_html__( 'Animation Duration', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'normal', 'options' => [ 'slow' => esc_html__( 'Slow', 'elementor' ), 'normal' => esc_html__( 'Normal', 'elementor' ), 'fast' => esc_html__( 'Fast', 'elementor' ), ], 'prefix_class' => 'animated-', ] ); $this->add_control( 'style_chat_button_animation_delay', [ 'label' => esc_html__( 'Animation Delay', 'elementor' ) . ' (ms)', 'type' => Controls_Manager::NUMBER, 'min' => 0, 'step' => 100, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-button-chat-button-animation-delay: {{SIZE}}ms;', ], 'render_type' => 'none', 'frontend_available' => true, 'separator' => 'after', ] ); } if ( $config['style']['chat_button_section']['has_box_shadow'] ) { $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'style_chat_button_box_shadow', 'selector' => '{{WRAPPER}} .e-contact-buttons__chat-button-shadow', ] ); } if ( $config['style']['chat_button_section']['has_drop_shadow'] ) { $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'style_chat_button_drop_shadow', 'fields_options' => [ 'box_shadow' => [ 'selectors' => [ '{{WRAPPER}} .e-contact-buttons__chat-button-drop-shadow' => 'filter: drop-shadow({{HORIZONTAL}}px {{VERTICAL}}px {{BLUR}}px {{COLOR}});', ], ], ], ] ); } if ( $config['style']['chat_button_section']['has_padding'] ) { $this->add_responsive_control( 'style_chat_button_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-chat-button-padding-block-end: {{BOTTOM}}{{UNIT}}; --e-contact-buttons-chat-button-padding-block-start: {{TOP}}{{UNIT}}; --e-contact-buttons-chat-button-padding-inline-end: {{RIGHT}}{{UNIT}}; --e-contact-buttons-chat-button-padding-inline-start: {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); } if ( 'custom' == $config['style']['chat_button_section']['hover_animation_type'] ) { $this->add_control( 'style_chat_button_custom_animation_heading', [ 'label' => esc_html__( 'Hover Animation', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'style_chat_button_custom_animation_alert', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => __( 'Hover animation is <b>desktop only</b>', 'elementor' ), ] ); $this->add_control( 'style_chat_button_custom_animation_transition', [ 'label' => esc_html__( 'Transition Duration', 'elementor' ) . ' (s)', 'type' => Controls_Manager::SLIDER, 'range' => [ 's' => [ 'min' => 0, 'max' => 3, 'step' => 0.1, ], ], 'default' => [ 'unit' => 's', 'size' => 0.3, ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-transition-duration: {{SIZE}}{{UNIT}}', ], ] ); } $this->end_controls_section(); } protected function add_style_top_bar_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'style_top_bar_section', [ 'label' => $config['content']['top_bar_section']['section_name'], 'tab' => Controls_Manager::TAB_STYLE, ] ); if ( $config['content']['top_bar_section']['has_image'] ) { $this->add_control( 'style_top_bar_profile_heading', [ 'label' => esc_html__( 'Profile Image', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'style_top_bar_image_size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'medium', 'options' => [ 'small' => esc_html__( 'Small', 'elementor' ), 'medium' => esc_html__( 'Medium', 'elementor' ), 'large' => esc_html__( 'Large', 'elementor' ), ], ] ); } if ( $config['style']['has_platform_colors'] ) { $this->add_control( 'style_top_bar_colors', [ 'label' => esc_html__( 'Colors', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'default', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), 'custom' => esc_html__( 'Custom', 'elementor' ), ], 'separator' => 'before', ] ); } if ( $config['style']['top_bar_section']['has_title_heading'] ) { $this->add_control( 'style_top_bar_title_heading', [ 'label' => $config['style']['top_bar_section']['title_heading_label'], 'type' => Controls_Manager::HEADING, 'separator' => ! $config['style']['has_platform_colors'] ? 'before' : false, ] ); } $this->add_control( 'style_top_bar_title_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-top-bar-title: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_top_bar_colors' => 'custom', ] ), ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_top_bar_title_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__top-bar-title', ] ); if ( $config['content']['top_bar_section']['has_subtitle'] ) { $this->add_control( 'style_top_bar_subtitle_heading', [ 'label' => $config['style']['top_bar_section']['subtitle_heading_label'], 'type' => Controls_Manager::HEADING, 'separator' => false, ] ); $this->add_control( 'style_top_bar_subtitle_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-top-bar-subtitle: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_top_bar_colors' => 'custom', ] ), ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_top_bar_subtitle_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__top-bar-subtitle', ] ); } $close_and_background_partial_divider = 'before'; if ( $config['style']['top_bar_section']['has_style_close_button'] ) { if ( $config['style']['top_bar_section']['has_close_button_heading'] ) { $this->add_control( 'style_top_bar_close_button_heading', [ 'label' => esc_html__( 'Close Button', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => $close_and_background_partial_divider, 'condition' => $this->get_platform_color_condition( [ 'style_top_bar_colors' => 'custom', ] ), ] ); $close_and_background_partial_divider = false; } $this->add_control( 'style_top_bar_close_button_color', [ 'label' => esc_html__( 'Close Button Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-close-button-color: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_top_bar_colors' => 'custom', ] ), 'separator' => $close_and_background_partial_divider, ] ); $close_and_background_partial_divider = false; } if ( $config['style']['top_bar_section']['has_background'] ) { if ( $config['style']['top_bar_section']['has_background_heading'] ) { $this->add_control( 'style_top_bar_background_heading', [ 'label' => esc_html__( 'Background', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => $close_and_background_partial_divider, 'condition' => $this->get_platform_color_condition( [ 'style_top_bar_colors' => 'custom', ] ), ] ); $close_and_background_partial_divider = false; } $this->add_control( 'style_top_bar_background_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-top-bar-bg: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_top_bar_colors' => 'custom', ] ), 'separator' => $close_and_background_partial_divider, ] ); } $this->end_controls_section(); } protected function add_style_message_bubble_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'style_bubble_section', [ 'label' => esc_html__( 'Message Bubble', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); if ( $config['style']['has_platform_colors'] ) { $this->add_control( 'style_bubble_colors', [ 'label' => esc_html__( 'Colors', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'default', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), 'custom' => esc_html__( 'Custom', 'elementor' ), ], ] ); } $this->add_control( 'style_bubble_name_heading', [ 'label' => esc_html__( 'Name', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => false, ] ); $this->add_control( 'style_bubble_name_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-message-bubble-name: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_bubble_colors' => 'custom', ] ), ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_bubble_name_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__message-bubble-name', ] ); $this->add_control( 'style_bubble_message_heading', [ 'label' => esc_html__( 'Message', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => false, ] ); $this->add_control( 'style_bubble_message_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-message-bubble-body: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_bubble_colors' => 'custom', ] ), ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_bubble_message_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__message-bubble-body', ] ); $this->add_control( 'style_bubble_time_heading', [ 'label' => esc_html__( 'Time', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => false, ] ); $this->add_control( 'style_bubble_time_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-message-bubble-time: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_bubble_colors' => 'custom', ] ), ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_bubble_time_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__message-bubble-time', ] ); $this->add_control( 'style_bubble_background_color', [ 'label' => esc_html__( 'Bubble Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-message-bubble-bubble-bg: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_bubble_colors' => 'custom', ] ), 'separator' => 'before', ] ); if ( $config['style']['message_bubble_section']['has_chat_background'] ) { $this->add_control( 'style_bubble_chat_color', [ 'label' => esc_html__( 'Chat Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-message-bubble-chat-bg: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_bubble_colors' => 'custom', ] ), ] ); } $this->end_controls_section(); } protected function add_style_contact_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'style_contact_section', [ 'label' => $config['content']['contact_section']['section_name'], 'tab' => Controls_Manager::TAB_STYLE, ] ); if ( $config['content']['contact_section']['has_cta_text'] ) { $this->add_control( 'style_contact_text_heading', [ 'label' => esc_html__( 'Call to Action Text', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => false, 'condition' => $this->get_platform_color_condition( [ 'style_bubble_colors' => 'custom', ] ), ] ); $this->add_control( 'style_contact_text_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-text: {{VALUE}}', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_contact_text_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__contact-text', ] ); } if ( $config['style']['contact_section']['has_buttons_heading'] ) { $this->add_control( 'style_contact_buttons_heading', [ 'label' => $config['style']['contact_section']['buttons_heading_label'], 'type' => Controls_Manager::HEADING, 'separator' => false, 'condition' => $this->get_platform_color_condition( [ 'style_bubble_colors' => 'custom', ] ), ] ); } if ( $config['style']['contact_section']['has_buttons_size'] ) { $this->add_control( 'style_contact_button_size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'small', 'options' => [ 'small' => esc_html__( 'Small', 'elementor' ), 'medium' => esc_html__( 'Medium', 'elementor' ), 'large' => esc_html__( 'Large', 'elementor' ), ], ] ); } if ( $config['style']['contact_section']['has_text_color'] ) { $this->add_control( 'style_contact_button_text_color', [ 'label' => $config['style']['contact_section']['icon_color_label'], 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-button-icon: {{VALUE}}', ], ] ); } if ( $config['style']['contact_section']['has_typography'] ) { $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_contact_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__contact-icon-link', ] ); } if ( $config['style']['contact_section']['has_bg_color'] ) { $this->add_control( 'style_contact_button_bg_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-button-bg: {{VALUE}}', ], ] ); } if ( $config['style']['contact_section']['has_tabs'] ) { $this->start_controls_tabs( 'style_contact_button_color_tabs' ); $this->start_controls_tab( 'style_contact_button_color_tabs_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_control( 'style_contact_button_color_icon', [ 'label' => $config['style']['contact_section']['icon_color_label'], 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-button-icon: {{VALUE}}', ], ] ); if ( $config['style']['contact_section']['has_icon_bg_color'] ) { $this->add_control( 'style_contact_button_color_background', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-button-bg: {{VALUE}}', ], ] ); } $this->end_controls_tab(); $this->start_controls_tab( 'style_contact_button_color_tabs_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_control( 'style_contact_button_color_icon_hover', [ 'label' => esc_html__( 'Icon Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-button-icon-hover: {{VALUE}}', ], ] ); if ( $config['style']['contact_section']['has_icon_bg_color'] ) { $this->add_control( 'style_contact_button_color_background_hover', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-button-bg-hover: {{VALUE}}', ], ] ); } if ( $config['style']['contact_section']['has_hover_animation'] ) { $this->add_hover_animation_control( 'style_contact_button_hover_animation', ); } $this->end_controls_tab(); $this->end_controls_tabs(); } if ( $config['style']['contact_section']['has_buttons_spacing'] ) { $this->add_responsive_control( 'style_contact_buttons_spacing', [ 'label' => esc_html__( 'Buttons Spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'min' => 10, 'max' => 100, ], 'px' => [ 'min' => 0, 'max' => 100, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-gap: {{SIZE}}{{UNIT}}', ], 'separator' => 'before', ] ); } if ( $config['style']['contact_section']['has_button_corners'] ) { $this->add_control( 'style_contact_corners', [ 'label' => esc_html__( 'Corners', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'round', 'options' => [ 'round' => esc_html__( 'Round', 'elementor' ), 'rounded' => esc_html__( 'Rounded', 'elementor' ), 'sharp' => esc_html__( 'Sharp', 'elementor' ), ], ] ); } if ( $config['style']['contact_section']['has_box_shadow'] ) { $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'style_contact_icons_box_shadow', 'selector' => '{{WRAPPER}} .e-contact-buttons__contact-box-shadow', ] ); } if ( $config['content']['contact_section']['repeater']['has_tooltip'] ) { $this->add_control( 'style_contact_tooltip_heading', [ 'label' => esc_html__( 'Tooltips', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'style_contact_tooltip_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-tooltip-text: {{VALUE}}', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_contact_tooltip_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__contact-tooltip', ] ); $this->add_control( 'style_contact_tooltip_bg_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-tooltip-bg: {{VALUE}}', ], ] ); } if ( $config['style']['contact_section']['has_chat_box_animation'] ) { $this->chat_box_animation_controls(); } if ( $config['style']['contact_section']['has_button_bar'] ) { $this->add_control( 'style_contact_button_bar_heading', [ 'label' => esc_html__( 'Button Bar', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_control( 'style_contact_button_bar_bg_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-button-bar-bg: {{VALUE}}', ], ] ); $this->add_control( 'style_contact_button_bar_corners', [ 'label' => esc_html__( 'Corners', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'round', 'options' => [ 'round' => esc_html__( 'Round', 'elementor' ), 'rounded' => esc_html__( 'Rounded', 'elementor' ), 'sharp' => esc_html__( 'Sharp', 'elementor' ), ], ] ); $this->add_responsive_control( 'style_contact_button_bar_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-button-bar-padding-block-end: {{BOTTOM}}{{UNIT}}; --e-contact-buttons-button-bar-padding-block-start: {{TOP}}{{UNIT}}; --e-contact-buttons-button-bar-padding-inline-end: {{RIGHT}}{{UNIT}}; --e-contact-buttons-button-bar-padding-inline-start: {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); } if ( $config['style']['contact_section']['has_padding'] ) { $this->add_responsive_control( 'style_contact_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-padding-block-end: {{BOTTOM}}{{UNIT}}; --e-contact-buttons-contact-padding-block-start: {{TOP}}{{UNIT}}; --e-contact-buttons-contact-padding-inline-end: {{RIGHT}}{{UNIT}}; --e-contact-buttons-contact-padding-inline-start: {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); } if ( $config['style']['contact_section']['has_hover_transition_duration'] ) { $this->add_control( 'style_contact_custom_animation_heading', [ 'label' => esc_html__( 'Animation', 'elementor' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_control( 'style_contact_custom_animation_alert', [ 'type' => Controls_Manager::ALERT, 'alert_type' => 'info', 'content' => __( 'Adjust transition duration to change the speed of the <b>hover animation on desktop</b> and the <b>click animation on touchscreen</b>.', 'elementor' ), ] ); $this->add_control( 'style_contact_custom_animation_transition', [ 'label' => esc_html__( 'Transition Duration', 'elementor' ) . ' (s)', 'type' => Controls_Manager::SLIDER, 'range' => [ 's' => [ 'min' => 0, 'max' => 3, 'step' => 0.1, ], ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-transition-duration: {{SIZE}}{{UNIT}}', ], ] ); } $this->end_controls_section(); } protected function add_style_resource_links_section(): void { $this->start_controls_section( 'style_resource_links_section', [ 'label' => esc_html__( 'Resource Links', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'style_resource_links_icons_heading', [ 'label' => esc_html__( 'Icons', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => false, ] ); $this->add_control( 'style_resource_links_button_size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'small', 'options' => [ 'small' => esc_html__( 'Small', 'elementor' ), 'medium' => esc_html__( 'Medium', 'elementor' ), 'large' => esc_html__( 'Large', 'elementor' ), ], ] ); $this->add_control( 'style_resource_links_color_select', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'custom', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), 'custom' => esc_html__( 'Custom', 'elementor' ), ], ] ); $this->add_control( 'style_contact_icon_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-button-icon: {{VALUE}}', ], 'condition' => [ 'style_resource_links_color_select' => 'custom', ], ] ); $this->add_control( 'style_resource_links_title_heading', [ 'label' => esc_html__( 'Title', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => false, ] ); $this->add_control( 'style_resource_links_title_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-title-text-color: {{VALUE}}', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_resource_links_title_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__contact-title', ] ); $this->add_control( 'style_resource_links_description_heading', [ 'label' => esc_html__( 'Description', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => false, ] ); $this->add_control( 'style_resource_links_description_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-description-text-color: {{VALUE}}', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_resource_links_description_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__contact-description', ] ); $this->add_control( 'style_resource_links_bg_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-contact-button-bg: {{VALUE}}', ], 'separator' => 'before', ] ); $this->add_hover_animation_control( 'style_resource_links_hover_animation', ); $this->end_controls_section(); } protected function add_style_info_links_section(): void { $this->start_controls_section( 'style_info_links_section', [ 'label' => esc_html__( 'Info Links', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'style_info_links_icon_position', [ 'label' => esc_html__( 'Icon Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Left', 'elementor' ), 'icon' => 'eicon-h-align-left', ], 'end' => [ 'title' => esc_html__( 'Right', 'elementor' ), 'icon' => 'eicon-h-align-right', ], ], 'default' => 'start', 'toggle' => true, ] ); $this->add_control( 'style_info_links_icon_spacing', [ 'label' => esc_html__( 'Icon Spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'min' => 10, 'max' => 100, ], 'px' => [ 'min' => 0, 'max' => 20, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-icon-link-gap: {{SIZE}}{{UNIT}}', ], ] ); $this->add_responsive_control( 'style_info_links_link_spacing', [ 'label' => esc_html__( 'Link Spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'min' => 10, 'max' => 100, ], 'px' => [ 'min' => 0, 'max' => 10, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-icon-link-spacing: {{SIZE}}{{UNIT}}', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_info_links_typography', 'selector' => '{{WRAPPER}} .e-contact-buttons__contact-icon-link', ] ); $this->start_controls_tabs( 'style_info_links_tabs' ); $this->start_controls_tab( 'style_info_links_tabs_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_control( 'style_info_links_normal_text_color', [ 'label' => esc_html__( 'Text and Icon Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-icon-link-text-color: {{VALUE}}', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'style_info_links_tabs_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_control( 'style_info_links_hover_text_color', [ 'label' => esc_html__( 'Text and Icon Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-icon-link-text-color-hover: {{VALUE}}', ], ] ); $this->add_control( 'style_info_links_hover_animation', [ 'label' => esc_html__( 'Hover Animation', 'elementor' ), 'type' => Controls_Manager::HOVER_ANIMATION, 'frontend_available' => true, ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_control( 'style_info_links_dividers', [ 'label' => esc_html__( 'Dividers', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'elementor' ), 'label_off' => esc_html__( 'Hide', 'elementor' ), 'return_value' => 'yes', 'default' => 'yes', 'separator' => 'before', ] ); $this->add_control( 'style_info_links_divider_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-icon-link-divider-color: {{VALUE}}', ], 'condition' => [ 'style_info_links_dividers' => 'yes', ], ] ); $this->add_responsive_control( 'style_info_links_divider_weight', [ 'label' => esc_html__( 'Weight', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'min' => 10, 'max' => 100, ], 'px' => [ 'min' => 1, 'max' => 10, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-icon-link-divider-weight: {{SIZE}}{{UNIT}}', ], 'condition' => [ 'style_info_links_dividers' => 'yes', ], ] ); $this->end_controls_section(); } protected function add_style_send_button_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'style_send_section', [ 'label' => $config['content']['send_button_section']['section_name'], 'tab' => Controls_Manager::TAB_STYLE, ] ); if ( $config['style']['send_button_section']['has_typography'] ) { $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_send_typography', 'selector' => $config['style']['send_button_section']['typography_selector'], ] ); } $this->start_controls_tabs( 'style_send_tabs' ); $this->start_controls_tab( 'style_send_tabs_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); if ( $config['style']['send_button_section']['has_platform_colors'] ) { $this->add_control( 'style_send_normal_colors', [ 'label' => esc_html__( 'Colors', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'default', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), 'custom' => esc_html__( 'Custom', 'elementor' ), ], ] ); } if ( $config['style']['send_button_section']['has_icon_color'] ) { $this->add_control( 'style_send_normal_icon_color', [ 'label' => esc_html__( 'Icon Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-send-button-icon: {{VALUE}}', ], 'condition' => [ 'style_send_normal_colors' => 'custom', ], ] ); } if ( $config['style']['send_button_section']['has_text_color'] ) { $this->add_control( 'style_send_normal_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-send-button-text: {{VALUE}}', ], ] ); } if ( $config['style']['send_button_section']['has_background_color'] ) { $this->add_control( 'style_send_normal_background_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-send-button-bg: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_send_normal_colors' => 'custom', ] ), ] ); } $this->end_controls_tab(); $this->start_controls_tab( 'style_send_tabs_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); if ( $config['style']['send_button_section']['has_platform_colors'] ) { $this->add_control( 'style_send_hover_colors', [ 'label' => esc_html__( 'Colors', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'default', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), 'custom' => esc_html__( 'Custom', 'elementor' ), ], ] ); } if ( $config['style']['send_button_section']['has_icon_color'] ) { $this->add_control( 'style_send_hover_icon_color', [ 'label' => esc_html__( 'Icon Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-send-button-icon-hover: {{VALUE}}', ], 'condition' => [ 'style_send_hover_colors' => 'custom', ], ] ); } if ( $config['style']['send_button_section']['has_text_color'] ) { $this->add_control( 'style_send_hover_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-send-button-text-hover: {{VALUE}}', ], ] ); } if ( $config['style']['send_button_section']['has_background_color'] ) { $this->add_control( 'style_send_hover_background_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-send-button-bg-hover: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_send_hover_colors' => 'custom', ] ), ] ); } $this->add_hover_animation_control( 'style_send_hover_animation', ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_responsive_control( 'style_chat_button_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-send-button-padding-block-end: {{BOTTOM}}{{UNIT}}; --e-contact-buttons-send-button-padding-block-start: {{TOP}}{{UNIT}}; --e-contact-buttons-send-button-padding-inline-end: {{RIGHT}}{{UNIT}}; --e-contact-buttons-send-button-padding-inline-start: {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); $this->end_controls_section(); } protected function chat_box_animation_controls(): void { $this->add_responsive_control( 'style_chat_box_entrance_animation', [ 'label' => esc_html__( 'Open Animation', 'elementor' ), 'type' => Controls_Manager::ANIMATION, 'frontend_available' => true, 'separator' => 'before', ] ); $this->add_responsive_control( 'style_chat_box_exit_animation', [ 'label' => esc_html__( 'Close Animation', 'elementor' ), 'type' => Controls_Manager::EXIT_ANIMATION, 'frontend_available' => true, ] ); $this->add_control( 'style_chat_box_animation_duration', [ 'label' => esc_html__( 'Animation Duration', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'normal', 'options' => [ 'slow' => esc_html__( 'Slow', 'elementor' ), 'normal' => esc_html__( 'Normal', 'elementor' ), 'fast' => esc_html__( 'Fast', 'elementor' ), ], 'prefix_class' => 'animated-', ] ); } protected function add_style_chat_box_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'style_chat_box_section', [ 'label' => $config['style']['chat_box_section']['section_name'], 'tab' => Controls_Manager::TAB_STYLE, ] ); if ( $config['style']['has_platform_colors'] ) { $this->add_control( 'style_chat_box_bg_select', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'default', 'options' => [ 'default' => esc_html__( 'Default', 'elementor' ), 'custom' => esc_html__( 'Custom', 'elementor' ), ], ] ); } $this->add_control( 'style_chat_box_bg_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-chat-box-bg: {{VALUE}}', ], 'condition' => $this->get_platform_color_condition( [ 'style_chat_box_bg_select' => 'custom', ] ), ] ); if ( $config['style']['chat_box_section']['has_width'] ) { $this->add_responsive_control( 'style_chat_box_width', [ 'label' => esc_html__( 'Width', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'min' => 10, 'max' => 100, ], 'px' => [ 'min' => 0, 'max' => 400, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-chat-box-width: {{SIZE}}{{UNIT}}', ], ] ); } $this->add_control( 'style_chat_box_corners', [ 'label' => esc_html__( 'Corners', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'rounded', 'options' => [ 'round' => esc_html__( 'Round', 'elementor' ), 'rounded' => esc_html__( 'Rounded', 'elementor' ), 'sharp' => esc_html__( 'Sharp', 'elementor' ), ], 'separator' => 'before', ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'style_chat_box_box_shadow', 'selector' => '{{WRAPPER}} .e-contact-buttons__content', ] ); if ( $config['style']['chat_box_section']['has_padding'] ) { $this->add_responsive_control( 'style_chat_box_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-chat-box-padding-block-end: {{BOTTOM}}{{UNIT}}; --e-contact-buttons-chat-box-padding-block-start: {{TOP}}{{UNIT}}; --e-contact-buttons-chat-box-padding-inline-end: {{RIGHT}}{{UNIT}}; --e-contact-buttons-chat-box-padding-inline-start: {{LEFT}}{{UNIT}};', ], ] ); } $this->chat_box_animation_controls(); $this->end_controls_section(); } protected function add_style_tab(): void { $this->add_style_chat_button_section(); $this->add_style_top_bar_section(); $this->add_style_message_bubble_section(); $this->add_style_send_button_section(); $this->add_style_chat_box_section(); } private function add_advanced_tab(): void { $config = static::get_configuration(); Controls_Manager::add_tab( static::TAB_ADVANCED, esc_html__( 'Advanced', 'elementor' ) ); if ( $config['advanced']['has_layout_position'] ) { $this->start_controls_section( 'advanced_layout_section', [ 'label' => esc_html__( 'Layout', 'elementor' ), 'tab' => static::TAB_ADVANCED, ] ); $this->add_control( 'advanced_horizontal_position', [ 'label' => esc_html__( 'Horizontal Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Left', 'elementor' ), 'icon' => 'eicon-h-align-left', ], 'center' => [ 'title' => esc_html__( 'Center', 'elementor' ), 'icon' => 'eicon-h-align-center', ], 'end' => [ 'title' => esc_html__( 'Right', 'elementor' ), 'icon' => 'eicon-h-align-right', ], ], 'default' => $config['advanced']['horizontal_position_default'], 'toggle' => false, ] ); if ( $config['advanced']['has_horizontal_offset'] ) { $this->add_responsive_control( 'advanced_horizontal_offset', [ 'label' => esc_html__( 'Offset', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'min' => 10, 'max' => 100, ], 'px' => [ 'min' => 0, 'max' => 100, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-horizontal-offset: {{SIZE}}{{UNIT}}', ], 'condition' => [ 'advanced_horizontal_position' => [ 'start', 'end', ], ], ] ); } $this->add_control( 'advanced_vertical_position', [ 'label' => esc_html__( 'Vertical Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'top' => [ 'title' => esc_html__( 'Top', 'elementor' ), 'icon' => 'eicon-v-align-top', ], 'middle' => [ 'title' => esc_html__( 'Middle', 'elementor' ), 'icon' => 'eicon-v-align-middle', ], 'bottom' => [ 'title' => esc_html__( 'Bottom', 'elementor' ), 'icon' => 'eicon-v-align-bottom', ], ], 'default' => 'bottom', 'toggle' => false, ] ); if ( $config['advanced']['has_vertical_offset'] ) { $this->add_responsive_control( 'advanced_vertical_offset', [ 'label' => esc_html__( 'Offset', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'min' => 10, 'max' => 100, ], 'px' => [ 'min' => 0, 'max' => 100, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-contact-buttons' => '--e-contact-buttons-vertical-offset: {{SIZE}}{{UNIT}}', ], 'condition' => [ 'advanced_vertical_position' => [ 'top', 'bottom', ], ], ] ); } if ( $config['advanced']['has_mobile_full_width'] ) { $this->add_control( 'advanced_mobile_full_width', [ 'label' => esc_html__( 'Full Width on Mobile', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'return_value' => 'yes', 'default' => 'yes', ] ); } $this->end_controls_section(); } $this->start_controls_section( 'advanced_responsive_section', [ 'label' => esc_html__( 'Responsive', 'elementor' ), 'tab' => static::TAB_ADVANCED, ] ); $this->add_control( 'responsive_description', [ 'raw' => __( 'Responsive visibility will take effect only on preview mode or live page, and not while editing in Elementor.', 'elementor' ), 'type' => Controls_Manager::RAW_HTML, 'content_classes' => 'elementor-descriptor', ] ); $this->add_hidden_device_controls(); $this->end_controls_section(); $this->start_controls_section( 'advanced_custom_controls_section', [ 'label' => esc_html__( 'CSS', 'elementor' ), 'tab' => static::TAB_ADVANCED, ] ); $this->add_control( 'advanced_custom_css_id', [ 'label' => esc_html__( 'CSS ID', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], 'title' => esc_html__( 'Add your custom id WITHOUT the Pound key. e.g: my-id', 'elementor' ), 'style_transfer' => false, ] ); $this->add_control( 'advanced_custom_css_classes', [ 'label' => esc_html__( 'CSS Classes', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], 'title' => esc_html__( 'Add your custom class WITHOUT the dot. e.g: my-class', 'elementor' ), ] ); $this->end_controls_section(); Plugin::$instance->controls_manager->add_custom_css_controls( $this, static::TAB_ADVANCED ); Plugin::$instance->controls_manager->add_custom_attributes_controls( $this, static::TAB_ADVANCED ); } protected function render(): void { $render_strategy = new Contact_Buttons_Core_Render( $this ); $render_strategy->render(); } } floating-buttons/base/widget-floating-bars-base.php 0000644 00000113211 15252521350 0016406 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Base; use Elementor\Modules\FloatingButtons\Classes\Render\Floating_Bars_Core_Render; use Elementor\Core\Base\Providers\Social_Network_Provider; use Elementor\Group_Control_Background; use Elementor\Group_Control_Typography; use Elementor\Plugin; use Elementor\Repeater; use Elementor\Controls_Manager; use Elementor\Widget_Base; abstract class Widget_Floating_Bars_Base extends Widget_Base { const TAB_ADVANCED = 'advanced-tab-floating-bars'; public function get_style_depends(): array { $widget_name = $this->get_name(); $style_depends = Plugin::$instance->experiments->is_feature_active( 'e_font_icon_svg' ) ? parent::get_style_depends() : [ 'elementor-icons-fa-solid', 'elementor-icons-fa-brands', 'elementor-icons-fa-regular' ]; $style_depends[] = 'widget-floating-bars-base'; $style_depends[] = "widget-{$widget_name}"; return $style_depends; } public function has_widget_inner_wrapper(): bool { return ! Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' ); } public function get_icon(): string { return 'eicon-banner'; } public function show_in_panel() { return false; } public function hide_on_search() { return true; } protected function get_initial_config(): array { return array_merge( parent::get_initial_config(), [ 'commonMerged' => true, ] ); } public static function get_configuration() { return [ 'content' => [ 'announcement_section' => [ 'icon_default' => [ 'value' => 'fas fa-tshirt', 'library' => 'fa-solid', ], 'text_label' => esc_html__( 'Text', 'elementor' ), 'text_default' => esc_html__( 'Just in! Cool summer tees', 'elementor' ), ], 'floating_bar_section' => [ 'close_switch_default' => 'yes', 'has_pause_switch' => false, 'accessible_name_default' => esc_html__( 'Banner', 'elementor' ), ], ], 'style' => [ 'floating_bar_section' => [ 'has_close_bg' => false, 'close_position_selectors' => [ '{{WRAPPER}} .e-floating-bars__close-button' => 'inset-inline-{{VALUE}}: 10px;', ], 'has_close_position_control' => true, 'background_selector' => '{{WRAPPER}} .e-floating-bars', 'align_elements_selector' => [ '{{WRAPPER}} .e-floating-bars' => 'justify-content: {{VALUE}};', '{{WRAPPER}} .e-floating-bars__cta-button-container' => 'justify-content: {{VALUE}};', '{{WRAPPER}} .e-floating-bars__announcement-text' => 'text-align: {{VALUE}};', ], ], ], 'advanced' => [], ]; } protected function register_controls(): void { $this->add_content_tab(); $this->add_style_tab(); $this->add_advanced_tab(); } protected function add_announcement_content_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'announcement_content_section', [ 'label' => __( 'Announcement', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); $this->add_control( 'announcement_icon', [ 'label' => esc_html__( 'Icon', 'elementor' ), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon', 'default' => $config['content']['announcement_section']['icon_default'], 'skin' => 'inline', 'label_block' => false, 'icon_exclude_inline_options' => [], ] ); $this->add_control( 'announcement_text', [ 'label' => $config['content']['announcement_section']['text_label'], 'type' => Controls_Manager::TEXTAREA, 'dynamic' => [ 'active' => true, ], 'placeholder' => esc_html__( 'Enter your text here', 'elementor' ), 'default' => $config['content']['announcement_section']['text_default'], ] ); $this->end_controls_section(); } protected function add_cta_button_content_section(): void { $this->start_controls_section( 'cta_button_content_section', [ 'label' => __( 'CTA Button', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); $this->add_control( 'cta_text', [ 'label' => esc_html__( 'Text', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'placeholder' => esc_html__( 'Enter text', 'elementor' ), 'default' => esc_html__( 'Shop now', 'elementor' ), ], ); $this->add_control( 'cta_link', [ 'label' => esc_html__( 'Link', 'elementor' ), 'type' => Controls_Manager::URL, 'placeholder' => esc_html__( 'Paste URL or type', 'elementor' ), 'dynamic' => [ 'active' => true, ], 'default' => [ 'url' => '', 'is_external' => true, 'nofollow' => false, ], ] ); $this->add_control( 'cta_icon', [ 'label' => esc_html__( 'Icon', 'elementor' ), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon', 'skin' => 'inline', 'label_block' => false, 'icon_exclude_inline_options' => [], ] ); $this->end_controls_section(); } protected function add_accessible_name_control(): void { $config = static::get_configuration(); $this->add_control( 'accessible_name', [ 'label' => esc_html__( 'Accessible Name', 'elementor' ), 'type' => Controls_Manager::TEXT, 'dynamic' => [ 'active' => true, ], 'placeholder' => esc_html__( 'Enter text', 'elementor' ), 'default' => $config['content']['floating_bar_section']['accessible_name_default'], 'condition' => [ 'floating_bar_close_switch' => 'yes', ], ], ); } protected function add_floating_bar_content_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'floating_bar_content_section', [ 'label' => __( 'Floating Bar', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); if ( $config['content']['floating_bar_section']['has_pause_switch'] ) { $this->add_control( 'floating_bar_pause_switch', [ 'label' => esc_html__( 'Pause and Play', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'elementor' ), 'label_off' => esc_html__( 'Hide', 'elementor' ), 'return_value' => 'yes', 'default' => 'no', ] ); $this->add_control( 'floating_bar_pause_icon', [ 'label' => esc_html__( 'Pause Icon', 'elementor' ), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon', 'default' => [ 'value' => 'fas fa-pause', 'library' => 'fa-solid', ], 'skin' => 'inline', 'label_block' => false, 'exclude_inline_options' => [ 'none' ], 'recommended' => [ 'fa-regular' => [ 'pause-circle', ], 'fa-solid' => [ 'pause-circle', ], ], 'condition' => [ 'floating_bar_pause_switch' => 'yes', ], ], ); $this->add_control( 'floating_bar_play_icon', [ 'label' => esc_html__( 'Play Icon', 'elementor' ), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon', 'default' => [ 'value' => 'fas fa-play', 'library' => 'fa-solid', ], 'skin' => 'inline', 'label_block' => false, 'exclude_inline_options' => [ 'none' ], 'recommended' => [ 'fa-regular' => [ 'play-circle', ], 'fa-solid' => [ 'play-circle', ], ], 'condition' => [ 'floating_bar_pause_switch' => 'yes', ], ], ); } $this->add_control( 'floating_bar_close_switch', [ 'label' => esc_html__( 'Close Button', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'elementor' ), 'label_off' => esc_html__( 'Hide', 'elementor' ), 'return_value' => 'yes', 'default' => $config['content']['floating_bar_section']['close_switch_default'], ] ); $this->add_accessible_name_control(); $this->end_controls_section(); } protected function add_headlines_content_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'headlines_content', [ 'label' => esc_html__( 'Headlines', 'elementor' ), 'tab' => Controls_Manager::TAB_CONTENT, ] ); $repeater = new Repeater(); $repeater->add_control( 'headlines_icon', [ 'label' => esc_html__( 'Icon', 'elementor' ), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon', 'skin' => 'inline', 'label_block' => false, 'icon_exclude_inline_options' => [], ] ); $repeater->add_control( 'headlines_text', [ 'label' => esc_html__( 'Text', 'elementor' ), 'type' => Controls_Manager::TEXTAREA, 'placeholder' => esc_html__( 'Enter your text', 'elementor' ), 'default' => esc_html__( 'Item Title', 'elementor' ), 'dynamic' => [ 'active' => true, ], ] ); $repeater->add_control( 'headlines_url', [ 'label' => esc_html__( 'Link', 'elementor' ), 'type' => Controls_Manager::URL, 'placeholder' => esc_html__( 'Paste URL or type', 'elementor' ), 'dynamic' => [ 'active' => true, ], 'frontend_available' => true, ] ); $this->add_control( 'headlines_repeater', [ 'type' => Controls_Manager::REPEATER, 'fields' => $repeater->get_controls(), 'title_field' => '{{{ headlines_text }}}', 'prevent_empty' => true, 'button_text' => esc_html__( 'Add Item', 'elementor' ), 'default' => [ [ 'headlines_text' => esc_html__( 'Item #1', 'elementor' ), ], [ 'headlines_text' => esc_html__( 'Item #2', 'elementor' ), ], [ 'headlines_text' => esc_html__( 'Item #3', 'elementor' ), ], ], ] ); $this->end_controls_section(); } protected function add_announcement_style_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'style_announcement', [ 'label' => esc_html__( 'Announcement', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'style_announcement_icon_heading', [ 'label' => esc_html__( 'Icon', 'elementor' ), 'type' => Controls_Manager::HEADING, 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'announcement_icon[value]', 'operator' => '!==', 'value' => '', ], [ 'name' => 'announcement_icon[value]', 'operator' => '!==', 'value' => null, ], ], ], ] ); $this->add_control( 'style_announcement_icon_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-announcement-icon-color: {{VALUE}}', ], 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'announcement_icon[value]', 'operator' => '!==', 'value' => '', ], [ 'name' => 'announcement_icon[value]', 'operator' => '!==', 'value' => null, ], ], ], ] ); $this->add_responsive_control( 'style_announcement_icon_position', [ 'label' => esc_html__( 'Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Left', 'elementor' ), 'icon' => 'eicon-h-align-left', ], 'end' => [ 'title' => esc_html__( 'Right', 'elementor' ), 'icon' => 'eicon-h-align-right', ], ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars__announcement-icon' => 'order: {{VALUE}};', ], 'selectors_dictionary' => [ 'start' => '-1', 'end' => '2', ], 'default' => 'start', 'toggle' => false, 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'announcement_icon[value]', 'operator' => '!==', 'value' => '', ], [ 'name' => 'announcement_icon[value]', 'operator' => '!==', 'value' => null, ], ], ], ] ); $this->add_responsive_control( 'style_announcement_icon_size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 0, 'max' => 150, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-announcement-icon-size: {{SIZE}}{{UNIT}}', ], 'separator' => 'after', 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'announcement_icon[value]', 'operator' => '!==', 'value' => '', ], [ 'name' => 'announcement_icon[value]', 'operator' => '!==', 'value' => null, ], ], ], ] ); $this->add_control( 'style_announcement_text_heading', [ 'label' => $config['content']['announcement_section']['text_label'], 'type' => Controls_Manager::HEADING, ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_announcement_text_typography', 'selector' => '{{WRAPPER}} .e-floating-bars__announcement-text', ] ); $this->add_control( 'style_announcement_text_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-announcement-text-color: {{VALUE}}', ], ] ); $this->end_controls_section(); } protected function add_cta_button_style_section(): void { $this->start_controls_section( 'style_cta_button', [ 'label' => esc_html__( 'CTA Button', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'style_cta_type', [ 'label' => esc_html__( 'Type', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'button', 'options' => [ 'button' => esc_html__( 'Button', 'elementor' ), 'link' => esc_html__( 'Link', 'elementor' ), ], ] ); $this->add_responsive_control( 'style_cta_icon_position', [ 'label' => esc_html__( 'Icon Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'default' => is_rtl() ? 'row-reverse' : 'row', 'toggle' => false, 'options' => [ 'row' => [ 'title' => esc_html__( 'Start', 'elementor' ), 'icon' => 'eicon-h-align-left', ], 'row-reverse' => [ 'title' => esc_html__( 'End', 'elementor' ), 'icon' => 'eicon-h-align-right', ], ], 'selectors_dictionary' => [ 'left' => is_rtl() ? 'row-reverse' : 'row', 'right' => is_rtl() ? 'row' : 'row-reverse', ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars__cta-button' => 'flex-direction: {{VALUE}};', ], 'condition' => [ 'cta_icon[value]!' => '', ], ] ); $this->add_control( 'style_cta_icon_spacing', [ 'label' => esc_html__( 'Icon Spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'range' => [ 'px' => [ 'max' => 50, ], 'em' => [ 'max' => 5, ], 'rem' => [ 'max' => 5, ], ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-icon-gap: {{SIZE}}{{UNIT}};', ], 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'cta_icon[value]', 'operator' => '!==', 'value' => '', ], [ 'name' => 'cta_icon[value]', 'operator' => '!==', 'value' => null, ], ], ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_cta_typography', 'selector' => '{{WRAPPER}} .e-floating-bars__cta-button', ] ); $this->start_controls_tabs( 'style_cta_button_tabs' ); $this->start_controls_tab( 'style_cta_button_tabs_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_control( 'style_cta_button_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-text-color: {{VALUE}}', ], ] ); $this->add_control( 'style_cta_button_bg_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-bg-color: {{VALUE}}', ], 'condition' => [ 'style_cta_type' => 'button', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'style_cta_button_tabs_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_control( 'style_cta_button_text_color_hover', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-text-color-hover: {{VALUE}}', ], ] ); $this->add_control( 'style_cta_button_bg_color_hover', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-bg-color-hover: {{VALUE}}', ], 'condition' => [ 'style_cta_type' => 'button', ], ] ); $this->add_control( 'style_cta_button_border_color_hover', [ 'label' => esc_html__( 'Border Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-border-color-hover: {{VALUE}}', ], 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'style_cta_button_show_border', 'operator' => '===', 'value' => 'yes', ], [ 'name' => 'style_cta_type', 'operator' => '===', 'value' => 'button', ], ], ], ] ); $this->add_control( 'style_cta_button_hover_animation', [ 'label' => esc_html__( 'Hover Animation', 'elementor' ), 'type' => Controls_Manager::HOVER_ANIMATION, 'frontend_available' => true, ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_control( 'style_cta_button_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' => 'yes', 'separator' => 'before', 'condition' => [ 'style_cta_type' => 'button', ], ] ); $this->add_responsive_control( 'style_cta_button_border_width', [ 'label' => esc_html__( 'Border Width', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'min' => 10, 'max' => 100, ], 'px' => [ 'min' => 0, 'max' => 10, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-border-width: {{SIZE}}{{UNIT}}', ], 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'style_cta_button_show_border', 'operator' => '===', 'value' => 'yes', ], [ 'name' => 'style_cta_type', 'operator' => '===', 'value' => 'button', ], ], ], ] ); $this->add_control( 'style_cta_button_border_color', [ 'label' => esc_html__( 'Border Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-border-color: {{VALUE}}', ], 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'style_cta_button_show_border', 'operator' => '===', 'value' => 'yes', ], [ 'name' => 'style_cta_type', 'operator' => '===', 'value' => 'button', ], ], ], ] ); $this->add_control( 'style_cta_button_corners', [ 'label' => esc_html__( 'Corners', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'round', 'options' => [ 'round' => esc_html__( 'Round', 'elementor' ), 'rounded' => esc_html__( 'Rounded', 'elementor' ), 'sharp' => esc_html__( 'Sharp', 'elementor' ), ], 'condition' => [ 'style_cta_type' => 'button', ], ] ); $this->add_responsive_control( 'style_cta_button_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem' ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-padding-block-end: {{BOTTOM}}{{UNIT}}; --e-floating-bars-cta-button-padding-block-start: {{TOP}}{{UNIT}}; --e-floating-bars-cta-button-padding-inline-end: {{RIGHT}}{{UNIT}}; --e-floating-bars-cta-button-padding-inline-start: {{LEFT}}{{UNIT}};', ], 'separator' => 'before', 'condition' => [ 'style_cta_type' => 'button', ], ] ); $this->add_responsive_control( 'style_cta_button_animation', [ 'label' => esc_html__( 'Entrance Animation', 'elementor' ), 'type' => Controls_Manager::ANIMATION, 'frontend_available' => true, 'separator' => 'before', ] ); $this->add_control( 'style_cta_button_animation_duration', [ 'label' => esc_html__( 'Animation Duration', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => '1000', 'options' => [ '2000' => esc_html__( 'Slow', 'elementor' ), '1000' => esc_html__( 'Normal', 'elementor' ), '800' => esc_html__( 'Fast', 'elementor' ), ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-animation-duration: {{VALUE}}ms', ], 'prefix_class' => 'animated-', 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'style_cta_button_animation', 'operator' => '!==', 'value' => '', ], [ 'name' => 'style_cta_button_animation', 'operator' => '!==', 'value' => 'none', ], ], ], ] ); $this->add_control( 'style_cta_button_animation_delay', [ 'label' => esc_html__( 'Animation Delay', 'elementor' ) . ' (ms)', 'type' => Controls_Manager::NUMBER, 'min' => 0, 'step' => 100, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-cta-button-animation-delay: {{SIZE}}ms;', ], 'render_type' => 'none', 'frontend_available' => true, 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'style_cta_button_animation', 'operator' => '!==', 'value' => '', ], [ 'name' => 'style_cta_button_animation', 'operator' => '!==', 'value' => 'none', ], ], ], ] ); $this->end_controls_section(); } protected function add_floating_bar_background_style_controls(): void { $config = static::get_configuration(); $this->add_control( 'floating_bar_background_heading', [ 'label' => esc_html__( 'Background', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'floating_bar_background_type', 'types' => [ 'classic', 'gradient' ], 'selector' => $config['style']['floating_bar_section']['background_selector'], 'fields_options' => [ 'background' => [ 'default' => 'classic', ], 'position' => [ 'default' => 'center center', ], 'size' => [ 'default' => 'cover', ], ], ] ); $this->add_control( 'floating_bar_background_overlay_heading', [ 'label' => esc_html__( 'Background Overlay', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_group_control( Group_Control_Background::get_type(), [ 'name' => 'floating_bar_background_overlay_type', 'types' => [ 'classic', 'gradient' ], 'selector' => '{{WRAPPER}} .e-floating-bars__overlay', 'fields_options' => [ 'background' => [ 'default' => 'classic', ], 'position' => [ 'default' => 'center center', ], 'size' => [ 'default' => 'cover', ], ], ] ); $this->add_responsive_control( 'floating_bar_background_overlay_opacity', [ 'label' => esc_html__( 'Opacity', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ '%' => [ 'max' => 1, 'min' => 0, 'step' => 0.01, ], ], 'default' => [ 'unit' => '%', 'size' => 0.5, ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-background-overlay-opacity: {{SIZE}};', ], ] ); } protected function add_floating_bar_close_button_style_controls(): void { $config = static::get_configuration(); $this->add_control( 'floating_bar_close_button_heading', [ 'label' => esc_html__( 'Close Button', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', 'condition' => [ 'floating_bar_close_switch' => 'yes', ], ] ); if ( $config['style']['floating_bar_section']['has_close_position_control'] ) { $this->add_responsive_control( 'floating_bar_close_button_position', [ 'label' => esc_html__( 'Horizontal position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'start' => [ 'title' => esc_html__( 'Left', 'elementor' ), 'icon' => 'eicon-h-align-left', ], 'end' => [ 'title' => esc_html__( 'Right', 'elementor' ), 'icon' => 'eicon-h-align-right', ], ], 'default' => 'end', 'toggle' => false, 'selectors' => $config['style']['floating_bar_section']['close_position_selectors'], 'condition' => [ 'floating_bar_close_switch' => 'yes', ], ] ); } $this->add_control( 'floating_bar_close_button_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-close-button-color: {{VALUE}}', ], 'condition' => [ 'floating_bar_close_switch' => 'yes', ], ] ); $this->add_responsive_control( 'style_floating_bar_close_button_size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 0, 'max' => 150, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-close-icon-size: {{SIZE}}{{UNIT}}', ], 'condition' => [ 'floating_bar_close_switch' => 'yes', ], ] ); if ( $config['style']['floating_bar_section']['has_close_bg'] ) { $this->add_control( 'floating_bar_close_bg_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-close-button-bg-color: {{VALUE}}', ], 'condition' => [ 'floating_bar_close_switch' => 'yes', ], 'separator' => 'after', ] ); } } protected function add_floating_bar_pause_style_controls(): void { $config = static::get_configuration(); $this->add_control( 'floating_bar_pause_button_heading', [ 'label' => esc_html__( 'Pause and Play', 'elementor' ), 'type' => Controls_Manager::HEADING, 'condition' => [ 'floating_bar_pause_switch' => 'yes', ], ] ); $this->add_control( 'floating_bar_pause_button_color', [ 'label' => esc_html__( 'Icon Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-pause-play-icon-color: {{VALUE}}', ], 'condition' => [ 'floating_bar_pause_switch' => 'yes', ], ] ); $this->add_control( 'floating_bar_pause_bg_color', [ 'label' => esc_html__( 'Background Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-pause-play-bg-color: {{VALUE}}', ], 'condition' => [ 'floating_bar_pause_switch' => 'yes', ], ] ); } protected function add_floating_bar_style_section(): void { $config = static::get_configuration(); $this->start_controls_section( 'style_floating_bar', [ 'label' => esc_html__( 'Floating Bar', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_floating_bar_close_button_style_controls(); $this->add_responsive_control( 'style_floating_bar_elements_align', [ 'label' => esc_html__( 'Align Elements', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'toggle' => false, 'default' => 'center', 'options' => [ 'start' => [ 'title' => esc_html__( 'Start', 'elementor' ), 'icon' => 'eicon-align-start-h', ], 'center' => [ 'title' => esc_html__( 'Center', 'elementor' ), 'icon' => 'eicon-align-center-h', ], 'end' => [ 'title' => esc_html__( 'End', 'elementor' ), 'icon' => 'eicon-align-end-h', ], 'space-between' => [ 'title' => esc_html__( 'Stretch', 'elementor' ), 'icon' => 'eicon-align-stretch-h', ], ], 'selectors' => $config['style']['floating_bar_section']['align_elements_selector'], 'separator' => 'before', ] ); $this->add_responsive_control( 'style_floating_bar_elements_spacing', [ 'label' => esc_html__( 'Element spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 0, 'max' => 50, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-elements-gap: {{SIZE}}{{UNIT}}', ], 'conditions' => [ 'relation' => 'and', 'terms' => [ [ 'name' => 'style_floating_bar_elements_align', 'operator' => '!==', 'value' => 'stretch', ], ], ], ] ); $this->add_responsive_control( 'style_floating_bar_elements_padding', [ 'label' => esc_html__( 'Padding', 'elementor' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em', 'rem' ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-elements-padding-block-end: {{BOTTOM}}{{UNIT}}; --e-floating-bars-elements-padding-block-start: {{TOP}}{{UNIT}}; --e-floating-bars-elements-padding-inline-end: {{RIGHT}}{{UNIT}}; --e-floating-bars-elements-padding-inline-start: {{LEFT}}{{UNIT}};', ], ] ); $this->add_floating_bar_background_style_controls(); $this->end_controls_section(); } protected function add_headlines_style_section(): void { $this->start_controls_section( 'style_headlines', [ 'label' => esc_html__( 'Headline', 'elementor' ), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'style_headlines_icon_heading', [ 'label' => esc_html__( 'Icon', 'elementor' ), 'type' => Controls_Manager::HEADING, ] ); $this->add_control( 'style_headlines_icon_color', [ 'label' => esc_html__( 'Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-headline-icon-color: {{VALUE}}', ], ] ); $this->add_responsive_control( 'style_headlines_icon_position', [ 'label' => esc_html__( 'Icon Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'default' => is_rtl() ? 'row-reverse' : 'row', 'toggle' => false, 'options' => [ 'row' => [ 'title' => esc_html__( 'Start', 'elementor' ), 'icon' => 'eicon-h-align-left', ], 'row-reverse' => [ 'title' => esc_html__( 'End', 'elementor' ), 'icon' => 'eicon-h-align-right', ], ], 'selectors_dictionary' => [ 'row' => is_rtl() ? 'row-reverse' : 'row', 'row-reverse' => is_rtl() ? 'row' : 'row-reverse', ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars__headline' => '--e-floating-bars-headline-icon-position: {{VALUE}};', ], ] ); $this->add_responsive_control( 'style_headlines_icon_size', [ 'label' => esc_html__( 'Size', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 0, 'max' => 150, ], ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-headline-icon-size: {{SIZE}}{{UNIT}}', ], ] ); $this->add_responsive_control( 'style_headlines_icon_spacing', [ 'label' => esc_html__( 'Icon Spacing', 'elementor' ), 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px', 'em', 'rem', 'custom' ], 'range' => [ 'px' => [ 'max' => 50, ], 'em' => [ 'max' => 5, ], 'rem' => [ 'max' => 5, ], ], 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-headline-icon-gap: {{SIZE}}{{UNIT}}', ], ] ); $this->add_control( 'style_headline_text_heading', [ 'label' => esc_html__( 'Text', 'elementor' ), 'type' => Controls_Manager::HEADING, 'separator' => 'before', ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'style_headline_text_typography', 'selector' => '{{WRAPPER}} .e-floating-bars__headline-text', ] ); $this->start_controls_tabs( 'style_headline_tabs' ); $this->start_controls_tab( 'style_headline_tabs_normal', [ 'label' => esc_html__( 'Normal', 'elementor' ), ] ); $this->add_control( 'style_headline_text_color', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-headline-text-color: {{VALUE}}', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'style_headline_tabs_hover', [ 'label' => esc_html__( 'Hover', 'elementor' ), ] ); $this->add_control( 'style_headline_text_color_hover', [ 'label' => esc_html__( 'Text Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .e-floating-bars' => '--e-floating-bars-headline-text-color-hover: {{VALUE}}', ], ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->end_controls_section(); } protected function add_advanced_tab(): void { Controls_Manager::add_tab( static::TAB_ADVANCED, esc_html__( 'Advanced', 'elementor' ) ); $this->start_controls_section( 'advanced_layout_section', [ 'label' => esc_html__( 'Layout', 'elementor' ), 'tab' => static::TAB_ADVANCED, ] ); $this->add_control( 'advanced_vertical_position', [ 'label' => esc_html__( 'Vertical Position', 'elementor' ), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'top' => [ 'title' => esc_html__( 'Top', 'elementor' ), 'icon' => 'eicon-v-align-top', ], 'bottom' => [ 'title' => esc_html__( 'Bottom', 'elementor' ), 'icon' => 'eicon-v-align-bottom', ], ], 'default' => 'top', 'toggle' => false, ] ); $this->add_control( 'advanced_toggle_sticky', [ 'label' => esc_html__( 'Sticky', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'return_value' => 'yes', 'default' => 'yes', ] ); $this->end_controls_section(); $this->start_controls_section( 'advanced_responsive_section', [ 'label' => esc_html__( 'Responsive', 'elementor' ), 'tab' => static::TAB_ADVANCED, ] ); $this->add_control( 'responsive_description', [ 'raw' => __( 'Responsive visibility will take effect only on preview mode or live page, and not while editing in Elementor.', 'elementor' ), 'type' => Controls_Manager::RAW_HTML, 'content_classes' => 'elementor-descriptor', ] ); $this->add_hidden_device_controls(); $this->end_controls_section(); $this->start_controls_section( 'advanced_custom_controls_section', [ 'label' => esc_html__( 'CSS', 'elementor' ), 'tab' => static::TAB_ADVANCED, ] ); $this->add_control( 'advanced_custom_css_id', [ 'label' => esc_html__( 'CSS ID', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], 'title' => esc_html__( 'Add your custom id WITHOUT the Pound key. e.g: my-id', 'elementor' ), 'style_transfer' => false, ] ); $this->add_control( 'advanced_custom_css_classes', [ 'label' => esc_html__( 'CSS Classes', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => '', 'ai' => [ 'active' => false, ], 'dynamic' => [ 'active' => true, ], 'title' => esc_html__( 'Add your custom class WITHOUT the dot. e.g: my-class', 'elementor' ), ] ); $this->end_controls_section(); Plugin::$instance->controls_manager->add_custom_css_controls( $this, static::TAB_ADVANCED ); Plugin::$instance->controls_manager->add_custom_attributes_controls( $this, static::TAB_ADVANCED ); } protected function add_content_tab(): void { $this->add_announcement_content_section(); $this->add_cta_button_content_section(); $this->add_floating_bar_content_section(); } protected function add_style_tab(): void { $this->add_announcement_style_section(); $this->add_cta_button_style_section(); $this->add_floating_bar_style_section(); } protected function render(): void { $render_strategy = new Floating_Bars_Core_Render( $this ); $render_strategy->render(); } } floating-buttons/control/hover-animation-floating-buttons.php 0000644 00000000662 15252521350 0020635 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Control; use Elementor\Control_Hover_Animation; class Hover_Animation_Floating_Buttons extends Control_Hover_Animation { const TYPE = 'hover_animation_contact_buttons'; public function get_type() { return static::TYPE; } public static function get_animations() { return [ 'grow' => 'Grow', 'pulse' => 'Pulse', 'push' => 'Push', 'float' => 'Float', ]; } } floating-buttons/widgets/contact-buttons.php 0000644 00000001132 15252521350 0015346 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Widgets; use Elementor\Modules\FloatingButtons\Base\Widget_Contact_Button_Base; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Contact Buttons widget. * * Elementor widget that displays contact buttons and a chat-like prompt message. * * @since 3.23.0 */ class Contact_Buttons extends Widget_Contact_Button_Base { public function get_name(): string { return 'contact-buttons'; } public function get_title(): string { return esc_html__( 'Single Chat', 'elementor' ); } } floating-buttons/widgets/floating-bars-var-1.php 0000644 00000001511 15252521350 0015674 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Widgets; use Elementor\Modules\FloatingButtons\Base\Widget_Floating_Bars_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor Floating Bars Var 1 widget. * * Elementor widget that displays a banner with icon and link * * @since 3.23.0 */ class Floating_Bars_Var_1 extends Widget_Floating_Bars_Base { public function get_name(): string { return 'floating-bars-var-1'; } public function get_title(): string { return esc_html__( 'Floating Bar CTA', 'elementor' ); } public function get_group_name(): string { return 'floating-bars'; } public function render(): void { $this->add_inline_editing_attributes( 'announcement_text', 'none' ); $this->add_inline_editing_attributes( 'cta_text', 'none' ); parent::render(); } } floating-buttons/classes/conditions/conditions-cache.php 0000644 00000002426 15252521350 0017600 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Classes\Conditions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Conditions_Cache { const CONDITIONS_CACHE_META_KEY = 'elementor_pro_theme_builder_conditions'; const CONDITION_TYPE = 'floating_buttons'; public function remove_from_cache( int $post_id ): void { $conditions = $this->get_conditions(); if ( isset( $conditions[ self::CONDITION_TYPE ][ $post_id ] ) ) { unset( $conditions[ self::CONDITION_TYPE ][ $post_id ] ); if ( empty( $conditions[ self::CONDITION_TYPE ] ) ) { unset( $conditions[ self::CONDITION_TYPE ] ); } $this->update_conditions( $conditions ); } } public function add_to_cache( int $post_id, array $condition_rules = [ 'include/general' ] ): void { $conditions = $this->get_conditions(); if ( ! isset( $conditions[ self::CONDITION_TYPE ] ) ) { $conditions[ self::CONDITION_TYPE ] = []; } $conditions[ self::CONDITION_TYPE ][ $post_id ] = $condition_rules; $this->update_conditions( $conditions ); } private function get_conditions(): array { return get_option( self::CONDITIONS_CACHE_META_KEY, [] ); } private function update_conditions( array $conditions ): void { update_option( self::CONDITIONS_CACHE_META_KEY, $conditions ); } } floating-buttons/classes/render/contact-buttons-core-render.php 0000644 00000003772 15252521350 0021033 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Classes\Render; /** * Class Contact_Buttons_Core_Render. * * This class handles the rendering of the Contact Buttons widget for the core version. * * @since 3.23.0 */ class Contact_Buttons_Core_Render extends Contact_Buttons_Render_Base { public function render(): void { $this->build_layout_render_attribute(); $this->add_content_wrapper_render_attribute(); $content_classnames = 'e-contact-buttons__content'; $animation_duration = $this->settings['style_chat_box_animation_duration']; if ( ! empty( $animation_duration ) ) { $content_classnames .= ' has-animation-duration-' . $animation_duration; } $this->widget->add_render_attribute( 'content', [ 'class' => $content_classnames, ] ); ?> <div <?php echo $this->widget->get_render_attribute_string( 'layout' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <div <?php echo $this->widget->get_render_attribute_string( 'content-wrapper' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <div <?php echo $this->widget->get_render_attribute_string( 'content' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php $this->render_top_bar(); $this->render_message_bubble(); $this->render_send_button(); ?> </div> </div> <?php $this->render_chat_button(); ?> </div> <?php } protected function add_layout_render_attribute( $layout_classnames ) { $this->widget->add_render_attribute( 'layout', [ 'class' => $layout_classnames, 'id' => $this->settings['advanced_custom_css_id'], 'data-document-id' => get_the_ID(), 'aria-role' => 'dialog', ] ); } protected function add_content_wrapper_render_attribute() { $this->widget->add_render_attribute( 'content-wrapper', [ 'aria-hidden' => 'true', 'aria-label' => __( 'Links window', 'elementor' ), 'class' => 'e-contact-buttons__content-wrapper hidden', 'id' => 'e-contact-buttons__content-wrapper', ] ); } } floating-buttons/classes/render/contact-buttons-render-base.php 0000644 00000040755 15252521350 0021017 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Classes\Render; use Elementor\Core\Base\Providers\Social_Network_Provider; use Elementor\Icons_Manager; use Elementor\Modules\FloatingButtons\Base\Widget_Contact_Button_Base; use Elementor\Utils; /** * Class Contact_Buttons_Render_Base. * * This is the base class that will hold shared functionality that will be needed by all the various widget versions. * * @since 3.23.0 */ abstract class Contact_Buttons_Render_Base { protected Widget_Contact_Button_Base $widget; protected array $settings; abstract public function render(): void; public function __construct( Widget_Contact_Button_Base $widget ) { $this->widget = $widget; $this->settings = $widget->get_settings_for_display(); } protected function render_chat_button_icon(): void { $platform = $this->settings['chat_button_platform'] ?? ''; $mapping = Social_Network_Provider::get_icon_mapping( $platform ); $icon_lib = explode( ' ', $mapping )[0]; $library = 'fab' === $icon_lib ? 'fa-brands' : 'fa-solid'; Icons_Manager::render_icon( [ 'library' => $library, 'value' => $mapping, ], [ 'aria-hidden' => 'true' ] ); } protected function render_chat_button(): void { $platform = $this->settings['chat_button_platform'] ?? ''; $display_dot = $this->settings['chat_button_show_dot'] ?? ''; $button_size = $this->settings['style_chat_button_size']; $hover_animation = $this->settings['style_button_color_hover_animation']; $entrance_animation = $this->settings['style_chat_button_animation']; $entrance_animation_duration = $this->settings['style_chat_button_animation_duration']; $entrance_animation_delay = $this->settings['style_chat_button_animation_delay']; $accessible_name = $this->settings['chat_aria_label']; $button_classnames = 'e-contact-buttons__chat-button e-contact-buttons__chat-button-shadow'; if ( ! empty( $button_size ) ) { $button_classnames .= ' has-size-' . $button_size; } if ( ! empty( $hover_animation ) ) { $button_classnames .= ' elementor-animation-' . $hover_animation; } if ( ! empty( $entrance_animation ) && 'none' != $entrance_animation ) { $button_classnames .= ' has-entrance-animation'; } if ( ! empty( $entrance_animation_delay ) ) { $button_classnames .= ' has-entrance-animation-delay'; } if ( ! empty( $entrance_animation_duration ) ) { $button_classnames .= ' has-entrance-animation-duration-' . $entrance_animation_duration; } if ( 'yes' === $display_dot ) { $button_classnames .= ' has-dot'; } $this->widget->add_render_attribute( 'button', [ 'class' => $button_classnames, 'aria-controls' => 'e-contact-buttons__content-wrapper', 'aria-label' => sprintf( /* translators: %s: Accessible name. */ esc_html__( 'Toggle %s', 'elementor' ), $accessible_name, ), 'type' => 'button', ] ); ?> <div class="e-contact-buttons__chat-button-container"> <button <?php echo $this->widget->get_render_attribute_string( 'button' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php $this->render_chat_button_icon(); ?> </button> </div> <?php } protected function render_close_button(): void { $accessible_name = $this->settings['chat_aria_label']; $this->widget->add_render_attribute( 'close-button', [ 'class' => 'e-contact-buttons__close-button', 'aria-controls' => 'e-contact-buttons__content-wrapper', 'aria-label' => sprintf( /* translators: %s: Accessible name. */ esc_html__( 'Close %s', 'elementor' ), $accessible_name, ), 'type' => 'button', ] ); ?> <button <?php echo $this->widget->get_render_attribute_string( 'close-button' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <i class="eicon-close"></i> </button> <?php } protected function render_top_bar(): void { $profile_image_value = $this->settings['top_bar_image'] ?? []; $has_profile_image = ! empty( $profile_image_value ) && ( ! empty( $profile_image_value['url'] || ! empty( $profile_image_value['id'] ) ) ); $profile_image_size = $this->settings['style_top_bar_image_size']; $display_profile_dot = $this->settings['top_bar_show_dot']; $profile_image_classnames = 'e-contact-buttons__profile-image'; if ( ! empty( $profile_image_size ) ) { $profile_image_classnames .= ' has-size-' . $profile_image_size; } if ( 'yes' === $display_profile_dot ) { $profile_image_classnames .= ' has-dot'; } $top_bar_title = $this->settings['top_bar_title'] ?? ''; $top_bar_subtitle = $this->settings['top_bar_subtitle'] ?? ''; $has_top_bar_title = ! empty( $top_bar_title ); $has_top_bar_subtitle = ! empty( $top_bar_subtitle ); $this->widget->add_render_attribute( 'profile-image', [ 'class' => $profile_image_classnames, ] ); ?> <div class="e-contact-buttons__top-bar"> <?php $this->render_close_button(); ?> <div <?php echo $this->widget->get_render_attribute_string( 'profile-image' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php if ( ! empty( $profile_image_value['id'] ) ) { echo wp_get_attachment_image( $profile_image_value['id'], 'medium', false, [ 'class' => 'e-contact-buttons__profile-image-el', ] ); } else { $this->widget->add_render_attribute( 'profile-image-src', [ 'alt' => '', 'class' => 'e-contact-buttons__profile-image-el', 'src' => esc_url( $profile_image_value['url'] ), ] ); ?> <img <?php echo $this->widget->get_render_attribute_string( 'profile-image-src' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> /> <?php } ?> </div> <div class="e-contact-buttons__top-bar-details"> <?php if ( $has_top_bar_title ) { ?> <p class="e-contact-buttons__top-bar-title"><?php echo esc_html( $top_bar_title ); ?></p> <?php } ?> <?php if ( $has_top_bar_subtitle ) { ?> <p class="e-contact-buttons__top-bar-subtitle"><?php echo esc_html( $top_bar_subtitle ); ?></p> <?php } ?> </div> </div> <?php } protected function render_message_bubble_typing_animation(): void { $has_typing_animation = 'yes' === $this->settings['chat_button_show_animation']; ?> <?php if ( $has_typing_animation ) { ?> <div class="e-contact-buttons__dots-container"> <span class="e-contact-buttons__dot e-contact-buttons__dot-1"></span> <span class="e-contact-buttons__dot e-contact-buttons__dot-2"></span> <span class="e-contact-buttons__dot e-contact-buttons__dot-3"></span> </div> <?php } ?> <?php } protected function render_message_bubble_container(): void { $message_bubble_name = $this->settings['message_bubble_name'] ?? ''; $message_bubble_body = $this->settings['message_bubble_body'] ?? ''; $has_message_bubble_name = ! empty( $message_bubble_name ); $has_message_bubble_body = ! empty( $message_bubble_body ); $time_format = $this->settings['chat_button_time_format']; ?> <div class="e-contact-buttons__bubble-container"> <div class="e-contact-buttons__bubble"> <?php if ( $has_message_bubble_name ) { ?> <p class="e-contact-buttons__message-bubble-name"><?php echo esc_html( $message_bubble_name ); ?></p> <?php } ?> <?php if ( $has_message_bubble_body ) { ?> <p class="e-contact-buttons__message-bubble-body"><?php echo esc_html( $message_bubble_body ); ?></p> <?php } ?> <p class="e-contact-buttons__message-bubble-time" data-time-format="<?php echo esc_attr( $time_format ); ?>"></p> </div> </div> <?php } protected function render_message_bubble_powered_by(): void { if ( Utils::has_pro() ) { return; } ?> <div class="e-contact-buttons__powered-container"> <p class="e-contact-buttons__powered-text"> <?php echo esc_attr__( 'Powered by Elementor', 'elementor' ); ?> </p> </div> <?php } protected function render_message_bubble(): void { $message_bubble_classnames = 'e-contact-buttons__message-bubble'; $show_animation = $this->settings['chat_button_show_animation'] ?? false; $has_typing_animation = $show_animation && 'yes' === $show_animation; if ( $has_typing_animation ) { $message_bubble_classnames .= ' has-typing-animation'; } $this->widget->add_render_attribute( 'message-bubble', [ 'class' => $message_bubble_classnames, ] ); ?> <div <?php echo $this->widget->get_render_attribute_string( 'message-bubble' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php $this->render_message_bubble_typing_animation(); $this->render_message_bubble_container(); ?> </div> <?php } protected function render_contact_text(): void { $contact_cta_text = $this->settings['contact_cta_text'] ?? ''; ?> <?php if ( ! empty( $contact_cta_text ) ) { ?> <p class="e-contact-buttons__contact-text"><?php echo esc_html( $contact_cta_text ); ?></p> <?php } ?> <?php } protected function render_contact_links(): void { $contact_icons = $this->settings['contact_repeater'] ?? []; $icons_size = $this->settings['style_contact_button_size'] ?? 'small'; $hover_animation = $this->settings['style_contact_button_hover_animation']; ?> <div class="e-contact-buttons__contact-links"> <?php foreach ( $contact_icons as $key => $icon ) { $icon_text_mapping = Social_Network_Provider::get_text_mapping( $icon['contact_icon_platform'] ); $aria_label = sprintf( /* translators: %s: Platform name. */ esc_html__( 'Open %s', 'elementor' ), $icon_text_mapping, ); $link = [ 'platform' => $icon['contact_icon_platform'], 'number' => $icon['contact_icon_number'] ?? '', 'username' => $icon['contact_icon_username'] ?? '', 'email_data' => [ 'contact_icon_mail' => $icon['contact_icon_mail'] ?? '', 'contact_icon_mail_subject' => $icon['contact_icon_mail_subject'] ?? '', 'contact_icon_mail_body' => $icon['contact_icon_mail_body'] ?? '', ], 'viber_action' => $icon['contact_icon_viber_action'] ?? '', ]; $formatted_link = $this->get_formatted_link( $link, 'contact_icon' ); $icon_classnames = 'e-contact-buttons__contact-icon-link has-size-' . $icons_size; if ( ! empty( $hover_animation ) ) { $icon_classnames .= ' elementor-animation-' . $hover_animation; } $this->widget->add_render_attribute( 'icon-link-' . $key, [ 'aria-label' => $aria_label, 'class' => $icon_classnames, 'href' => $formatted_link, 'rel' => 'noopener noreferrer', 'target' => '_blank', ] ); ?> <a <?php echo $this->widget->get_render_attribute_string( 'icon-link-' . $key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php $mapping = Social_Network_Provider::get_icon_mapping( $icon['contact_icon_platform'] ); $icon_lib = explode( ' ', $mapping )[0]; $library = 'fab' === $icon_lib ? 'fa-brands' : 'fa-solid'; Icons_Manager::render_icon( [ 'library' => $library, 'value' => $mapping, ], [ 'aria-hidden' => 'true' ] ); ?> </a> <?php } ?> </div> <?php } protected function render_contact_section(): void { ?> <div class="e-contact-buttons__contact"> <?php $this->render_contact_text(); $this->render_contact_links(); ?> </div> <?php } protected function render_send_button(): void { $platform = $this->settings['chat_button_platform'] ?? ''; $send_button_text = $this->settings['send_button_text']; $hover_animation = $this->settings['style_send_hover_animation']; $cta_classnames = 'e-contact-buttons__send-cta'; $link = [ 'platform' => $platform, 'number' => $this->settings['chat_button_number'] ?? '', 'username' => $this->settings['chat_button_username'] ?? '', 'email_data' => [ 'chat_button_mail' => $this->settings['chat_button_mail'], 'chat_button_mail_subject' => $this->settings['chat_button_mail_subject'] ?? '', 'chat_button_mail_body' => $this->settings['chat_button_mail_body'] ?? '', ], 'viber_action' => $this->settings['chat_button_viber_action'], ]; $formatted_link = $this->get_formatted_link( $link, 'chat_button' ); if ( ! empty( $hover_animation ) ) { $cta_classnames .= ' elementor-animation-' . $hover_animation; } $this->widget->add_render_attribute( 'formatted-cta', [ 'class' => $cta_classnames, 'href' => $formatted_link, 'rel' => 'noopener noreferrer', 'target' => '_blank', ] ); ?> <div class="e-contact-buttons__send-button"> <?php $this->render_message_bubble_powered_by(); ?> <div class="e-contact-buttons__send-button-container"> <?php if ( $send_button_text ) { ?> <a <?php echo $this->widget->get_render_attribute_string( 'formatted-cta' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php $mapping = Social_Network_Provider::get_icon_mapping( $platform ); $icon_lib = explode( ' ', $mapping )[0]; $library = 'fab' === $icon_lib ? 'fa-brands' : 'fa-solid'; Icons_Manager::render_icon( [ 'library' => $library, 'value' => $mapping, ], [ 'aria-hidden' => 'true' ] ); ?> <?php echo esc_html( $send_button_text ); ?> </a> <?php } ?> </div> </div> <?php } protected function get_formatted_link( array $link, string $prefix ): string { // Ensure we clear the default link value if the matching type value is empty switch ( $link['platform'] ) { case Social_Network_Provider::EMAIL: $formatted_link = Social_Network_Provider::build_email_link( $link['email_data'], $prefix ); break; case Social_Network_Provider::SMS: $formatted_link = ! empty( $link['number'] ) ? 'sms:' . $link['number'] : ''; break; case Social_Network_Provider::MESSENGER: $formatted_link = ! empty( $link['username'] ) ? Social_Network_Provider::build_messenger_link( $link['username'] ) : ''; break; case Social_Network_Provider::WHATSAPP: $formatted_link = ! empty( $link['number'] ) ? 'https://wa.me/' . $link['number'] : ''; break; case Social_Network_Provider::VIBER: $formatted_link = Social_Network_Provider::build_viber_link( $link['viber_action'], $link['number'] ); break; case Social_Network_Provider::SKYPE: $formatted_link = ! empty( $link['username'] ) ? 'skype:' . $link['username'] . '?chat' : ''; break; case Social_Network_Provider::TELEPHONE: $formatted_link = ! empty( $link['number'] ) ? 'tel:' . $link['number'] : ''; break; default: break; } return esc_html( $formatted_link ); } protected function is_url_link( string $platform ): bool { return Social_Network_Provider::URL === $platform || Social_Network_Provider::WAZE === $platform; } protected function render_link_attributes( array $link, string $key ) { switch ( $link['platform'] ) { case Social_Network_Provider::WAZE: if ( empty( $link['location']['url'] ) ) { $link['location']['url'] = '#'; } $this->widget->add_link_attributes( $key, $link['location'] ); break; case Social_Network_Provider::URL: if ( empty( $link['url']['url'] ) ) { $link['url']['url'] = '#'; } $this->widget->add_link_attributes( $key, $link['url'] ); break; default: break; } } protected function build_layout_render_attribute(): void { $layout_classnames = 'e-contact-buttons e-' . $this->widget->get_name(); $platform = $this->settings['chat_button_platform'] ?? ''; $border_radius = $this->settings['style_chat_box_corners']; $alignment_position_horizontal = $this->settings['advanced_horizontal_position']; $alignment_position_vertical = $this->settings['advanced_vertical_position']; $has_animations = ! empty( $this->settings['style_chat_box_exit_animation'] ) || ! empty( $this->settings['style_chat_box_entrance_animation'] ); $custom_classes = $this->settings['advanced_custom_css_classes'] ?? ''; $icon_name_mapping = Social_Network_Provider::get_name_mapping( $platform ); if ( ! empty( $platform ) ) { $layout_classnames .= ' has-platform-' . $icon_name_mapping; } if ( ! empty( $border_radius ) ) { $layout_classnames .= ' has-corners-' . $border_radius; } if ( ! empty( $alignment_position_horizontal ) ) { $layout_classnames .= ' has-h-alignment-' . $alignment_position_horizontal; } if ( ! empty( $alignment_position_vertical ) ) { $layout_classnames .= ' has-v-alignment-' . $alignment_position_vertical; } if ( $has_animations ) { $layout_classnames .= ' has-animations'; } if ( $custom_classes ) { $layout_classnames .= ' ' . $custom_classes; } $this->add_layout_render_attribute( $layout_classnames ); } } floating-buttons/classes/render/floating-bars-render-base.php 0000644 00000003356 15252521350 0020414 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Classes\Render; use Elementor\Modules\FloatingButtons\Base\Widget_Floating_Bars_Base; /** * Class Floating_Bars_Render_Base. * * This is the base class that will hold shared functionality that will be needed by all the various widget versions. * * @since 3.23.0 */ abstract class Floating_Bars_Render_Base { protected Widget_Floating_Bars_Base $widget; protected array $settings; abstract public function render(): void; public function __construct( Widget_Floating_Bars_Base $widget ) { $this->widget = $widget; $this->settings = $widget->get_settings_for_display(); } protected function add_layout_render_attribute( $layout_classnames ) { $this->widget->add_render_attribute( 'layout', [ 'class' => $layout_classnames, 'id' => $this->settings['advanced_custom_css_id'], 'data-document-id' => get_the_ID(), 'role' => 'alertdialog', ] ); } public static function get_layout_classnames( Widget_Floating_Bars_Base $widget, array $settings ): string { $layout_classnames = 'e-floating-bars e-' . $widget->get_name(); $vertical_position = $settings['advanced_vertical_position']; $is_sticky = $settings['advanced_toggle_sticky']; $has_close_button = $settings['floating_bar_close_switch']; $layout_classnames .= ' has-vertical-position-' . $vertical_position; if ( 'yes' === $has_close_button ) { $layout_classnames .= ' has-close-button'; } if ( 'yes' === $is_sticky ) { $layout_classnames .= ' is-sticky'; } return $layout_classnames; } protected function build_layout_render_attribute(): void { $layout_classnames = static::get_layout_classnames( $this->widget, $this->settings ); $this->add_layout_render_attribute( $layout_classnames ); } } floating-buttons/classes/render/floating-bars-core-render.php 0000644 00000010626 15252521350 0020430 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Classes\Render; use Elementor\Icons_Manager; /** * Class Floating_Bars_Core_Render. * * This class handles the rendering of the Floating Bars widget for the core version. * * @since 3.23.0 */ class Floating_Bars_Core_Render extends Floating_Bars_Render_Base { protected function render_announcement_icon(): void { $icon = $this->settings['announcement_icon'] ?? ''; if ( '' !== $icon['value'] ) : ?> <span class="e-floating-bars__announcement-icon"><?php Icons_Manager::render_icon( $icon, [ 'aria-hidden' => 'true' ] ); ?></span> <?php endif; } protected function render_announcement_text(): void { $text = $this->settings['announcement_text'] ?? ''; $this->widget->add_render_attribute( 'announcement_text', [ 'class' => 'e-floating-bars__announcement-text', ] ); if ( '' !== $text ) : ?> <p <?php $this->widget->print_render_attribute_string( 'announcement_text' ); ?>> <?php echo esc_html( $text ); ?> </p> <?php endif; } protected function render_cta_icon(): void { $icon = $this->settings['cta_icon'] ?? ''; $icon_classnames = 'e-floating-bars__cta-icon'; $this->widget->add_render_attribute( 'cta-icon', [ 'class' => $icon_classnames, ] ); if ( '' !== $icon['value'] ) : ?> <span <?php echo $this->widget->get_render_attribute_string( 'cta-icon' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php Icons_Manager::render_icon( $icon, [ 'aria-hidden' => 'true' ] ); ?></span> <?php endif; } protected function render_cta_button(): void { $link = $this->settings['cta_link'] ?? ''; $text = $this->settings['cta_text'] ?? ''; $hover_animation = $this->settings['style_cta_button_hover_animation']; $corners = $this->settings['style_cta_button_corners']; $link_type = $this->settings['style_cta_type']; $entrance_animation = $this->settings['style_cta_button_animation']; $has_border = $this->settings['style_cta_button_show_border']; $cta_classnames = 'e-floating-bars__cta-button'; if ( ! empty( $hover_animation ) ) { $cta_classnames .= ' elementor-animation-' . $hover_animation; } if ( ! empty( $corners ) ) { $cta_classnames .= ' has-corners-' . $corners; } if ( ! empty( $link_type ) ) { $cta_classnames .= ' is-type-' . $link_type; } if ( ! empty( $entrance_animation ) && 'none' != $entrance_animation ) { $cta_classnames .= ' has-entrance-animation'; } if ( 'yes' == $has_border ) { $cta_classnames .= ' has-border'; } $this->widget->add_render_attribute( 'cta-button', [ 'class' => $cta_classnames, ] ); $this->widget->add_render_attribute( 'cta_text', [ 'class' => 'e-floating-bars__cta-text', ] ); if ( ! empty( $text ) ) { $this->widget->add_link_attributes( 'cta-button', $link ); ?> <div class="e-floating-bars__cta-button-container"> <a <?php echo $this->widget->get_render_attribute_string( 'cta-button' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php $this->render_cta_icon(); ?> <span <?php $this->widget->print_render_attribute_string( 'cta_text' ); ?>><?php echo esc_html( $text ); ?></span> </a> </div> <?php } } protected function render_close_button(): void { $accessible_name = $this->settings['accessible_name']; $close_button_classnames = 'e-floating-bars__close-button'; $this->widget->add_render_attribute( 'close-button', [ 'class' => $close_button_classnames, 'aria-label' => sprintf( /* translators: %s: Accessible name. */ esc_html__( 'Close %s', 'elementor' ), $accessible_name, ), 'type' => 'button', 'aria-controls' => 'e-floating-bars', ] ); ?> <button <?php echo $this->widget->get_render_attribute_string( 'close-button' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <i class="eicon-close"></i> </button> <?php } public function render(): void { $this->build_layout_render_attribute(); $has_close_button = $this->settings['floating_bar_close_switch']; ?> <div <?php echo $this->widget->get_render_attribute_string( 'layout' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> <?php $this->render_announcement_text(); $this->render_announcement_icon(); $this->render_cta_button(); if ( 'yes' === $has_close_button ) { $this->render_close_button(); } ?> <div class="e-floating-bars__overlay"></div> </div> <?php } } floating-buttons/classes/action/action-handler.php 0000644 00000004723 15252521350 0016364 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Classes\Action; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use Elementor\Modules\FloatingButtons\Classes\Conditions\Conditions_Cache; use Elementor\Modules\FloatingButtons\Documents\Floating_Buttons; use Elementor\Modules\FloatingButtons\Module; class Action_Handler { protected string $action; protected array $menu_args; protected Conditions_Cache $conditions_cache; public function __construct( string $action, array $menu_args ) { $this->action = $action; $this->menu_args = $menu_args; $this->conditions_cache = new Conditions_Cache(); } public function process_action() { if ( ! current_user_can( 'edit_posts' ) ) { return; } switch ( $this->action ) { case 'remove_from_entire_site': $this->handle_remove_from_entire_site(); break; case 'set_as_entire_site': $this->handle_set_as_entire_site(); break; default: break; } } private function handle_remove_from_entire_site(): void { $post_id = filter_input( INPUT_GET, 'post', FILTER_VALIDATE_INT ); check_admin_referer( 'remove_from_entire_site_' . $post_id ); delete_post_meta( $post_id, '_elementor_conditions' ); $this->conditions_cache->remove_from_cache( $post_id ); wp_redirect( $this->menu_args['menu_slug'] ); exit; } private function handle_set_as_entire_site(): void { $post_id = filter_input( INPUT_GET, 'post', FILTER_VALIDATE_INT ); check_admin_referer( 'set_as_entire_site_' . $post_id ); $posts = $this->get_published_floating_elements( $post_id ); foreach ( $posts as $post_id_to_delete ) { delete_post_meta( $post_id_to_delete, '_elementor_conditions' ); $this->conditions_cache->remove_from_cache( $post_id_to_delete ); } update_post_meta( $post_id, '_elementor_conditions', [ 'include/general' ] ); $this->conditions_cache->add_to_cache( $post_id ); wp_redirect( $this->menu_args['menu_slug'] ); exit; } private function get_published_floating_elements( int $post_id ): array { return get_posts( [ 'post_type' => Module::CPT_FLOATING_BUTTONS, 'posts_per_page' => -1, 'post_status' => 'publish', 'fields' => 'ids', 'no_found_rows' => true, 'update_post_term_cache' => false, 'update_post_meta_cache' => false, 'meta_query' => Floating_Buttons::get_meta_query_for_floating_buttons( Floating_Buttons::get_floating_element_type( $post_id ) ), ] ); } } floating-buttons/module.php 0000644 00000042201 15252521350 0012040 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons; use Elementor\Controls_Manager; use Elementor\Core\Base\Document; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Documents_Manager; use Elementor\Modules\FloatingButtons\Base\Widget_Floating_Bars_Base; use Elementor\Modules\FloatingButtons\Base\Widget_Contact_Button_Base; use Elementor\Modules\FloatingButtons\Classes\Action\Action_Handler; use Elementor\Modules\FloatingButtons\Control\Hover_Animation_Floating_Buttons; use Elementor\Modules\FloatingButtons\Documents\Floating_Buttons; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils as ElementorUtils; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\Modules\FloatingButtons\AdminMenuItems\Editor_One_Floating_Elements_Menu; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const FLOATING_ELEMENTS_TYPE_META_KEY = '_elementor_floating_elements_type'; const ROUTER_OPTION_KEY = 'elementor_floating_buttons_router_version'; const META_CLICK_TRACKING = '_elementor_click_tracking'; const CLICK_TRACKING_NONCE = 'elementor-conversion-center-click'; const FLOATING_BUTTONS_DOCUMENT_TYPE = 'floating-buttons'; const CPT_FLOATING_BUTTONS = 'e-floating-buttons'; const ADMIN_PAGE_SLUG_CONTACT = 'edit.php?post_type=e-floating-buttons'; const WIDGET_HAS_CUSTOM_BREAKPOINTS = true; private $has_contact_pages = null; private $trashed_contact_pages; public static function is_active(): bool { return Plugin::$instance->experiments->is_feature_active( 'container' ); } public static function get_floating_elements_types() { return [ 'floating-buttons' => esc_html__( 'Floating Buttons', 'elementor' ), 'floating-bars' => esc_html__( 'Floating Bars', 'elementor' ), ]; } public function get_name(): string { return 'floating-buttons'; } public function get_widgets(): array { return [ 'Contact_Buttons', 'Floating_Bars_Var_1', ]; } private function register_editor_one_menu( Menu_Data_Provider $menu_data_provider ) { $menu_data_provider->register_menu( new Editor_One_Floating_Elements_Menu() ); } public function __construct() { parent::__construct(); if ( Floating_Buttons::is_creating_floating_buttons_page() || Floating_Buttons::is_editing_existing_floating_buttons_page() ) { Controls_Manager::add_tab( Widget_Contact_Button_Base::TAB_ADVANCED, esc_html__( 'Advanced', 'elementor' ) ); Controls_Manager::add_tab( Widget_Floating_Bars_Base::TAB_ADVANCED, esc_html__( 'Advanced', 'elementor' ) ); } $this->register_contact_pages_cpt(); add_action( 'elementor/documents/register', function ( Documents_Manager $documents_manager ) { $documents_manager->register_document_type( static::FLOATING_BUTTONS_DOCUMENT_TYPE, Floating_Buttons::get_class_full_name() ); } ); add_action( 'current_screen', function() { $screen = get_current_screen(); if ( $screen && 'edit-e-floating-buttons' === $screen->id ) { $this->flush_permalinks_on_elementor_version_change(); } }); add_filter( 'elementor/editor-one/menu/elementor_post_types', function ( array $elementor_post_types ): array { $elementor_post_types[ static::CPT_FLOATING_BUTTONS ] = [ 'menu_slug' => 'elementor-editor-templates', 'child_slug' => 'edit.php?post_type=e-floating-buttons', ]; return $elementor_post_types; } ); add_action( 'wp_ajax_elementor_send_clicks', [ $this, 'handle_click_tracking' ] ); add_action( 'wp_ajax_nopriv_elementor_send_clicks', [ $this, 'handle_click_tracking' ] ); add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] ); add_action( 'elementor/controls/register', function ( Controls_Manager $controls_manager ) { $controls_manager->register( new Hover_Animation_Floating_Buttons() ); }); add_filter( 'elementor/widget/common/register_css_attributes_control', function ( $common_controls ) { if ( Floating_Buttons::is_creating_floating_buttons_page() || Floating_Buttons::is_editing_existing_floating_buttons_page() ) { return false; } return $common_controls; } ); add_filter( 'elementor/settings/controls/checkbox_list_cpt/post_type_objects', function ( $post_types ) { unset( $post_types[ static::CPT_FLOATING_BUTTONS ] ); return $post_types; } ); add_filter( 'elementor/template_library/sources/local/is_valid_template_type', function ( $is_valid_template_type, $cpt ) { if ( in_array( static::CPT_FLOATING_BUTTONS, $cpt, true ) ) { return true; } return $is_valid_template_type; }, 10, 2 ); if ( ! ElementorUtils::has_pro() ) { add_action( 'wp_footer', function () { $this->render_floating_buttons(); } ); } add_action( 'elementor/admin-top-bar/is-active', function ( $is_top_bar_active, $current_screen ) { if ( strpos( $current_screen->id ?? '', static::CPT_FLOATING_BUTTONS ) !== false ) { return true; } return $is_top_bar_active; }, 10, 2 ); add_action( 'elementor/editor-one/menu/register', function ( Menu_Data_Provider $menu_data_provider ) { $this->register_editor_one_menu( $menu_data_provider ); } ); add_action( 'elementor/admin/localize_settings', function ( array $settings ) { return $this->admin_localize_settings( $settings ); } ); add_action( 'elementor/editor/localize_settings', function ( $data ) { return $this->editor_localize_settings( $data ); } ); add_filter( 'elementor/template_library/sources/local/register_taxonomy_cpts', function ( array $cpts ) { $cpts[] = static::CPT_FLOATING_BUTTONS; return $cpts; } ); add_action( 'admin_init', function () { $action = sanitize_text_field( filter_input( INPUT_GET, 'action' ) ); if ( $action ) { $menu_args = $this->get_contact_menu_args(); $action_handler = new Action_Handler( $action, $menu_args ); $action_handler->process_action(); } } ); add_action( 'manage_' . static::CPT_FLOATING_BUTTONS . '_posts_columns', function( $posts_columns ) { $source_local = Plugin::$instance->templates_manager->get_source( 'local' ); unset( $posts_columns['date'] ); unset( $posts_columns['comments'] ); $posts_columns['click_tracking'] = esc_html__( 'Click Tracking', 'elementor' ); if ( ! ElementorUtils::has_pro() ) { $posts_columns['instances'] = esc_html__( 'Instances', 'elementor' ); } return $source_local->admin_columns_headers( $posts_columns ); } ); add_action( 'manage_' . static::CPT_FLOATING_BUTTONS . '_posts_custom_column', [ $this, 'set_admin_columns_content' ], 10, 2 ); add_action( 'admin_bar_menu', function ( $admin_bar ) { $this->override_admin_bar_add_contact( $admin_bar ); }, 100 ); } public function is_preview_for_document( $post_id ) { $preview_id = ElementorUtils::get_super_global_value( $_GET, 'preview_id' ); $preview = ElementorUtils::get_super_global_value( $_GET, 'preview' ); return 'true' === $preview && (int) $post_id === (int) $preview_id; } public function handle_click_tracking() { $data = filter_input_array( INPUT_POST, [ 'clicks' => [ 'filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_REQUIRE_ARRAY, ], '_nonce' => FILTER_UNSAFE_RAW, ] ); if ( ! wp_verify_nonce( $data['_nonce'], static::CLICK_TRACKING_NONCE ) ) { wp_send_json_error( [ 'message' => 'Invalid nonce' ] ); } if ( ! check_ajax_referer( static::CLICK_TRACKING_NONCE, '_nonce', false ) ) { wp_send_json_error( [ 'message' => 'Invalid referrer' ] ); } $posts_to_update = []; foreach ( $data['clicks'] as $post_id ) { if ( ! isset( $posts_to_update[ $post_id ] ) ) { $starting_clicks = (int) get_post_meta( $post_id, static::META_CLICK_TRACKING, true ); $posts_to_update[ $post_id ] = $starting_clicks ? $starting_clicks : 0; } ++$posts_to_update[ $post_id ]; } foreach ( $posts_to_update as $post_id => $clicks ) { if ( self::CPT_FLOATING_BUTTONS !== get_post_type( $post_id ) ) { continue; } if ( 'publish' !== get_post_status( $post_id ) ) { continue; } update_post_meta( $post_id, static::META_CLICK_TRACKING, $clicks ); } wp_send_json_success(); } public function set_admin_columns_content( $column_name, $post_id ) { $document = Plugin::$instance->documents->get( $post_id ); if ( method_exists( $document, 'admin_columns_content' ) ) { $document->admin_columns_content( $column_name ); } switch ( $column_name ) { case 'click_tracking': $click_tracking = get_post_meta( $post_id, static::META_CLICK_TRACKING, true ); echo esc_html( $click_tracking ); break; case 'instances': if ( ElementorUtils::has_pro() ) { break; } $instances = get_post_meta( $post_id, '_elementor_conditions', true ); if ( $instances ) { echo esc_html__( 'Entire Site', 'elementor' ); } break; default: break; } } public function flush_permalinks_on_elementor_version_change() { if ( get_option( static::ROUTER_OPTION_KEY ) !== ELEMENTOR_VERSION ) { flush_rewrite_rules(); update_option( static::ROUTER_OPTION_KEY, ELEMENTOR_VERSION ); } } private function get_trashed_contact_posts(): array { if ( $this->trashed_contact_pages ) { return $this->trashed_contact_pages; } $this->trashed_contact_pages = $this->get_trashed_posts( static::CPT_FLOATING_BUTTONS, static::FLOATING_BUTTONS_DOCUMENT_TYPE ); return $this->trashed_contact_pages; } private function get_trashed_posts( string $cpt, string $document_type ) { $query = new \WP_Query( [ 'no_found_rows' => true, 'post_type' => $cpt, 'post_status' => 'trash', 'posts_per_page' => 1, 'meta_key' => '_elementor_template_type', 'meta_value' => $document_type, ] ); return $query->posts; } private function get_add_new_contact_page_url() { if ( ElementorUtils::has_pro() ) { return Plugin::$instance->documents->get_create_new_post_url( static::CPT_FLOATING_BUTTONS, static::FLOATING_BUTTONS_DOCUMENT_TYPE ); } return Plugin::$instance->documents->get_create_new_post_url( static::CPT_FLOATING_BUTTONS, static::FLOATING_BUTTONS_DOCUMENT_TYPE ) . '#library'; } public function print_empty_contact_pages_page() { $template_sources = Plugin::$instance->templates_manager->get_registered_sources(); $source_local = $template_sources['local']; $trashed_posts = $this->get_trashed_contact_posts(); ?> <div class="e-landing-pages-empty"> <?php /** @var Source_Local $source_local */ $source_local->print_blank_state_template( esc_html__( 'Floating Element', 'elementor' ), $this->get_add_new_contact_page_url(), nl2br( esc_html__( 'Add a Floating element so your users can easily get in touch!', 'elementor' ) ) ); if ( ! empty( $trashed_posts ) ) : ?> <div class="e-trashed-items"> <?php printf( /* translators: %1$s Link open tag, %2$s: Link close tag. */ esc_html__( 'Or view %1$sTrashed Items%2$s', 'elementor' ), '<a href="' . esc_url( admin_url( 'edit.php?post_status=trash&post_type=' . self::CPT_FLOATING_BUTTONS ) ) . '">', '</a>' ); ?> </div> <?php endif; ?> </div> <?php } private function admin_localize_settings( $settings ) { $contact_menu_slug = $this->get_contact_menu_args()['menu_slug']; if ( static::CPT_FLOATING_BUTTONS === $contact_menu_slug ) { $contact_menu_slug = 'admin.php?page=' . $contact_menu_slug; } $additional_settings = [ 'urls' => [ 'addNewLinkUrlContact' => $this->get_add_new_contact_page_url(), 'viewContactPageUrl' => $contact_menu_slug, ], 'contactPages' => [ 'hasPages' => $this->has_contact_pages(), ], ]; return array_replace_recursive( $settings, $additional_settings ); } private function register_contact_pages_cpt() { $this->register_post_type( Floating_Buttons::get_labels(), static::CPT_FLOATING_BUTTONS ); } private function register_post_type( array $labels, string $cpt ) { $args = [ 'labels' => $labels, 'public' => true, 'show_in_menu' => 'edit.php?post_type=elementor_library&tabs_group=library', 'show_in_nav_menus' => false, 'capabilities' => [ 'edit_post' => 'manage_options', 'read_post' => 'manage_options', 'delete_post' => 'manage_options', 'edit_posts' => 'manage_options', 'edit_others_posts' => 'manage_options', 'publish_posts' => 'manage_options', 'read_private_posts' => 'manage_options', 'create_posts' => 'manage_options', ], 'taxonomies' => [ Source_Local::TAXONOMY_TYPE_SLUG ], 'show_in_rest' => true, 'supports' => [ 'title', 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes', 'thumbnail', 'custom-fields', 'post-formats', 'elementor', ], ]; register_post_type( $cpt, $args ); } private function has_contact_pages(): bool { if ( null !== $this->has_contact_pages ) { return $this->has_contact_pages; } $this->has_contact_pages = $this->has_pages( static::CPT_FLOATING_BUTTONS, static::FLOATING_BUTTONS_DOCUMENT_TYPE ); return $this->has_contact_pages; } private function has_pages( string $cpt, string $document_type ): bool { $posts_query = new \WP_Query( [ 'no_found_rows' => true, 'post_type' => $cpt, 'post_status' => 'any', 'posts_per_page' => 1, 'meta_key' => '_elementor_template_type', 'meta_value' => $document_type, ] ); return $posts_query->post_count > 0; } private function get_contact_menu_args(): array { if ( $this->has_contact_pages() ) { $menu_slug = static::ADMIN_PAGE_SLUG_CONTACT; $function = null; } else { $menu_slug = static::CPT_FLOATING_BUTTONS; $function = [ $this, 'print_empty_contact_pages_page' ]; } return [ 'menu_slug' => $menu_slug, 'function' => $function, ]; } public function override_admin_bar_add_contact( $admin_bar ): void { $new_contact_page_node = $admin_bar->get_node( 'new-e-floating-buttons' ); if ( $new_contact_page_node ) { $new_contact_page_node->href = $this->get_add_new_contact_page_url(); $admin_bar->add_node( $new_contact_page_node ); } } private function editor_localize_settings( $data ) { $data['admin_floating_button_admin_url'] = admin_url( $this->get_contact_menu_args()['menu_slug'] ); return $data; } private function render_floating_buttons(): void { if ( Plugin::$instance->preview->is_preview_mode() ) { $post_id = ElementorUtils::get_super_global_value( $_GET, 'elementor-preview' ); $document = Plugin::$instance->documents->get( $post_id ); if ( $document instanceof Document && $document->get_name() === static::FLOATING_BUTTONS_DOCUMENT_TYPE ) { return; } } $query = new \WP_Query( [ 'post_type' => static::CPT_FLOATING_BUTTONS, 'posts_per_page' => - 1, 'post_status' => 'publish', 'fields' => 'ids', 'meta_key' => '_elementor_conditions', 'meta_compare' => 'EXISTS', ] ); if ( ! $query->have_posts() ) { return; } foreach ( $query->posts as $post_id ) { $conditions = get_post_meta( $post_id, '_elementor_conditions', true ); if ( ! $conditions ) { continue; } if ( in_array( 'include/general', $conditions ) && ! $this->is_preview_for_document( $post_id ) && get_the_ID() !== $post_id ) { $document = Plugin::$instance->documents->get( $post_id ); $document->print_content(); } } } /** * Register styles. * * At build time, Elementor compiles `/modules/floating-buttons/assets/scss/widgets/*.scss` * to `/assets/css/widget-*.min.css`. * * @return void */ public function register_styles() { $direction_suffix = is_rtl() ? '-rtl' : ''; $widget_styles = $this->get_widgets_style_list(); $has_custom_breakpoints = Plugin::$instance->breakpoints->has_custom_breakpoints(); foreach ( $widget_styles as $widget_style_name => $widget_has_responsive_style ) { $should_load_responsive_css = $widget_has_responsive_style ? $has_custom_breakpoints : false; wp_register_style( $widget_style_name, $this->get_frontend_file_url( "{$widget_style_name}{$direction_suffix}.min.css", $should_load_responsive_css ), [ 'elementor-frontend', 'elementor-icons' ], $should_load_responsive_css ? null : ELEMENTOR_VERSION ); } } private function get_widgets_style_list(): array { return [ 'widget-floating-buttons' => self::WIDGET_HAS_CUSTOM_BREAKPOINTS, // TODO: Remove in v3.27.0 [ED-15717] 'widget-floating-bars-base' => self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-floating-bars-var-2' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-floating-bars-var-3' => self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-contact-buttons-base' => self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-contact-buttons-var-1' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-contact-buttons-var-3' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-contact-buttons-var-4' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-contact-buttons-var-6' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-contact-buttons-var-7' => self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-contact-buttons-var-8' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-contact-buttons-var-9' => self::WIDGET_HAS_CUSTOM_BREAKPOINTS, 'widget-contact-buttons-var-10' => self::WIDGET_HAS_CUSTOM_BREAKPOINTS, ]; } } floating-buttons/admin-menu-items/editor-one-floating-elements-menu.php 0000644 00000001614 15252521350 0022351 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\AdminMenuItems; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Modules\FloatingButtons\Module; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Floating_Elements_Menu implements Menu_Item_Interface { public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'Floating Elements', 'elementor' ); } public function get_position(): int { return 40; } public function get_slug(): string { return Module::ADMIN_PAGE_SLUG_CONTACT; } public function get_group_id(): string { return Menu_Config::TEMPLATES_GROUP_ID; } } floating-buttons/documents/floating-buttons.php 0000644 00000017734 15252521350 0016070 0 ustar 00 <?php namespace Elementor\Modules\FloatingButtons\Documents; use Elementor\Core\Base\Document; use Elementor\Core\DocumentTypes\PageBase; use Elementor\Modules\FloatingButtons\Module; use Elementor\Modules\Library\Traits\Library as Library_Trait; use Elementor\Modules\FloatingButtons\Module as Floating_Buttons_Module; use Elementor\Modules\PageTemplates\Module as Page_Templates_Module; use Elementor\Plugin; use Elementor\TemplateLibrary\Source_Local; use Elementor\Utils as ElementorUtils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Floating_Buttons extends PageBase { use Library_Trait; public static function get_properties() { $properties = parent::get_properties(); $properties['support_kit'] = true; $properties['support_site_editor'] = false; $properties['cpt'] = [ Floating_Buttons_Module::CPT_FLOATING_BUTTONS ]; $properties['show_navigator'] = false; $properties['allow_adding_widgets'] = false; $properties['support_page_layout'] = false; $properties['library_close_title'] = esc_html__( 'Go To Dashboard', 'elementor' ); $properties['publish_button_title'] = esc_html__( 'After publishing this widget, you will be able to set it as visible on the entire site in the Admin Table.', 'elementor' ); $properties['allow_closing_remote_library'] = false; return $properties; } public static function get_floating_element_type( $post_id ) { $meta = get_post_meta( $post_id, Floating_Buttons_Module::FLOATING_ELEMENTS_TYPE_META_KEY, true ); return $meta ? $meta : 'floating-buttons'; } public static function is_editing_existing_floating_buttons_page() { $action = ElementorUtils::get_super_global_value( $_GET, 'action' ); $post_id = ElementorUtils::get_super_global_value( $_GET, 'post' ); return 'elementor' === $action && static::is_floating_buttons_type_meta_key( $post_id ); } public static function is_creating_floating_buttons_page() { $action = ElementorUtils::get_super_global_value( $_POST, 'action' ); //phpcs:ignore WordPress.Security.NonceVerification.Missing $post_id = ElementorUtils::get_super_global_value( $_POST, 'editor_post_id' ); //phpcs:ignore WordPress.Security.NonceVerification.Missing return 'elementor_ajax' === $action && static::is_floating_buttons_type_meta_key( $post_id ); } public static function is_floating_buttons_type_meta_key( $post_id ) { return Module::FLOATING_BUTTONS_DOCUMENT_TYPE === get_post_meta( $post_id, Document::TYPE_META_KEY, true ); } public function print_content() { $plugin = \Elementor\Plugin::$instance; if ( $plugin->preview->is_preview_mode( $this->get_main_id() ) ) { // PHPCS - the method builder_wrapper is safe. echo $plugin->preview->builder_wrapper( '' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } else { // PHPCS - the method get_content is safe. echo $this->get_content(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } public function get_location() { return self::get_property( 'location' ); } public static function get_type() { return Floating_Buttons_Module::FLOATING_BUTTONS_DOCUMENT_TYPE; } public static function register_post_fields_control( $document ) {} public static function register_hide_title_control( $document ) {} public function get_name() { return Floating_Buttons_Module::FLOATING_BUTTONS_DOCUMENT_TYPE; } public function filter_admin_row_actions( $actions ) { unset( $actions['edit'] ); unset( $actions['inline hide-if-no-js'] ); $built_with_elementor = parent::filter_admin_row_actions( [] ); if ( isset( $actions['trash'] ) ) { $delete = $actions['trash']; unset( $actions['trash'] ); $actions['trash'] = $delete; } if ( 'publish' === $this->get_post()->post_status ) { $actions = $this->set_as_entire_site( $actions ); } return $built_with_elementor + $actions; } public static function get_meta_query_for_floating_buttons( string $floating_element_type ): array { $meta_query = [ 'relation' => 'AND', [ 'key' => '_elementor_conditions', 'compare' => 'EXISTS', ], ]; if ( 'floating-buttons' === $floating_element_type ) { $meta_query[] = [ 'relation' => 'OR', [ 'key' => Module::FLOATING_ELEMENTS_TYPE_META_KEY, 'compare' => 'NOT EXISTS', ], [ 'key' => Module::FLOATING_ELEMENTS_TYPE_META_KEY, 'value' => 'floating-buttons', ], ]; } else { $meta_query[] = [ 'key' => Module::FLOATING_ELEMENTS_TYPE_META_KEY, 'value' => $floating_element_type, ]; } return $meta_query; } /** * Tries to find the post id of the floating element that is set as entire site. * If found, returns the post id, otherwise returns 0. * * @param string $floating_element_type * * @return int */ public static function get_set_as_entire_site_post_id( string $floating_element_type ): int { static $types = []; if ( isset( $types[ $floating_element_type ] ) ) { return $types[ $floating_element_type ]; } $query = new \WP_Query( [ 'post_type' => Floating_Buttons_Module::CPT_FLOATING_BUTTONS, 'posts_per_page' => -1, 'post_status' => 'publish', 'fields' => 'ids', 'no_found_rows' => true, 'update_post_term_cache' => false, 'meta_query' => static::get_meta_query_for_floating_buttons( $floating_element_type ), ] ); foreach ( $query->get_posts() as $post_id ) { $conditions = get_post_meta( $post_id, '_elementor_conditions', true ); if ( ! $conditions ) { continue; } if ( in_array( 'include/general', $conditions ) ) { $types[ $floating_element_type ] = $post_id; return $post_id; } } return 0; } public function set_as_entire_site( $actions ) { $floating_element_type = static::get_floating_element_type( $this->get_main_id() ); $current_set_as_entire_site_post_id = static::get_set_as_entire_site_post_id( $floating_element_type ); if ( $current_set_as_entire_site_post_id === $this->get_main_id() ) { $actions['set_as_entire_site'] = sprintf( '<a style="color:red;" href="?post=%s&action=remove_from_entire_site&_wpnonce=%s">%s</a>', $this->get_post()->ID, wp_create_nonce( 'remove_from_entire_site_' . $this->get_post()->ID ), esc_html__( 'Remove From Entire Site', 'elementor' ) ); } else { $actions['set_as_entire_site'] = sprintf( '<a href="?post=%s&action=set_as_entire_site&_wpnonce=%s">%s</a>', $this->get_post()->ID, wp_create_nonce( 'set_as_entire_site_' . $this->get_post()->ID ), esc_html__( 'Set as Entire Site', 'elementor' ) ); } return $actions; } public static function get_title() { return esc_html__( 'Floating Element', 'elementor' ); } public static function get_plural_title() { return esc_html__( 'Floating Elements', 'elementor' ); } public static function get_create_url() { return parent::get_create_url() . '#library'; } public function save( $data ) { if ( empty( $data['settings']['template'] ) ) { $data['settings']['template'] = Page_Templates_Module::TEMPLATE_CANVAS; } return parent::save( $data ); } public function admin_columns_content( $column_name ) { if ( 'elementor_library_type' === $column_name ) { $admin_filter_url = admin_url( Source_Local::ADMIN_MENU_SLUG . '&elementor_library_type=' . $this->get_name() ); $meta = static::get_floating_element_type( $this->get_main_id() ); printf( '<a href="%s">%s</a>', $admin_filter_url, Module::get_floating_elements_types()[ $meta ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } public function get_edit_url() { return add_query_arg( [ 'post' => $this->get_main_id(), 'action' => 'elementor', 'floating_element' => get_post_meta( $this->get_main_id(), Module::FLOATING_ELEMENTS_TYPE_META_KEY, true ), ], admin_url( 'post.php' ) ); } protected function get_remote_library_config() { $config = [ 'type' => 'floating_button', 'default_route' => 'templates/floating-buttons', 'autoImportSettings' => true, ]; return array_replace_recursive( parent::get_remote_library_config(), $config ); } } editor-app-bar/module.php 0000644 00000002562 15252521350 0011355 0 ustar 00 <?php namespace Elementor\Modules\EditorAppBar; use Elementor\Core\Base\Module as BaseModule; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const PACKAGES = [ 'editor-app-bar', ]; const STYLES = [ 'editor-v2-app-bar-overrides', ]; public function get_name() { return 'editor-app-bar'; } public function __construct() { parent::__construct(); add_filter( 'elementor/editor/v2/packages', fn( $packages ) => $this->add_packages( $packages ) ); add_filter( 'elementor/editor/v2/styles', fn( $styles ) => $this->add_styles( $styles ) ); add_filter( 'elementor/editor/templates', fn( $templates ) => $this->remove_templates( $templates ) ); add_action( 'elementor/editor/v2/scripts/enqueue', fn() => $this->dequeue_scripts() ); add_action( 'elementor/editor/v2/styles/enqueue', fn() => $this->dequeue_styles() ); } private function add_packages( $packages ) { return array_merge( $packages, self::PACKAGES ); } private function add_styles( $styles ) { return array_merge( $styles, self::STYLES ); } private function remove_templates( $templates ) { return array_diff( $templates, [ 'responsive-bar' ] ); } private function dequeue_scripts() { wp_dequeue_script( 'elementor-responsive-bar' ); } private function dequeue_styles() { wp_dequeue_style( 'elementor-responsive-bar' ); } } variables/utils/template-library-variables.php 0000644 00000016344 15252521350 0015620 0 ustar 00 <?php namespace Elementor\Modules\Variables\Utils; use Elementor\Core\Utils\Template_Library_Import_Export_Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Template_Library_Variables { public static function add_variables_snapshot( array $snapshots, $content, $template_id, array $export_data ): array { if ( ! is_array( $content ) ) { return $snapshots; } if ( ! empty( $snapshots['global_variables'] ) ) { return $snapshots; } $global_classes_snapshot = $snapshots['global_classes'] ?? null; $snapshot = self::build_snapshot_for_elements( $content, $global_classes_snapshot ); if ( ! empty( $snapshot ) ) { $snapshots['global_variables'] = $snapshot; } return $snapshots; } public static function extract_variables_from_data( array $snapshots, array $decoded_data, array $data ): array { $snapshot = $decoded_data['global_variables'] ?? null; if ( ! empty( $snapshot ) && is_array( $snapshot ) ) { $snapshots['global_variables'] = $snapshot; } return $snapshots; } public static function process_variables_import( array $result, string $import_mode, array $data ): array { $snapshot = $data['global_variables'] ?? null; if ( empty( $snapshot ) || ! is_array( $snapshot ) ) { return $result; } $processed = Template_Library_Import_Export_Utils::process_import_by_mode( $import_mode, $result['content'], $snapshot, [ self::class, 'merge_snapshot_and_get_id_map' ], [ self::class, 'create_all_as_new' ], [ self::class, 'rewrite_elements_variable_ids' ], [ self::class, 'flatten_elements_variables' ] ); $result['content'] = $processed['content']; $result['updated_global_variables'] = $processed['operation_result']['variables'] ?? null; $result['variables_id_map'] = $processed['id_map']; $result['variables_to_flatten'] = $processed['ids_to_flatten']; $result['variables_snapshot'] = $snapshot; return $result; } private static function build_snapshot_for_elements( array $elements, ?array $global_classes_snapshot = null ): ?array { return Template_Library_Variables_Snapshot_Builder::build_snapshot_for_elements( $elements, $global_classes_snapshot ); } public static function merge_snapshot_and_get_id_map( array $snapshot ): array { return Template_Library_Variables_Snapshot_Builder::merge_snapshot_and_get_id_map( $snapshot ); } public static function rewrite_elements_variable_ids( array $elements, array $id_map ): array { return Template_Library_Variables_Element_Transformer::rewrite_elements_variable_ids( $elements, $id_map ); } public static function flatten_elements_variables( array $elements, array $global_variables, ?array $only_ids = null ): array { return Template_Library_Variables_Element_Transformer::flatten_elements_variables( $elements, $global_variables, $only_ids ); } public static function create_all_as_new( array $snapshot ): array { return Template_Library_Variables_Snapshot_Builder::create_snapshot_as_new( $snapshot ); } public static function transform_variables_in_classes_snapshot( array $classes_snapshot, string $import_mode, array $result, array $data ): array { $variables_id_map = $result['variables_id_map'] ?? []; $variables_to_flatten = $result['variables_to_flatten'] ?? []; $variables_snapshot = $result['variables_snapshot'] ?? ( $data['global_variables'] ?? null ); if ( Template_Library_Import_Export_Utils::IMPORT_MODE_KEEP_FLATTEN === $import_mode && ! empty( $variables_snapshot ) ) { $classes_snapshot = self::flatten_variables_in_classes_snapshot( $classes_snapshot, $variables_snapshot ); } else { if ( ! empty( $variables_id_map ) ) { $classes_snapshot = self::rewrite_variable_ids_in_classes_snapshot( $classes_snapshot, $variables_id_map ); } if ( ! empty( $variables_to_flatten ) && ! empty( $variables_snapshot ) ) { $classes_snapshot = self::flatten_variables_in_classes_snapshot( $classes_snapshot, $variables_snapshot, $variables_to_flatten ); } } return $classes_snapshot; } public static function rewrite_variable_ids_in_classes_snapshot( array $snapshot, array $id_map ): array { if ( empty( $snapshot['items'] ) || empty( $id_map ) ) { return $snapshot; } $variable_types = Variable_Type_Keys::get_all(); foreach ( $snapshot['items'] as $class_id => &$class_item ) { if ( empty( $class_item['variants'] ) || ! is_array( $class_item['variants'] ) ) { continue; } foreach ( $class_item['variants'] as &$variant ) { if ( empty( $variant['props'] ) || ! is_array( $variant['props'] ) ) { continue; } $variant['props'] = self::rewrite_variable_refs_recursive( $variant['props'], $id_map, $variable_types ); } } return $snapshot; } private static function rewrite_variable_refs_recursive( array $data, array $id_map, array $variable_types ): array { foreach ( $data as $key => $value ) { if ( ! is_array( $value ) ) { continue; } $type = $value['$$type'] ?? null; $val = $value['value'] ?? null; if ( $type && in_array( $type, $variable_types, true ) && is_string( $val ) && isset( $id_map[ $val ] ) ) { $data[ $key ]['value'] = $id_map[ $val ]; continue; } $data[ $key ] = self::rewrite_variable_refs_recursive( $value, $id_map, $variable_types ); } return $data; } public static function flatten_variables_in_classes_snapshot( array $classes_snapshot, array $variables_snapshot, ?array $only_ids = null ): array { if ( empty( $classes_snapshot['items'] ) ) { return $classes_snapshot; } $variable_data = $variables_snapshot['data'] ?? []; $variable_types = Variable_Type_Keys::get_all(); $type_map = Variable_Type_Keys::get_type_mappings(); $ids_to_flatten = null !== $only_ids ? array_fill_keys( $only_ids, true ) : null; foreach ( $classes_snapshot['items'] as $class_id => &$class_item ) { if ( empty( $class_item['variants'] ) || ! is_array( $class_item['variants'] ) ) { continue; } foreach ( $class_item['variants'] as &$variant ) { if ( empty( $variant['props'] ) || ! is_array( $variant['props'] ) ) { continue; } $variant['props'] = self::flatten_variable_refs_in_props( $variant['props'], $variable_data, $variable_types, $type_map, $ids_to_flatten ); } } return $classes_snapshot; } private static function flatten_variable_refs_in_props( array $data, array $variable_data, array $variable_types, array $type_map, ?array $ids_to_flatten = null ): array { foreach ( $data as $key => $value ) { if ( ! is_array( $value ) ) { continue; } $type = $value['$$type'] ?? null; $var_id = $value['value'] ?? null; if ( $type && in_array( $type, $variable_types, true ) && is_string( $var_id ) && isset( $variable_data[ $var_id ] ) ) { if ( null !== $ids_to_flatten && ! isset( $ids_to_flatten[ $var_id ] ) ) { continue; } $resolved_type = $type_map[ $type ] ?? null; $resolved_value = $variable_data[ $var_id ]['value'] ?? null; if ( $resolved_type && null !== $resolved_value ) { $data[ $key ] = [ '$$type' => $resolved_type, 'value' => Variable_Type_Keys::convert_value_for_resolved_type( $resolved_type, $resolved_value ), ]; } continue; } $data[ $key ] = self::flatten_variable_refs_in_props( $value, $variable_data, $variable_types, $type_map, $ids_to_flatten ); } return $data; } } variables/utils/template-library-variables-element-transformer.php 0000644 00000010575 15252521350 0021607 0 ustar 00 <?php namespace Elementor\Modules\Variables\Utils; use Elementor\Core\Utils\Template_Library_Element_Iterator; if ( ! defined( 'ABSPATH' ) ) { exit; } class Template_Library_Variables_Element_Transformer { public static function rewrite_elements_variable_ids( array $elements, array $id_map ): array { if ( empty( $elements ) || empty( $id_map ) ) { return $elements; } $variable_types_map = array_fill_keys( Variable_Type_Keys::get_all(), true ); return Template_Library_Element_Iterator::iterate( $elements, function ( $element_data ) use ( $id_map, $variable_types_map ) { return self::rewrite_variable_ids_in_element( $element_data, $id_map, $variable_types_map ); } ); } public static function flatten_elements_variables( array $elements, array $global_variables, ?array $only_ids = null ): array { $variable_data = $global_variables['data'] ?? []; if ( empty( $elements ) || empty( $variable_data ) ) { return $elements; } $variable_types_map = array_fill_keys( Variable_Type_Keys::get_all(), true ); $ids_to_flatten = null !== $only_ids ? array_fill_keys( $only_ids, true ) : null; return Template_Library_Element_Iterator::iterate( $elements, function ( $element_data ) use ( $variable_data, $variable_types_map, $ids_to_flatten ) { return self::flatten_variable_refs_in_element( $element_data, $variable_data, $variable_types_map, $ids_to_flatten ); } ); } private static function rewrite_variable_ids_in_element( array $element_data, array $id_map, array $variable_types_map ): array { if ( ! empty( $element_data['settings'] ) && is_array( $element_data['settings'] ) ) { $element_data['settings'] = self::rewrite_variable_ids_recursive( $element_data['settings'], $id_map, $variable_types_map ); } if ( ! empty( $element_data['styles'] ) && is_array( $element_data['styles'] ) ) { $element_data['styles'] = self::rewrite_variable_ids_recursive( $element_data['styles'], $id_map, $variable_types_map ); } return $element_data; } private static function rewrite_variable_ids_recursive( $data, array $id_map, array $variable_types_map ) { if ( ! is_array( $data ) ) { return $data; } if ( isset( $data['$$type'], $variable_types_map[ $data['$$type'] ] ) ) { if ( isset( $data['value'] ) && is_string( $data['value'] ) && isset( $id_map[ $data['value'] ] ) ) { $data['value'] = $id_map[ $data['value'] ]; } return $data; } foreach ( $data as $key => $value ) { if ( is_array( $value ) ) { $data[ $key ] = self::rewrite_variable_ids_recursive( $value, $id_map, $variable_types_map ); } } return $data; } private static function flatten_variable_refs_in_element( array $element_data, array $variable_data, array $variable_types_map, ?array $ids_to_flatten ): array { if ( ! empty( $element_data['settings'] ) && is_array( $element_data['settings'] ) ) { $element_data['settings'] = self::flatten_variable_refs_recursive( $element_data['settings'], $variable_data, $variable_types_map, $ids_to_flatten ); } if ( ! empty( $element_data['styles'] ) && is_array( $element_data['styles'] ) ) { $element_data['styles'] = self::flatten_variable_refs_recursive( $element_data['styles'], $variable_data, $variable_types_map, $ids_to_flatten ); } return $element_data; } private static function flatten_variable_refs_recursive( $data, array $variable_data, array $variable_types_map, ?array $ids_to_flatten = null ) { if ( ! is_array( $data ) ) { return $data; } if ( isset( $data['$$type'], $variable_types_map[ $data['$$type'] ] ) ) { $var_id = $data['value'] ?? null; if ( is_string( $var_id ) && isset( $variable_data[ $var_id ] ) ) { if ( null !== $ids_to_flatten && ! isset( $ids_to_flatten[ $var_id ] ) ) { return $data; } $variable = $variable_data[ $var_id ]; $resolved_value = $variable['value'] ?? null; $resolved_type = Variable_Type_Keys::get_resolved_type( $data['$$type'] ); if ( null !== $resolved_value && null !== $resolved_type ) { return [ '$$type' => $resolved_type, 'value' => Variable_Type_Keys::convert_value_for_resolved_type( $resolved_type, $resolved_value ), ]; } } return $data; } foreach ( $data as $key => $value ) { if ( is_array( $value ) ) { $data[ $key ] = self::flatten_variable_refs_recursive( $value, $variable_data, $variable_types_map, $ids_to_flatten ); } } return $data; } } variables/utils/variable-type-keys.php 0000644 00000004327 15252521350 0014110 0 ustar 00 <?php namespace Elementor\Modules\Variables\Utils; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; use Elementor\Modules\Variables\Adapters\Prop_Type_Adapter; use Elementor\Modules\Variables\PropTypes\Color_Variable_Prop_Type; use Elementor\Modules\Variables\PropTypes\Font_Variable_Prop_Type; use Elementor\Modules\Variables\PropTypes\Size_Variable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; } class Variable_Type_Keys { private static ?array $types_cache = null; private static ?array $mappings_cache = null; public static function get_all(): array { if ( null === self::$types_cache ) { self::$types_cache = [ Color_Variable_Prop_Type::get_key(), Font_Variable_Prop_Type::get_key(), Size_Variable_Prop_Type::get_key(), Prop_Type_Adapter::GLOBAL_CUSTOM_SIZE_VARIABLE_KEY, ]; } return self::$types_cache; } public static function is_variable_type( $type ): bool { if ( ! is_string( $type ) || '' === $type ) { return false; } return in_array( $type, self::get_all(), true ); } public static function get_type_mappings(): array { if ( null === self::$mappings_cache ) { self::$mappings_cache = [ Color_Variable_Prop_Type::get_key() => 'color', Font_Variable_Prop_Type::get_key() => 'string', Size_Variable_Prop_Type::get_key() => 'size', Prop_Type_Adapter::GLOBAL_CUSTOM_SIZE_VARIABLE_KEY => 'size', ]; } return self::$mappings_cache; } public static function get_resolved_type( string $variable_type ): ?string { return self::get_type_mappings()[ $variable_type ] ?? null; } public static function convert_value_for_resolved_type( string $resolved_type, $value ) { if ( 'size' !== $resolved_type || ! is_string( $value ) ) { return $value; } return self::parse_size_string( $value ); } private static function parse_size_string( string $value ): array { $value = trim( strtolower( $value ) ); if ( 'auto' === $value ) { return [ 'size' => '', 'unit' => 'auto', ]; } if ( preg_match( '/^(-?\d*\.?\d+)([a-z%]+)$/i', $value, $matches ) ) { return [ 'size' => $matches[1] + 0, 'unit' => strtolower( $matches[2] ), ]; } return [ 'size' => $value, 'unit' => Size_Constants::DEFAULT_UNIT, ]; } } variables/utils/template-library-variables-snapshot-builder.php 0000644 00000015127 15252521350 0021077 0 ustar 00 <?php namespace Elementor\Modules\Variables\Utils; use Elementor\Core\Utils\Template_Library_Element_Iterator; use Elementor\Core\Utils\Template_Library_Import_Export_Utils; use Elementor\Core\Utils\Template_Library_Snapshot_Processor; use Elementor\Modules\Variables\Storage\Constants; use Elementor\Modules\Variables\Storage\Variables_Collection; use Elementor\Modules\Variables\Storage\Variables_Repository; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Template_Library_Variables_Snapshot_Builder extends Template_Library_Snapshot_Processor { private static ?self $instance = null; public static function make(): self { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } public static function extract_used_variable_ids_from_elements( array $elements ): array { $ids = []; $variable_types = Variable_Type_Keys::get_all(); if ( empty( $elements ) ) { return []; } Template_Library_Element_Iterator::iterate( $elements, function ( $element_data ) use ( &$ids, $variable_types ) { self::collect_variable_ids_from_element( $element_data, $ids, $variable_types ); return $element_data; } ); return array_values( array_unique( $ids ) ); } public static function build_snapshot_for_ids( array $ids ): ?array { if ( empty( $ids ) ) { return null; } $instance = self::make(); if ( ! $instance->can_access_repository() ) { return null; } $ids = Template_Library_Import_Export_Utils::normalize_string_ids( $ids ); if ( empty( $ids ) ) { return null; } $all_data = $instance->load_current_data(); $all_variables = $all_data['items'] ?? []; $filtered_data = Template_Library_Import_Export_Utils::filter_items_by_ids( $all_variables, $ids ); if ( empty( $filtered_data ) ) { return null; } return self::build_snapshot_from_data( $filtered_data, $all_data['version'] ?? Constants::FORMAT_VERSION_V1 ); } public static function build_snapshot_for_elements( array $elements, ?array $global_classes_snapshot = null ): ?array { $ids = self::extract_used_variable_ids_from_elements( $elements ); if ( ! empty( $global_classes_snapshot ) ) { $ids_from_classes = self::extract_variable_ids_from_data( $global_classes_snapshot ); $ids = array_merge( $ids, $ids_from_classes ); } $ids = array_values( array_unique( $ids ) ); if ( empty( $ids ) ) { return null; } return self::build_snapshot_for_ids( $ids ); } public static function extract_variable_ids_from_data( array $data ): array { $ids = []; $variable_types = Variable_Type_Keys::get_all(); self::extract_variable_ids_recursive( $data, $ids, $variable_types ); return array_values( array_unique( $ids ) ); } public static function merge_snapshot_and_get_id_map( array $snapshot ): array { return self::make()->merge_and_get_id_map( $snapshot ); } public static function create_snapshot_as_new( array $snapshot ): array { return self::make()->create_all_as_new( $snapshot ); } protected function is_matching_item( array $existing_item, array $incoming_item ): bool { // For variables, if the type and label match, we consider them the same item // when merging, so we reuse the existing variable and ignore incoming values or extra fields. return ( $existing_item['type'] ?? '' ) === ( $incoming_item['type'] ?? '' ); } protected function get_item_prefix(): string { return Template_Library_Import_Export_Utils::VARIABLE_ID_PREFIX; } protected function get_max_items(): int { return Constants::TOTAL_VARIABLES_COUNT; } protected function can_access_repository(): bool { return null !== $this->get_repository_or_null(); } protected function load_current_data(): array { $repository = $this->get_repository_or_null(); if ( ! $repository ) { return [ 'items' => [], 'order' => [], 'watermark' => 0, 'version' => Constants::FORMAT_VERSION_V1, ]; } $collection = $repository->load(); $serialized = $collection->serialize(); return [ 'items' => $serialized['data'] ?? [], 'order' => [], 'watermark' => $serialized['watermark'] ?? 0, 'version' => $serialized['version'] ?? Constants::FORMAT_VERSION_V1, ]; } protected function parse_incoming_snapshot( array $snapshot ): ?array { $incoming_data = $snapshot['data'] ?? []; if ( empty( $incoming_data ) ) { return null; } return $snapshot; } protected function get_incoming_items( array $parsed_snapshot ): array { return $parsed_snapshot['data'] ?? []; } protected function count_current_items( array $items ): int { $count = 0; foreach ( $items as $item ) { if ( empty( $item['deleted'] ) ) { ++$count; } } return $count; } protected function save_data( array $data, array $metadata ): array { $repository = $this->get_repository_or_null(); if ( ! $repository ) { return []; } $items = $data['updated_items'] ?? []; $updated_collection = Variables_Collection::hydrate( [ 'data' => $items, 'watermark' => $metadata['watermark'] ?? 0, 'version' => $metadata['version'] ?? Constants::FORMAT_VERSION_V1, ] ); $repository->save( $updated_collection ); return [ 'variables' => [ 'data' => $items, 'watermark' => $updated_collection->watermark(), ], ]; } private static function extract_variable_ids_recursive( $data, array &$ids, array $variable_types ): void { if ( ! is_array( $data ) ) { return; } if ( isset( $data['$$type'] ) && in_array( $data['$$type'], $variable_types, true ) ) { if ( isset( $data['value'] ) && is_string( $data['value'] ) && '' !== $data['value'] ) { $ids[] = $data['value']; } } foreach ( $data as $value ) { if ( is_array( $value ) ) { self::extract_variable_ids_recursive( $value, $ids, $variable_types ); } } } private static function collect_variable_ids_from_element( array $element_data, array &$ids, array $variable_types ): void { if ( ! empty( $element_data['settings'] ) && is_array( $element_data['settings'] ) ) { self::extract_variable_ids_recursive( $element_data['settings'], $ids, $variable_types ); } if ( ! empty( $element_data['styles'] ) && is_array( $element_data['styles'] ) ) { self::extract_variable_ids_recursive( $element_data['styles'], $ids, $variable_types ); } } private function get_repository_or_null(): ?Variables_Repository { $kit = Plugin::instance()->kits_manager->get_active_kit(); if ( ! $kit ) { return null; } return new Variables_Repository( $kit ); } private static function build_snapshot_from_data( array $data, int $version ): array { return [ 'data' => $data, 'watermark' => 0, 'version' => $version, ]; } } variables/prop-types/color-variable-prop-type.php 0000644 00000000532 15252521350 0016205 0 ustar 00 <?php namespace Elementor\Modules\Variables\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Color_Variable_Prop_Type extends String_Prop_Type { public static function get_key(): string { return 'global-color-variable'; } } variables/prop-types/font-variable-prop-type.php 0000644 00000000530 15252521350 0016033 0 ustar 00 <?php namespace Elementor\Modules\Variables\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Font_Variable_Prop_Type extends String_Prop_Type { public static function get_key(): string { return 'global-font-variable'; } } variables/prop-types/size-variable-prop-type.php 0000644 00000000530 15252521350 0016037 0 ustar 00 <?php namespace Elementor\Modules\Variables\PropTypes; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Size_Variable_Prop_Type extends String_Prop_Type { public static function get_key(): string { return 'global-size-variable'; } } variables/classes/style-schema.php 0000644 00000005731 15252521350 0013266 0 ustar 00 <?php namespace Elementor\Modules\Variables\Classes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Elementor\Modules\Variables\PropTypes\Color_Variable_Prop_Type; use Elementor\Modules\Variables\PropTypes\Font_Variable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Style_Schema { public function augment( array $schema ): array { foreach ( $schema as $key => $prop_type ) { $schema[ $key ] = $this->update( $prop_type ); if ( method_exists( $prop_type, 'get_meta' ) && method_exists( $schema[ $key ], 'meta' ) ) { $meta = $schema[ $key ]->get_meta() ?? []; foreach ( $meta as $meta_key => $meta_value ) { $schema[ $key ]->meta( $meta_key, $meta_value ); } } } if ( isset( $schema['font-family'] ) ) { $schema['font-family'] = $this->update_font_family( $schema['font-family'] ); } return $schema; } private function update( $prop_type ) { if ( $prop_type instanceof Color_Prop_Type ) { return $this->update_color( $prop_type ); } if ( $prop_type instanceof Union_Prop_Type ) { return $this->update_union( $prop_type ); } if ( $prop_type instanceof Object_Prop_Type ) { return $this->update_object( $prop_type ); } if ( $prop_type instanceof Array_Prop_Type ) { return $this->update_array( $prop_type ); } return $prop_type; } private function update_font_family( $prop_type ): Union_Prop_Type { if ( $prop_type instanceof String_Prop_Type ) { return Union_Prop_Type::create_from( $prop_type ) ->add_prop_type( Font_Variable_Prop_Type::make() ); } if ( $prop_type instanceof Union_Prop_Type ) { $prop_type->add_prop_type( Font_Variable_Prop_Type::make() ); } return $prop_type; } private function update_color( Color_Prop_Type $color_prop_type ): Union_Prop_Type { return Union_Prop_Type::create_from( $color_prop_type ) ->add_prop_type( Color_Variable_Prop_Type::make() ); } private function update_array( Array_Prop_Type $array_prop_type ): Array_Prop_Type { return $array_prop_type->set_item_type( $this->update( $array_prop_type->get_item_type() ) ); } private function update_object( Object_Prop_Type $object_prop_type ): Object_Prop_Type { return $object_prop_type->set_shape( $this->augment( $object_prop_type->get_shape() ) ); } private function update_union( Union_Prop_Type $union_prop_type ): Union_Prop_Type { foreach ( $union_prop_type->get_prop_types() as $prop_type ) { $updated = $this->update( $prop_type ); if ( $updated instanceof Union_Prop_Type ) { foreach ( $updated->get_prop_types() as $updated_prop_type ) { $union_prop_type->add_prop_type( $updated_prop_type ); } } } return $union_prop_type; } } variables/classes/style-transformers.php 0000644 00000001404 15252521350 0014544 0 ustar 00 <?php namespace Elementor\Modules\Variables\Classes; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers_Registry; use Elementor\Modules\Variables\PropTypes\Color_Variable_Prop_Type; use Elementor\Modules\Variables\PropTypes\Font_Variable_Prop_Type; use Elementor\Modules\Variables\Transformers\Global_Variable_Transformer; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Style_Transformers { public function append_to( Transformers_Registry $transformers_registry ): self { $transformer = new Global_Variable_Transformer(); $transformers_registry->register( Color_Variable_Prop_Type::get_key(), $transformer ); $transformers_registry->register( Font_Variable_Prop_Type::get_key(), $transformer ); return $this; } } variables/classes/variables.php 0000644 00000001006 15252521350 0012627 0 ustar 00 <?php namespace Elementor\Modules\Variables\Classes; use Elementor\Modules\Variables\Services\Variables_Service; use Elementor\Modules\Variables\Storage\Repository as Variables_Repository; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Variables { private static $lookup = []; public static function init( Variables_Service $service ) { self::$lookup = $service->get_variables_list(); } public static function by_id( string $id ) { return self::$lookup[ $id ] ?? null; } } variables/classes/rest-api.php 0000644 00000041407 15252521350 0012414 0 ustar 00 <?php namespace Elementor\Modules\Variables\Classes; use Elementor\Modules\Variables\Storage\Exceptions\Type_Mismatch; use WP_Error; use Exception; use WP_REST_Server; use WP_REST_Request; use Elementor\Plugin; use WP_REST_Response; use Elementor\Modules\Variables\Services\Variables_Service; use Elementor\Modules\Variables\Module as Variables_Module; use Elementor\Modules\Variables\Storage\Exceptions\VariablesLimitReached; use Elementor\Modules\Variables\Storage\Exceptions\RecordNotFound; use Elementor\Modules\Variables\Storage\Exceptions\DuplicatedLabel; use Elementor\Modules\Variables\Storage\Exceptions\BatchOperationFailed; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Rest_Api { const API_NAMESPACE = 'elementor/v1'; const API_BASE = 'variables'; const HTTP_OK = 200; const HTTP_CREATED = 201; const HTTP_BAD_REQUEST = 400; const HTTP_NOT_FOUND = 404; const HTTP_SERVER_ERROR = 500; const MAX_ID_LENGTH = 64; const MAX_LABEL_LENGTH = 50; const MAX_VALUE_LENGTH = 512; private Variables_Service $service; public function __construct( Variables_Service $service ) { $this->service = $service; } public function enough_permissions_to_perform_ro_action() { return current_user_can( 'edit_posts' ); } public function enough_permissions_to_perform_rw_action() { return current_user_can( 'manage_options' ); } public function register_routes() { register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/list', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_variables' ], 'permission_callback' => [ $this, 'enough_permissions_to_perform_ro_action' ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/create', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'create_variable' ], 'permission_callback' => [ $this, 'enough_permissions_to_perform_rw_action' ], 'args' => [ 'type' => [ 'required' => true, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_type' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], 'label' => [ 'required' => true, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_label' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], 'value' => [ 'required' => true, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_value' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/update', [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [ $this, 'update_variable' ], 'permission_callback' => [ $this, 'enough_permissions_to_perform_rw_action' ], 'args' => [ 'id' => [ 'required' => true, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_id' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], 'label' => [ 'required' => true, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_label' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], 'value' => [ 'required' => true, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_value' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], 'order' => [ 'required' => false, 'type' => 'integer', 'validate_callback' => [ $this, 'is_valid_order' ], ], 'type' => [ 'required' => false, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_type' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/delete', [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [ $this, 'delete_variable' ], 'permission_callback' => [ $this, 'enough_permissions_to_perform_rw_action' ], 'args' => [ 'id' => [ 'required' => true, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_id' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/restore', [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [ $this, 'restore_variable' ], 'permission_callback' => [ $this, 'enough_permissions_to_perform_rw_action' ], 'args' => [ 'id' => [ 'required' => true, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_id' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], 'label' => [ 'required' => false, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_label' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], 'value' => [ 'required' => false, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_value' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], 'type' => [ 'required' => false, 'type' => 'string', 'validate_callback' => [ $this, 'is_valid_variable_type' ], 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ], ], ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/batch', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'process_batch' ], 'permission_callback' => [ $this, 'enough_permissions_to_perform_rw_action' ], 'args' => [ 'watermark' => [ 'required' => true, 'type' => 'integer', 'validate_callback' => [ $this, 'is_valid_watermark' ], ], 'operations' => [ 'required' => true, 'type' => 'array', 'validate_callback' => [ $this, 'is_valid_operations_array' ], ], ], ] ); } public function trim_and_sanitize_text_field( $value ) { return trim( sanitize_text_field( $value ) ); } public function is_valid_variable_id( $id ) { $id = trim( $id ); if ( empty( $id ) ) { return new WP_Error( 'invalid_variable_id_empty', __( 'ID cannot be empty', 'elementor' ) ); } if ( self::MAX_ID_LENGTH < strlen( $id ) ) { return new WP_Error( 'invalid_variable_id_length', sprintf( /* translators: %d: Maximum ID length. */ __( 'ID cannot exceed %d characters', 'elementor' ), self::MAX_ID_LENGTH ) ); } return true; } public function is_valid_variable_type( $type ) { $allowed_types = array_keys( Variables_Module::instance()->get_variable_types_registry()->all() ); return in_array( $type, $allowed_types, true ); } public function is_valid_variable_label( $label ) { $label = trim( $label ); if ( empty( $label ) ) { return new WP_Error( 'invalid_variable_label_empty', __( 'Label cannot be empty', 'elementor' ) ); } if ( self::MAX_LABEL_LENGTH < strlen( $label ) ) { return new WP_Error( 'invalid_variable_label_length', sprintf( /* translators: %d: Maximum label length. */ __( 'Label cannot exceed %d characters', 'elementor' ), self::MAX_LABEL_LENGTH ) ); } return true; } public function is_valid_order( $order ) { if ( ! is_numeric( $order ) || $order < 0 ) { return new WP_Error( 'invalid_order', __( 'Order must be a non-negative integer', 'elementor' ) ); } return true; } public function is_valid_variable_value( $value ) { $value = trim( $value ); if ( empty( $value ) ) { return new WP_Error( 'invalid_variable_value_empty', __( 'Value cannot be empty', 'elementor' ) ); } if ( self::MAX_VALUE_LENGTH < strlen( $value ) ) { return new WP_Error( 'invalid_variable_value_length', sprintf( /* translators: %d: Maximum value length. */ __( 'Value cannot exceed %d characters', 'elementor' ), self::MAX_VALUE_LENGTH ) ); } return true; } public function create_variable( WP_REST_Request $request ) { try { return $this->create_new_variable( $request ); } catch ( Exception $e ) { return $this->error_response( $e ); } } protected function clear_cache() { Plugin::$instance->files_manager->clear_cache(); } private function create_new_variable( WP_REST_Request $request ) { $type = $request->get_param( 'type' ); $label = $request->get_param( 'label' ); $value = $request->get_param( 'value' ); $result = $this->service->create( [ 'type' => $type, 'label' => $label, 'value' => $value, ] ); $this->clear_cache(); return $this->success_response( [ 'variable' => $result['variable'], 'watermark' => $result['watermark'], ], self::HTTP_CREATED ); } public function update_variable( WP_REST_Request $request ) { try { return $this->update_existing_variable( $request ); } catch ( Exception $e ) { return $this->error_response( $e ); } } private function update_existing_variable( WP_REST_Request $request ) { $id = $request->get_param( 'id' ); $label = $request->get_param( 'label' ); $value = $request->get_param( 'value' ); $order = $request->get_param( 'order' ); $type = $request->get_param( 'type' ); $update_data = [ 'label' => $label, 'value' => $value, ]; if ( $type ) { $update_data['type'] = $type; } if ( null !== $order ) { $update_data['order'] = $order; } $result = $this->service->update( $id, $update_data ); $this->clear_cache(); return $this->success_response( [ 'variable' => $result['variable'], 'watermark' => $result['watermark'], ] ); } public function delete_variable( WP_REST_Request $request ) { try { return $this->delete_existing_variable( $request ); } catch ( Exception $e ) { return $this->error_response( $e ); } } private function delete_existing_variable( WP_REST_Request $request ) { $id = $request->get_param( 'id' ); $result = $this->service->delete( $id ); $this->clear_cache(); return $this->success_response( [ 'variable' => $result['variable'], 'watermark' => $result['watermark'], ] ); } public function restore_variable( WP_REST_Request $request ) { try { return $this->restore_existing_variable( $request ); } catch ( Exception $e ) { return $this->error_response( $e ); } } private function restore_existing_variable( WP_REST_Request $request ) { $id = $request->get_param( 'id' ); $overrides = []; $label = $request->get_param( 'label' ); if ( $label ) { $overrides['label'] = $label; } $value = $request->get_param( 'value' ); if ( $value ) { $overrides['value'] = $value; } $type = $request->get_param( 'type' ); if ( $type ) { $overrides['type'] = $type; } $result = $this->service->restore( $id, $overrides ); $this->clear_cache(); return $this->success_response( [ 'variable' => $result['variable'], 'watermark' => $result['watermark'], ] ); } public function get_variables() { try { return $this->list_of_variables(); } catch ( Exception $e ) { return $this->error_response( $e ); } } private function list_of_variables() { $db_record = $this->service->load(); return $this->success_response( [ 'variables' => $db_record['data'] ?? [], 'total' => count( $db_record['data'] ), 'watermark' => $db_record['watermark'], ] ); } private function success_response( $payload, $status_code = null ) { return new WP_REST_Response( [ 'success' => true, 'data' => $payload, ], $status_code ?? self::HTTP_OK ); } private function error_response( Exception $e ) { if ( $e instanceof VariablesLimitReached ) { return $this->prepare_error_response( self::HTTP_BAD_REQUEST, 'invalid_variable_limit_reached', __( 'Reached the maximum number of variables', 'elementor' ) ); } if ( $e instanceof DuplicatedLabel ) { return $this->prepare_error_response( self::HTTP_BAD_REQUEST, 'duplicated_label', __( 'Variable label already exists', 'elementor' ) ); } if ( $e instanceof RecordNotFound ) { return $this->prepare_error_response( self::HTTP_NOT_FOUND, 'variable_not_found', __( 'Variable not found', 'elementor' ) ); } if ( $e instanceof Type_Mismatch ) { return $this->prepare_error_response( self::HTTP_BAD_REQUEST, 'type_mismatch', $e->getMessage() ); } return $this->prepare_error_response( self::HTTP_SERVER_ERROR, 'unexpected_server_error', __( 'Unexpected server error', 'elementor' ) ); } private function prepare_error_response( $status_code, $error, $message ) { return new WP_REST_Response( [ 'code' => $error, 'message' => $message, 'data' => [ 'status' => $status_code, ], ], $status_code ); } public function is_valid_watermark( $watermark ) { if ( ! is_numeric( $watermark ) || $watermark < 0 ) { return new WP_Error( 'invalid_watermark', __( 'Watermark must be a non-negative integer', 'elementor' ) ); } return true; } public function is_valid_operations_array( $operations ) { if ( ! is_array( $operations ) || empty( $operations ) ) { return new WP_Error( 'invalid_operations_empty', __( 'Operations array cannot be empty', 'elementor' ) ); } foreach ( $operations as $index => $operation ) { if ( ! is_array( $operation ) || ! isset( $operation['type'] ) ) { $sanitized_index = absint( $index ); return new WP_Error( 'invalid_operation_structure', sprintf( /* translators: %d: operation index */ __( 'Invalid operation structure at index %d', 'elementor' ), $sanitized_index ) ); } $allowed_types = [ 'create', 'update', 'delete', 'restore', 'reorder' ]; if ( ! in_array( $operation['type'], $allowed_types, true ) ) { $sanitized_index = absint( $index ); return new WP_Error( 'invalid_operation_type', sprintf( /* translators: %d: operation index */ __( 'Invalid operation type at index %d', 'elementor' ), $sanitized_index ) ); } } return true; } public function process_batch( WP_REST_Request $request ) { try { return $this->process_batch_operations( $request ); } catch ( Exception $e ) { return $this->batch_error_response( $e ); } } private function process_batch_operations( WP_REST_Request $request ) { $operations = $request->get_param( 'operations' ); $result = $this->service->process_batch( $operations ); $this->clear_cache(); return $this->success_response( $result ); } private function batch_error_response( Exception $e ) { if ( $e instanceof BatchOperationFailed ) { $error_details = $e->getErrorDetails(); $batch_error_context = $this->determine_batch_error_context( $error_details ); return new WP_REST_Response( [ 'success' => false, 'code' => $batch_error_context['code'], 'message' => $batch_error_context['message'], 'data' => $batch_error_context['filtered_errors'], ], self::HTTP_BAD_REQUEST ); } return $this->error_response( $e ); } private function determine_batch_error_context( array $error_details ) { $error_config = [ 'invalid_variable_limit_reached' => [ 'batch_code' => 'batch_variables_limit_reached', 'batch_message' => __( 'Batch operation failed: Reached the maximum number of variables', 'elementor' ), 'status' => self::HTTP_BAD_REQUEST, 'message' => __( 'Reached the maximum number of variables', 'elementor' ), ], 'duplicated_label' => [ 'batch_code' => 'batch_duplicated_label', 'batch_message' => __( 'Batch operation failed: Variable labels already exist', 'elementor' ), 'status' => self::HTTP_BAD_REQUEST, 'message' => __( 'Variable label already exists', 'elementor' ), ], 'variable_not_found' => [ 'batch_code' => 'batch_variables_not_found', 'batch_message' => __( 'Batch operation failed: Variables not found', 'elementor' ), 'status' => self::HTTP_NOT_FOUND, 'message' => __( 'Variable not found', 'elementor' ), ], ]; $grouped_errors = []; foreach ( $error_details as $id => $error_detail ) { $error_code = $error_detail['code'] ?? ''; if ( isset( $error_config[ $error_code ] ) ) { $config = $error_config[ $error_code ]; $grouped_errors[ $error_code ][ $id ] = [ 'status' => $config['status'], 'message' => $config['message'], ]; } else { $grouped_errors['unknown'][ $id ] = [ 'status' => self::HTTP_SERVER_ERROR, 'message' => $error_detail['message'] ?? __( 'Unexpected error', 'elementor' ), ]; } } foreach ( $error_config as $error_code => $config ) { if ( ! empty( $grouped_errors[ $error_code ] ) ) { return [ 'code' => $config['batch_code'], 'message' => $config['batch_message'], 'filtered_errors' => $grouped_errors[ $error_code ], ]; } } return [ 'code' => 'batch_operation_failed', 'message' => __( 'Batch operation failed', 'elementor' ), 'filtered_errors' => $grouped_errors['unknown'] ?? [], ]; } } variables/classes/css-renderer.php 0000644 00000003370 15252521350 0013261 0 ustar 00 <?php namespace Elementor\Modules\Variables\Classes; use Elementor\Modules\Variables\Services\Variables_Service; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class CSS_Renderer { private Variables_Service $service; public function __construct( Variables_Service $service ) { $this->service = $service; } private function global_variables(): array { return $this->service->get_variables_list(); } public function raw_css(): string { $list_of_variables = $this->global_variables(); if ( empty( $list_of_variables ) ) { return ''; } $css_entries = $this->css_entries_for( $list_of_variables ); if ( empty( $css_entries ) ) { return ''; } return $this->wrap_with_root( $css_entries ); } private function css_entries_for( array $list_of_variables ): array { $entries = []; foreach ( $list_of_variables as $variable_id => $variable ) { $entry = $this->build_css_variable_entry( $variable_id, $variable ); if ( empty( $entry ) ) { continue; } $entries[] = $entry; } return $entries; } private function build_css_variable_entry( string $id, array $variable ): ?string { $variable_name = sanitize_text_field( $id ); if ( ! array_key_exists( 'deleted_at', $variable ) ) { $variable_name = sanitize_text_field( $variable['label'] ?? '' ); } $value = sanitize_text_field( $variable['value'] ?? '' ); if ( empty( $value ) || empty( $variable_name ) ) { return null; } $entry = "--{$variable_name}:{$value};"; $additional = apply_filters( 'elementor/variables/css_entry_additional', '', $variable, $id ); return $entry . $additional; } private function wrap_with_root( array $css_entries ): string { return ':root { ' . implode( ' ', $css_entries ) . ' }'; } } variables/classes/fonts.php 0000644 00000001755 15252521350 0012023 0 ustar 00 <?php namespace Elementor\Modules\Variables\Classes; use Elementor\Modules\Variables\Services\Variables_Service; use Elementor\Plugin; use Elementor\Core\Files\CSS\Post as Post_CSS; use Elementor\Modules\Variables\PropTypes\Font_Variable_Prop_Type; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Fonts { private Variables_Service $service; public function __construct( Variables_Service $service ) { $this->service = $service; } public function append_to( Post_CSS $post_css ) { if ( ! Plugin::$instance->kits_manager->is_kit( $post_css->get_post_id() ) ) { return; } $list_of_variables = $this->service->get_variables_list(); foreach ( $list_of_variables as $variable ) { if ( Font_Variable_Prop_Type::get_key() !== $variable['type'] ) { continue; } $font_family = sanitize_text_field( $variable['value'] ?? '' ); if ( empty( $font_family ) ) { continue; } $post_css->add_font( $font_family ); } return $this; } } variables/classes/variable-types-registry.php 0000644 00000001060 15252521350 0015454 0 ustar 00 <?php namespace Elementor\Modules\Variables\Classes; use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type; use InvalidArgumentException; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Variable_Types_Registry { private array $types = []; public function register( string $key, Transformable_Prop_Type $prop_type ): void { $this->types[ $key ] = $prop_type; } public function get( $key ) { return $this->types[ $key ] ?? null; } public function all(): array { return $this->types; } } variables/classes/size-style-schema.php 0000644 00000006341 15252521350 0014234 0 ustar 00 <?php namespace Elementor\Modules\Variables\Classes; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Array_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Grid_Track_Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; use Elementor\Modules\Variables\PropTypes\Size_Variable_Prop_Type; use Elementor\Utils as ElementorUtils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Size_Style_Schema { private const PRO_VERSION_FOR_GRID_TRACK_VARIABLES = '4.2'; private $units_to_skip = []; public function __construct() { $this->units_to_skip = [ ...Size_Constants::angle(), ...Size_Constants::time(), ]; } private function skip_by_units( Size_Prop_Type $size_prop_type ) { $settings = $size_prop_type->get_settings(); if ( ! array_key_exists( 'available_units', $settings ) ) { return false; } $available_units = $settings['available_units']; return count( array_intersect( $available_units, $this->units_to_skip ) ) > 0; } public function augment( array $schema ): array { foreach ( $schema as $css_property => $prop_type ) { $schema[ $css_property ] = $this->update( $prop_type ); } return $schema; } private function update( $prop_type ) { if ( $prop_type instanceof Size_Prop_Type ) { return $this->update_size( $prop_type ); } if ( $prop_type instanceof Union_Prop_Type ) { return $this->update_union( $prop_type ); } if ( $prop_type instanceof Object_Prop_Type ) { return $this->update_object( $prop_type ); } if ( $prop_type instanceof Array_Prop_Type ) { return $this->update_array( $prop_type ); } return $prop_type; } private function update_size( Size_Prop_Type $size_prop_type ) { if ( $this->skip_by_units( $size_prop_type ) ) { return $size_prop_type; } if ( $size_prop_type instanceof Grid_Track_Size_Prop_Type && ! $this->is_grid_track_variables_supported_by_pro() ) { return $size_prop_type; } return Union_Prop_Type::create_from( $size_prop_type ) ->add_prop_type( Size_Variable_Prop_Type::make() ); } private function is_grid_track_variables_supported_by_pro(): bool { return ElementorUtils::has_pro() && version_compare( ELEMENTOR_PRO_VERSION, self::PRO_VERSION_FOR_GRID_TRACK_VARIABLES, '>=' ); } private function update_array( Array_Prop_Type $array_prop_type ): Array_Prop_Type { return $array_prop_type->set_item_type( $this->update( $array_prop_type->get_item_type() ) ); } private function update_object( Object_Prop_Type $object_prop_type ): Object_Prop_Type { return $object_prop_type->set_shape( $this->augment( $object_prop_type->get_shape() ) ); } private function update_union( Union_Prop_Type $union_prop_type ): Union_Prop_Type { foreach ( $union_prop_type->get_prop_types() as $prop_type ) { $updated = $this->update( $prop_type ); if ( $updated instanceof Union_Prop_Type ) { foreach ( $updated->get_prop_types() as $updated_prop_type ) { $union_prop_type->add_prop_type( $updated_prop_type ); } } } return $union_prop_type; } } variables/module.php 0000644 00000006773 15252521350 0010527 0 ustar 00 <?php namespace Elementor\Modules\Variables; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as ExperimentsManager; use Elementor\Modules\AtomicWidgets\Module as AtomicWidgetsModule; use Elementor\Modules\Variables\Classes\Variable_Types_Registry; use Elementor\Modules\Variables\ImportExportCustomization\Import_Export_Customization; use Elementor\Modules\Variables\PropTypes\Color_Variable_Prop_Type; use Elementor\Modules\Variables\PropTypes\Font_Variable_Prop_Type; use Elementor\Modules\Variables\PropTypes\Size_Variable_Prop_Type; use Elementor\Modules\Variables\Storage\Constants; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Module extends BaseModule { const MODULE_NAME = 'e-variables'; const EXPERIMENT_NAME = 'e_variables'; const EXPERIMENT_MANAGER_NAME = 'e_variables_manager'; private Variable_Types_Registry $variable_types_registry; public function get_name() { return self::MODULE_NAME; } public static function get_experimental_data(): array { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Variables', 'elementor' ), 'description' => esc_html__( 'Enable variables. (For this feature to work - Atomic Widgets must be active)', 'elementor' ), 'hidden' => true, 'default' => ExperimentsManager::STATE_ACTIVE, 'release_status' => ExperimentsManager::RELEASE_STATUS_ALPHA, ]; } private function hooks() { return new Hooks(); } public function __construct() { parent::__construct(); if ( ! $this->is_experiment_active() ) { return; } $this->register_features(); $this->hooks()->register(); ( new Import_Export_Customization() )->register_hooks(); add_action( 'init', [ $this, 'init_variable_types_registry' ] ); add_filter( 'elementor/kit/meta_to_preserve_on_kit_import', [ $this, 'add_meta_to_preserve_on_kit_import' ] ); add_action( 'elementor/editor/before_enqueue_scripts', fn () => $this->enqueue_editor_scripts() ); } private function register_features() { Plugin::$instance->experiments->add_feature([ 'name' => self::EXPERIMENT_MANAGER_NAME, 'title' => esc_html__( 'Variables Manager', 'elementor' ), 'description' => esc_html__( 'Enable variables manager. (For this feature to work - Variables must be active)', 'elementor' ), 'hidden' => true, 'default' => ExperimentsManager::STATE_ACTIVE, 'release_status' => ExperimentsManager::RELEASE_STATUS_ALPHA, ]); } private function is_experiment_active(): bool { return Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME ) && Plugin::$instance->experiments->is_feature_active( AtomicWidgetsModule::EXPERIMENT_NAME ); } public function init_variable_types_registry(): void { $this->variable_types_registry = new Variable_Types_Registry(); do_action( 'elementor/variables/register', $this->variable_types_registry ); } public function get_variable_types_registry(): Variable_Types_Registry { return $this->variable_types_registry; } private function get_quota_config(): array { return [ Color_Variable_Prop_Type::get_key() => 100000, Font_Variable_Prop_Type::get_key() => 100000, ]; } public function enqueue_editor_scripts() { wp_add_inline_script( 'elementor-common', 'window.ElementorVariablesQuotaConfig = ' . wp_json_encode( $this->get_quota_config() ) . ';', 'before' ); } public function add_meta_to_preserve_on_kit_import( array $meta_keys ): array { return array_merge( $meta_keys, [ Constants::VARIABLES_META_KEY, ] ); } } variables/transformers/global-variable-transformer.php 0000644 00000002034 15252521350 0017334 0 ustar 00 <?php namespace Elementor\Modules\Variables\Transformers; use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context; use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base; use Elementor\Modules\AtomicWidgets\Styles\Grid_Track_Renderer; use Elementor\Modules\Variables\Classes\Variables; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Global_Variable_Transformer extends Transformer_Base { public function transform( $value, Props_Resolver_Context $context ) { $variable = Variables::by_id( $value ); if ( ! $variable ) { return null; } if ( Grid_Track_Renderer::is_grid_track_property( $context->get_key() ) ) { $count = (int) trim( $variable['value'] ?? '' ); return Grid_Track_Renderer::format_repeat( $count ); } if ( array_key_exists( 'deleted', $variable ) && $variable['deleted'] ) { return "var(--{$value})"; } $identifier = trim( $variable['label'] ?? '' ); if ( '' === $identifier ) { return null; } return "var(--{$identifier})"; } } variables/hooks.php 0000644 00000011336 15252521350 0010354 0 ustar 00 <?php namespace Elementor\Modules\Variables; use Elementor\Modules\Variables\Adapters\Prop_Type_Adapter; use Elementor\Modules\Variables\Classes\Variable_Types_Registry; use Elementor\Modules\Variables\PropTypes\Color_Variable_Prop_Type; use Elementor\Modules\Variables\PropTypes\Font_Variable_Prop_Type; use Elementor\Modules\Variables\PropTypes\Size_Variable_Prop_Type; use Elementor\Modules\Variables\Services\Batch_Operations\Batch_Processor; use Elementor\Modules\Variables\Services\Variables_Service; use Elementor\Modules\Variables\Storage\Variables_Repository; use Elementor\Modules\Variables\Utils\Template_Library_Variables; use Elementor\Plugin; use Elementor\Core\Files\CSS\Post as Post_CSS; use Elementor\Modules\Variables\Classes\CSS_Renderer as Variables_CSS_Renderer; use Elementor\Modules\Variables\Classes\Fonts; use Elementor\Modules\Variables\Classes\Rest_Api as Variables_API; use Elementor\Modules\Variables\Classes\Style_Schema; use Elementor\Modules\Variables\Classes\Size_Style_Schema; use Elementor\Modules\Variables\Classes\Style_Transformers; use Elementor\Modules\Variables\Classes\Variables; if ( ! defined( 'ABSPATH' ) ) { exit; } class Hooks { const PACKAGES = [ 'editor-variables', ]; public function register() { $this->register_styles_transformers() ->register_css_renderer() ->register_packages() ->register_fonts() ->register_api_endpoints() ->filter_for_style_schema() ->register_variable_types() ->register_template_library_import(); return $this; } private function register_variable_types() { add_action( 'elementor/variables/register', function ( Variable_Types_Registry $registry ) { $registry->register( Color_Variable_Prop_Type::get_key(), new Color_Variable_Prop_Type() ); $registry->register( Font_Variable_Prop_Type::get_key(), new Font_Variable_Prop_Type() ); $registry->register( Prop_Type_Adapter::GLOBAL_CUSTOM_SIZE_VARIABLE_KEY, new Size_Variable_Prop_Type() ); $registry->register( Size_Variable_Prop_Type::get_key(), new Size_Variable_Prop_Type() ); } ); return $this; } private function register_packages() { add_filter( 'elementor/editor/v2/packages', function ( $packages ) { return array_merge( $packages, self::PACKAGES ); } ); return $this; } private function register_styles_transformers() { add_action( 'elementor/atomic-widgets/styles/transformers/register', function ( $registry ) { Variables::init( $this->variables_service() ); ( new Style_Transformers() )->append_to( $registry ); } ); return $this; } private function filter_for_style_schema() { add_filter( 'elementor/atomic-widgets/styles/schema', function ( array $schema ) { return ( new Style_Schema() )->augment( $schema ); } ); add_filter( 'elementor/atomic-widgets/styles/schema', function ( array $schema ) { return ( new Size_Style_Schema() )->augment( $schema ); } ); return $this; } private function css_renderer() { return new Variables_CSS_Renderer( $this->variables_service() ); } private function register_css_renderer() { add_action( 'elementor/css-file/post/parse', function ( Post_CSS $post_css ) { if ( ! Plugin::$instance->kits_manager->is_kit( $post_css->get_post_id() ) ) { return; } $post_css->get_stylesheet()->add_raw_css( $this->css_renderer()->raw_css() ); } ); return $this; } private function fonts() { return new Fonts( $this->variables_service() ); } private function register_fonts() { add_action( 'elementor/css-file/post/parse', function ( $post_css ) { $this->fonts()->append_to( $post_css ); } ); return $this; } private function rest_api() { return new Variables_API( $this->variables_service() ); } private function register_api_endpoints() { add_action( 'rest_api_init', function () { $this->rest_api()->register_routes(); } ); return $this; } private function variables_service() { $repository = new Variables_Repository( Plugin::$instance->kits_manager->get_active_kit() ); return new Variables_Service( $repository, new Batch_Processor() ); } private function register_template_library_import() { add_filter( 'elementor/template_library/export/build_snapshots', [ Template_Library_Variables::class, 'add_variables_snapshot' ], 20, 4 ); add_filter( 'elementor/template_library/get_data/extract_snapshots', [ Template_Library_Variables::class, 'extract_variables_from_data' ], 10, 3 ); add_filter( 'elementor/template_library/import/process_content', [ Template_Library_Variables::class, 'process_variables_import' ], 10, 3 ); add_filter( 'elementor/global_classes/import/transform_snapshot', [ Template_Library_Variables::class, 'transform_variables_in_classes_snapshot' ], 10, 4 ); return $this; } } variables/services/variables-service.php 0000644 00000013502 15252521350 0014457 0 ustar 00 <?php namespace Elementor\Modules\Variables\Services; use Elementor\Modules\Variables\Services\Batch_Operations\Batch_Error_Formatter; use Elementor\Modules\Variables\Services\Batch_Operations\Batch_Processor; use Elementor\Modules\Variables\Storage\Entities\Variable; use Elementor\Modules\Variables\Storage\Exceptions\BatchOperationFailed; use Elementor\Modules\Variables\Storage\Variables_Repository; use Elementor\Modules\Variables\Storage\Exceptions\FatalError; use Elementor\Modules\Variables\PropTypes\Size_Variable_Prop_Type; use Elementor\Utils as ElementorUtils; class Variables_Service { private Variables_Repository $repo; private Batch_Processor $batch_processor; public function __construct( Variables_Repository $repository, Batch_Processor $batch_processor ) { $this->repo = $repository; $this->batch_processor = $batch_processor; } public function get_variables_list(): array { return $this->load()['data']; } public function find_by_label_or_id( string $needle ): ?array { $needle = trim( $needle ); $needle = ltrim( $needle, '-' ); if ( '' === $needle ) { return null; } $variables = $this->get_variables_list(); if ( isset( $variables[ $needle ] ) ) { $variable = $variables[ $needle ]; if ( ! empty( $variable['deleted'] ) ) { return null; } return array_merge( [ 'id' => $needle ], $variable ); } foreach ( $variables as $id => $variable ) { if ( ! empty( $variable['deleted'] ) ) { continue; } if ( strcasecmp( $variable['label'] ?? '', $needle ) === 0 ) { return array_merge( [ 'id' => $id ], $variable ); } } return null; } public function load() { $collection = $this->repo->load()->serialize( true ); foreach ( $collection['data'] as $id => $variable ) { if ( ! ElementorUtils::has_pro() && Size_Variable_Prop_Type::get_key() === $variable['type'] ) { unset( $collection['data'][ $id ] ); } } return $collection; } /** * @throws BatchOperationFailed Thrown when one of the operations fails. * @throws FatalError Failed to save after batch. */ public function process_batch( array $operations ) { $collection = $this->repo->load(); $results = []; $errors = []; $error_formatter = new Batch_Error_Formatter(); foreach ( $operations as $index => $operation ) { try { $results[] = $this->batch_processor->apply_operation( $collection, $operation ); } catch ( \Exception $e ) { $errors[ $this->batch_processor->operation_id( $operation, $index ) ] = [ 'status' => $error_formatter->status_for( $e ), 'code' => $error_formatter->error_code_for( $e ), 'message' => $e->getMessage(), ]; } } if ( ! empty( $errors ) ) { // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped throw new BatchOperationFailed( 'Batch failed', $errors ); } $watermark = $this->repo->save( $collection ); if ( false === $watermark ) { throw new FatalError( 'Failed to save batch operations' ); } return [ 'success' => true, 'results' => $results, 'watermark' => $watermark, ]; } /** * @throws FatalError If variable create fails or validation errors occur. */ public function create( array $data ): array { $collection = $this->repo->load(); $collection->assert_limit_not_reached(); $collection->assert_label_is_unique( $data['label'] ); $id = $collection->next_id(); $data['id'] = $id; // TODO: we need to look into this maybe we dont need order to be sent from client. Just implemented as it was if ( ! isset( $data['order'] ) ) { $data['order'] = $collection->get_next_order(); } $variable = Variable::from_array( $data ); $variable->validate(); $collection->add_variable( $variable ); $watermark = $this->repo->save( $collection ); if ( false === $watermark ) { throw new FatalError( 'Failed to create variable' ); } return [ 'variable' => array_merge( [ 'id' => $id ], $variable->to_array() ), 'watermark' => $collection->watermark(), ]; } /** * @throws FatalError If variable update fails. */ public function update( string $id, array $data ): array { $collection = $this->repo->load(); $variable = $collection->find_or_fail( $id ); if ( isset( $data['label'] ) ) { $collection->assert_label_is_unique( $data['label'], $id ); } $variable->apply_changes( $data ); $variable->validate(); $watermark = $this->repo->save( $collection ); if ( false === $watermark ) { throw new FatalError( 'Failed to update variable' ); } return [ 'variable' => array_merge( [ 'id' => $id ], $variable->to_array() ), 'watermark' => $watermark, ]; } /** * @throws FatalError If variable delete fails. */ public function delete( string $id ) { $collection = $this->repo->load(); $variable = $collection->find_or_fail( $id ); $variable->soft_delete(); $watermark = $this->repo->save( $collection ); if ( false === $watermark ) { throw new FatalError( 'Failed to delete variable' ); } return [ 'watermark' => $watermark, 'variable' => array_merge( [ 'id' => $id, 'deleted' => true, ], $variable->to_array() ), ]; } /** * @throws FatalError If variable restore fails. */ public function restore( string $id, $overrides = [] ) { $collection = $this->repo->load(); $variable = $collection->find_or_fail( $id ); $label = $variable->label(); if ( isset( $overrides['label'] ) ) { $label = $overrides['label']; } $collection->assert_limit_not_reached(); $collection->assert_label_is_unique( $label, $variable->id() ); $variable->apply_changes( $overrides ); $variable->validate(); $variable->restore(); $watermark = $this->repo->save( $collection ); if ( false === $watermark ) { throw new FatalError( 'Failed to delete variable' ); } return [ 'variable' => array_merge( [ 'id' => $id ], $variable->to_array() ), 'watermark' => $watermark, ]; } } variables/services/batch-operations/batch-error-formatter.php 0000644 00000002036 15252521350 0020524 0 ustar 00 <?php namespace Elementor\Modules\Variables\Services\Batch_Operations; use Exception; use Elementor\Modules\Variables\Storage\Exceptions\DuplicatedLabel; use Elementor\Modules\Variables\Storage\Exceptions\RecordNotFound; use Elementor\Modules\Variables\Storage\Exceptions\VariablesLimitReached; class Batch_Error_Formatter { private const ERROR_MAP = [ RecordNotFound::class => [ 'code' => 'variable_not_found', 'status' => 404, ], DuplicatedLabel::class => [ 'code' => 'duplicated_label', 'status' => 400, ], VariablesLimitReached::class => [ 'code' => 'invalid_variable_limit_reached', 'status' => 400, ], ]; public function status_for( Exception $e ): int { foreach ( self::ERROR_MAP as $class => $map ) { if ( $e instanceof $class ) { return $map['status']; } } return 500; } public function error_code_for( Exception $e ): string { foreach ( self::ERROR_MAP as $class => $map ) { if ( $e instanceof $class ) { return $map['code']; } } return 'unexpected_server_error'; } } variables/services/batch-operations/batch-processor.php 0000644 00000006114 15252521350 0017412 0 ustar 00 <?php namespace Elementor\Modules\Variables\Services\Batch_Operations; use Elementor\Modules\Variables\Storage\Entities\Variable; use Elementor\Modules\Variables\Storage\Variables_Collection; use Elementor\Modules\Variables\Storage\Exceptions\BatchOperationFailed; class Batch_Processor { private const OPERATION_MAP = [ 'create' => 'op_create', 'update' => 'op_update', 'delete' => 'op_delete', 'restore' => 'op_restore', ]; /** * @throws BatchOperationFailed Invalid operation type. */ public function apply_operation( Variables_Collection $collection, array $operation ): array { $type = $operation['type']; if ( ! isset( self::OPERATION_MAP[ $type ] ) ) { throw new BatchOperationFailed( 'Invalid operation type: ' . esc_html( $type ), [] ); } $method = self::OPERATION_MAP[ $type ]; return $this->$method( $collection, $operation ); } private function op_create( Variables_Collection $collection, array $operation ): array { $data = $operation['variable']; $temp_id = $data['id'] ?? null; $data['id'] = $collection->next_id(); $collection->assert_limit_not_reached(); $collection->assert_label_is_unique( $data['label'] ); if ( ! isset( $data['order'] ) ) { $data['order'] = $collection->get_next_order(); } $variable = Variable::create_new( $data ); $variable->validate(); $collection->add_variable( $variable ); // TODO: do we need to return all this payload maybe return what the clients want return [ 'type' => 'create', 'id' => $data['id'], 'temp_id' => $temp_id, 'variable' => $variable->to_array(), ]; } private function op_update( Variables_Collection $collection, array $operation ): array { $id = $operation['id']; $data = $operation['variable']; $variable = $collection->find_or_fail( $id ); if ( isset( $data['label'] ) ) { $collection->assert_label_is_unique( $data['label'], $id ); } $variable->apply_changes( $data ); return [ 'type' => 'update', 'id' => $id, 'variable' => $variable->to_array(), ]; } private function op_delete( Variables_Collection $collection, array $operation ): array { $id = $operation['id']; $variable = $collection->find_or_fail( $id ); $variable->soft_delete(); return [ 'type' => 'delete', 'id' => $id, 'deleted' => true, ]; } private function op_restore( Variables_Collection $collection, array $operation ): array { $id = $operation['id']; $variable = $collection->find_or_fail( $id ); $collection->assert_limit_not_reached(); if ( isset( $operation['label'] ) ) { $collection->assert_label_is_unique( $operation['label'], $id ); $variable->validate(); } $variable->apply_changes( $operation ); $variable->restore(); return [ 'type' => 'restore', 'id' => $id, 'variable' => $variable->to_array(), ]; } public function operation_id( array $operation, int $index ): string { if ( 'create' === $operation['type'] && isset( $operation['variable']['id'] ) ) { return $operation['variable']['id']; } if ( isset( $operation['id'] ) ) { return $operation['id']; } return "operation_{$index}"; } } variables/storage/exceptions/duplicated-label.php 0000644 00000000305 15252521350 0016243 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class DuplicatedLabel extends Exception {} variables/storage/exceptions/batch-operation-failed.php 0000644 00000000707 15252521350 0017357 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage\Exceptions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class BatchOperationFailed extends \Exception { private array $error_details; public function __construct( string $message, array $error_details = [] ) { parent::__construct( $message ); $this->error_details = $error_details; } public function getErrorDetails(): array { return $this->error_details; } } variables/storage/exceptions/fatal-error.php 0000644 00000000300 15252521350 0015261 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class FatalError extends Exception {} variables/storage/exceptions/record-not-found.php 0000644 00000000304 15252521350 0016234 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class RecordNotFound extends Exception {} variables/storage/exceptions/type-mismatch.php 0000644 00000000303 15252521350 0015632 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Type_Mismatch extends Exception {} variables/storage/exceptions/invalid-variable.php 0000644 00000000305 15252521350 0016261 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class InvalidVariable extends Exception {} variables/storage/exceptions/variables-limit-reached.php 0000644 00000000313 15252521350 0017524 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class VariablesLimitReached extends Exception {} variables/storage/entities/variable.php 0000644 00000007555 15252521350 0014316 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage\Entities; use Elementor\Modules\Variables\Adapters\Prop_Type_Adapter; use Elementor\Modules\Variables\PropTypes\Size_Variable_Prop_Type; use Elementor\Modules\Variables\Storage\Exceptions\Type_Mismatch; use Elementor\Modules\Variables\Storage\Exceptions\InvalidVariable; use InvalidArgumentException; class Variable { private array $data; private function __construct( array $data ) { $this->data = $data; } public static function create_new( array $data ): self { $now = gmdate( 'Y-m-d H:i:s' ); $data['created_at'] = $now; $data['updated_at'] = $now; return self::from_array( $data ); } public static function from_array( array $data ): self { $required = [ 'id', 'type', 'label', 'value' ]; foreach ( $required as $key ) { if ( ! array_key_exists( $key, $data ) ) { throw new InvalidArgumentException( sprintf( "Missing required field '%s' in %s::from_array()", esc_html( $key ), self::class ) ); } } return new self( $data ); } public function soft_delete(): void { $this->data['deleted_at'] = $this->now(); } public function restore(): void { unset( $this->data['deleted_at'] ); // TODO to be removed if client is no longer need this unset( $this->data['deleted'] ); $this->data['updated_at'] = $this->now(); } private function now() { return gmdate( 'Y-m-d H:i:s' ); } public function to_array(): array { return array_diff_key( $this->data, array_flip( [ 'id' ] ) ); } public function id(): string { return $this->data['id']; } public function label(): string { return $this->data['label']; } public function order(): int { return $this->data['order']; } public function value() { return $this->data['value']; } public function set_value( $value ) { $this->data['value'] = $value; } public function type() { return $this->data['type']; } public function set_type( $type ) { $this->data['type'] = $type; } public function has_order(): int { return isset( $this->data['order'] ); } public function is_deleted(): bool { return isset( $this->data['deleted_at'] ); } /** * @throws Type_Mismatch If a type that is not allowed to be changed is passed. */ private function maybe_apply_type( array $data ) { if ( ! array_key_exists( 'type', $data ) ) { return false; } $current_type = $this->type(); $target_type = $data['type']; if ( $current_type === $target_type ) { return false; } $custom_size_prop_type = Prop_Type_Adapter::GLOBAL_CUSTOM_SIZE_VARIABLE_KEY; $size_prop_type = Size_Variable_Prop_Type::get_key(); $allowed_types = [ $custom_size_prop_type, $size_prop_type ]; $is_valid_transition = in_array( $current_type, $allowed_types, true ) && in_array( $target_type, $allowed_types, true ); if ( ! $is_valid_transition ) { throw new Type_Mismatch( 'Type change is forbidden' ); } $this->set_type( $data['type'] ); return true; } /** * @throws InvalidVariable If the variable is not valid. */ public function apply_changes( array $data ): void { $this->validate(); $allowed_fields = [ 'label', 'value', 'order', 'type', 'sync_to_v3' ]; $has_changes = $this->maybe_apply_type( $data ); foreach ( $allowed_fields as $field ) { if ( isset( $data[ $field ] ) ) { $this->data[ $field ] = $data[ $field ]; $has_changes = true; } } if ( $has_changes ) { $this->data['updated_at'] = $this->now(); } } /** * @return bool True if the variable is valid, throws an exception otherwise. * @throws InvalidVariable If the variable is not valid. */ public function validate(): bool { if ( strpos( $this->label(), ' ' ) !== false ) { throw new InvalidVariable( 'Label cannot contain spaces' ); } if ( strlen( $this->label() ) > 50 ) { throw new InvalidVariable( 'Label cannot be longer than 50 characters' ); } return true; } } variables/storage/variables-collection.php 0000644 00000007565 15252521350 0015007 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage; use Elementor\Core\Utils\Collection; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Modules\Variables\Storage\Entities\Variable; use Elementor\Modules\Variables\Storage\Exceptions\DuplicatedLabel; use Elementor\Modules\Variables\Storage\Exceptions\RecordNotFound; use Elementor\Modules\Variables\Storage\Exceptions\VariablesLimitReached; /** * TODO: a tradeoff when you want to use collection base methods they are * performing immutable process ( creating new instances ) * we will see if we need to extend collection as time goes on */ class Variables_Collection extends Collection { private int $watermark; private int $version; private function __construct( array $items = [], ?int $watermark = 0, ?int $version = null ) { parent::__construct(); $this->items = $items; $this->watermark = $watermark; $this->version = $version ?? Constants::FORMAT_VERSION_V1; } public static function hydrate( array $record ): self { $variables = []; foreach ( $record['data'] ?? [] as $id => $item ) { $data = array_merge( [ 'id' => $id ], $item ); $variables[ $id ] = Variable::from_array( $data ); } $watermark = $record['watermark']; $version = $record['version'] ?? null; return new self( $variables, $watermark, $version ); } public function serialize( bool $include_deleted_key = false ): array { $data = []; foreach ( $this->all() as $variable ) { $var = $variable->to_array(); if ( $include_deleted_key && $variable->is_deleted() ) { $var['deleted'] = true; } $data[ $variable->id() ] = $var; } return [ 'data' => $data, 'watermark' => $this->watermark, 'version' => $this->version, ]; } public function set_version( $version ): void { $this->version = $version; } public static function default(): self { return new self( [], 0, Constants::FORMAT_VERSION_V1 ); } public function watermark(): int { return $this->watermark; } private function reset_watermark() { $this->watermark = 0; } public function increment_watermark() { if ( PHP_INT_MAX === $this->watermark ) { $this->reset_watermark(); } ++$this->watermark; } public function add_variable( Variable $variable ): void { $this->items[ $variable->id() ] = $variable; } /** * @throws RecordNotFound When a variable is not found. */ public function find_or_fail( string $id ): Variable { $variable = $this->get( $id ); if ( ! isset( $variable ) ) { throw new RecordNotFound( 'Variable not found' ); } return $variable; } /** * @throws DuplicatedLabel If there is a duplicate label in the database. */ public function assert_label_is_unique( string $label, ?string $ignore_id = null ): void { foreach ( $this->all() as $variable ) { if ( $variable->is_deleted() ) { continue; } if ( null !== $ignore_id && $variable->id() === $ignore_id ) { continue; } if ( strcasecmp( $variable->label(), $label ) === 0 ) { throw new DuplicatedLabel( esc_html( "Variable label '$label' already exists." ) ); } } } /** * @throws VariablesLimitReached If variable limit reached. */ public function assert_limit_not_reached(): void { $active_count = 0; foreach ( $this->all() as $variable ) { if ( ! $variable->is_deleted() ) { ++$active_count; } } if ( Constants::TOTAL_VARIABLES_COUNT <= $active_count ) { throw new VariablesLimitReached( 'Total variables count limit reached' ); } } public function next_id(): string { return Utils::generate_id( 'e-gv-', array_keys( $this->all() ) ); } public function get_next_order(): int { $highest_order = 0; foreach ( $this->all() as $variable ) { if ( $variable->is_deleted() ) { continue; } if ( $variable->has_order() && $variable->order() > $highest_order ) { $highest_order = $variable->order(); } } return $highest_order + 1; } } variables/storage/repository.php 0000644 00000032561 15252521350 0013117 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Modules\Variables\Storage\Exceptions\DuplicatedLabel; use Elementor\Modules\Variables\Storage\Exceptions\RecordNotFound; use Elementor\Modules\Variables\Storage\Exceptions\VariablesLimitReached; use Elementor\Modules\Variables\Storage\Exceptions\FatalError; use Elementor\Modules\Variables\Storage\Exceptions\BatchOperationFailed; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Repository { private Kit $kit; public function __construct( Kit $kit ) { $this->kit = $kit; } /** * @throws VariablesLimitReached If database connection fails or query execution errors occur. */ private function assert_if_variables_limit_reached( array $db_record ) { $variables_in_use = 0; foreach ( $db_record['data'] as $variable ) { if ( isset( $variable['deleted'] ) && $variable['deleted'] ) { continue; } ++$variables_in_use; } if ( Constants::TOTAL_VARIABLES_COUNT < $variables_in_use ) { throw new VariablesLimitReached( 'Total variables count limit reached' ); } } /** * @throws DuplicatedLabel If variable creation fails or validation errors occur. */ private function assert_if_variable_label_is_duplicated( array $db_record, array $variable = [] ) { foreach ( $db_record['data'] as $id => $existing_variable ) { if ( isset( $existing_variable['deleted'] ) && $existing_variable['deleted'] ) { continue; } if ( isset( $variable['id'] ) && $variable['id'] === $id ) { continue; } if ( ! isset( $variable['label'] ) || ! isset( $existing_variable['label'] ) ) { continue; } if ( strtolower( $existing_variable['label'] ) === strtolower( $variable['label'] ) ) { throw new DuplicatedLabel( 'Variable label already exists' ); } } } public function variables(): array { $db_record = $this->load(); return $db_record['data'] ?? []; } public function load(): array { $db_record = $this->kit->get_json_meta( Constants::VARIABLES_META_KEY ); if ( is_array( $db_record ) && ! empty( $db_record ) ) { return $db_record; } return $this->get_default_meta(); } /** * @throws FatalError If variable update fails or validation errors occur. */ public function create( array $variable ) { $db_record = $this->load(); $list_of_variables = $db_record['data'] ?? []; $id = $this->new_id_for( $list_of_variables ); $new_variable = $this->extract_from( $variable, [ 'type', 'label', 'value', 'order', ] ); if ( ! isset( $new_variable['order'] ) ) { $new_variable['order'] = $this->get_next_order( $list_of_variables ); } $this->assert_if_variable_label_is_duplicated( $db_record, $new_variable ); $list_of_variables[ $id ] = $new_variable; $db_record['data'] = $list_of_variables; $this->assert_if_variables_limit_reached( $db_record ); $watermark = $this->save( $db_record ); if ( false === $watermark ) { throw new FatalError( 'Failed to create variable' ); } return [ 'variable' => array_merge( [ 'id' => $id ], $list_of_variables[ $id ] ), 'watermark' => $watermark, ]; } /** * @throws RecordNotFound If variable deletion fails or database errors occur. * @throws FatalError If variable deletion fails or database errors occur. */ public function update( string $id, array $variable ) { $db_record = $this->load(); $list_of_variables = $db_record['data'] ?? []; if ( ! isset( $list_of_variables[ $id ] ) ) { throw new RecordNotFound( 'Variable not found' ); } $updated_variable = array_merge( $list_of_variables[ $id ], $this->extract_from( $variable, [ 'label', 'value', 'order', ] ) ); $this->assert_if_variable_label_is_duplicated( $db_record, array_merge( $updated_variable, [ 'id' => $id ] ) ); $list_of_variables[ $id ] = $updated_variable; $db_record['data'] = $list_of_variables; $watermark = $this->save( $db_record ); if ( false === $watermark ) { throw new FatalError( 'Failed to update variable' ); } return [ 'variable' => array_merge( [ 'id' => $id ], $list_of_variables[ $id ] ), 'watermark' => $watermark, ]; } /** * @throws RecordNotFound If bulk operation fails or validation errors occur. * @throws FatalError If bulk operation fails or validation errors occur. */ public function delete( string $id ) { $db_record = $this->load(); $list_of_variables = $db_record['data'] ?? []; if ( ! isset( $list_of_variables[ $id ] ) ) { throw new RecordNotFound( 'Variable not found' ); } $list_of_variables[ $id ]['deleted'] = true; $list_of_variables[ $id ]['deleted_at'] = $this->now(); $db_record['data'] = $list_of_variables; $watermark = $this->save( $db_record ); if ( false === $watermark ) { throw new FatalError( 'Failed to delete variable' ); } return [ 'variable' => array_merge( [ 'id' => $id ], $list_of_variables[ $id ] ), 'watermark' => $watermark, ]; } /** * @throws RecordNotFound If export operation fails or data serialization errors occur. * @throws FatalError If export operation fails or data serialization errors occur. */ public function restore( string $id, $overrides = [] ) { $db_record = $this->load(); $list_of_variables = $db_record['data'] ?? []; if ( ! isset( $list_of_variables[ $id ] ) ) { throw new RecordNotFound( 'Variable not found' ); } $restored_variable = $this->extract_from( $list_of_variables[ $id ], [ 'label', 'value', 'type', 'order', ] ); if ( array_key_exists( 'label', $overrides ) ) { $restored_variable['label'] = $overrides['label']; } if ( array_key_exists( 'value', $overrides ) ) { $restored_variable['value'] = $overrides['value']; } $this->assert_if_variable_label_is_duplicated( $db_record, array_merge( $restored_variable, [ 'id' => $id ] ) ); $list_of_variables[ $id ] = $restored_variable; $db_record['data'] = $list_of_variables; $this->assert_if_variables_limit_reached( $db_record ); $watermark = $this->save( $db_record ); if ( false === $watermark ) { throw new FatalError( 'Failed to restore variable' ); } return [ 'variable' => array_merge( [ 'id' => $id ], $restored_variable ), 'watermark' => $watermark, ]; } /** * Process multiple operations atomically * * @throws BatchOperationFailed If batch operation fails or validation errors occur. * @throws FatalError If batch operation fails or validation errors occur. */ public function process_atomic_batch( array $operations, int $expected_watermark ): array { $db_record = $this->load(); $results = []; $errors = []; foreach ( $operations as $index => $operation ) { try { $result = $this->process_single_operation( $db_record, $operation ); $results[] = $result; } catch ( Exception $e ) { $operation_id = $this->get_operation_identifier( $operation, $index ); $errors[ $operation_id ] = [ 'status' => $this->get_error_status_code( $e ), 'code' => $this->get_error_code( $e ), 'message' => $e->getMessage(), ]; } } if ( ! empty( $errors ) ) { $error_details = []; foreach ( $errors as $operation_id => $error ) { $error_details[ esc_html( $operation_id ) ] = [ 'status' => (int) $error['status'], 'code' => $error['code'], 'message' => esc_html( $error['message'] ), ]; } // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped throw new BatchOperationFailed( 'Batch operation failed', $error_details ); } $watermark = $this->save( $db_record ); if ( false === $watermark ) { throw new FatalError( 'Failed to save batch operations' ); } return [ 'success' => true, 'watermark' => $watermark, 'results' => $results, ]; } private function process_single_operation( array &$db_record, array $operation ): array { switch ( $operation['type'] ) { case 'create': return $this->process_create_operation( $db_record, $operation ); case 'update': return $this->process_update_operation( $db_record, $operation ); case 'delete': return $this->process_delete_operation( $db_record, $operation ); case 'restore': return $this->process_restore_operation( $db_record, $operation ); default: throw new BatchOperationFailed( 'Invalid operation type: ' . esc_html( $operation['type'] ), [] ); } } private function process_create_operation( array &$db_record, array $operation ): array { $variable_data = $operation['variable']; $temp_id = $variable_data['id'] ?? null; $new_variable = $this->extract_from( $variable_data, [ 'type', 'label', 'value', 'order' ] ); if ( ! isset( $new_variable['order'] ) ) { $new_variable['order'] = $this->get_next_order( $db_record['data'] ); } $this->assert_if_variable_label_is_duplicated( $db_record, $new_variable ); $this->assert_if_variables_limit_reached( $db_record ); $id = $this->new_id_for( $db_record['data'] ); $now = $this->now(); $new_variable['created_at'] = $now; $new_variable['updated_at'] = $now; $db_record['data'][ $id ] = $new_variable; return [ 'id' => $id, 'type' => 'create', 'variable' => array_merge( [ 'id' => $id ], $new_variable ), 'temp_id' => $temp_id, ]; } private function process_update_operation( array &$db_record, array $operation ): array { $id = $operation['id']; $variable_data = $operation['variable']; if ( ! isset( $db_record['data'][ $id ] ) ) { throw new RecordNotFound( 'Variable not found' ); } $updated_fields = $this->extract_from( $variable_data, [ 'label', 'value', 'order' ] ); $updated_variable = array_merge( $db_record['data'][ $id ], $updated_fields ); $updated_variable['updated_at'] = $this->now(); $this->assert_if_variable_label_is_duplicated( $db_record, array_merge( $updated_variable, [ 'id' => $id ] ) ); $db_record['data'][ $id ] = $updated_variable; return [ 'id' => $id, 'type' => 'update', 'variable' => array_merge( [ 'id' => $id ], $updated_variable ), ]; } private function process_delete_operation( array &$db_record, array $operation ): array { $id = $operation['id']; if ( ! isset( $db_record['data'][ $id ] ) ) { throw new RecordNotFound( 'Variable not found' ); } $db_record['data'][ $id ]['deleted'] = true; $db_record['data'][ $id ]['deleted_at'] = $this->now(); return [ 'id' => $id, 'type' => 'delete', 'deleted' => true, ]; } private function process_restore_operation( array &$db_record, array $operation ): array { $id = $operation['id']; if ( ! isset( $db_record['data'][ $id ] ) ) { throw new RecordNotFound( 'Variable not found' ); } $overrides = []; if ( isset( $operation['label'] ) ) { $overrides['label'] = $operation['label']; } if ( isset( $operation['value'] ) ) { $overrides['value'] = $operation['value']; } $restored_variable = $this->extract_from( $db_record['data'][ $id ], [ 'label', 'value', 'type' ] ); $restored_variable = array_merge( $restored_variable, $overrides ); $restored_variable['updated_at'] = $this->now(); $this->assert_if_variable_label_is_duplicated( $db_record, array_merge( $restored_variable, [ 'id' => $id ] ) ); $this->assert_if_variables_limit_reached( $db_record ); $db_record['data'][ $id ] = $restored_variable; return [ 'id' => $id, 'type' => 'restore', 'variable' => array_merge( [ 'id' => $id ], $restored_variable ), ]; } private function get_operation_identifier( array $operation, int $index ): string { if ( 'create' === $operation['type'] && isset( $operation['variable']['id'] ) ) { return $operation['variable']['id']; } if ( isset( $operation['id'] ) ) { return $operation['id']; } return "operation_{$index}"; } private function get_error_status_code( Exception $e ): int { if ( $e instanceof RecordNotFound ) { return 404; } if ( $e instanceof DuplicatedLabel || $e instanceof VariablesLimitReached ) { return 400; } return 500; } private function get_error_code( Exception $e ): string { if ( $e instanceof VariablesLimitReached ) { return 'invalid_variable_limit_reached'; } if ( $e instanceof DuplicatedLabel ) { return 'duplicated_label'; } if ( $e instanceof RecordNotFound ) { return 'variable_not_found'; } return 'unexpected_server_error'; } private function save( array $db_record ) { if ( PHP_INT_MAX === $db_record['watermark'] ) { $db_record['watermark'] = 0; } ++$db_record['watermark']; if ( $this->kit->update_json_meta( Constants::VARIABLES_META_KEY, $db_record ) ) { return $db_record['watermark']; } return false; } private function new_id_for( array $list_of_variables ): string { return Utils::generate_id( 'e-gv-', array_keys( $list_of_variables ) ); } private function now(): string { return gmdate( 'Y-m-d H:i:s' ); } private function extract_from( array $source, array $fields ): array { return array_intersect_key( $source, array_flip( $fields ) ); } private function get_default_meta(): array { return [ 'data' => [], 'watermark' => 0, 'version' => Constants::FORMAT_VERSION_V1, ]; } private function get_next_order( array $list_of_variables ): int { $highest_order = 0; foreach ( $list_of_variables as $variable ) { if ( isset( $variable['deleted'] ) && $variable['deleted'] ) { continue; } if ( isset( $variable['order'] ) && $variable['order'] > $highest_order ) { $highest_order = $variable['order']; } } return $highest_order + 1; } } variables/storage/constants.php 0000644 00000000513 15252521350 0012704 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Constants { public const FORMAT_VERSION_V1 = 1; public const FORMAT_VERSION_V2 = 2; public const TOTAL_VARIABLES_COUNT = 1000; public const VARIABLES_META_KEY = '_elementor_global_variables'; } variables/storage/variables-repository.php 0000644 00000001662 15252521350 0015063 0 ustar 00 <?php namespace Elementor\Modules\Variables\Storage; use Elementor\Core\Kits\Documents\Kit; use Elementor\Modules\Variables\Adapters\Prop_Type_Adapter; class Variables_Repository { private Kit $kit; public function __construct( Kit $kit ) { $this->kit = $kit; } public function load(): Variables_Collection { $db_record = $this->kit->get_json_meta( Constants::VARIABLES_META_KEY ); if ( is_array( $db_record ) && ! empty( $db_record ) ) { $collection = Variables_Collection::hydrate( $db_record ); Prop_Type_Adapter::from_storage( $collection ); return $collection; } return Variables_Collection::default(); } public function save( Variables_Collection $collection ) { $collection->increment_watermark(); $record = Prop_Type_Adapter::to_storage( $collection ); if ( $this->kit->update_json_meta( Constants::VARIABLES_META_KEY, $record ) ) { return $collection->watermark(); } return false; } } variables/adapters/prop-type-adapter.php 0000644 00000006737 15252521350 0014422 0 ustar 00 <?php namespace Elementor\Modules\Variables\Adapters; use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type; use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type; use Elementor\Modules\AtomicWidgets\Styles\Size_Constants; use Elementor\Modules\Variables\PropTypes\Color_Variable_Prop_Type; use Elementor\Modules\Variables\PropTypes\Font_Variable_Prop_Type; use Elementor\Modules\Variables\PropTypes\Size_Variable_Prop_Type; use Elementor\Modules\Variables\Storage\Entities\Variable; use Elementor\Modules\Variables\Storage\Constants; use Elementor\Modules\Variables\Storage\Variables_Collection; class Prop_Type_Adapter { public const GLOBAL_CUSTOM_SIZE_VARIABLE_KEY = 'global-custom-size-variable'; public static function to_storage( Variables_Collection $collection ): array { $schema = self::get_schema(); $collection->set_version( Constants::FORMAT_VERSION_V2 ); $record = $collection->serialize(); $collection->each( function( Variable $variable ) use ( $schema, &$record ) { $type = $variable->type(); $value = $variable->value(); $id = $variable->id(); $variable = $variable->to_array(); $prop_type = $schema[ $type ] ?? null; if ( is_array( $value ) || ! $prop_type ) { return; } if ( Size_Variable_Prop_Type::get_key() === $type ) { $value = self::parse_size_value( $value ); } if ( self::GLOBAL_CUSTOM_SIZE_VARIABLE_KEY === $type ) { $value = [ 'size' => $value, 'unit' => 'custom', ]; $variable['type'] = Size_Variable_Prop_Type::get_key(); } $record['data'][ $id ] = array_merge( $variable, [ 'value' => $prop_type::generate( $value ) ] ); } ); return $record; } public static function from_storage( Variables_Collection $collection ): Variables_Collection { $collection->each( function( Variable $variable ) { $value = $variable->value(); if ( ! is_array( $value ) ) { return; } $value = $value['value']; if ( isset( $value['unit'] ) && 'custom' === $value['unit'] ) { $value = $value['size']; $variable->set_type( self::GLOBAL_CUSTOM_SIZE_VARIABLE_KEY ); } if ( Size_Variable_Prop_Type::get_key() === $variable->type() ) { if ( ! is_array( $value ) ) { $value = [ 'size' => '', 'unit' => Size_Constants::DEFAULT_UNIT, ]; } $value['size'] = $value['size'] ?? ''; $value['unit'] = empty( $value['unit'] ) ? Size_Constants::DEFAULT_UNIT : $value['unit']; $value = $value['size'] . $value['unit']; } $variable->set_value( $value ); } ); $collection->set_version( Constants::FORMAT_VERSION_V1 ); return $collection; } private static function get_schema(): array { return [ Color_Variable_Prop_Type::get_key() => Color_Prop_Type::class, Font_Variable_Prop_Type::get_key() => String_Prop_Type::class, Size_Variable_Prop_Type::get_key() => Size_Prop_Type::class, self::GLOBAL_CUSTOM_SIZE_VARIABLE_KEY => Size_Prop_Type::class, ]; } private static function parse_size_value( ?string $value ) { $value = trim( strtolower( $value ) ); if ( 'auto' === $value ) { return [ 'size' => '', 'unit' => 'auto', ]; } if ( preg_match( '/^(-?\d*\.?\d+)([a-z%]+)$/i', trim( $value ), $matches ) ) { return [ 'size' => $matches[1] + 0, 'unit' => strtolower( $matches[2] ), ]; } if ( empty( $value ) ) { return [ 'size' => '', 'unit' => Size_Constants::DEFAULT_UNIT, ]; } return $value; } } variables/import-export-customization/runners/import.php 0000644 00000015207 15252521351 0020000 0 ustar 00 <?php namespace Elementor\Modules\Variables\ImportExportCustomization\Runners; use Elementor\App\Modules\ImportExportCustomization\Design_System_Import_Context; use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Import_Runner_Base; use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils; use Elementor\Modules\AtomicWidgets\Utils\Utils; use Elementor\Modules\Variables\ImportExportCustomization\Import_Export_Customization; use Elementor\Modules\Variables\Storage\Entities\Variable; use Elementor\Modules\Variables\Storage\Variables_Collection; use Elementor\Modules\Variables\Storage\Variables_Repository; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Import extends Import_Runner_Base { const EMPTY_RESULT = [ 'created' => [], 'renamed' => [], 'replaced' => [], 'skipped' => [], 'failed' => [], ]; public static function get_name(): string { return 'global-variables'; } public function should_import( array $data ): bool { $import_context = Design_System_Import_Context::from_data( $data ); return ( $import_context->is_included() && ! empty( $data['extracted_directory_path'] ) && $this->is_variables_enabled( $data ) ); } private function is_variables_enabled( array $data ): bool { if ( isset( $data['customization']['settings']['variables'] ) ) { return (bool) $data['customization']['settings']['variables']; } return true; } public function import( array $data, array $imported_data ): array { $kit = Plugin::$instance->kits_manager->get_active_kit(); $file_name = Import_Export_Customization::FILE_NAME; $variables_data = ImportExportUtils::read_json_file( "{$data['extracted_directory_path']}/{$file_name}.json" ); if ( ! $kit || ! $variables_data || empty( $variables_data['data'] ) ) { return self::EMPTY_RESULT; } $repository = new Variables_Repository( $kit ); $import_context = Design_System_Import_Context::from_data( $data ); $conflict_resolution = $import_context->resolve_conflict_resolution( $data, 'variablesOverrideAll' ); if ( 'override-all' === $conflict_resolution ) { $imported_collection = Variables_Collection::hydrate( $variables_data ); $this->save_collection( $repository, $imported_collection ); return $this->build_override_all_result( $imported_collection ); } $existing_collection = $this->get_existing_collection( $repository ); $imported_collection = Variables_Collection::hydrate( $variables_data ); $result = $this->resolve_collections( $existing_collection, $imported_collection, $conflict_resolution ); $this->save_collection( $repository, $existing_collection ); return $result; } private function build_override_all_result( Variables_Collection $imported_collection ): array { $result = self::EMPTY_RESULT; foreach ( $imported_collection->all() as $variable ) { if ( $variable->is_deleted() ) { continue; } $result['created'][] = [ 'import_entry' => $this->format_variable_as_entry( $variable ), ]; } return $result; } private function get_existing_collection( Variables_Repository $repository ): Variables_Collection { return $repository->load(); } private function resolve_collections( Variables_Collection $existing, Variables_Collection $imported, string $conflict_resolution ): array { $existing_labels = $this->get_existing_labels( $existing ); $existing_label_type_map = $this->build_label_type_map( $existing ); $existing_ids = array_keys( $existing->all() ); $result = self::EMPTY_RESULT; foreach ( $imported->all() as $variable ) { if ( $variable->is_deleted() ) { continue; } $import_entry = $this->format_variable_as_entry( $variable ); $label_lower = strtolower( $variable->label() ); $has_label_conflict = in_array( $label_lower, $existing_labels, true ); if ( $has_label_conflict && 'skip' === $conflict_resolution ) { $result['skipped'][] = [ 'import_entry' => $import_entry ]; continue; } if ( $has_label_conflict && 'replace' === $conflict_resolution ) { $existing_entry = $existing_label_type_map[ $label_lower ] ?? null; $type_matches = $existing_entry && $existing_entry['type'] === $variable->type(); if ( $type_matches ) { $existing_var = $existing->get( $existing_entry['id'] ); $existing_var->set_value( $variable->value() ); $result['replaced'][] = [ 'import_entry' => $import_entry, 'result_entry' => $this->format_variable_as_entry( $existing_var ), ]; continue; } } $original_id = $variable->id(); $id_exists = in_array( $original_id, $existing_ids, true ); $new_id = $id_exists ? $this->generate_unique_id( $existing_ids ) : $original_id; $existing_ids[] = $new_id; $new_label = ImportExportUtils::resolve_label_conflict( $variable->label(), $existing_labels ); $existing_labels[] = strtolower( $new_label ); $new_variable = Variable::create_new( [ 'id' => $new_id, 'type' => $variable->type(), 'label' => $new_label, 'value' => $variable->value(), 'order' => $existing->get_next_order(), ] ); $existing->add_variable( $new_variable ); $was_renamed = strtolower( $new_label ) !== $label_lower; $result_entry = $this->format_variable_as_entry( $new_variable ); if ( $was_renamed ) { $result['renamed'][] = [ 'import_entry' => $import_entry, 'result_entry' => $result_entry, ]; } else { $result['created'][] = [ 'import_entry' => $import_entry ]; } } return $result; } private function build_label_type_map( Variables_Collection $collection ): array { $map = []; foreach ( $collection->all() as $variable ) { if ( ! $variable->is_deleted() ) { $map[ strtolower( $variable->label() ) ] = [ 'id' => $variable->id(), 'type' => $variable->type(), ]; } } return $map; } private function get_existing_labels( Variables_Collection $collection ): array { $labels = []; foreach ( $collection->all() as $variable ) { if ( ! $variable->is_deleted() ) { $labels[] = strtolower( $variable->label() ); } } return $labels; } private function generate_unique_id( array $existing_ids ): string { return Utils::generate_id( 'e-gv-', $existing_ids ); } private function save_collection( Variables_Repository $repository, Variables_Collection $collection ): void { $result = $repository->save( $collection ); if ( false === $result ) { throw new \RuntimeException( 'Failed to save global variables during import.' ); } Plugin::$instance->files_manager->clear_cache(); } private function format_variable_as_entry( Variable $variable ): array { return [ 'id' => $variable->id(), 'label' => $variable->label(), ]; } } variables/import-export-customization/runners/export.php 0000644 00000003624 15252521351 0020007 0 ustar 00 <?php namespace Elementor\Modules\Variables\ImportExportCustomization\Runners; use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Export_Runner_Base; use Elementor\Modules\AtomicWidgets\Module as Atomic_Widgets_Module; use Elementor\Modules\Variables\ImportExportCustomization\Import_Export_Customization; use Elementor\Modules\Variables\Module as Variables_Module; use Elementor\Modules\Variables\Storage\Variables_Repository; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; } class Export extends Export_Runner_Base { public static function get_name(): string { return 'global-variables'; } public function should_export( array $data ): bool { return ( isset( $data['include'] ) && in_array( 'settings', $data['include'], true ) && $this->is_variables_enabled( $data ) ); } private function is_variables_enabled( array $data ): bool { if ( ! $this->is_feature_active() ) { return false; } if ( isset( $data['customization']['settings']['variables'] ) ) { return (bool) $data['customization']['settings']['variables']; } return true; } private function is_feature_active(): bool { return Plugin::$instance->experiments->is_feature_active( Variables_Module::EXPERIMENT_NAME ) && Plugin::$instance->experiments->is_feature_active( Atomic_Widgets_Module::EXPERIMENT_NAME ); } public function export( array $data ): array { $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return [ 'manifest' => [], 'files' => [], ]; } $repository = new Variables_Repository( $kit ); $collection = $repository->load(); $variables_data = $collection->serialize(); if ( empty( $variables_data['data'] ) ) { return [ 'manifest' => [], 'files' => [], ]; } return [ 'files' => [ 'path' => Import_Export_Customization::FILE_NAME, 'data' => $variables_data, ], 'manifest' => [], ]; } } variables/import-export-customization/import-export-customization.php 0000644 00000001524 15252521351 0022526 0 ustar 00 <?php namespace Elementor\Modules\Variables\ImportExportCustomization; use Elementor\App\Modules\ImportExportCustomization\Processes\Export; use Elementor\App\Modules\ImportExportCustomization\Processes\Import; use Elementor\Modules\Variables\ImportExportCustomization\Runners\Export as Export_Runner; use Elementor\Modules\Variables\ImportExportCustomization\Runners\Import as Import_Runner; if ( ! defined( 'ABSPATH' ) ) { exit; } class Import_Export_Customization { const FILE_NAME = 'global-variables'; public function register_hooks() { add_action( 'elementor/import-export-customization/export-kit', function ( Export $export ) { $export->register( new Export_Runner() ); } ); add_action( 'elementor/import-export-customization/import-kit', function ( Import $import ) { $import->register( new Import_Runner() ); } ); } } editor-one/classes/active-menu-resolver.php 0000644 00000005242 15252521351 0015037 0 ustar 00 <?php namespace Elementor\Modules\EditorOne\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; } class Active_Menu_Resolver { private const HOME_SLUG = 'elementor'; private Url_Matcher $url_matcher; public function __construct( Url_Matcher $url_matcher ) { $this->url_matcher = $url_matcher; } public function resolve( array $menu_items, array $level4_groups, string $current_page, string $current_uri ): array { if ( 'elementor-editor' === $current_page || Menu_Config::EDITOR_MENU_SLUG === $current_page || Menu_Config::ELEMENTOR_MENU_SLUG === $current_page ) { return $this->create_active_state( self::HOME_SLUG ); } $pro_post_type_match = $this->get_pro_post_type_active_state(); if ( $pro_post_type_match ) { return $pro_post_type_match; } return $this->find_best_matching_menu_item( $menu_items, $level4_groups, $current_uri ); } public function create_active_state( string $menu_slug, string $child_slug = '', int $score = 0 ): array { return [ 'menu_slug' => $menu_slug, 'child_slug' => $child_slug, 'score' => $score, ]; } private function find_best_matching_menu_item( array $menu_items, array $level4_groups, string $current_uri ): array { $best_match = $this->create_active_state( '', '', -1 ); foreach ( $menu_items as $item ) { $best_match = $this->update_best_match_from_level4( $item, $level4_groups, $current_uri, $best_match ); $score = $this->url_matcher->get_match_score( $item['url'], $current_uri ); if ( $score > $best_match['score'] ) { $best_match = $this->create_active_state( $item['slug'], '', $score ); } } return $this->create_active_state( $best_match['menu_slug'], $best_match['child_slug'] ); } private function update_best_match_from_level4( array $item, array $level4_groups, string $current_uri, array $best_match ): array { if ( empty( $item['group_id'] ) || ! isset( $level4_groups[ $item['group_id'] ] ) ) { return $best_match; } $group = $level4_groups[ $item['group_id'] ]; if ( empty( $group['items'] ) ) { return $best_match; } foreach ( $group['items'] as $child_item ) { $score = $this->url_matcher->get_match_score( $child_item['url'], $current_uri ); if ( $score > $best_match['score'] ) { $best_match = $this->create_active_state( $item['slug'], $child_item['slug'], $score ); } } return $best_match; } private function get_pro_post_type_active_state(): ?array { $current_post_type = filter_input( INPUT_GET, 'post_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? ''; if ( empty( $current_post_type ) ) { return null; } return Menu_Data_Provider::get_elementor_post_types()[ $current_post_type ] ?? null; } } editor-one/classes/menu-config.php 0000644 00000010134 15252521351 0013166 0 ustar 00 <?php namespace Elementor\Modules\EditorOne\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; } class Menu_Config { const ELEMENTOR_MENU_SLUG = 'elementor'; const ELEMENTOR_HOME_MENU_SLUG = 'elementor-home'; const EDITOR_MENU_SLUG = 'elementor-editor'; const TEMPLATES_GROUP_ID = 'elementor-editor-templates'; const SETTINGS_GROUP_ID = 'elementor-editor-settings'; const EDITOR_GROUP_ID = 'elementor-editor-items'; const CUSTOM_ELEMENTS_GROUP_ID = 'elementor-editor-custom-elements'; const SYSTEM_GROUP_ID = 'elementor-editor-system'; const THIRD_PARTY_GROUP_ID = 'elementor-editor-third-party'; const LEGACY_TEMPLATES_SLUG = 'edit.php?post_type=elementor_library'; const CAPABILITY_EDIT_POSTS = 'edit_posts'; const CAPABILITY_MANAGE_OPTIONS = 'manage_options'; const MENU_POSITION = 58.5; public static function get_excluded_level4_slugs(): array { // add new which is automatically added to templates and categories $default_slugs = [ 'edit-tags.php?taxonomy=elementor_library_category&post_type=elementor_library', ]; return apply_filters( 'elementor/editor-one/menu/excluded_level4_slugs', $default_slugs ); } public static function get_excluded_level3_slugs(): array { // elementor pro slugs $default_slugs = [ 'elementor-theme-builder', 'elementor-pro-notes-proxy', self::EDITOR_MENU_SLUG, ]; return apply_filters( 'elementor/editor-one/menu/excluded_level3_slugs', $default_slugs ); } public static function get_legacy_slug_mapping(): array { $default_mapping = [ self::LEGACY_TEMPLATES_SLUG => self::TEMPLATES_GROUP_ID, ]; return apply_filters( 'elementor/editor-one/menu/legacy_slug_mapping', $default_mapping ); } public static function get_legacy_pro_mapping(): array { $default_mapping = [ 'elementor-license' => [ 'group' => self::SYSTEM_GROUP_ID ], 'e-form-submissions' => [ 'group' => self::EDITOR_GROUP_ID ], 'edit.php?post_type=elementor_font' => [ 'group' => self::CUSTOM_ELEMENTS_GROUP_ID, 'label' => __( 'Fonts', 'elementor' ), ], 'edit.php?post_type=elementor_icons' => [ 'group' => self::CUSTOM_ELEMENTS_GROUP_ID, 'label' => __( 'Icons', 'elementor' ), ], 'edit.php?post_type=elementor_snippet' => [ 'group' => self::CUSTOM_ELEMENTS_GROUP_ID, 'label' => __( 'Code', 'elementor' ), ], 'e-custom-fonts' => [ 'group' => self::CUSTOM_ELEMENTS_GROUP_ID, 'label' => __( 'Fonts', 'elementor' ), ], 'e-custom-icons' => [ 'group' => self::CUSTOM_ELEMENTS_GROUP_ID, 'label' => __( 'Icons', 'elementor' ), ], 'e-custom-code' => [ 'group' => self::CUSTOM_ELEMENTS_GROUP_ID, 'label' => __( 'Code', 'elementor' ), ], ]; return apply_filters( 'elementor/editor-one/menu/legacy_pro_mapping', $default_mapping ); } public static function get_attribute_mapping(): array { $default_mapping = [ 'e-form-submissions' => [ 'position' => 70, 'icon' => 'send', ], ]; return apply_filters( 'elementor/editor-one/menu/position_mapping', $default_mapping ); } public static function get_custom_code_url(): string { $pro_custom_code_cpt = 'elementor_snippet'; if ( post_type_exists( $pro_custom_code_cpt ) ) { $default_url = admin_url( 'edit.php?post_type=' . $pro_custom_code_cpt ); } else { $default_url = admin_url( 'admin.php?page=elementor_custom_code' ); } return apply_filters( 'elementor/editor-one/menu/custom_code_url', $default_url ); } public static function get_elementor_home_url(): string { return admin_url( 'admin.php?page=' . self::ELEMENTOR_MENU_SLUG ); } public static function get_elementor_post_types(): array { $default_values = [ 'elementor_icons' => [ 'menu_slug' => 'elementor-custom-elements', 'child_slug' => 'edit.php?post_type=elementor_icons', ], 'elementor_font' => [ 'menu_slug' => 'elementor-custom-elements', 'child_slug' => 'edit.php?post_type=elementor_font', ], 'elementor_snippet' => [ 'menu_slug' => 'elementor-custom-elements', 'child_slug' => 'edit.php?post_type=elementor_snippet', ], ]; return apply_filters( 'elementor/editor-one/menu/elementor_post_types', $default_values ); } } editor-one/classes/slug-normalizer.php 0000644 00000001421 15252521351 0014110 0 ustar 00 <?php namespace Elementor\Modules\EditorOne\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; } class Slug_Normalizer { public function normalize( string $slug ): string { if ( 0 !== strpos( $slug, 'http' ) ) { return $slug; } $parsed = wp_parse_url( $slug ); $path = basename( $parsed['path'] ?? '' ); if ( ! empty( $parsed['query'] ) ) { $path .= '?' . $parsed['query']; } if ( ! empty( $parsed['fragment'] ) ) { $path .= '#' . $parsed['fragment']; } return $path; } public function is_excluded( string $item_slug, array $excluded_slugs ): bool { if ( in_array( $item_slug, $excluded_slugs, true ) ) { return true; } $normalized_slug = $this->normalize( $item_slug ); return in_array( $normalized_slug, $excluded_slugs, true ); } } editor-one/classes/menu-data-provider.php 0000644 00000037750 15252521351 0014477 0 ustar 00 <?php namespace Elementor\Modules\EditorOne\Classes; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Third_Level_Interface; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_With_Custom_Url_Interface; use Elementor\Plugin; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Menu_Data_Provider { public const THIRD_LEVEL_EDITOR_FLYOUT = 'editor_flyout'; public const THIRD_LEVEL_FLYOUT_MENU = 'flyout_menu'; private static ?Menu_Data_Provider $instance = null; private array $level3_items = []; private array $level4_items = []; private ?string $theme_builder_url = null; private ?array $cached_level3_sidebar_data = null; private ?array $cached_level4_sidebar_data = null; private ?array $cached_flyout_menu_data = null; private Slug_Normalizer $slug_normalizer; public static function instance(): self { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->slug_normalizer = new Slug_Normalizer(); } public function get_slug_normalizer(): Slug_Normalizer { return $this->slug_normalizer; } public function register_menu( Menu_Item_Interface $item ): void { if ( ! ( $item instanceof Menu_Item_Third_Level_Interface ) ) { $this->register_level4_item( $item ); return; } $group_id = $item->get_group_id(); $collapsible_groups = [ Menu_Config::TEMPLATES_GROUP_ID, Menu_Config::CUSTOM_ELEMENTS_GROUP_ID, Menu_Config::SYSTEM_GROUP_ID, ]; if ( in_array( $group_id, $collapsible_groups, true ) && ! $item->has_children() ) { $this->register_level4_item( $item ); } else { $this->register_level3_item( $item ); } } public function register_level3_item( Menu_Item_Third_Level_Interface $item ): void { $group_id = $item->get_group_id(); $item_slug = $item->get_slug(); if ( ! isset( $this->level3_items[ $group_id ] ) ) { $this->level3_items[ $group_id ] = []; } $this->level3_items[ $group_id ][ $item_slug ] = $item; $this->invalidate_cache(); } public function register_level4_item( Menu_Item_Interface $item ): void { $group_id = $item->get_group_id(); $item_slug = $item->get_slug(); if ( ! isset( $this->level4_items[ $group_id ] ) ) { $this->level4_items[ $group_id ] = []; } $this->level4_items[ $group_id ][ $item_slug ] = $item; $this->invalidate_cache(); } public function get_level3_items(): array { return $this->level3_items; } public function get_level4_items(): array { return $this->level4_items; } public function is_item_already_registered( string $item_slug ): bool { $all_items = array_merge( $this->level3_items, $this->level4_items ); foreach ( $all_items as $group_items ) { if ( isset( $group_items[ $item_slug ] ) ) { return true; } } return false; } public function get_third_level_data( string $variant ): array { if ( self::THIRD_LEVEL_EDITOR_FLYOUT === $variant ) { return $this->get_third_level_data_from_cache( $this->cached_level3_sidebar_data, [ $this, 'build_level3_flyout_items' ] ); } if ( self::THIRD_LEVEL_FLYOUT_MENU === $variant ) { return $this->get_third_level_data_from_cache( $this->cached_flyout_menu_data, [ $this, 'build_flyout_items_with_expanded_third_party' ] ); } return []; } public function get_level4_flyout_data(): array { if ( null !== $this->cached_level4_sidebar_data ) { return $this->cached_level4_sidebar_data; } $groups = $this->build_level4_flyout_groups(); foreach ( $groups as $group_id => $group ) { if ( ! empty( $group['items'] ) ) { $this->sort_items_by_priority( $groups[ $group_id ]['items'] ); } } $this->cached_level4_sidebar_data = $groups; return $this->cached_level4_sidebar_data; } private function get_third_level_data_from_cache( ?array &$cache, callable $items_builder ): array { if ( null !== $cache ) { return $cache; } $items = $items_builder(); $this->sort_items_by_priority( $items ); $cache = [ 'parent_slug' => Menu_Config::EDITOR_MENU_SLUG, 'items' => $items, ]; return $cache; } public function get_theme_builder_url(): string { if ( null === $this->theme_builder_url ) { $pro_url = Plugin::$instance->app ? Plugin::$instance->app->get_settings( 'menu_url' ) : null; $return_to = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) ); if ( $pro_url ) { if ( false !== strpos( $pro_url, '#' ) ) { $url = $this->add_return_to_url( $pro_url, $return_to ); } else { $url = $pro_url; } } else { $url = $this->add_return_to_url( admin_url( 'admin.php?page=elementor-app' ) . '#/site-editor/promotion', $return_to ); } $this->theme_builder_url = apply_filters( 'elementor/editor-one/menu/theme_builder_url', $url ); } return $this->theme_builder_url; } private function add_return_to_url( string $url, string $return_to ): string { $hash_position = strpos( $url, '#' ); if ( false === $hash_position ) { return add_query_arg( [ 'return_to' => $return_to ], $url ); } $base_url = substr( $url, 0, $hash_position ); $hash_fragment = substr( $url, $hash_position ); return add_query_arg( [ 'return_to' => $return_to ], $base_url ) . $hash_fragment; } public function get_all_sidebar_page_slugs(): array { $base_slugs = [ Menu_Config::ELEMENTOR_MENU_SLUG, Menu_Config::EDITOR_MENU_SLUG, ]; $slugs = array_merge( $base_slugs, $this->get_dynamic_page_slugs() ); return array_values( array_unique( $slugs ) ); } public function is_elementor_editor_page(): bool { if ( ! get_current_screen() ) { return false; } $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? ''; if ( Menu_Config::ELEMENTOR_HOME_MENU_SLUG === $page ) { return false; } if ( in_array( $page, $this->get_all_sidebar_page_slugs(), true ) ) { return true; } $post_type = filter_input( INPUT_GET, 'post_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? ''; return $this->is_elementor_post_type( $post_type ); } public function is_editor_one_post_edit_screen(): bool { $screen = get_current_screen(); if ( ! $screen || empty( $screen->post_type ) ) { return false; } if ( 'post' !== $screen->base ) { return false; } $post_types = apply_filters( 'elementor/editor-one/admin-edit-post-types', [] ); return in_array( $screen->post_type, $post_types, true ); } public function is_editor_one_admin_page(): bool { return $this->is_elementor_editor_page() || $this->is_editor_one_post_edit_screen(); } private function is_elementor_post_type( string $post_type ): bool { if ( empty( $post_type ) ) { return false; } return isset( Menu_Config::get_elementor_post_types()[ $post_type ] ); } public static function get_elementor_post_types(): array { return Menu_Config::get_elementor_post_types(); } private function get_dynamic_page_slugs(): array { $slugs = []; foreach ( $this->level3_items as $group_items ) { $slugs = array_merge( $slugs, array_keys( $group_items ) ); } foreach ( $this->level4_items as $group_items ) { foreach ( $group_items as $item_slug => $item ) { $slugs[] = $item_slug; } } $allowed_prefixes = [ 'elementor', 'e-', 'popup_templates' ]; return array_values( array_filter( $slugs, function( string $slug ) use ( $allowed_prefixes ): bool { foreach ( $allowed_prefixes as $prefix ) { if ( 0 === strpos( $slug, $prefix ) ) { return true; } } return false; } ) ); } private function build_level3_flyout_items(): array { return $this->build_flyout_items( false ); } private function build_flyout_items_with_expanded_third_party(): array { $items = $this->build_flyout_items( true ); if ( ! Utils::has_pro() ) { $items[] = $this->build_theme_builder_flyout_item(); } return $items; } private function build_theme_builder_flyout_item(): array { return [ 'slug' => 'elementor-theme-builder', 'label' => esc_html__( 'Theme Builder', 'elementor' ), 'url' => $this->get_theme_builder_url(), 'icon' => 'theme-builder', 'group_id' => '', 'priority' => 50, 'has_divider_before' => false, ]; } private function build_flyout_items( bool $expand_third_party ): array { $items = []; $existing_slugs = []; $excluded_slugs = Menu_Config::get_excluded_level3_slugs(); $excluded_level4_slugs = $expand_third_party ? Menu_Config::get_excluded_level4_slugs() : []; foreach ( $this->level3_items as $group_items ) { foreach ( $group_items as $item_slug => $item ) { if ( ! $this->should_include_flyout_item( $item, $item_slug, $existing_slugs, $excluded_slugs ) ) { continue; } if ( $expand_third_party && $this->is_third_party_parent_with_children( $item ) ) { $children = $this->level4_items[ Menu_Config::THIRD_PARTY_GROUP_ID ] ?? []; $is_first_child = true; foreach ( $children as $child_slug => $child ) { if ( ! $this->is_item_accessible( $child ) ) { continue; } if ( $this->is_slug_excluded( $child_slug, $excluded_level4_slugs, true ) ) { continue; } if ( in_array( $child_slug, $existing_slugs, true ) ) { continue; } $child_data = $this->create_expanded_child_item_data( $child, $child_slug, $is_first_child ); $items[] = $child_data; $existing_slugs[] = $child_slug; $is_first_child = false; } continue; } $items[] = $this->create_flyout_item_data( $item, $item_slug ); $existing_slugs[] = $item_slug; } } return $items; } private function is_third_party_parent_with_children( Menu_Item_Interface $item ): bool { if ( Menu_Config::THIRD_PARTY_GROUP_ID !== $item->get_group_id() ) { return false; } if ( ! $item->has_children() ) { return false; } $children = $this->level4_items[ Menu_Config::THIRD_PARTY_GROUP_ID ] ?? []; return ! empty( $children ); } private function create_expanded_child_item_data( Menu_Item_Interface $item, string $item_slug, bool $is_first ): array { $url = $this->resolve_item_url( $item, $item_slug ); return [ 'slug' => $item_slug, 'label' => $this->title_case( $item->get_label() ), 'url' => $url, 'group_id' => '', 'priority' => $this->get_item_priority( $item ), 'has_divider_before' => $is_first, ]; } private function should_include_flyout_item( Menu_Item_Interface $item, string $item_slug, array $existing_slugs, array $excluded_slugs ): bool { if ( ! $this->is_item_accessible( $item ) ) { return false; } if ( in_array( $item_slug, $existing_slugs, true ) ) { return false; } if ( $this->is_slug_excluded( $item_slug, $excluded_slugs ) ) { return false; } if ( empty( trim( wp_strip_all_tags( $item->get_label() ) ) ) ) { return false; } return true; } private function create_flyout_item_data( Menu_Item_Interface $item, string $item_slug ): array { $has_children = $item->has_children(); $group_id = $has_children ? $item->get_group_id() : ''; $is_third_party_parent = Menu_Config::THIRD_PARTY_GROUP_ID === $item->get_group_id(); return [ 'slug' => $item_slug, 'label' => $this->title_case( $item->get_label() ), 'url' => $this->resolve_flyout_item_url( $item, $item_slug ), 'icon' => $item->get_icon(), 'group_id' => $group_id, 'priority' => $this->get_item_priority( $item ), 'has_divider_before' => $is_third_party_parent, ]; } private function resolve_flyout_item_url( Menu_Item_Interface $item, string $item_slug ): string { $url = $this->resolve_item_url( $item, $item_slug ); if ( ! $item->has_children() ) { return $url; } $children = $this->get_level4_items()[ $item->get_group_id() ] ?? []; if ( empty( $children ) ) { return $url; } $first_child_url = $this->get_first_accessible_child_url( $children ); return $first_child_url ?? $url; } private function get_first_accessible_child_url( array $children ): ?string { $children_data = []; foreach ( $children as $child_slug => $child_item ) { if ( ! $this->is_item_accessible( $child_item ) ) { continue; } $children_data[] = [ 'url' => $this->resolve_item_url( $child_item, $child_slug ), 'priority' => $this->get_item_priority( $child_item ), ]; } if ( empty( $children_data ) ) { return null; } $this->sort_items_by_priority( $children_data ); return $children_data[0]['url']; } private function build_level4_flyout_groups(): array { $groups = []; $excluded_slugs = Menu_Config::get_excluded_level4_slugs(); foreach ( $this->level4_items as $group_id => $items ) { $groups[ $group_id ] = [ 'items' => [] ]; $existing_labels = []; foreach ( $items as $item_slug => $item ) { if ( ! $this->is_item_accessible( $item ) ) { continue; } if ( $this->is_slug_excluded( $item_slug, $excluded_slugs, true ) ) { continue; } $label = $item->get_label(); $label_lower = strtolower( $label ); if ( in_array( $label_lower, $existing_labels, true ) ) { continue; } $url = $this->resolve_item_url( $item, $item_slug ); $groups[ $group_id ]['items'][] = [ 'slug' => $item_slug, 'label' => $this->title_case( $item->get_label() ), 'url' => $url, 'priority' => $this->get_item_priority( $item ), ]; $existing_labels[] = $label_lower; } } return $groups; } public function is_item_accessible( Menu_Item_Interface $item ): bool { return $item->is_visible() && current_user_can( $item->get_capability() ); } private function get_item_url( string $item_slug, ?string $parent_slug = null ): string { $admin_path_prefixes = [ 'edit.php', 'post-new.php', 'admin.php' ]; foreach ( $admin_path_prefixes as $prefix ) { if ( 0 === strpos( $item_slug, $prefix ) ) { return admin_url( $item_slug ); } } if ( 0 === strpos( $item_slug, 'http' ) ) { return $item_slug; } if ( $parent_slug && 0 === strpos( $parent_slug, 'edit.php' ) ) { return admin_url( $parent_slug . '&page=' . $item_slug ); } return admin_url( 'admin.php?page=' . $item_slug ); } private function resolve_item_url( Menu_Item_Interface $item, string $item_slug ): string { if ( $item instanceof Menu_Item_With_Custom_Url_Interface ) { return $item->get_menu_url(); } return $this->get_item_url( $item_slug, $item->get_parent_slug() ); } private function get_item_priority( Menu_Item_Interface $item ): int { return $item->get_position() ?? 100; } private function is_slug_excluded( string $item_slug, array $excluded_slugs, bool $use_normalizer = false ): bool { if ( $use_normalizer ) { return $this->slug_normalizer->is_excluded( $item_slug, $excluded_slugs ); } return in_array( $item_slug, $excluded_slugs, true ); } private function sort_items_by_priority( array &$items ): void { usort( $items, function ( array $a, array $b ): int { return ( $a['priority'] ?? 100 ) <=> ( $b['priority'] ?? 100 ); } ); } private function title_case( string $text ): string { if ( function_exists( 'mb_convert_case' ) ) { return mb_convert_case( $text, MB_CASE_TITLE, 'UTF-8' ); } return ucwords( strtolower( $text ) ); } private function invalidate_cache(): void { $this->cached_level3_sidebar_data = null; $this->cached_level4_sidebar_data = null; $this->cached_flyout_menu_data = null; } public static function get_current_user_capabilities(): array { $user = wp_get_current_user(); if ( ! $user || ! $user->exists() ) { return [ 'user' => null, 'has_edit_posts' => false, 'has_manage_options' => false, 'is_edit_posts_user' => false, ]; } $has_edit_posts = isset( $user->allcaps[ Menu_Config::CAPABILITY_EDIT_POSTS ] ) && $user->allcaps[ Menu_Config::CAPABILITY_EDIT_POSTS ]; $has_manage_options = isset( $user->allcaps[ Menu_Config::CAPABILITY_MANAGE_OPTIONS ] ) && $user->allcaps[ Menu_Config::CAPABILITY_MANAGE_OPTIONS ]; $is_edit_posts_user = $has_edit_posts && ! $has_manage_options; return [ 'user' => $user, 'has_edit_posts' => $has_edit_posts, 'has_manage_options' => $has_manage_options, 'is_edit_posts_user' => $is_edit_posts_user, ]; } } editor-one/classes/legacy-submenu-interceptor.php 0000644 00000011232 15252521351 0016233 0 ustar 00 <?php namespace Elementor\Modules\EditorOne\Classes; use Elementor\Core\Admin\EditorOneMenu\Menu\Legacy_Submenu_Item; use Elementor\Core\Admin\EditorOneMenu\Menu\Legacy_Submenu_Item_Not_Mapped; use Elementor\Core\Admin\EditorOneMenu\Menu\Third_Party_Pages_Menu; if ( ! defined( 'ABSPATH' ) ) { exit; } class Legacy_Submenu_Interceptor { private Menu_Data_Provider $menu_data_provider; private Slug_Normalizer $slug_normalizer; private bool $third_party_parent_menu_registered = false; public function __construct( Menu_Data_Provider $menu_data_provider, Slug_Normalizer $slug_normalizer ) { $this->menu_data_provider = $menu_data_provider; $this->slug_normalizer = $slug_normalizer; } public function intercept_all( bool $is_pro_module_enabled ): void { global $submenu; $this->intercept_elementor_menu_items( $submenu[ Menu_Config::ELEMENTOR_MENU_SLUG ] ?? [], $is_pro_module_enabled ); if ( $is_pro_module_enabled ) { return; } $this->intercept_templates_menu_items( $submenu[ Menu_Config::LEGACY_TEMPLATES_SLUG ] ?? [] ); } public function intercept_elementor_menu_items( array $submenu_items, bool $is_pro_module_enabled ): array { if ( empty( $submenu_items ) ) { return $submenu_items; } $legacy_pro_mapping = Menu_Config::get_legacy_pro_mapping(); $items_to_remove = []; foreach ( $submenu_items as $index => $submenu_item ) { $item_slug = $submenu_item[2] ?? ''; if ( empty( $item_slug ) ) { continue; } if ( $this->menu_data_provider->is_item_already_registered( $item_slug ) ) { continue; } $mapping_key = $this->find_mapping_key( $item_slug, $legacy_pro_mapping ); if ( null !== $mapping_key ) { if ( ! $is_pro_module_enabled ) { $this->register_mapped_item( $submenu_item, $mapping_key, $legacy_pro_mapping ); } } else { $this->register_unmapped_item( $submenu_item ); } $items_to_remove[] = $index; } foreach ( $items_to_remove as $index ) { unset( $submenu_items[ $index ] ); } return $submenu_items; } public function intercept_templates_menu_items( array $submenu_items ): array { if ( empty( $submenu_items ) ) { return $submenu_items; } $items_to_remove = []; foreach ( $submenu_items as $index => $submenu_item ) { $item_slug = $submenu_item[2] ?? ''; if ( empty( $item_slug ) ) { continue; } if ( $this->menu_data_provider->is_item_already_registered( $item_slug ) ) { $items_to_remove[] = $index; continue; } $submenu_item[4] = Menu_Config::TEMPLATES_GROUP_ID; $legacy_item = new Legacy_Submenu_Item( $submenu_item, Menu_Config::LEGACY_TEMPLATES_SLUG ); $this->menu_data_provider->register_menu( $legacy_item ); $items_to_remove[] = $index; } foreach ( $items_to_remove as $index ) { unset( $submenu_items[ $index ] ); } return $submenu_items; } public function find_mapping_key( string $item_slug, array $mapping ): ?string { if ( isset( $mapping[ $item_slug ] ) ) { return $item_slug; } $decoded_slug = html_entity_decode( $item_slug ); if ( isset( $mapping[ $decoded_slug ] ) ) { return $decoded_slug; } $normalized_slug = $this->slug_normalizer->normalize( $item_slug ); foreach ( $mapping as $key => $value ) { $normalized_key = $this->slug_normalizer->normalize( $key ); if ( $normalized_slug === $normalized_key ) { return $key; } } return null; } private function register_mapped_item( array $submenu_item, string $mapping_key, array $legacy_pro_mapping ): void { $item_slug = $submenu_item[2]; if ( isset( $legacy_pro_mapping[ $mapping_key ]['label'] ) ) { $submenu_item[0] = $legacy_pro_mapping[ $mapping_key ]['label']; } $position = Menu_Config::get_attribute_mapping()[ $item_slug ]['position'] ?? 100; $group_id = $legacy_pro_mapping[ $mapping_key ]['group']; $submenu_item[4] = $group_id; $legacy_item = new Legacy_Submenu_Item( $submenu_item, Menu_Config::ELEMENTOR_MENU_SLUG, $position ); $this->menu_data_provider->register_menu( $legacy_item ); } private function register_unmapped_item( array $submenu_item ): void { $this->ensure_third_party_parent_registered(); $item_slug = $submenu_item[2]; $position = Menu_Config::get_attribute_mapping()[ $item_slug ]['position'] ?? 100; $legacy_item = new Legacy_Submenu_Item_Not_Mapped( $submenu_item, Menu_Config::ELEMENTOR_MENU_SLUG, $position ); $this->menu_data_provider->register_menu( $legacy_item ); } private function ensure_third_party_parent_registered(): void { if ( $this->third_party_parent_menu_registered ) { return; } $this->menu_data_provider->register_menu( new Third_Party_Pages_Menu() ); $this->third_party_parent_menu_registered = true; } } editor-one/classes/url-matcher.php 0000644 00000002263 15252521351 0013206 0 ustar 00 <?php namespace Elementor\Modules\EditorOne\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; } class Url_Matcher { public function get_match_score( string $menu_url, string $current_uri ): int { $menu_parsed = wp_parse_url( $menu_url ); if ( empty( $menu_parsed['path'] ) ) { return -1; } $current_parsed = wp_parse_url( $current_uri ); if ( empty( $current_parsed['path'] ) ) { return -1; } if ( basename( $menu_parsed['path'] ) !== basename( $current_parsed['path'] ) ) { return -1; } $menu_query = $this->parse_query_string( $menu_parsed['query'] ?? '' ); $current_query = $this->parse_query_string( $current_parsed['query'] ?? '' ); if ( ! $this->query_params_match( $menu_query, $current_query ) ) { return -1; } return count( $menu_query ); } public function parse_query_string( string $query ): array { $params = []; if ( '' !== $query ) { parse_str( $query, $params ); } return $params; } public function query_params_match( array $required, array $actual ): bool { foreach ( $required as $key => $value ) { if ( ! isset( $actual[ $key ] ) || $actual[ $key ] !== $value ) { return false; } } return true; } } editor-one/module.php 0000644 00000005042 15252521351 0010611 0 ustar 00 <?php namespace Elementor\Modules\EditorOne; use Elementor\Core\Admin\EditorOneMenu\Elementor_One_Menu_Manager; use Elementor\Core\Base\Module as BaseModule; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\Modules\EditorOne\Components\Sidebar_Navigation_Handler; use Elementor\Modules\EditorOne\Components\Top_Bar_Handler; if ( ! defined( 'ABSPATH' ) ) { exit; } class Module extends BaseModule { const CUSTOM_REACT_APP_PAGES = [ 'elementor-element-manager', ]; public function get_name(): string { return 'editor-one'; } public function __construct() { parent::__construct(); if ( is_admin() ) { $this->add_component( 'editor-one-menu-manager', new Elementor_One_Menu_Manager() ); $this->add_component( 'sidebar-navigation-handler', new Sidebar_Navigation_Handler() ); $this->add_component( 'top-bar-handler', new Top_Bar_Handler() ); } add_action( 'current_screen', function () { $menu_data_provider = Menu_Data_Provider::instance(); if ( ! $menu_data_provider->is_editor_one_admin_page() ) { return; } add_action( 'admin_enqueue_scripts', function () { $this->enqueue_styles(); } ); } ); add_filter( 'elementor/admin-top-bar/is-active', function ( $_is_active ) { return false; } ); } /** * Check if current page has a custom React app that uses @elementor/ui * * @return bool */ private function is_custom_react_app_page() { $current_screen = get_current_screen(); if ( ! $current_screen ) { return false; } foreach ( self::CUSTOM_REACT_APP_PAGES as $page_slug ) { if ( str_contains( $current_screen->id ?? '', $page_slug ) ) { return true; } } return false; } /** * Enqueue admin styles */ private function enqueue_styles() { wp_enqueue_style( 'elementor-admin' ); wp_enqueue_style( 'elementor-editor-one-common', $this->get_css_assets_url( 'editor-one-common' ), [ 'elementor-admin' ], ELEMENTOR_VERSION ); if ( ! $this->is_custom_react_app_page() ) { wp_enqueue_style( 'elementor-editor-one-elements', $this->get_css_assets_url( 'editor-one-elements' ), [ 'elementor-editor-one-common' ], ELEMENTOR_VERSION ); wp_enqueue_style( 'elementor-editor-one-tables', $this->get_css_assets_url( 'editor-one-tables' ), [ 'elementor-editor-one-common' ], ELEMENTOR_VERSION ); } wp_enqueue_script( 'editor-one-admin', $this->get_js_assets_url( 'editor-one-admin' ), [ 'jquery' ], ELEMENTOR_VERSION, true ); } } editor-one/components/sidebar-navigation-handler.php 0000644 00000007274 15252521351 0016703 0 ustar 00 <?php namespace Elementor\Modules\EditorOne\Components; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Modules\EditorOne\Classes\Active_Menu_Resolver; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\Modules\EditorOne\Classes\Url_Matcher; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Sidebar_Navigation_Handler { private const PROMOTION_URL = 'https://go.elementor.com/go-pro-upgrade-wp-editor-inner-menu/'; private Menu_Data_Provider $menu_data_provider; private Active_Menu_Resolver $active_menu_resolver; public function __construct() { $this->menu_data_provider = Menu_Data_Provider::instance(); $this->active_menu_resolver = new Active_Menu_Resolver( new Url_Matcher() ); $this->register_actions(); } private function register_actions(): void { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_sidebar_assets' ] ); add_action( 'in_admin_header', [ $this, 'render_sidebar_container' ] ); add_filter( 'admin_body_class', [ $this, 'add_body_class' ] ); } public function add_body_class( string $classes ): string { if ( ! $this->menu_data_provider->is_editor_one_admin_page() ) { return $classes; } $classes .= ' e-has-sidebar-navigation e-has-elementor-home-menu'; return $classes; } public function enqueue_sidebar_assets(): void { if ( ! $this->menu_data_provider->is_editor_one_admin_page() ) { return; } $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_style( 'elementor-sidebar-navigation', ELEMENTOR_ASSETS_URL . 'css/modules/editor-one/sidebar-navigation' . $min_suffix . '.css', [], ELEMENTOR_VERSION ); wp_enqueue_script( 'editor-one-sidebar-navigation', ELEMENTOR_ASSETS_URL . 'js/editor-one-sidebar-navigation' . $min_suffix . '.js', [ 'react', 'react-dom', 'elementor-common', 'elementor-v2-ui', 'elementor-v2-icons', ], ELEMENTOR_VERSION, true ); wp_localize_script( 'editor-one-sidebar-navigation', 'editorOneSidebarConfig', $this->get_sidebar_config() ); wp_set_script_translations( 'editor-one-sidebar-navigation', 'elementor' ); } public function render_sidebar_container(): void { if ( ! $this->menu_data_provider->is_editor_one_admin_page() ) { return; } echo '<div id="editor-one-sidebar-navigation"></div>'; } private function get_sidebar_config(): array { $flyout_data = $this->menu_data_provider->get_third_level_data( Menu_Data_Provider::THIRD_LEVEL_EDITOR_FLYOUT ); $level4_groups = $this->menu_data_provider->get_level4_flyout_data(); $promotion = $this->get_promotion_data(); $active_state = $this->get_active_menu_state( $flyout_data['items'], $level4_groups ); return [ 'menuItems' => $flyout_data['items'], 'level4Groups' => $level4_groups, 'activeMenuSlug' => $active_state['menu_slug'], 'activeChildSlug' => $active_state['child_slug'], 'siteTitle' => esc_html__( 'Editor', 'elementor' ), 'hasPro' => Utils::has_pro(), 'upgradeUrl' => $promotion['url'], 'upgradeText' => $promotion['text'], ]; } private function get_promotion_data(): array { return Filtered_Promotions_Manager::get_filtered_promotion_data( [ 'text' => esc_html__( 'Upgrade plan', 'elementor' ), 'url' => self::PROMOTION_URL, ], 'elementor/sidebar/promotion', 'url' ); } private function get_active_menu_state( array $menu_items, array $level4_groups ): array { $current_page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? ''; $current_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) ); return $this->active_menu_resolver->resolve( $menu_items, $level4_groups, $current_page, $current_uri ); } } editor-one/components/top-bar-handler.php 0000644 00000003153 15252521351 0014471 0 ustar 00 <?php namespace Elementor\Modules\EditorOne\Components; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Top_Bar_Handler { private Menu_Data_Provider $menu_data_provider; public function __construct() { $this->menu_data_provider = Menu_Data_Provider::instance(); $this->register_actions(); } private function register_actions(): void { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'in_admin_header', [ $this, 'render_top_bar_container' ] ); } public function enqueue_assets(): void { if ( ! $this->menu_data_provider->is_editor_one_admin_page() ) { return; } $min_suffix = Utils::is_script_debug() ? '' : '.min'; wp_enqueue_style( 'elementor-one-top-bar', ELEMENTOR_ASSETS_URL . 'css/modules/editor-one/top-bar' . $min_suffix . '.css', [], ELEMENTOR_VERSION ); wp_enqueue_script( 'editor-one-top-bar', ELEMENTOR_ASSETS_URL . 'js/editor-one-top-bar' . $min_suffix . '.js', [ 'react', 'react-dom', 'elementor-common', 'elementor-v2-ui', 'elementor-v2-icons', ], ELEMENTOR_VERSION, true ); wp_localize_script( 'editor-one-top-bar', 'elementorOneTopBarConfig', [ 'version' => ELEMENTOR_VERSION, 'title' => __( 'website builder', 'elementor' ), 'environment' => apply_filters( 'elementor/environment', 'production' ), ] ); } public function render_top_bar_container(): void { if ( ! $this->menu_data_provider->is_editor_one_admin_page() ) { return; } echo '<div id="editor-one-top-bar"></div>'; } } feedback/module.php 0000644 00000003474 15252521351 0010277 0 ustar 00 <?php namespace Elementor\Modules\Feedback; use Elementor\Core\Base\Module as Module_Base; use Elementor\Modules\Feedback\Data\Controller; use Elementor\Plugin; use Elementor\Api; use Elementor\Core\Common\Modules\Connect\Rest\Rest_Api; use Elementor\Utils; use http\Cookie as HttpCookie; use WP_Http_Cookie; use WpOrg\Requests\Cookie; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends Module_Base { public function __construct() { add_action( 'rest_api_init', fn() => self::register_routes() ); } protected function register_routes() { register_rest_route( 'elementor/v1/feedback', '/submit', [ 'methods' => 'POST', 'callback' => fn( $request ) => $this->handle_submit( $request ), 'permission_callback' => '__return_true', ] ); } protected function handle_submit( $request, $additional_cookies = [] ) { $user_meta = get_user_meta( get_current_user_id(), 'wp_elementor_connect_common_data' ); $app = Plugin::$instance->common->get_component( 'connect' )->get_app( 'feedback' ); $body = [ 'title' => 'Editor Feedback', 'description' => $request->get_param( 'description' ), 'product' => 'EDITOR', 'subject' => 'Editor Feedback', ]; $response = $app->submit( $body ); $response_code = $response['response']['code']; if ( 'OK' === $response['response']['message'] ) { return [ 'success' => true, 'code' => $response_code, 'message' => esc_html__( 'Feedback submitted successfully.', 'elementor' ), ]; } else { $message = $response['data']['message'] ?? esc_html__( 'Failed to submit feedback.', 'elementor' ); return [ 'success' => false, 'code' => $response_code, 'message' => $message, ]; } } /** * Retrieve the module name. * * @return string */ public function get_name() { return 'feedback'; } } element-cache/module.php 0000644 00000010577 15252521351 0011247 0 ustar 00 <?php namespace Elementor\Modules\ElementCache; use Elementor\Controls_Manager; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Experiments\Manager as ExperimentsManager; use Elementor\Element_Base; use Elementor\Plugin; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const OPTION_UNIQUE_ID = '_elementor_element_cache_unique_id'; public function get_name() { return 'element-cache'; } public function __construct() { parent::__construct(); $this->register_shortcode(); add_filter( 'elementor/element_cache/unique_id', [ $this, 'get_unique_id' ] ); $this->add_advanced_tab_actions(); if ( is_admin() ) { add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 100 ); } $this->clear_cache_on_site_changed(); } public function get_unique_id() { $unique_id = get_option( static::OPTION_UNIQUE_ID ); if ( ! $unique_id ) { $unique_id = md5( uniqid( wp_generate_password() ) ); update_option( static::OPTION_UNIQUE_ID, $unique_id ); } return $unique_id; } private function register_shortcode() { add_shortcode( 'elementor-element', function ( $atts ) { if ( empty( $atts['data'] ) ) { return ''; } if ( empty( $atts['k'] ) || $atts['k'] !== $this->get_unique_id() ) { return ''; } $widget_data = json_decode( base64_decode( $atts['data'] ), true ); if ( empty( $widget_data ) || ! is_array( $widget_data ) ) { return ''; } ob_start(); $element = Plugin::$instance->elements_manager->create_element_instance( $widget_data ); if ( $element ) { $element->print_element(); } return ob_get_clean(); } ); } private function add_advanced_tab_actions() { $hooks = [ 'elementor/element/common/_section_style/after_section_end' => '_css_classes', // Widgets ]; foreach ( $hooks as $hook => $injection_position ) { add_action( $hook, function( $element, $args ) use ( $injection_position ) { $this->add_control_to_advanced_tab( $element, $args, $injection_position ); }, 10, 2 ); } } private function add_control_to_advanced_tab( Element_Base $element, $args, $injection_position ) { $element->start_injection( [ 'of' => $injection_position, ] ); $control_data = [ 'label' => esc_html__( 'Cache Settings', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => '', 'options' => [ '' => esc_html__( 'Default', 'elementor' ), 'yes' => esc_html__( 'Inactive', 'elementor' ), 'no' => esc_html__( 'Active', 'elementor' ), ], ]; $element->add_control( '_element_cache', $control_data ); $element->end_injection(); } public function register_admin_fields( Settings $settings ) { $settings->add_field( Settings::TAB_PERFORMANCE, Settings::TAB_PERFORMANCE, 'element_cache_ttl', [ 'label' => esc_html__( 'Element Cache', 'elementor' ), 'field_args' => [ 'class' => 'elementor-element-cache-ttl', 'type' => 'select', 'std' => '24', 'options' => [ 'disable' => esc_html__( 'Disable', 'elementor' ), '1' => esc_html__( '1 Hour', 'elementor' ), '6' => esc_html__( '6 Hours', 'elementor' ), '12' => esc_html__( '12 Hours', 'elementor' ), '24' => esc_html__( '1 Day', 'elementor' ), '72' => esc_html__( '3 Days', 'elementor' ), '168' => esc_html__( '1 Week', 'elementor' ), '336' => esc_html__( '2 Weeks', 'elementor' ), '720' => esc_html__( '1 Month', 'elementor' ), '8760' => esc_html__( '1 Year', 'elementor' ), ], 'desc' => esc_html__( 'Specify the duration for which data is stored in the cache. Elements caching speeds up loading by serving pre-rendered copies of elements, rather than rendering them fresh each time. This control ensures efficient performance and up-to-date content.', 'elementor' ), ], ] ); } private function clear_cache_on_site_changed() { add_action( 'activated_plugin', [ $this, 'clear_cache' ] ); add_action( 'deactivated_plugin', [ $this, 'clear_cache' ] ); add_action( 'switch_theme', [ $this, 'clear_cache' ] ); add_action( 'upgrader_process_complete', [ $this, 'clear_cache' ] ); add_action( 'update_option_elementor_element_cache_ttl', [ $this, 'clear_cache' ] ); } public function clear_cache() { Plugin::$instance->files_manager->clear_cache(); } } element-manager/ajax.php 0000644 00000013343 15252521351 0011246 0 ustar 00 <?php namespace Elementor\Modules\ElementManager; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; use Elementor\Modules\Usage\Module as Usage_Module; use Elementor\Api; use Elementor\Plugin; use Elementor\User; use Elementor\Utils; use Elementor\Core\Utils\Promotions\Validate_Promotion; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Ajax { const ELEMENT_MANAGER_PROMOTION_URL = 'https://go.elementor.com/go-pro-element-manager/'; const FREE_TO_PRO_PERMISSIONS_PROMOTION_URL = 'https://go.elementor.com/go-pro-element-manager-permissions/'; const PRO_TO_ADVANCED_PERMISSIONS_PROMOTION_URL = 'https://go.elementor.com/go-pro-advanced-element-manager-permissions/'; public function register_endpoints() { add_action( 'wp_ajax_elementor_element_manager_get_admin_app_data', [ $this, 'ajax_get_admin_page_data' ] ); add_action( 'wp_ajax_elementor_element_manager_save_disabled_elements', [ $this, 'ajax_save_disabled_elements' ] ); add_action( 'wp_ajax_elementor_element_manager_get_widgets_usage', [ $this, 'ajax_get_widgets_usage' ] ); } public function ajax_get_admin_page_data() { $this->verify_permission(); $this->force_enabled_all_elements(); $widgets = []; $plugins = []; foreach ( Plugin::$instance->widgets_manager->get_widget_types() as $widget ) { $widget_title = sanitize_user( $widget->get_title() ); if ( empty( $widget_title ) || ! $widget->show_in_panel() ) { continue; } $plugin_name = $this->get_plugin_name_from_widget_instance( $widget ); if ( ! in_array( $plugin_name, $plugins ) ) { $plugins[] = $plugin_name; } $widgets[] = [ 'name' => $widget->get_name(), 'plugin' => $plugin_name, 'title' => $widget_title, 'icon' => $widget->get_icon(), ]; } $notice_id = 'e-element-manager-intro-1'; $data = [ 'disabled_elements' => Options::get_disabled_elements(), 'promotion_widgets' => [], 'widgets' => $widgets, 'plugins' => $plugins, 'notice_data' => [ 'notice_id' => $notice_id, 'is_viewed' => User::is_user_notice_viewed( $notice_id ), 'nonce' => wp_create_nonce( 'elementor_set_admin_notice_viewed' ), ], 'promotion_data' => [ 'manager_permissions' => [ 'pro' => $this->get_element_manager_promotion( [ 'text' => esc_html__( 'Upgrade Now', 'elementor' ), 'url' => self::FREE_TO_PRO_PERMISSIONS_PROMOTION_URL, ], 'pro_permissions' ), 'advanced' => $this->get_element_manager_promotion( [ 'text' => esc_html__( 'Upgrade Now', 'elementor' ), 'url' => self::PRO_TO_ADVANCED_PERMISSIONS_PROMOTION_URL, ], 'advanced_permissions' ), ], 'element_manager' => $this->get_element_manager_promotion( [ 'text' => esc_html__( 'Upgrade Now', 'elementor' ), 'url' => self::ELEMENT_MANAGER_PROMOTION_URL, ], 'element_manager' ), ], ]; if ( ! Utils::has_pro() ) { $data['promotion_widgets'] = Api::get_promotion_widgets(); } $data['additional_data'] = apply_filters( 'elementor/element_manager/admin_app_data/additional_data', [] ); wp_send_json_success( $data ); } private function get_element_manager_promotion( $promotion_data, $filter_id ): array { return Filtered_Promotions_Manager::get_filtered_promotion_data( $promotion_data, 'elementor/element_manager/admin_app_data/promotion_data/' . $filter_id, 'url' ); } private function verify_permission() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( esc_html__( 'You do not have permission to edit these settings.', 'elementor' ) ); } $nonce = Utils::get_super_global_value( $_POST, 'nonce' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'e-element-manager-app' ) ) { wp_send_json_error( esc_html__( 'Invalid nonce.', 'elementor' ) ); } } private function force_enabled_all_elements() { remove_all_filters( 'elementor/widgets/is_widget_enabled' ); } private function get_plugin_name_from_widget_instance( $widget ) { if ( in_array( 'wordpress', $widget->get_categories() ) ) { // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText return esc_html__( 'WordPress Widgets', 'elementor' ); } $class_reflection = new \ReflectionClass( $widget ); $plugin_basename = plugin_basename( $class_reflection->getFileName() ); $plugin_directory = strtok( $plugin_basename, '/' ); $plugins_data = get_plugins( '/' . $plugin_directory ); $plugin_data = array_shift( $plugins_data ); return $plugin_data['Name'] ?? esc_html__( 'Unknown', 'elementor' ); } public function ajax_save_disabled_elements() { $this->verify_permission(); $elements = Utils::get_super_global_value( $_POST, 'widgets' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( empty( $elements ) ) { wp_send_json_error( esc_html__( 'No elements to save.', 'elementor' ) ); } $disabled_elements = json_decode( $elements ); if ( ! is_array( $disabled_elements ) ) { wp_send_json_error( esc_html__( 'Unexpected elements data.', 'elementor' ) ); } Options::update_disabled_elements( $disabled_elements ); do_action( 'elementor/element_manager/save_disabled_elements' ); wp_send_json_success(); } public function ajax_get_widgets_usage() { $this->verify_permission(); /** @var Usage_Module $usage_module */ $usage_module = Usage_Module::instance(); $usage_module->recalc_usage(); $widgets_usage = []; foreach ( $usage_module->get_formatted_usage( 'raw' ) as $data ) { foreach ( $data['elements'] as $element => $count ) { if ( ! isset( $widgets_usage[ $element ] ) ) { $widgets_usage[ $element ] = 0; } $widgets_usage[ $element ] += $count; } } wp_send_json_success( $widgets_usage ); } } element-manager/module.php 0000644 00000006021 15252521351 0011603 0 ustar 00 <?php namespace Elementor\Modules\ElementManager; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\Admin\Menu\Admin_Menu_Manager; use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider; use Elementor\Modules\ElementManager\AdminMenuItems\Editor_One_Elements_Manager_Menu; use Elementor\Widget_Base; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const PAGE_ID = 'elementor-element-manager'; public function get_name() { return 'element-manager'; } public function __construct() { parent::__construct(); $ajax = new Ajax(); $ajax->register_endpoints(); add_action( 'elementor/editor-one/menu/register', function ( Menu_Data_Provider $menu_data_provider ) { $this->register_editor_one_menu( $menu_data_provider ); } ); add_action( 'elementor/editor-one/menu/after_register_hidden_submenus', function ( array $hooks ) { $this->enqueue_assets_for_editor_one_menu( $hooks ); } ); add_filter( 'elementor/widgets/is_widget_enabled', function( $should_register, Widget_Base $widget_instance ) { return ! Options::is_element_disabled( $widget_instance->get_name() ); }, 10, 2 ); add_filter( 'elementor/system-info/usage/settings', function( $usage ) { $disabled_elements = Options::get_disabled_elements(); if ( ! empty( $disabled_elements ) ) { $usage['disabled_elements'] = implode( ', ', $disabled_elements ); } return $usage; } ); add_filter( 'elementor/tracker/send_tracking_data_params', function( $params ) { $disabled_elements = Options::get_disabled_elements(); if ( ! empty( $disabled_elements ) ) { $params['usages']['disabled_elements'] = $disabled_elements; } return $params; } ); } public function enqueue_assets_for_editor_one_menu( array $hooks ): void { if ( ! empty( $hooks[ static::PAGE_ID ] ) ) { add_action( "admin_print_scripts-{$hooks[ static::PAGE_ID ]}", [ $this, 'enqueue_assets' ] ); add_action( "admin_footer-{$hooks[ static::PAGE_ID ]}", [ $this, 'print_styles' ], 1000 ); } } private function register_editor_one_menu( Menu_Data_Provider $menu_data_provider ): void { $menu_data_provider->register_menu( new Editor_One_Elements_Manager_Menu() ); } public function enqueue_assets() { wp_enqueue_script( 'e-element-manager-app', $this->get_js_assets_url( 'element-manager-admin' ), [ 'wp-element', 'wp-components', 'wp-dom-ready', 'wp-i18n', 'elementor-v2-ui', 'elementor-v2-icons', ], ELEMENTOR_VERSION ); wp_localize_script( 'e-element-manager-app', 'eElementManagerConfig', [ 'nonce' => wp_create_nonce( 'e-element-manager-app' ), 'ajaxurl' => admin_url( 'admin-ajax.php' ), ] ); wp_set_script_translations( 'e-element-manager-app', 'elementor' ); wp_enqueue_style( 'wp-components' ); wp_enqueue_style( 'wp-format-library' ); } public function print_styles() { ?> <style> .components-button.is-secondary:disabled { box-shadow: inset 0 0 0 1px #949494; } </style> <?php } } element-manager/admin-menu-items/editor-one-elements-manager-menu.php 0000644 00000002402 15252521351 0021717 0 ustar 00 <?php namespace Elementor\Modules\ElementManager\AdminMenuItems; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface; use Elementor\Modules\EditorOne\Classes\Menu_Config; use Elementor\Modules\ElementManager\Module; if ( ! defined( 'ABSPATH' ) ) { exit; } class Editor_One_Elements_Manager_Menu implements Menu_Item_Interface, Admin_Menu_Item_With_Page { public function get_capability(): string { return 'manage_options'; } public function get_parent_slug(): string { return Menu_Config::ELEMENTOR_MENU_SLUG; } public function is_visible(): bool { return true; } public function get_label(): string { return esc_html__( 'Element Manager', 'elementor' ); } public function get_position(): int { return 20; } public function get_slug(): string { return Module::PAGE_ID; } public function get_group_id(): string { return Menu_Config::SYSTEM_GROUP_ID; } public function get_page_title() { return $this->get_label(); } public function render() { echo '<div class="wrap">'; echo '<h1 class="wp-heading-inline">' . esc_html__( 'Element Manager', 'elementor' ) . '</h1>'; echo '<div id="elementor-element-manager-wrap"></div>'; echo '</div>'; } } element-manager/admin-menu-app.php 0000644 00000001564 15252521351 0013135 0 ustar 00 <?php namespace Elementor\Modules\ElementManager; use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page; use Elementor\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin_Menu_App implements Admin_Menu_Item_With_Page { public function is_visible() { return true; } public function get_parent_slug() { return Settings::PAGE_ID; } public function get_label() { return esc_html__( 'Element Manager', 'elementor' ); } public function get_page_title() { return esc_html__( 'Element Manager', 'elementor' ); } public function get_capability() { return 'manage_options'; } public function render() { echo '<div class="wrap">'; echo '<h3 class="wp-heading-inline">' . esc_html__( 'Element Manager', 'elementor' ) . '</h3>'; echo '<div id="elementor-element-manager-wrap"></div>'; echo '</div>'; } } element-manager/options.php 0000644 00000001024 15252521351 0012007 0 ustar 00 <?php namespace Elementor\Modules\ElementManager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Options { public static function get_disabled_elements() { return (array) get_option( 'elementor_disabled_elements', [] ); } public static function update_disabled_elements( $elements ) { update_option( 'elementor_disabled_elements', (array) $elements ); } public static function is_element_disabled( $element_name ) { return in_array( $element_name, self::get_disabled_elements() ); } } dynamic-tags/module.php 0000644 00000006034 15252521351 0011126 0 ustar 00 <?php namespace Elementor\Modules\DynamicTags; use Elementor\Core\Base\Module as BaseModule; use Elementor\Core\DynamicTags\Base_Tag; use Elementor\Core\DynamicTags\Manager; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor dynamic tags module. * * Elementor dynamic tags module handler class is responsible for registering * and managing Elementor dynamic tags modules. * * @since 2.0.0 */ class Module extends BaseModule { /** * Base dynamic tag group. */ const BASE_GROUP = 'base'; /** * Dynamic tags text category. */ const TEXT_CATEGORY = 'text'; /** * Dynamic tags URL category. */ const URL_CATEGORY = 'url'; /** * Dynamic tags image category. */ const IMAGE_CATEGORY = 'image'; /** * Dynamic tags media category. */ const MEDIA_CATEGORY = 'media'; /** * Dynamic tags post meta category. */ const POST_META_CATEGORY = 'post_meta'; /** * Dynamic tags gallery category. */ const GALLERY_CATEGORY = 'gallery'; /** * Dynamic tags number category. */ const NUMBER_CATEGORY = 'number'; /** * Dynamic tags number category. */ const COLOR_CATEGORY = 'color'; /** * Dynamic tags datetime category. */ const DATETIME_CATEGORY = 'datetime'; /** * Dynamic tags SVG category. */ const SVG_CATEGORY = 'svg'; /** * Dynamic tags module constructor. * * Initializing Elementor dynamic tags module. * * @since 2.0.0 * @access public */ public function __construct() { $this->register_groups(); add_action( 'elementor/dynamic_tags/register', [ $this, 'register_tags' ] ); } /** * Get module name. * * Retrieve the dynamic tags module name. * * @since 2.0.0 * @access public * * @return string Module name. */ public function get_name() { return 'dynamic_tags'; } /** * Get classes names. * * Retrieve the dynamic tag classes names. * * @since 2.0.0 * @access public * * @return array Tag dynamic tag classes names. */ public function get_tag_classes_names() { return []; } /** * Get groups. * * Retrieve the dynamic tag groups. * * @since 2.0.0 * @access public * * @return array Tag dynamic tag groups. */ public function get_groups() { return [ self::BASE_GROUP => [ 'title' => 'Base Tags', ], ]; } /** * Register groups. * * Add all the available tag groups. * * @since 2.0.0 * @access private */ private function register_groups() { foreach ( $this->get_groups() as $group_name => $group_settings ) { Plugin::$instance->dynamic_tags->register_group( $group_name, $group_settings ); } } /** * Register tags. * * Add all the available dynamic tags. * * @since 2.0.0 * @access public * * @param Manager $dynamic_tags */ public function register_tags( $dynamic_tags ) { foreach ( $this->get_tag_classes_names() as $tag_class ) { /** @var Base_Tag $class_name */ $class_name = $this->get_reflection()->getNamespaceName() . '\Tags\\' . $tag_class; $dynamic_tags->register( new $class_name() ); } } } elementor-capabilities-mcp/module.php 0000644 00000003324 15252521351 0013743 0 ustar 00 <?php namespace Elementor\Modules\ElementorCapabilitiesMcp; use Elementor\Core\Base\Module as BaseModule; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; } class Module extends BaseModule { private const PACKAGE_NAME = 'elementor-capabilities-mcp'; private const REQUIRED_PACKAGES = [ 'utils', 'schema', 'elementor-mcp-common', 'editor-v1-adapters', 'editor-mcp', 'elementor-capabilities-mcp', ]; public function get_name(): string { return self::PACKAGE_NAME; } public static function is_active(): bool { return is_admin(); } public function __construct() { parent::__construct(); add_action( 'admin_enqueue_scripts', [ $this, 'register_packages' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ], 20 ); add_filter( 'elementor/editor/v2/packages', [ $this, 'add_editor_packages' ] ); } public function add_editor_packages( array $packages ): array { $packages[] = self::PACKAGE_NAME; return $packages; } public function register_packages(): void { $suffix = Utils::is_script_debug() ? '' : '.min'; foreach ( self::REQUIRED_PACKAGES as $package ) { $asset_file = ELEMENTOR_ASSETS_PATH . "js/packages/{$package}/{$package}.asset.php"; if ( ! file_exists( $asset_file ) ) { continue; } $asset = require $asset_file; $handle = $asset['handle'] ?? "elementor-v2-{$package}"; if ( wp_script_is( $handle, 'registered' ) ) { continue; } wp_register_script( $handle, ELEMENTOR_ASSETS_URL . "js/packages/{$package}/{$package}{$suffix}.js", $asset['deps'] ?? [], ELEMENTOR_VERSION, true ); } } public function enqueue_scripts(): void { wp_enqueue_script( 'elementor-v2-' . self::PACKAGE_NAME ); } } content-sanitizer/module.php 0000644 00000002503 15252521351 0012223 0 ustar 00 <?php namespace Elementor\Modules\ContentSanitizer; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseModule { const WIDGET_TO_SANITIZE = 'heading'; public function __construct() { parent::__construct(); add_filter( 'elementor/document/save/data', [ $this, 'sanitize_content' ], 10, 2 ); } public function get_name() { return 'content-sanitizer'; } public function sanitize_content( $data, $document ): array { if ( current_user_can( 'manage_options' ) || empty( $data['elements'] ) ) { return $data; } if ( ! $this->is_widget_present( $data ) ) { return $data; } return Plugin::$instance->db->iterate_data( $data, function ( $element ) { if ( $this->is_target_widget( $element ) ) { $element['settings']['title'] = Plugin::$instance->widgets_manager->get_widget_types( self::WIDGET_TO_SANITIZE )->sanitize( $element['settings']['title'] ); } return $element; }); } private function is_target_widget( $element ) { return self::WIDGET_TO_SANITIZE === $element['widgetType']; } private function is_widget_present( array $elements ): bool { $json = wp_json_encode( $elements ); return false !== strpos( $json, '"widgetType":"' . self::WIDGET_TO_SANITIZE . '"' ); } } content-sanitizer/interfaces/sanitizable.php 0000644 00000000311 15252521351 0015361 0 ustar 00 <?php namespace Elementor\Modules\ContentSanitizer\Interfaces; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Sanitizable { public function sanitize( $content ); } announcements/classes/announcement.php 0000644 00000002400 15252521351 0014256 0 ustar 00 <?php namespace Elementor\Modules\Announcements\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Announcement { /** * @var array */ protected $raw_data; /** * @var array */ protected $triggers; public function __construct( array $data ) { $this->raw_data = $data; $this->set_triggers(); } /** * @return array */ protected function get_triggers(): array { return $this->triggers; } protected function set_triggers() { $triggers = $this->raw_data['triggers'] ?? []; foreach ( $triggers as $trigger ) { $this->triggers[] = Utils::get_trigger_object( $trigger ); } } /** * Is Active is_active * * @return bool */ public function is_active(): bool { $triggers = $this->get_triggers(); if ( empty( $triggers ) ) { return true; } foreach ( $triggers as $trigger ) { if ( ! $trigger->is_active() ) { return false; } } return true; } public function after_triggered() { foreach ( $this->get_triggers() as $trigger ) { if ( $trigger->is_active() ) { $trigger->after_triggered(); } } } /** * @return array */ public function get_prepared_data(): array { $raw_data = $this->raw_data; unset( $raw_data['triggers'] ); return $raw_data; } } announcements/classes/utils.php 0000644 00000001440 15252521351 0012727 0 ustar 00 <?php namespace Elementor\Modules\Announcements\Classes; use Elementor\Modules\Announcements\Triggers\{ IsFlexContainerInactive, AiStarted }; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Utils { /** * Get trigger object. * * @param $trigger * * @return IsFlexContainerInactive|false */ public static function get_trigger_object( $trigger ) { $object_trigger = apply_filters( 'elementor/announcements/trigger_object', false, $trigger ); if ( false !== $object_trigger ) { return $object_trigger; } // @TODO - replace with trigger manager switch ( $trigger['action'] ) { case 'isFlexContainerInactive': return new IsFlexContainerInactive(); case 'aiStarted': return new AiStarted(); default: return false; } } } announcements/classes/trigger-base.php 0000644 00000000672 15252521351 0014150 0 ustar 00 <?php namespace Elementor\Modules\Announcements\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Trigger_Base { /** * @var string */ protected $name = 'trigger-base'; /** * @return string */ public function get_name(): string { return $this->name; } /** * @return bool */ public function is_active(): bool { return true; } public function after_triggered() { } } announcements/module.php 0000644 00000006142 15252521351 0011423 0 ustar 00 <?php namespace Elementor\Modules\Announcements; use Elementor\Core\Base\App as BaseApp; use Elementor\Modules\Announcements\Classes\Announcement; use Elementor\Settings as ElementorSettings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends BaseApp { /** * @return bool */ public static function is_active(): bool { return is_admin(); } /** * @return string */ public function get_name(): string { return 'announcements'; } /** * Render wrapper for the app to load. */ private function render_app_wrapper() { ?> <div id="e-announcements-root"></div> <?php } /** * Enqueue app scripts. */ private function enqueue_scripts() { wp_enqueue_script( 'announcements-app', $this->get_js_assets_url( 'announcements-app' ), [ 'wp-i18n', ], ELEMENTOR_VERSION, true ); wp_set_script_translations( 'announcements-app', 'elementor' ); $this->print_config( 'announcements-app' ); } /** * Get initialization settings to use in frontend. * * @return array[] */ protected function get_init_settings(): array { $active_announcements = $this->get_active_announcements(); $additional_settings = []; foreach ( $active_announcements as $announcement ) { $additional_settings[] = $announcement->get_prepared_data(); // @TODO - replace with ajax request from the front after actually triggered $announcement->after_triggered(); } return [ 'announcements' => $additional_settings, ]; } /** * Enqueue the module styles. */ public function enqueue_styles() { wp_enqueue_style( 'announcements-app', $this->get_css_assets_url( 'modules/announcements/announcements' ), [], ELEMENTOR_VERSION ); } /** * Retrieve all announcement in raw format ( array ). * * @return array[] */ private function get_raw_announcements(): array { $raw_announcements = []; // DO NOT USE THIS FILTER return apply_filters( 'elementor/announcements/raw_announcements', $raw_announcements ); } /** * Retrieve all announcement objects. * * @return array */ private function get_announcements(): array { $announcements = []; foreach ( $this->get_raw_announcements() as $announcement_data ) { $announcements[] = new Announcement( $announcement_data ); } return $announcements; } /** * Retrieve all active announcement objects. * * @return array */ private function get_active_announcements(): array { $active_announcements = []; foreach ( $this->get_announcements() as $announcement ) { if ( $announcement->is_active() ) { $active_announcements[] = $announcement; } } return $active_announcements; } public function __construct() { parent::__construct(); add_action( 'elementor/init', [ $this, 'on_elementor_init' ] ); } public function on_elementor_init() { if ( empty( $this->get_active_announcements() ) ) { return; } add_action( 'elementor/editor/footer', function () { $this->render_app_wrapper(); } ); add_action( 'elementor/editor/after_enqueue_scripts', function () { $this->enqueue_scripts(); $this->enqueue_styles(); } ); } } announcements/triggers/ai-started.php 0000644 00000001152 15252521351 0014015 0 ustar 00 <?php namespace Elementor\Modules\Announcements\Triggers; use Elementor\Modules\Announcements\Classes\Trigger_Base; use Elementor\User; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class AiStarted extends Trigger_Base { /** * @var string */ protected $name = 'ai-get-started-announcement'; public function after_triggered() { User::set_introduction_viewed( [ 'introductionKey' => $this->name ] ); } /** * @return bool */ public function is_active(): bool { return ! User::get_introduction_meta( 'ai_get_started' ) && ! User::get_introduction_meta( $this->name ); } } announcements/triggers/is-flex-container-inactive.php 0000644 00000002241 15252521351 0017107 0 ustar 00 <?php namespace Elementor\Modules\Announcements\Triggers; use Elementor\Modules\Announcements\Classes\Trigger_Base; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class IsFlexContainerInactive extends Trigger_Base { const USER_META_KEY = 'announcements_user_counter'; /** * @var string */ protected $name = 'is-flex-container-inactive'; /** * @return int */ protected function get_view_count(): int { $user_counter = $this->get_user_announcement_count(); return ! empty( $user_counter ) ? (int) $user_counter : 0; } public function after_triggered() { $new_counter = $this->get_view_count() + 1; update_user_meta( get_current_user_id(), self::USER_META_KEY, $new_counter ); } /** * @return bool */ public function is_active(): bool { $is_feature_active = Plugin::$instance->experiments->is_feature_active( 'container' ); $counter = $this->get_user_announcement_count(); return ! $is_feature_active && (int) $counter < 1; } /** * @return string */ private function get_user_announcement_count(): string { return get_user_meta( get_current_user_id(), self::USER_META_KEY, true ); } }
dvadf
dvadf
| ver. 1.4 |
Github
|
.
| PHP 8.2.33 | Generation time: 0.56 |
proxy
|
phpinfo
|
Settings