first commit

This commit is contained in:
2026-04-09 15:31:08 +02:00
commit f42e416839
8067 changed files with 2816468 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
<?php
namespace ElementorPro\Modules\AdminTopBar;
use ElementorPro\Plugin;
use ElementorPro\Base\Module_Base;
use ElementorPro\Core\Connect\Apps\Activate;
use ElementorPro\License\API;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
public function get_name() {
return 'admin-top-bar';
}
/**
* Module constructor.
*/
public function __construct() {
parent::__construct();
add_action( 'elementor/admin-top-bar/init', function ( $module ) {
$settings = $module->get_settings();
$current_screen = null;
// For BC support.
// when the action 'elementor/admin-top-bar/init' triggered before screen is registered.
// TODO: need to remove if elementor core version 3.5.0 is stable
if ( function_exists( 'get_current_screen' ) ) {
$current_screen = get_current_screen();
}
/** @var Activate $activate */
$activate = Plugin::elementor()->common->get_component( 'connect' )->get_app( 'activate' );
$settings['is_user_connected'] = $settings['is_user_connected'] && API::is_license_active();
$settings['connect_url'] = ! API::is_license_active() ?
$activate->get_admin_url( 'authorize', [
'utm_source' => 'top-bar',
'utm_medium' => 'wp-dash',
'utm_campaign' => 'connect-and-activate-license',
'utm_content' => $current_screen ? $current_screen->id : '',
] ) :
$settings['connect_url'];
$module->set_settings( $settings );
} );
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace ElementorPro\Modules\AnimatedHeadline;
use ElementorPro\Base\Module_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
public function __construct() {
parent::__construct();
add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] );
}
public function get_widgets() {
return [
'Animated_Headline',
];
}
public function get_name() {
return 'animated-headline';
}
/**
* Get the base URL for assets.
*
* @return string
*/
public function get_assets_base_url(): string {
return ELEMENTOR_PRO_URL;
}
/**
* Register styles.
*
* At build time, Elementor compiles `/modules/animated-headline/assets/scss/frontend.scss`
* to `/assets/css/widget-animated-headline.min.css`.
*
* @return void
*/
public function register_styles() {
wp_register_style(
'widget-animated-headline',
$this->get_css_assets_url( 'widget-animated-headline', null, true, true ),
[ 'elementor-frontend' ],
ELEMENTOR_PRO_VERSION
);
}
}

View File

@@ -0,0 +1,685 @@
<?php
namespace ElementorPro\Modules\AnimatedHeadline\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_Typography;
use Elementor\Group_Control_Text_Stroke;
use Elementor\Group_Control_Text_Shadow;
use Elementor\Modules\DynamicTags\Module as TagsModule;
use Elementor\Utils;
use ElementorPro\Base\Base_Widget;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Animated_Headline extends Base_Widget {
public function get_name() {
return 'animated-headline';
}
public function get_title() {
return esc_html__( 'Animated Headline', 'elementor-pro' );
}
public function get_icon() {
return 'eicon-animated-headline';
}
public function get_keywords() {
return [ 'headline', 'heading', 'animation', 'title', 'text' ];
}
protected function is_dynamic_content(): bool {
return false;
}
public function has_widget_inner_wrapper(): bool {
return ! Plugin::elementor()->experiments->is_feature_active( 'e_optimized_markup' );
}
/**
* 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-animated-headline' ];
}
protected function register_controls() {
$this->start_controls_section(
'text_elements',
[
'label' => esc_html__( 'Headline', 'elementor-pro' ),
]
);
$this->add_control(
'headline_style',
[
'label' => esc_html__( 'Animation Style', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'highlight',
'options' => [
'highlight' => esc_html__( 'Highlighted Text', 'elementor-pro' ),
'rotate' => esc_html__( 'Rotating Text', 'elementor-pro' ),
],
'prefix_class' => 'elementor-headline--style-',
'render_type' => 'template',
'frontend_available' => true,
]
);
$this->add_control(
'animation_type',
[
'label' => esc_html__( 'Animation Type', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'options' => [
'typing' => 'Typing',
'clip' => 'Clip',
'flip' => 'Flip',
'swirl' => 'Swirl',
'blinds' => 'Blinds',
'drop-in' => 'Drop-in',
'wave' => 'Wave',
'slide' => 'Slide',
'slide-down' => 'Slide Down',
],
'default' => 'typing',
'condition' => [
'headline_style' => 'rotate',
],
'frontend_available' => true,
]
);
$this->add_control(
'marker',
[
'label' => esc_html__( 'Animation Shape', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'circle',
'options' => [
'circle' => _x( 'Circle', 'Shapes', 'elementor-pro' ),
'curly' => _x( 'Curly', 'Shapes', 'elementor-pro' ),
'underline' => _x( 'Underline', 'Shapes', 'elementor-pro' ),
'double' => _x( 'Double', 'Shapes', 'elementor-pro' ),
'double_underline' => _x( 'Double Underline', 'Shapes', 'elementor-pro' ),
'underline_zigzag' => _x( 'Underline Zigzag', 'Shapes', 'elementor-pro' ),
'diagonal' => _x( 'Diagonal', 'Shapes', 'elementor-pro' ),
'strikethrough' => _x( 'Strikethrough', 'Shapes', 'elementor-pro' ),
'x' => 'X',
],
'render_type' => 'template',
'condition' => [
'headline_style' => 'highlight',
],
'frontend_available' => true,
]
);
$this->add_control(
'before_text',
[
'label' => esc_html__( 'Before Text', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'dynamic' => [
'active' => true,
'categories' => [
TagsModule::TEXT_CATEGORY,
],
],
'default' => esc_html__( 'This page is', 'elementor-pro' ),
'placeholder' => esc_html__( 'Enter your headline', 'elementor-pro' ),
'label_block' => true,
'separator' => 'before',
]
);
$this->add_control(
'highlighted_text',
[
'label' => esc_html__( 'Highlighted Text', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'dynamic' => [
'active' => true,
'categories' => [
TagsModule::TEXT_CATEGORY,
],
],
'default' => esc_html__( 'Amazing', 'elementor-pro' ),
'label_block' => true,
'condition' => [
'headline_style' => 'highlight',
],
'frontend_available' => true,
]
);
$this->add_control(
'rotating_text',
[
'label' => esc_html__( 'Rotating Text', 'elementor-pro' ),
'type' => Controls_Manager::TEXTAREA,
'placeholder' => esc_html__( 'Enter each word in a separate line', 'elementor-pro' ),
'default' => "Better\nBigger\nFaster",
'dynamic' => [
'active' => true,
'categories' => [
TagsModule::TEXT_CATEGORY,
],
],
'condition' => [
'headline_style' => 'rotate',
],
'frontend_available' => true,
]
);
$this->add_control(
'after_text',
[
'label' => esc_html__( 'After Text', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'dynamic' => [
'active' => true,
'categories' => [
TagsModule::TEXT_CATEGORY,
],
],
'placeholder' => esc_html__( 'Enter your headline', 'elementor-pro' ),
'label_block' => true,
]
);
$this->add_control(
'loop',
[
'label' => esc_html__( 'Infinite Loop', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'render_type' => 'template',
'frontend_available' => true,
'selectors' => [
'{{WRAPPER}}' => '--iteration-count: infinite',
],
'separator' => 'before',
]
);
$this->add_control(
'highlight_animation_duration',
[
'label' => esc_html__( 'Duration', 'elementor-pro' ) . ' (ms)',
'type' => Controls_Manager::NUMBER,
'default' => 1200,
'render_type' => 'template',
'frontend_available' => true,
'selectors' => [
'{{WRAPPER}}' => '--animation-duration: {{VALUE}}ms',
],
'condition' => [
'headline_style' => 'highlight',
],
]
);
$this->add_control(
'highlight_iteration_delay',
[
'label' => esc_html__( 'Delay', 'elementor-pro' ) . ' (ms)',
'type' => Controls_Manager::NUMBER,
'default' => 8000,
'render_type' => 'template',
'frontend_available' => true,
'condition' => [
'headline_style' => 'highlight',
'loop' => 'yes',
],
]
);
$this->add_control(
'rotate_iteration_delay',
[
'label' => esc_html__( 'Duration', 'elementor-pro' ) . ' (ms)',
'type' => Controls_Manager::NUMBER,
'default' => 2500,
'render_type' => 'template',
'frontend_available' => true,
'condition' => [
'headline_style' => 'rotate',
],
]
);
$this->add_control(
'link',
[
'label' => esc_html__( 'Link', 'elementor-pro' ),
'type' => Controls_Manager::URL,
'dynamic' => [
'active' => true,
],
'separator' => 'before',
]
);
$this->add_control(
'tag',
[
'label' => esc_html__( 'HTML Tag', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'options' => [
'h1' => 'H1',
'h2' => 'H2',
'h3' => 'H3',
'h4' => 'H4',
'h5' => 'H5',
'h6' => 'H6',
'div' => 'div',
'span' => 'span',
'p' => 'p',
],
'default' => 'h3',
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_style_text',
[
'label' => esc_html__( 'Headline', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_responsive_control(
'alignment',
[
'label' => esc_html__( 'Alignment', 'elementor-pro' ),
'type' => Controls_Manager::CHOOSE,
'options' => [
'start' => [
'title' => esc_html__( 'Start', 'elementor-pro' ),
'icon' => 'eicon-text-align-left',
],
'center' => [
'title' => esc_html__( 'Center', 'elementor-pro' ),
'icon' => 'eicon-text-align-center',
],
'end' => [
'title' => esc_html__( 'End', 'elementor-pro' ),
'icon' => 'eicon-text-align-right',
],
],
'default' => 'center',
'classes' => 'elementor-control-start-end',
'selectors_dictionary' => [
'left' => is_rtl() ? 'end' : 'start',
'right' => is_rtl() ? 'start' : 'end',
],
'selectors' => [
'{{WRAPPER}} .elementor-headline' => 'text-align: {{VALUE}}',
],
]
);
$this->add_control(
'heading_style',
[
'type' => Controls_Manager::HEADING,
'label' => esc_html__( 'Text', 'elementor-pro' ),
'separator' => 'before',
]
);
$this->add_control(
'title_color',
[
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'global' => [
'default' => Global_Colors::COLOR_SECONDARY,
],
'selectors' => [
'{{WRAPPER}} .elementor-headline-plain-text' => 'color: {{VALUE}}',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'title_typography',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_PRIMARY,
],
'selector' => '{{WRAPPER}} .elementor-headline',
]
);
$this->add_group_control(
Group_Control_Text_Stroke::get_type(),
[
'name' => 'text_stroke',
'selector' => '{{WRAPPER}} .elementor-headline .elementor-headline-plain-text',
]
);
$this->add_group_control(
Group_Control_Text_Shadow::get_type(),
[
'name' => 'title_text_shadow',
'selector' => '{{WRAPPER}} .elementor-headline .elementor-headline-plain-text',
]
);
$this->add_control(
'heading_words_style',
[
'type' => Controls_Manager::HEADING,
'label' => esc_html__( 'Animated Text', 'elementor-pro' ),
'separator' => 'before',
]
);
$this->add_control(
'words_color',
[
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'global' => [
'default' => Global_Colors::COLOR_SECONDARY,
],
'selectors' => [
'{{WRAPPER}}' => '--dynamic-text-color: {{VALUE}}',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'words_typography',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_PRIMARY,
],
'selector' => '{{WRAPPER}} .elementor-headline-dynamic-text',
'exclude' => [ 'font_size' ],
]
);
$this->add_group_control(
Group_Control_Text_Stroke::get_type(),
[
'name' => 'animated_text_stroke',
'selector' => '{{WRAPPER}} .elementor-headline .elementor-headline-dynamic-wrapper',
]
);
$this->add_group_control(
Group_Control_Text_Shadow::get_type(),
[
'name' => 'animated_text_shadow',
'selector' => '{{WRAPPER}} .elementor-headline .elementor-headline-dynamic-wrapper',
]
);
$this->add_control(
'typing_animation_highlight_colors',
[
'type' => Controls_Manager::HEADING,
'label' => esc_html__( 'Selected Text', 'elementor-pro' ),
'separator' => 'before',
'condition' => [
'headline_style' => 'rotate',
'animation_type' => 'typing',
],
]
);
$this->add_control(
'highlighted_text_background_color',
[
'label' => esc_html__( 'Background Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}}' => '--typing-selected-bg-color: {{VALUE}}',
],
'condition' => [
'headline_style' => 'rotate',
'animation_type' => 'typing',
],
]
);
$this->add_control(
'highlighted_text_color',
[
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}}' => '--typing-selected-color: {{VALUE}}',
],
'condition' => [
'headline_style' => 'rotate',
'animation_type' => 'typing',
],
]
);
$this->add_control(
'highlight_animation_shape_colors',
[
'type' => Controls_Manager::HEADING,
'label' => esc_html__( 'Highlighted Shape', 'elementor-pro' ),
'separator' => 'before',
'condition' => [
'headline_style' => 'highlight',
],
]
);
$this->add_control(
'marker_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'global' => [
'default' => Global_Colors::COLOR_ACCENT,
],
'selectors' => [
'{{WRAPPER}} .elementor-headline-dynamic-wrapper path' => 'stroke: {{VALUE}}',
],
'condition' => [
'headline_style' => 'highlight',
],
]
);
$this->add_control(
'stroke_width',
[
'label' => esc_html__( 'Width', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'min' => 1,
'max' => 20,
],
'em' => [
'max' => 2,
],
'rem' => [
'max' => 2,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-headline-dynamic-wrapper path' => 'stroke-width: {{SIZE}}{{UNIT}}',
],
'condition' => [
'headline_style' => 'highlight',
],
]
);
$this->add_control(
'above_content',
[
'label' => esc_html__( 'Bring to Front', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'selectors' => [
'{{WRAPPER}} .elementor-headline-dynamic-wrapper svg' => 'z-index: 2',
'{{WRAPPER}} .elementor-headline-dynamic-text' => 'z-index: auto',
],
'condition' => [
'headline_style' => 'highlight',
],
]
);
$this->add_control(
'rounded_edges',
[
'label' => esc_html__( 'Rounded Edges', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'selectors' => [
'{{WRAPPER}} .elementor-headline-dynamic-wrapper path' => 'stroke-linecap: round; stroke-linejoin: round',
],
'condition' => [
'headline_style' => 'highlight',
],
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
$tag = Utils::validate_html_tag( $settings['tag'] );
$this->add_render_attribute( 'headline', 'class', 'elementor-headline' );
if ( 'rotate' === $settings['headline_style'] ) {
$this->add_render_attribute( 'headline', 'class', 'elementor-headline-animation-type-' . $settings['animation_type'] );
$is_letter_animation = in_array( $settings['animation_type'], [ 'typing', 'swirl', 'blinds', 'wave' ] );
if ( $is_letter_animation ) {
$this->add_render_attribute( 'headline', 'class', 'elementor-headline-letters' );
}
}
if ( ! empty( $settings['link']['url'] ) ) {
$this->add_link_attributes( 'url', $settings['link'] );
?>
<a <?php $this->print_render_attribute_string( 'url' ); ?>>
<?php
}
?>
<<?php Utils::print_validated_html_tag( $tag ); ?> <?php $this->print_render_attribute_string( 'headline' ); ?>>
<?php if ( ! empty( $settings['before_text'] ) ) : ?>
<span class="elementor-headline-plain-text elementor-headline-text-wrapper"><?php echo wp_kses_post( $this->get_settings_for_display( 'before_text' ) ); ?></span>
<?php endif; ?>
<span class="elementor-headline-dynamic-wrapper elementor-headline-text-wrapper">
<?php if ( 'rotate' === $settings['headline_style'] && $settings['rotating_text'] ) :
$rotating_text = explode( "\n", $settings['rotating_text'] );
foreach ( $rotating_text as $key => $text ) : ?>
<span class="elementor-headline-dynamic-text<?php echo 1 > $key ? ' elementor-headline-text-active' : ''; ?>">
<?php Utils::print_unescaped_internal_string( str_replace( ' ', '&nbsp;', $text ) ); ?>
</span>
<?php endforeach; ?>
<?php elseif ( 'highlight' === $settings['headline_style'] && ! empty( $settings['highlighted_text'] ) ) : ?>
<span class="elementor-headline-dynamic-text elementor-headline-text-active"><?php echo wp_kses_post( $this->get_settings_for_display( 'highlighted_text' ) ); ?></span>
<?php endif ?>
</span>
<?php if ( ! empty( $settings['after_text'] ) ) : ?>
<span class="elementor-headline-plain-text elementor-headline-text-wrapper"><?php echo wp_kses_post( $this->get_settings_for_display( 'after_text' ) ); ?></span>
<?php endif; ?>
</<?php Utils::print_validated_html_tag( $tag ); ?>>
<?php
if ( ! empty( $settings['link']['url'] ) ) {
echo '</a>';
}
}
/**
* Render Animated Headline widget output in the editor.
*
* Written as a Backbone JavaScript template and used to generate the live preview.
*
* @since 2.9.0
* @access protected
*/
protected function content_template() {
?>
<#
var headlineClasses = 'elementor-headline',
tag = elementor.helpers.validateHTMLTag( settings.tag );
const sanitizedAnimationType = elementor.helpers.sanitize( settings.animation_type ).replaceAll(/'|"/g, '');
if ( 'rotate' === settings.headline_style ) {
headlineClasses += ' elementor-headline-animation-type-' + sanitizedAnimationType;
var isLetterAnimation = -1 !== [ 'typing', 'swirl', 'blinds', 'wave' ].indexOf( settings.animation_type );
if ( isLetterAnimation ) {
headlineClasses += ' elementor-headline-letters';
}
}
if ( settings.link?.url ) { #>
<a href="#">
<# } #>
<{{{ tag }}} class="{{{ headlineClasses }}}">
<# if ( settings.before_text ) { #>
<span class="elementor-headline-plain-text elementor-headline-text-wrapper">{{{ elementor.helpers.sanitize( settings.before_text, { ALLOW_DATA_ATTR: false } ) }}}</span>
<# } #>
<# if ( settings.rotating_text ) { #>
<span class="elementor-headline-dynamic-wrapper elementor-headline-text-wrapper">
<# if ( 'rotate' === settings.headline_style && settings.rotating_text ) {
var rotatingText = ( settings.rotating_text || '' ).split( '\n' );
for ( var i = 0; i < rotatingText.length; i++ ) {
var statusClass = 0 === i ? 'elementor-headline-text-active' : ''; #>
<span class="elementor-headline-dynamic-text {{ statusClass }}">
{{{ rotatingText[ i ].replace( ' ', '&nbsp;' ) }}}
</span>
<# }
}
else if ( 'highlight' === settings.headline_style && settings.highlighted_text ) { #>
<span class="elementor-headline-dynamic-text elementor-headline-text-active">{{{ elementor.helpers.sanitize( settings.highlighted_text, { ALLOW_DATA_ATTR: false } ) }}}</span>
<# } #>
</span>
<# } #>
<# if ( settings.after_text ) { #>
<span class="elementor-headline-plain-text elementor-headline-text-wrapper">{{{ elementor.helpers.sanitize( settings.after_text, { ALLOW_DATA_ATTR: false } ) }}}</span>
<# } #>
</{{{ tag }}}>
<# if ( settings.link?.url ) { #>
</a>
<# } #>
<?php
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace ElementorPro\Modules\Announcements;
use Elementor\Core\Base\App as BaseApp;
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';
}
public function __construct() {
parent::__construct();
add_filter( 'elementor/announcements/raw_announcements', function ( $raw_announcements ) {
$raw_announcement = [
'title' => esc_html__( 'Keep Your Websites Shine On', 'elementor-pro' ),
'description' => sprintf(
'<p>%s</p><ul><li>%s</li><li>%s</li><li>%s</li><li>%s</li></ul>',
esc_html__( 'Your Elementor Pro subscription has expired. Renew it now to regain access to the Pro features that elevate your website.', 'elementor-pro' ),
esc_html__( 'Manage and edit every part of your website, including pages, templates, headers, footers, and more.', 'elementor-pro' ),
esc_html__( 'Increase engagement and conversion with Elementors marketing features including Forms, and Popups.', 'elementor-pro' ),
esc_html__( 'Update your websites content and design using Elementor Pros professional widgets and features for any need.', 'elementor-pro' ),
esc_html__( 'Keep your website secure and compatible by updating your Elementor Pro website to the latest version.', 'elementor-pro' )
),
'media' => [
'type' => 'image',
'src' => ELEMENTOR_PRO_ASSETS_URL . 'images/announcements/license-expired.png?' . ELEMENTOR_PRO_VERSION,
],
'cta' => [
[
'label' => esc_html__( 'Renew Now', 'elementor-pro' ),
'variant' => 'primary',
'target' => '_blank',
'url' => 'https://go.elementor.com/renew-license-editor-expired-modal/',
],
[
'label' => esc_html__( 'Learn More', 'elementor-pro' ),
'target' => '_blank',
'url' => 'https://go.elementor.com//learn-more-editor-expired-modal/',
],
],
'triggers' => [
[
'action' => 'isLicenseExpired',
],
],
];
array_unshift( $raw_announcements, $raw_announcement );
return $raw_announcements;
}, 400 );
add_filter( 'elementor/announcements/trigger_object', function( $object_trigger, $trigger ) {
if ( ! empty( $trigger['action'] ) && 'isLicenseExpired' === $trigger['action'] ) {
$object_trigger = new Triggers\IsLicenseExpired();
}
return $object_trigger;
}, 400, 2 );
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace ElementorPro\Modules\Announcements\Triggers;
use Elementor\Modules\Announcements\Classes\Trigger_Base;
use ElementorPro\License\API;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class IsLicenseExpired extends Trigger_Base {
const META_KEY = '_elementor_pro_announcements_license_expired';
const MUTED_PERIOD = 1;
public function after_triggered() {
update_user_meta( get_current_user_id(), self::META_KEY, time() );
}
/**
* @return bool
*/
public function is_active(): bool {
if ( ! API::is_license_expired() ) {
return false;
}
$dismissed_time = get_user_meta( get_current_user_id(), self::META_KEY, true );
if ( ! empty( $dismissed_time ) && $dismissed_time > strtotime( '-' . static::MUTED_PERIOD . ' days' ) ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems;
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item;
use Elementor\Settings;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Custom_Fonts_Menu_Item implements Admin_Menu_Item {
public function get_capability(): string {
return Fonts_Manager::CAPABILITY;
}
public function get_label(): string {
return esc_html__( 'Custom Fonts', 'elementor-pro' );
}
public function get_parent_slug(): string {
return Settings::PAGE_ID;
}
public function get_position(): ?int {
return null;
}
public function is_visible(): bool {
return true;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems;
use ElementorPro\Modules\Tiers\AdminMenuItems\Base_Promotion_Item;
use ElementorPro\License\API;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Custom_Fonts_Promotion_Menu_Item extends Base_Promotion_Item {
public function get_name(): string {
return 'custom-fonts-promotion';
}
public function get_position(): ?int {
return null;
}
public function get_cta_url(): string {
$connect_url = Plugin::instance()->license_admin->get_connect_url( [
'utm_source' => 'wp-custom-fonts',
'utm_medium' => 'wp-dash',
'utm_campaign' => 'connect-and-activate-license',
] );
$renew_url = 'https://go.elementor.com/renew-custom-fonts/';
return API::is_license_expired()
? $renew_url
: $connect_url;
}
public function get_cta_text(): string {
return API::is_license_expired()
? esc_html__( 'Renew now', 'elementor-pro' )
: esc_html__( 'Connect & Activate', 'elementor-pro' );
}
public function get_label(): string {
return esc_html__( 'Custom Fonts', 'elementor-pro' );
}
public function get_page_title(): string {
return esc_html__( 'Custom Fonts', 'elementor-pro' );
}
public function get_promotion_title(): string {
return esc_html__( 'Add Your Custom Fonts', 'elementor-pro' );
}
public function get_promotion_description(): string {
return esc_html__(
'Custom Fonts allows you to add your self-hosted fonts and use them on your Elementor projects to create a unique brand language.',
'elementor-pro'
);
}
/**
* @deprecated use get_promotion_description instead
* @return void
*/
public function render_promotion_description() {
echo $this->get_promotion_description(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems;
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item;
use Elementor\Settings;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Custom_Icons_Menu_Item implements Admin_Menu_Item {
public function get_capability(): string {
return Icons_Manager::CAPABILITY;
}
public function get_label(): string {
return esc_html__( 'Custom Icons', 'elementor-pro' );
}
public function get_parent_slug(): string {
return Settings::PAGE_ID;
}
public function get_position(): ?int {
return null;
}
public function is_visible(): bool {
return true;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems;
use ElementorPro\Modules\Tiers\AdminMenuItems\Base_Promotion_Item;
use ElementorPro\License\API;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Custom_Icons_Promotion_Menu_Item extends Base_Promotion_Item {
public function get_name(): string {
return 'custom-icons-promotion';
}
public function get_cta_url(): string {
$connect_url = Plugin::instance()->license_admin->get_connect_url( [
'utm_source' => 'wp-custom-icons',
'utm_medium' => 'wp-dash',
'utm_campaign' => 'connect-and-activate-license',
] );
$renew_url = 'https://go.elementor.com/renew-custom-icons/';
return API::is_license_expired()
? $renew_url
: $connect_url;
}
public function get_position(): ?int {
return null;
}
public function get_cta_text(): string {
return API::is_license_expired()
? esc_html__( 'Renew now', 'elementor-pro' )
: esc_html__( 'Connect & Activate', 'elementor-pro' );
}
public function get_label(): string {
return esc_html__( 'Custom Icons', 'elementor-pro' );
}
public function get_page_title(): string {
return esc_html__( 'Custom Icons', 'elementor-pro' );
}
public function get_promotion_title(): string {
return esc_html__( 'Add Your Custom Icons', 'elementor-pro' );
}
public function get_promotion_description(): string {
return esc_html__(
'Don\'t rely solely on the FontAwesome icons everyone else is using! Differentiate your website and your style with custom icons you can upload from your favorite icons source.',
'elementor-pro'
);
}
/**
* @deprecated use get_promotion_description instead
* @return void
*/
public function render_promotion_description() {
echo $this->get_promotion_description(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\EditorOneMenuItems;
use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface;
use Elementor\Modules\EditorOne\Classes\Menu_Config;
use ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems\Custom_Fonts_Menu_Item;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Editor_One_Fonts_Menu_Item extends Custom_Fonts_Menu_Item implements Menu_Item_Interface {
public function get_position(): int {
return 10;
}
public function get_slug(): string {
return Fonts_Manager::MENU_SLUG;
}
public function get_parent_slug(): string {
return Menu_Config::ELEMENTOR_MENU_SLUG;
}
public function get_label(): string {
return esc_html__( 'Fonts', 'elementor-pro' );
}
public function get_group_id(): string {
return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\EditorOneMenuItems;
use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface;
use Elementor\Modules\EditorOne\Classes\Menu_Config;
use ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems\Custom_Fonts_Promotion_Menu_Item;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Editor_One_Fonts_Promotion extends Custom_Fonts_Promotion_Menu_Item implements Menu_Item_Interface {
public function get_position(): int {
return 10;
}
public function get_slug(): string {
return Fonts_Manager::PROMOTION_MENU_SLUG;
}
public function get_parent_slug(): string {
return Menu_Config::ELEMENTOR_MENU_SLUG;
}
public function get_label(): string {
return esc_html__( 'Fonts', 'elementor-pro' );
}
public function get_group_id(): string {
return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\EditorOneMenuItems;
use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface;
use Elementor\Modules\EditorOne\Classes\Menu_Config;
use ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems\Custom_Icons_Menu_Item;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Editor_One_Icons_Menu_Item extends Custom_Icons_Menu_Item implements Menu_Item_Interface {
public function get_position(): int {
return 20;
}
public function get_slug(): string {
return Icons_Manager::MENU_SLUG;
}
public function get_parent_slug(): string {
return Menu_Config::ELEMENTOR_MENU_SLUG;
}
public function get_label(): string {
return esc_html__( 'Icons', 'elementor-pro' );
}
public function get_group_id(): string {
return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\EditorOneMenuItems;
use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface;
use Elementor\Modules\EditorOne\Classes\Menu_Config;
use ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems\Custom_Icons_Promotion_Menu_Item;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons_Manager;
class Editor_One_Icons_Promotion extends Custom_Icons_Promotion_Menu_Item implements Menu_Item_Interface {
public function get_position(): int {
return 20;
}
public function get_slug(): string {
return Icons_Manager::PROMOTION_MENU_SLUG;
}
public function get_parent_slug(): string {
return Menu_Config::ELEMENTOR_MENU_SLUG;
}
public function get_label(): string {
return esc_html__( 'Icons', 'elementor-pro' );
}
public function get_group_id(): string {
return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID;
}
}

View File

@@ -0,0 +1,755 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes;
use Elementor\Core\Admin\Menu\Admin_Menu_Manager;
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider;
use Elementor\Plugin;
use Elementor\Settings;
use Elementor\Utils;
use ElementorPro\Base\Editor_One_Trait;
use ElementorPro\Core\Behaviors\Feature_Lock;
use ElementorPro\Core\Utils as Pro_Utils;
use ElementorPro\License\API;
use ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems\Custom_Fonts_Menu_Item;
use ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems\Custom_Fonts_Promotion_Menu_Item;
use ElementorPro\Modules\AssetsManager\AssetTypes\EditorOneMenuItems\Editor_One_Fonts_Menu_Item;
use ElementorPro\Modules\AssetsManager\AssetTypes\EditorOneMenuItems\Editor_One_Fonts_Promotion;
use ElementorPro\Modules\AssetsManager\Classes;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Fonts_Manager {
use Editor_One_Trait;
const CAPABILITY = 'manage_options';
const CPT = 'elementor_font';
const TAXONOMY = 'elementor_font_type';
const FONTS_OPTION_NAME = 'elementor_fonts_manager_fonts';
const FONTS_NAME_TYPE_OPTION_NAME = 'elementor_fonts_manager_font_types';
const MENU_SLUG = 'edit.php?post_type=' . self::CPT;
const PROMOTION_MENU_SLUG = 'e-custom-fonts';
private $post_type_object;
private $taxonomy_object;
private $enqueued_fonts = [];
protected $font_types = [];
private $has_fonts = null;
/**
* get a font type object for a given type
*
* @param null $type
*
* @return array|bool|\ElementorPro\Modules\AssetsManager\Classes\Font_Base
*/
public function get_font_type_object( $type = null ) {
if ( null === $type ) {
return $this->font_types;
}
if ( isset( $this->font_types[ $type ] ) ) {
return $this->font_types[ $type ];
}
return false;
}
/**
* Add a font type to the font manager
*
* @param string $font_type
* @param Classes\Font_Base $instance
*/
public function add_font_type( $font_type, $instance ) {
$this->font_types[ $font_type ] = $instance;
}
/**
* Register elementor font custom post type and elementor font type custom taxonomy
*/
public function register_post_type_and_tax() {
$labels = [
'name' => _x( 'Custom Fonts', 'CPT Name', 'elementor-pro' ),
'singular_name' => _x( 'Font', 'CPT Singular Name', 'elementor-pro' ),
'add_new' => esc_html__( 'Add New', 'elementor-pro' ),
'add_new_item' => esc_html__( 'Add New Font', 'elementor-pro' ),
'edit_item' => esc_html__( 'Edit Font', 'elementor-pro' ),
'new_item' => esc_html__( 'New Font', 'elementor-pro' ),
'all_items' => esc_html__( 'All Fonts', 'elementor-pro' ),
'view_item' => esc_html__( 'View Font', 'elementor-pro' ),
'search_items' => esc_html__( 'Search Font', 'elementor-pro' ),
'not_found' => esc_html__( 'No fonts found', 'elementor-pro' ),
'not_found_in_trash' => esc_html__( 'No fonts found in trash', 'elementor-pro' ),
'parent_item_colon' => '',
'menu_name' => _x( 'Custom Fonts', 'CPT Menu Name', 'elementor-pro' ),
];
$args = [
'labels' => $labels,
'public' => false,
'rewrite' => false,
'show_ui' => true,
'show_in_menu' => false,
'show_in_nav_menus' => false,
'exclude_from_search' => true,
'capability_type' => 'post',
'hierarchical' => false,
'supports' => [ 'title' ],
];
$this->post_type_object = register_post_type( self::CPT, $args );
$taxonomy_labels = [
'name' => _x( 'Font Types', 'Font type taxonomy general name', 'elementor-pro' ),
'singular_name' => _x( 'Font Type', 'Font type singular name', 'elementor-pro' ),
'search_items' => esc_html__( 'Search Font Types', 'elementor-pro' ),
'popular_items' => esc_html__( 'Popular Font Types', 'elementor-pro' ),
'all_items' => esc_html__( 'All Font Types', 'elementor-pro' ),
'edit_item' => esc_html__( 'Edit Font Type', 'elementor-pro' ),
'update_item' => esc_html__( 'Update Font Type', 'elementor-pro' ),
'add_new_item' => esc_html__( 'Add New Font Type', 'elementor-pro' ),
'new_item_name' => esc_html__( 'New Font Type Name', 'elementor-pro' ),
'separate_items_with_commas' => esc_html__( 'Separate Font Types with commas', 'elementor-pro' ),
'add_or_remove_items' => esc_html__( 'Add or remove Font Types', 'elementor-pro' ),
'choose_from_most_used' => esc_html__( 'Choose from the most used Font Types', 'elementor-pro' ),
'not_found' => esc_html__( 'No Font Types found.', 'elementor-pro' ),
'menu_name' => esc_html__( 'Font Types', 'elementor-pro' ),
];
$taxonomy_args = [
'labels' => $taxonomy_labels,
'hierarchical' => false,
'show_ui' => true,
'show_in_nav_menus' => false,
'query_var' => is_admin(),
'rewrite' => false,
'public' => false,
'meta_box_cb' => [ $this, 'print_taxonomy_metabox' ],
];
$this->taxonomy_object = register_taxonomy( self::TAXONOMY, self::CPT, $taxonomy_args );
}
public function post_updated_messages( $messages ) {
$messages[ self::CPT ] = [
0 => '', // Unused. Messages start at index 1.
1 => esc_html__( 'Font updated.', 'elementor-pro' ),
2 => esc_html__( 'Custom field updated.', 'elementor-pro' ),
3 => esc_html__( 'Custom field deleted.', 'elementor-pro' ),
4 => esc_html__( 'Font updated.', 'elementor-pro' ),
/* translators: %s: Date and time of the revision. */
5 => isset( $_GET['revision'] ) ? sprintf( esc_html__( 'Font restored to revision from %s', 'elementor-pro' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
6 => esc_html__( 'Font saved.', 'elementor-pro' ),
7 => esc_html__( 'Font saved.', 'elementor-pro' ),
8 => esc_html__( 'Font submitted.', 'elementor-pro' ),
9 => esc_html__( 'Font updated.', 'elementor-pro' ),
10 => esc_html__( 'Font draft updated.', 'elementor-pro' ),
];
return $messages;
}
/**
* Print Font Type metabox
*
* @param $post
* @param $box
*/
public function print_taxonomy_metabox( $post, $box ) {
wp_nonce_field( self::CPT, self::CPT . '_nonce' );
$name = self::TAXONOMY;
?>
<div id="taxonomy-<?php echo esc_attr( $name ); ?>" class="categorydiv">
<?php
$term_obj = wp_get_object_terms( $post->ID, $name );
$slug = false;
if ( is_array( $term_obj ) && isset( $term_obj[0] ) ) {
$slug = $term_obj[0]->slug;
}
$options = '';
foreach ( $this->font_types as $type => $instance ) {
$options .= sprintf( '<option value="%s"%s>%s</option>' . "\n", $type, selected( $slug, $type, false ), $instance->get_name() );
}
?>
<select class="widefat" name="<?php echo esc_attr( $name ); ?>">
<?php Utils::print_unescaped_internal_string( $options ); ?>
</select>
</div>
<?php
}
/**
* Add Font manager link to admin menu
*/
private function register_admin_menu( Admin_Menu_Manager $admin_menu_manager ) {
if ( $this->can_use_custom_fonts() ) {
$admin_menu_manager->register( static::MENU_SLUG, new Custom_Fonts_Menu_Item() );
} else {
$admin_menu_manager->register( static::PROMOTION_MENU_SLUG, new Custom_Fonts_Promotion_Menu_Item() );
}
}
private function can_use_custom_fonts() {
return ( API::is_license_active() || $this->has_fonts() );
}
private function has_fonts() {
if ( null !== $this->has_fonts ) {
return $this->has_fonts;
}
$existing_fonts = new \WP_Query( [
'post_type' => static::CPT,
'posts_per_page' => 1,
] );
$this->has_fonts = $existing_fonts->post_count > 0;
return $this->has_fonts;
}
public function redirect_admin_old_page_to_new() {
if ( ! empty( $_GET['page'] ) && 'elementor_custom_fonts' === $_GET['page'] ) {
wp_safe_redirect( admin_url( static::MENU_SLUG ) );
die;
}
}
/**
* Render preview column in font manager admin listing
*
* @param $column
* @param $post_id
*/
public function render_columns( $column, $post_id ) {
if ( 'font_preview' === $column ) {
$font_type = $this->get_font_type_by_post_id( $post_id, true );
if ( false === $font_type ) {
return;
}
$font_type->render_preview_column( $post_id );
}
if ( 'font_type' === $column ) {
$font_type = $this->get_font_type_by_post_id( $post_id, true );
if ( false === $font_type ) {
return;
}
$font_type->render_type_column( $post_id );
}
}
/**
* Handle editor request to embed/link font CSS per font type
*
* @param array $data
*
* @return array
* @throws \Exception
*/
public function assets_manager_panel_action_data( array $data ) {
$document = Pro_Utils::_unstable_get_document_for_edit( $data['editor_post_id'] );
if ( empty( $data['type'] ) ) {
throw new \Exception( 'Font type is required.' );
}
if ( empty( $data['font'] ) ) {
throw new \Exception( 'Font is required.' );
}
if ( 'variable' === $data['type'] ) {
$data['type'] = 'custom';
}
$asset = $this->get_font_type_object( $data['type'] );
if ( ! $asset ) {
throw new \Exception( 'Font type not found.' );
}
try {
return $asset->handle_panel_request( $data );
} catch ( \Exception $exception ) {
throw $exception;
}
}
/**
* Clean up admin Font manager admin listing
*/
public function clean_admin_listing_page() {
global $typenow;
if ( self::CPT !== $typenow ) {
return;
}
add_filter( 'months_dropdown_results', '__return_empty_array' );
add_action( 'manage_' . self::CPT . '_posts_custom_column', [ $this, 'render_columns' ], 10, 2 );
add_filter( 'display_post_states', [ $this, 'display_post_states' ], 10, 2 );
add_filter( 'screen_options_show_screen', '__return_false' );
}
public function update_enter_title_here( $title, $post ) {
if ( isset( $post->post_type ) && self::CPT === $post->post_type ) {
return esc_html__( 'Enter Font Family', 'elementor-pro' );
}
return $title;
}
public function get_font_variables( $font_variables ) {
$font_manager_fonts = $this->get_fonts();
if ( empty( $font_manager_fonts ) ) {
return $font_variables;
}
foreach ( $font_manager_fonts as $font_family => $font_data ) {
if ( empty( $font_data['variables'] ) ) {
continue;
}
$font_variables[ $font_family ] = $font_data['variables'];
}
return $font_variables;
}
public function get_font_variable_ranges( $font_variable_ranges ) {
$font_manager_fonts = $this->get_fonts();
if ( empty( $font_manager_fonts ) ) {
return $font_variable_ranges;
}
foreach ( $font_manager_fonts as $font_family => $font_data ) {
if ( empty( $font_data['variable_ranges'] ) ) {
continue;
}
$font_variable_ranges[ $font_family ] = $font_data['variable_ranges'];
}
return $font_variable_ranges;
}
public function post_row_actions( $actions, $post ) {
if ( self::CPT !== $post->post_type ) {
return $actions;
}
unset( $actions['inline hide-if-no-js'] );
return $actions;
}
public function display_post_states( $post_states, $post ) {
$font_type = $this->get_font_type_by_post_id( $post->ID, true );
if ( false !== $font_type ) {
$font_type->get_font_variations_count( $post->ID );
}
return $post_states;
}
/**
* Define which columns to display in font manager admin listing
*
* @param $columns
*
* @return array
*/
public function manage_columns( $columns ) {
return [
'cb' => '<input type="checkbox" />',
'title' => esc_html__( 'Font Family', 'elementor-pro' ),
'font_preview' => esc_html__( 'Preview', 'elementor-pro' ),
'font_type' => esc_html__( 'Type', 'elementor-pro' ),
];
}
public function register_fonts_in_control( $fonts ) {
$custom_fonts = $this->get_font_types();
if ( empty( $custom_fonts ) ) {
$this->generate_fonts_list();
$custom_fonts = $this->get_font_types();
}
return array_replace( $custom_fonts, $fonts );
}
public function register_fonts_groups( $font_groups ) {
$new_groups = [];
foreach ( $this->get_font_type_object() as $type => $instance ) {
$new_groups[ $type ] = $instance->get_name();
}
$new_groups['variable'] = esc_html__( 'Variable Fonts', 'elementor-pro' );
return array_replace( $new_groups, $font_groups );
}
/**
* Gets a Font type for any given post id
*
* @param $post_id
* @param bool $return_object
*
* @return array|bool|Classes\Font_Base
*/
private function get_font_type_by_post_id( $post_id, $return_object = false ) {
$term_obj = get_the_terms( $post_id, self::TAXONOMY );
if ( is_array( $term_obj ) ) {
$type_obj = array_shift( $term_obj );
if ( false === $return_object ) {
return $type_obj->slug;
}
return $this->get_font_type_object( $type_obj->slug );
}
return false;
}
/**
* Get font manager fonts as font family => font type array
* @return array
*/
private function get_font_types() {
static $font_types = false;
if ( ! $font_types ) {
$font_types = get_option( self::FONTS_NAME_TYPE_OPTION_NAME, [] );
}
return $font_types;
}
/**
* Generates a list of all Font Manager fonts and stores it in the options table
* @return array
*/
private function generate_fonts_list() {
$fonts = new \WP_Query( [
'post_type' => self::CPT,
'posts_per_page' => -1,
] );
$new_fonts = [];
$font_types = [];
foreach ( $fonts->posts as $font ) {
$font_type = $this->get_font_type_by_post_id( $font->ID, true );
if ( false === $font_type ) {
continue;
}
$font_types = array_replace( $font_types, $font_type->get_font_family_type( $font->ID, $font->post_title ) );
$new_fonts = array_replace( $new_fonts, $font_type->get_font_data( $font->ID, $font->post_title ) );
}
update_option( self::FONTS_NAME_TYPE_OPTION_NAME, $font_types );
update_option( self::FONTS_OPTION_NAME, $new_fonts );
return $new_fonts;
}
/**
* runs on Elementor font post save and calls the font type handler save meta method
*
* @param int $post_id
* @param \WP_Post $post
* @param bool $update
*
* @return mixed
*/
public function save_post_meta( $post_id, $post, $update ) {
// If this is an autosave, our form has not been submitted,
// so we don't want to do anything.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
// Check the user's permissions.
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
// Check if our nonce is set.
if ( ! isset( $_POST[ self::CPT . '_nonce' ] ) ) {
return $post_id;
}
// Verify that the nonce is valid.
if ( ! wp_verify_nonce(
Pro_Utils::_unstable_get_super_global_value( $_POST, self::CPT . '_nonce' ),
self::CPT
) ) {
return $post_id;
}
// Save font type
// only custom for now
$custom_font = $this->get_font_type_object( 'custom' );
wp_set_object_terms( $post_id, $custom_font->get_type(), self::TAXONOMY );
// Let Font type handle saving
// Sanitize the whole $_POST array
$custom_font->save_meta( $post_id, Pro_Utils::_unstable_get_super_global_value( [ 'data' => $_POST ], 'data' ) );
}
/**
* Helper to clean font list on save/update
*/
public function clear_fonts_list() {
delete_option( self::FONTS_OPTION_NAME );
delete_option( self::FONTS_NAME_TYPE_OPTION_NAME );
}
/**
* Get fonts array form the database or generate a new list if $force is set to true
*
* @param bool $force
*
* @return array|bool|mixed
*/
public function get_fonts() {
static $fonts = false;
if ( false !== $fonts ) {
return $fonts;
}
$fonts = $this->generate_fonts_list();
$fonts = get_option( self::FONTS_OPTION_NAME, false );
return $fonts;
}
private function resolve_font_data( $font_family ) {
if ( in_array( $font_family, $this->enqueued_fonts, true ) ) {
return null;
}
$font_types = $this->get_font_types();
if ( ! isset( $font_types[ $font_family ] ) ) {
return null;
}
$font_type_name = $font_types[ $font_family ];
if ( 'variable' === $font_type_name ) {
$font_type_name = 'custom';
}
$font_type = $this->get_font_type_object( $font_type_name );
if ( ! $font_type ) {
return null;
}
$font_manager_fonts = $this->get_fonts();
$font_data = isset( $font_manager_fonts[ $font_family ] ) ? $font_manager_fonts[ $font_family ] : [];
return [
'font_type' => $font_type,
'font_data' => $font_data,
];
}
public function enqueue_fonts( $post_css ) {
$used_fonts = $post_css->get_fonts();
foreach ( $used_fonts as $font_family ) {
$resolved = $this->resolve_font_data( $font_family );
if ( ! $resolved ) {
continue;
}
$resolved['font_type']->enqueue_font( $font_family, $resolved['font_data'], $post_css );
$this->enqueued_fonts[] = $font_family;
}
}
public function print_font_link( $font_family ) {
$resolved = $this->resolve_font_data( $font_family );
if ( ! $resolved ) {
return;
}
$font_face = isset( $resolved['font_data']['font_face'] ) ? $resolved['font_data']['font_face'] : '';
if ( empty( $font_face ) ) {
return;
}
wp_add_inline_style( 'elementor-pro-custom-fonts', $font_face );
$this->enqueued_fonts[] = $font_family;
}
public function register_custom_font_styles( $fonts_to_enqueue ) {
foreach ( $fonts_to_enqueue as $font_family ) {
$this->print_font_link( $font_family );
}
}
public function register_ajax_actions( Ajax $ajax ) {
$ajax->register_ajax_action( 'pro_assets_manager_panel_action_data', [ $this, 'assets_manager_panel_action_data' ] );
}
public function add_finder_item( array $categories ) {
$categories['settings']['items']['custom-fonts'] = [
'title' => esc_html__( 'Custom Fonts', 'elementor-pro' ),
'icon' => 'typography',
'url' => admin_url( static::MENU_SLUG ),
'keywords' => [ 'custom', 'fonts', 'elementor' ],
];
if ( ! $this->can_use_custom_fonts() ) {
$lock = new Feature_Lock( [ 'type' => 'custom-font' ] );
$categories['settings']['items']['custom-fonts']['lock'] = $lock->get_config();
}
return $categories;
}
public function admin_menu_make_open_on_subpage( $parent_file ) {
if ( static::MENU_SLUG === $parent_file ) {
$parent_file = Settings::PAGE_ID;
}
return $parent_file;
}
protected function actions() {
add_action( 'init', [ $this, 'register_post_type_and_tax' ] );
if ( is_admin() ) {
add_action( 'init', [ $this, 'redirect_admin_old_page_to_new' ] );
add_action( 'elementor/admin/menu/register', function ( Admin_Menu_Manager $admin_menu_manager ) {
if ( $this->is_editor_one_active() ) {
return;
}
$this->register_admin_menu( $admin_menu_manager );
} );
// TODO: BC - Remove after `Admin_Menu_Manager` will be the standard.
add_action( 'admin_menu', function () {
if ( did_action( 'elementor/admin/menu/register' ) ) {
return;
}
$menu_title = _x( 'Custom Fonts', 'Elementor Font', 'elementor-pro' );
add_submenu_page(
Settings::PAGE_ID,
$menu_title,
$menu_title,
self::CAPABILITY,
static::MENU_SLUG
);
}, 50 );
add_action( 'admin_head', [ $this, 'clean_admin_listing_page' ] );
add_action( 'elementor/editor-one/menu/register', function ( Menu_Data_Provider $menu_data_provider ) {
if ( $this->can_use_custom_fonts() ) {
$menu_data_provider->register_menu( new Editor_One_Fonts_Menu_Item() );
} else {
$menu_data_provider->register_menu( new Editor_One_Fonts_Promotion() );
}
} );
}
// TODO: Maybe just ignore all of those when the user can't use custom fonts?
add_filter( 'post_row_actions', [ $this, 'post_row_actions' ], 10, 2 );
add_filter( 'manage_' . self::CPT . '_posts_columns', [ $this, 'manage_columns' ], 100 );
add_action( 'save_post_' . self::CPT, [ $this, 'save_post_meta' ], 10, 3 );
add_action( 'save_post_' . self::CPT, [ $this, 'clear_fonts_list' ], 100 );
add_filter( 'elementor/fonts/groups', [ $this, 'register_fonts_groups' ] );
add_filter( 'elementor/fonts/additional_fonts', [ $this, 'register_fonts_in_control' ] );
add_filter( 'elementor/finder/categories', [ $this, 'add_finder_item' ] );
add_action( 'elementor/css-file/post/parse', [ $this, 'enqueue_fonts' ] );
add_action( 'elementor/css-file/global/parse', [ $this, 'enqueue_fonts' ] );
add_action( 'elementor/fonts/register_styles', [ $this, 'register_custom_font_styles' ] );
add_action( 'elementor/fonts/print_font_links/custom', [ $this, 'print_font_link' ] );
add_action( 'elementor/fonts/print_font_links/variable', [ $this, 'print_font_link' ] );
add_action( 'wp_enqueue_scripts', function () {
wp_register_style( 'elementor-pro-custom-fonts', false, [], ELEMENTOR_PRO_VERSION );
wp_enqueue_style( 'elementor-pro-custom-fonts' );
}, 15 );
add_filter( 'post_updated_messages', [ $this, 'post_updated_messages' ] );
add_filter( 'enter_title_here', [ $this, 'update_enter_title_here' ], 10, 2 );
add_filter( 'elementor/typography/font_variables', [ $this, 'get_font_variables' ] );
add_filter( 'elementor/typography/font_variable_ranges', [ $this, 'get_font_variable_ranges' ] );
add_filter( 'parent_file', [ $this, 'admin_menu_make_open_on_subpage' ] );
// Ajax.
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
add_filter( 'elementor/editor-one/admin-edit-post-types', function ( array $post_types ) {
$post_types[] = self::CPT;
return $post_types;
} );
/**
* Elementor fonts manager loaded.
*
* Fires after the fonts manager was fully loaded and instantiated.
*
* @since 2.0.0
*
* @param Fonts_Manager $this An instance of fonts manager.
*/
do_action( 'elementor_pro/fonts_manager_loaded', $this );
}
/**
* Fonts_Manager constructor.
*/
public function __construct() {
$this->actions();
$this->add_font_type( 'custom', new Fonts\Custom_Fonts() );
$this->add_font_type( 'typekit', new Fonts\Typekit_Fonts() );
}
}

View File

@@ -0,0 +1,664 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Fonts;
use Elementor\Core\Files\Assets\Files_Upload_Handler;
use Elementor\Core\Files\CSS\Base;
use ElementorPro\Modules\AssetsManager\Classes;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Custom_Fonts extends Classes\Font_Base {
const FONT_META_KEY = 'elementor_font_files';
const FONT_FACE_META_KEY = 'elementor_font_face';
public function get_name() {
return esc_html__( 'Custom Fonts', 'elementor-pro' );
}
public function get_type() {
return 'custom';
}
private function get_file_types() {
return [
'woff' => 'font/woff|application/font-woff|application/x-font-woff|application/octet-stream',
'woff2' => 'font/woff2|application/octet-stream|font/x-woff2',
'ttf' => 'application/x-font-ttf|application/octet-stream|font/ttf',
'svg' => 'image/svg+xml|application/octet-stream|image/x-svg+xml',
'eot' => 'application/vnd.ms-fontobject|application/octet-stream|application/x-vnd.ms-fontobject',
];
}
public function add_meta_box() {
add_meta_box(
'elementor-font-' . $this->get_type() . 'metabox',
__( 'Manage Your Font Files', 'elementor-pro' ),
[ $this, 'render_metabox' ],
Fonts_Manager::CPT,
'normal',
'default'
);
}
public function render_metabox( $post ) {
wp_enqueue_media();
$fields = [
[
'id' => 'font_type',
'field_type' => 'input',
'input_type' => 'hidden',
],
[
'id' => 'open_div',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'repeater-content-top',
],
],
[
'id' => 'font_weight',
'field_type' => 'select',
'label' => esc_html__( 'Weight', 'elementor-pro' ) . ':',
'extra_attributes' => [
'class' => 'font_weight',
],
'options' => $this->get_font_weight_options(),
],
[
'id' => 'font_style',
'field_type' => 'select',
'label' => esc_html__( 'Style', 'elementor-pro' ) . ':',
'extra_attributes' => [
'class' => 'font_style',
],
'options' => $this->get_font_style_options(),
],
[
'id' => 'preview_label',
'field_type' => 'html',
'label' => false,
'raw_html' => sprintf( '<div class="inline-preview">%s</div>', esc_html__( 'Elementor Is Making the Web Beautiful', 'elementor-pro' ) ),
],
[
'id' => 'toolbar',
'field_type' => 'toolbar',
'label' => false,
],
[
'id' => 'close_div',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
],
[
'id' => 'open_div',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'repeater-content-bottom',
],
],
[
'id' => 'variable_description',
'field_type' => 'html',
'label' => false,
'raw_html' => sprintf( '<div class="variable-description">%s %s</div>',
esc_html__( 'Check the boxes to enable Width and Weight, then define the minimum and maximum values for each.', 'elementor-pro' ),
'<a href="https://go.elementor.com/wp-dash-variable-fonts-help/" target="_blank">' . esc_html__( 'Learn More', 'elementor-pro' ) . '.</a>'
),
],
[
'id' => 'open_div',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'variable-width-wrap',
],
],
[
'id' => 'variable_width',
'field_type' => 'input',
'input_type' => 'checkbox',
'label' => esc_html__( 'Width', 'elementor-pro' ),
'value' => 'yes',
],
[
'id' => 'variable_width_min',
'field_type' => 'input',
'input_type' => 'number',
'label' => esc_html__( 'Min Width', 'elementor-pro' ),
],
[
'id' => 'variable_width_max',
'field_type' => 'input',
'input_type' => 'number',
'label' => esc_html__( 'Max Width', 'elementor-pro' ),
],
[
'id' => 'close_div',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
],
[
'id' => 'open_div',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'variable-weight-wrap',
],
],
[
'id' => 'variable_weight',
'field_type' => 'input',
'input_type' => 'checkbox',
'label' => esc_html__( 'Weight', 'elementor-pro' ),
'value' => 'yes',
],
[
'id' => 'variable_weight_min',
'field_type' => 'input',
'input_type' => 'number',
'label' => esc_html__( 'Min Weight', 'elementor-pro' ),
],
[
'id' => 'variable_weight_max',
'field_type' => 'input',
'input_type' => 'number',
'label' => esc_html__( 'Max Weight', 'elementor-pro' ),
],
[
'id' => 'close_div',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
],
];
foreach ( $this->get_file_types() as $type => $mine ) {
$fields[] = [
'id' => $type,
'field_type' => 'file',
'mine' => str_replace( '|', ',', $mine ),
'ext' => $type,
/* translators: %s: Font file format. */
'label' => sprintf( esc_html__( '%s File', 'elementor-pro' ), strtoupper( $type ) ),
/* translators: %s: Font file format. */
'box_title' => sprintf( esc_html__( 'Upload font .%s file', 'elementor-pro' ), $type ),
/* translators: %s: Font file format. */
'box_action' => sprintf( esc_html__( 'Select .%s file', 'elementor-pro' ), $type ),
'preview_anchor' => 'none',
'description' => $this->get_file_type_description( $type ),
];
}
$fields[] = [
'id' => 'close_div',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
];
$font_data = get_post_meta( $post->ID, self::FONT_META_KEY, true );
$repeater = [
'fields' => $fields,
'id' => 'font_face',
'label' => false,
'add_label' => esc_html__( 'Add Static Font', 'elementor-pro' ),
'toggle_title' => esc_html__( 'Edit', 'elementor-pro' ),
'remove_title' => esc_html__( 'Delete', 'elementor-pro' ),
'field_type' => 'repeater',
'row_label' => [
'default' => 'Settings',
'selector' => '.font_weight',
],
'saved' => $font_data,
];
$add_variable_font_button = [
'id' => 'add-variable-font',
'field_type' => 'input',
'input_type' => 'button',
'value' => esc_html__( 'Add Variable Font', 'elementor-pro' ),
'class' => 'button button-secondary',
];
$this->print_metabox( [ $repeater, $add_variable_font_button ] );
// PHPCS - Dedicated for CSS.
printf( '<style>%s</style>', get_post_meta( $post->ID, self::FONT_FACE_META_KEY, true ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function save_meta( $post_id, $data ) {
if ( ! isset( $data['font_face'] ) || ! is_array( $data['font_face'] ) ) {
return;
}
// Sanitize a little
$font_face = [];
foreach ( $data['font_face'] as $font_data ) {
$font_face[] = $this->sanitize_text_field_recursive( $font_data );
}
// All good save the files array
update_post_meta( $post_id, self::FONT_META_KEY, $font_face );
// Save font face
update_post_meta( $post_id, self::FONT_FACE_META_KEY, $this->generate_font_face( $post_id ) );
}
public function upload_mimes( $mine_types ) {
if ( current_user_can( Fonts_Manager::CAPABILITY ) && $this->is_elementor_font_upload() ) {
foreach ( $this->get_file_types() as $type => $mine ) {
if ( ! isset( $mine_types[ $type ] ) ) {
$mine_types[ $type ] = $mine;
}
}
}
return $mine_types;
}
public function wp_handle_upload_prefilter( $file ) {
if ( ! $this->is_elementor_font_upload() ) {
return $file;
}
$ext = pathinfo( $file['name'], PATHINFO_EXTENSION );
if ( 'svg' !== $ext ) {
return $file;
}
/**
* @var \Elementor\Core\Files\Assets\Svg\Svg_Handler $svg_handler;
*/
$svg_handler = Plugin::elementor()->assets_manager->get_asset( 'svg-handler' );
if ( Files_Upload_Handler::file_sanitizer_can_run() && ! $svg_handler->sanitize_svg( $file['tmp_name'] ) ) {
$file['error'] = esc_html__( 'Invalid SVG Format, file not uploaded for security reasons', 'elementor-pro' );
}
return $file;
}
private function is_elementor_font_upload() {
return isset( $_POST['uploadTypeCaller'] ) && 'elementor-admin-font-upload' === $_POST['uploadTypeCaller']; // phpcs:ignore
}
/**
* A workaround for upload validation which relies on a PHP extension (fileinfo) with inconsistent reporting behaviour.
* ref: https://core.trac.wordpress.org/ticket/39550
* ref: https://core.trac.wordpress.org/ticket/40175
*/
public function filter_fix_wp_check_filetype_and_ext( $data, $file, $filename, $mimes ) {
if ( ! empty( $data['ext'] ) && ! empty( $data['type'] ) ) {
return $data;
}
$registered_file_types = $this->get_file_types();
$filetype = wp_check_filetype( $filename, $mimes );
if ( ! isset( $registered_file_types[ $filetype['ext'] ] ) ) {
return $data;
}
// Fix incorrect file mime type
$filetype['type'] = explode( '|', $filetype['type'] )[0];
return [
'ext' => $filetype['ext'],
'type' => $filetype['type'],
'proper_filename' => $data['proper_filename'],
];
}
public function generate_font_face( $post_id ) {
$saved = get_post_meta( $post_id, self::FONT_META_KEY, true );
if ( ! is_array( $saved ) ) {
return false;
}
$font_family = get_the_title( $post_id );
$font_face = '';
foreach ( $saved as $font_data ) {
$font_face .= $this->get_font_face_from_data( $font_family, $font_data ) . PHP_EOL;
}
return $font_face;
}
private function get_font_variables( $post_id ) {
$saved = get_post_meta( $post_id, self::FONT_META_KEY, true );
if ( ! is_array( $saved ) ) {
return false;
}
$variables = [];
foreach ( $saved as $font_data ) {
if ( ! empty( $font_data['variable_weight'] ) ) {
$variables[] = 'weight';
}
if ( ! empty( $font_data['variable_width'] ) ) {
$variables[] = 'width';
}
break;
}
return $variables;
}
private function get_font_variable_ranges( $post_id ) {
$saved = get_post_meta( $post_id, self::FONT_META_KEY, true );
if ( ! is_array( $saved ) ) {
return false;
}
$variable_ranges = [];
foreach ( $saved as $font_data ) {
if ( ! empty( $font_data['variable_weight'] ) ) {
$variable_ranges['weight'] = [
'min' => 1,
'max' => 1000,
];
if ( ! empty( $font_data['variable_weight_min'] ) ) {
$variable_ranges['weight']['min'] = (int) $font_data['variable_weight_min'];
}
if ( ! empty( $font_data['variable_weight_max'] ) ) {
$variable_ranges['weight']['max'] = (int) $font_data['variable_weight_max'];
}
}
if ( ! empty( $font_data['variable_width'] ) ) {
$variable_ranges['width'] = [
'min' => 0,
'max' => 150,
];
if ( ! empty( $font_data['variable_width_min'] ) ) {
$variable_ranges['width']['min'] = (int) $font_data['variable_width_min'];
}
if ( ! empty( $font_data['variable_width_max'] ) ) {
$variable_ranges['width']['max'] = (int) $font_data['variable_width_max'];
}
}
break;
}
return $variable_ranges;
}
private function is_font_variable( $post_id ): bool {
$saved = get_post_meta( $post_id, self::FONT_META_KEY, true );
if ( ! is_array( $saved ) ) {
return false;
}
foreach ( $saved as $font_data ) {
return ! empty( $font_data['font_type'] ) && 'variable' === $font_data['font_type'];
}
return false;
}
public function get_font_face_from_data( $font_family, $data ) {
$src = [];
foreach ( [ 'eot', 'woff2', 'woff', 'ttf', 'svg' ] as $type ) {
if ( ! isset( $data[ $type ] ) || ! isset( $data[ $type ]['url'] ) || empty( $data[ $type ]['url'] ) ) {
continue;
}
if ( 'svg' === $type ) {
$data[ $type ]['url'] .= '#' . str_replace( ' ', '', $font_family );
}
$src[] = $this->get_font_src_per_type( $type, $data[ $type ]['url'] );
}
$font_face = '@font-face {' . PHP_EOL;
$font_face .= "\tfont-family: '" . $font_family . "';" . PHP_EOL;
if ( empty( $data['font_type'] ) || 'variable' !== $data['font_type'] ) {
$font_face .= "\tfont-style: " . $data['font_style'] . ';' . PHP_EOL;
$font_face .= "\tfont-weight: " . $data['font_weight'] . ';' . PHP_EOL;
}
$font_face .= "\tfont-display: " . apply_filters( 'elementor_pro/custom_fonts/font_display', 'auto', $font_family, $data ) . ';' . PHP_EOL;
if ( isset( $data['eot'] ) && isset( $data['eot']['url'] ) && ! empty( $data['eot']['url'] ) ) {
$font_face .= "\tsrc: url('" . esc_attr( $data['eot']['url'] ) . "');" . PHP_EOL;
}
$font_face .= "\tsrc: " . implode( ',' . PHP_EOL . "\t\t", $src ) . ';' . PHP_EOL . '}';
return $font_face;
}
private function get_font_src_per_type( $type, $url ) {
$src = 'url(\'' . esc_attr( $url ) . '\') ';
switch ( $type ) {
case 'woff':
case 'woff2':
case 'svg':
$src .= 'format(\'' . $type . '\')';
break;
case 'ttf':
$src .= 'format(\'truetype\')';
break;
case 'eot':
$src = 'url(\'' . esc_attr( $url ) . '?#iefix\') format(\'embedded-opentype\')';
break;
}
return $src;
}
public function get_fonts( $force = false ) {
$fonts = get_option( self::FONTS_OPTION_NAME, false );
if ( $fonts && ! $force ) {
return $fonts;
}
$fonts = new \WP_Query( [
'post_type' => Fonts_Manager::CPT,
'posts_per_page' => -1,
] );
$new_fonts = [];
foreach ( $fonts->posts as $font ) {
$new_fonts[ $font->post_title ] = 'custom';
}
update_option( self::FONTS_OPTION_NAME, $new_fonts );
return $new_fonts;
}
private function get_font_face_by_font_family( $font_family ) {
global $wpdb;
$id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_title = %s AND post_type = %s LIMIT 1", $font_family, Fonts_Manager::CPT ) );
if ( $id ) {
return get_post_meta( $id, self::FONT_FACE_META_KEY, true );
}
return '';
}
public function render_preview_column( $post_id ) {
$font_face = get_post_meta( $post_id, self::FONT_FACE_META_KEY, true );
if ( ! $font_face ) {
return;
}
// PHPCS - the variable $font_face is CSS. the property $this->font_preview_phrase is safe.
printf( '<style>%s</style><span style="font-family: \'%s\';">%s</span>', $font_face, esc_html( get_the_title( $post_id ) ), $this->font_preview_phrase ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function render_type_column( $post_id ) {
echo $this->is_font_variable( $post_id ) ? esc_html__( 'Variable', 'elementor-pro' ) : esc_html__( 'Static', 'elementor-pro' );
}
public function get_font_family_type( $post_id, $post_title ) {
$type = $this->get_type();
if ( $this->is_font_variable( $post_id ) ) {
$type = 'variable';
}
return [
$post_title => $type,
];
}
public function get_font_data( $post_id, $post_title ) {
$font_data = [
'font_face' => $this->generate_font_face( $post_id ),
'post_id' => $post_id,
];
$variables = $this->get_font_variables( $post_id );
if ( ! empty( $variables ) ) {
$font_data['variables'] = $variables;
}
$variable_ranges = $this->get_font_variable_ranges( $post_id );
if ( ! empty( $variable_ranges ) ) {
$font_data['variable_ranges'] = $variable_ranges;
}
return [
$post_title => $font_data,
];
}
public function get_font_variations_count( $post_id ) {
$data = get_post_meta( $post_id, self::FONT_META_KEY, true );
if ( ! empty( $data ) && count( $data ) > 0 ) {
echo sprintf( '<span class="font-variations-count">%d</span>', count( $data ) );
}
}
/**
* @param string $font_family
* @param array $font_data
* @param Base $post_css
*/
public function enqueue_font( $font_family, $font_data, $post_css ) {
$font_faces = isset( $font_data['font_face'] ) ? $font_data['font_face'] : $this->get_font_face_by_font_family( $font_family );
// Add a css comment
$custom_css = '/* Start Custom Fonts CSS */' . $font_faces . '/* End Custom Fonts CSS */';
$post_css->get_stylesheet()->add_raw_css( $custom_css );
}
/**
* @param array $data
*
* @return array
* @throws \Exception
*/
public function handle_panel_request( array $data ) {
$font_family = sanitize_text_field( $data['font'] );
$font_face = $this->get_font_face_by_font_family( $font_family );
if ( empty( $font_face ) ) {
/* translators: %s: Font family. */
$error_message = sprintf( esc_html__( 'Font %s was not found.', 'elementor-pro' ), $font_family );
throw new \Exception( $error_message );
}
return [
'font_face' => $font_face,
];
}
private function get_font_style_options() {
return [
'normal' => esc_html__( 'Normal', 'elementor-pro' ),
'italic' => esc_html__( 'Italic', 'elementor-pro' ),
'oblique' => esc_html__( 'Oblique', 'elementor-pro' ),
];
}
private function get_font_weight_options() {
return [
'normal' => esc_html__( 'Normal', 'elementor-pro' ),
'bold' => esc_html__( 'Bold', 'elementor-pro' ),
'100' => '100',
'200' => '200',
'300' => '300',
'400' => '400',
'500' => '500',
'600' => '600',
'700' => '700',
'800' => '800',
'900' => '900',
];
}
private function get_file_type_description( $file_type ) {
$descriptions = [
'eot' => esc_html__( 'Embedded OpenType, Used by IE6-IE9 Browsers', 'elementor-pro' ),
'woff2' => esc_html__( 'The Web Open Font Format 2, Used by Super Modern Browsers', 'elementor-pro' ),
'woff' => esc_html__( 'The Web Open Font Format, Used by Modern Browsers', 'elementor-pro' ),
'ttf' => esc_html__( 'TrueType Fonts, Used for better supporting Safari, Android, iOS', 'elementor-pro' ),
'svg' => esc_html__( 'SVG fonts allow SVG to be used as glyphs when displaying text, Used by Legacy iOS', 'elementor-pro' ),
];
return isset( $descriptions[ $file_type ] ) ? $descriptions[ $file_type ] : '';
}
private function replace_urls( $rows_affected, $from, $to ) {
global $wpdb;
$rows_affected = $wpdb->query(
"UPDATE {$wpdb->postmeta} " .
$wpdb->prepare( 'SET `meta_value` = REPLACE(`meta_value`, %s, %s) ', $from, $to ) .
'WHERE `meta_key` = \'' . self::FONT_FACE_META_KEY . '\''
);
return $rows_affected;
}
protected function actions() {
parent::actions();
add_filter( 'elementor/tools/replace-urls', function( $rows_affected, $from, $to ) {
return $this->replace_urls( $rows_affected, $from, $to );
}, 10, 3 );
add_filter( 'wp_check_filetype_and_ext', [ $this, 'filter_fix_wp_check_filetype_and_ext' ], 10, 4 );
add_filter( 'wp_handle_upload_prefilter', [ $this, 'wp_handle_upload_prefilter' ] );
add_filter( 'upload_mimes', [ $this, 'upload_mimes' ] );
add_action( 'add_meta_boxes_' . Fonts_Manager::CPT, [ $this, 'add_meta_box' ] );
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\ImportExportCustomization;
use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Export_Runner_Base;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\Custom_Fonts;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Export_Runner extends Export_Runner_Base {
public static function get_name(): string {
return 'custom-fonts';
}
public function should_export( array $data ) {
return (
isset( $data['include'] ) &&
in_array( 'settings', $data['include'], true )
);
}
public function export( array $data ) {
$custom_fonts = new Custom_Fonts();
$fonts = $custom_fonts->get_fonts( true );
$include_custom_fonts = $data['customization']['settings']['customFonts'] ?? true;
if ( empty( $fonts ) || ! $include_custom_fonts ) {
return [
'manifest' => [],
'files' => [],
];
}
$fonts_data = [];
$manifest = [];
foreach ( $fonts as $font_family => $font_type ) {
$font_data = $this->prepare_font_data( $font_family );
if ( $font_data ) {
$fonts_data[] = $font_data;
$manifest['custom-fonts'][ $font_family ] = $font_data;
}
}
return [
'files' => [
'path' => Import_Export_Customization::FILE_NAME,
'data' => $fonts_data,
],
'manifest' => [
$manifest,
],
];
}
private function prepare_font_data( $font_family ) {
$font_query = new \WP_Query( [
'post_type' => Fonts_Manager::CPT,
'post_status' => 'any',
'posts_per_page' => 1,
'title' => $font_family,
] );
if ( ! $font_query->have_posts() ) {
return null;
}
$font_post = $font_query->posts[0];
$custom_fonts = new Custom_Fonts();
$font_files = get_post_meta( $font_post->ID, Custom_Fonts::FONT_META_KEY, true );
$font_face = get_post_meta( $font_post->ID, Custom_Fonts::FONT_FACE_META_KEY, true );
$font_type = $custom_fonts->get_font_family_type( $font_post->ID, $font_post->post_title );
$is_font_variable = 'variable' === $font_type;
$font_variables = [];
$font_variable_ranges = [];
if ( $is_font_variable ) {
$font_data = $custom_fonts->get_font_data( $font_post->ID, $font_post->post_title );
$font_variables = $font_data[ $font_post->post_title ]['variables'] ?? null;
$font_variable_ranges = $font_data[ $font_post->post_title ]['variable_ranges'] ?? null;
}
$attachments = get_attached_media( '', $font_post->ID );
$attachments_data = [];
foreach ( $attachments as $attachment ) {
$url = wp_get_attachment_url( $attachment->ID );
$attachments_data[] = [
'id' => $attachment->ID,
'title' => $attachment->post_title,
'url' => $url,
];
do_action( 'elementor/templates/collect_media_url', $url, (array) $attachment );
}
return [
'ID' => $font_post->ID,
'post_title' => $font_post->post_title,
'post_content' => $font_post->post_content,
'post_status' => $font_post->post_status,
'font_type' => $font_type,
'font_files' => $font_files,
'font_face' => $font_face,
'font_variables' => $font_variables,
'font_variable_ranges' => $font_variable_ranges,
'attachments' => $attachments_data,
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\ImportExportCustomization;
use Elementor\App\Modules\ImportExportCustomization\Processes\Export;
use Elementor\App\Modules\ImportExportCustomization\Processes\Import;
use Elementor\App\Modules\ImportExportCustomization\Processes\Revert;
class Import_Export_Customization {
const FILE_NAME = 'custom-fonts';
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() );
} );
add_action( 'elementor/import-export-customization/revert-kit', function ( Revert $revert ) {
$revert->register( new Revert_Runner() );
} );
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\ImportExportCustomization;
use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Import_Runner_Base;
use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\Custom_Fonts;
use ElementorPro\Modules\AssetsManager\AssetTypes\ImportExport\Traits\External_Attachment_Trait;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Import_Runner extends Import_Runner_Base {
use External_Attachment_Trait;
private $session_id;
private $imported_fonts = [];
public static function get_name(): string {
return 'custom-fonts';
}
public function should_import( array $data ) {
return (
isset( $data['include'] ) &&
in_array( 'settings', $data['include'], true )
);
}
public function import( array $data, array $imported_data ) {
$this->session_id = $data['session_id'];
$include_custom_fonts = $data['customization']['settings']['customFonts'] ?? true;
$custom_fonts = new Custom_Fonts();
$custom_fonts->get_fonts( true );
$result = [];
$custom_fonts_file_path = $data['extracted_directory_path'] . Import_Export_Customization::FILE_NAME;
$fonts_data = ImportExportUtils::read_json_file( $custom_fonts_file_path );
if ( empty( $fonts_data ) || ! $include_custom_fonts ) {
return $result;
}
foreach ( $fonts_data as $font_data ) {
$this->import_font( $font_data );
}
if ( empty( $this->imported_fonts ) ) {
return $result;
}
$result['site-settings']['custom-fonts'] = $this->imported_fonts;
return $result;
}
public function get_import_session_metadata(): array {
return [
'imported_fonts' => $this->imported_fonts,
];
}
private function import_font( $font_data ) {
$existing_font = $this->get_existing_font( $font_data['post_title'] );
if ( $existing_font ) {
return [];
}
$font_id = $this->create_font( $font_data );
if ( $font_id ) {
$this->imported_fonts[] = [
'id' => $font_id,
'title' => $font_data['post_title'],
];
}
}
private function get_existing_font( $font_title ) {
$font_query = new \WP_Query( [
'post_type' => Fonts_Manager::CPT,
'post_status' => 'publish',
'posts_per_page' => 1,
'title' => $font_title,
] );
if ( $font_query->have_posts() ) {
$font_post = $font_query->posts[0];
return [
'id' => $font_post->ID,
'title' => $font_post->post_title,
];
}
return null;
}
private function create_font( $font_data ) {
$font_id = wp_insert_post( [
'post_title' => $font_data['post_title'],
'post_content' => $font_data['post_content'],
'post_status' => $font_data['post_status'],
'post_type' => Fonts_Manager::CPT,
] );
if ( is_wp_error( $font_id ) ) {
return false;
}
$this->set_session_post_meta( $font_id, $this->session_id );
if ( ! empty( $font_data['font_files'] ) ) {
update_post_meta( $font_id, Custom_Fonts::FONT_META_KEY, $font_data['font_files'] );
}
if ( ! empty( $font_data['font_face'] ) ) {
update_post_meta( $font_id, Custom_Fonts::FONT_FACE_META_KEY, $font_data['font_face'] );
}
if ( ! empty( $font_data['attachments'] ) ) {
$this->import_font_attachments( $font_id, $font_data['attachments'] );
}
return $font_id;
}
private function import_font_attachments( $font_id, $attachments_data ) {
$this->create_attachments_from_urls( $font_id, $attachments_data );
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\ImportExportCustomization;
use Elementor\App\Modules\ImportExportCustomization\Runners\Revert\Revert_Runner_Base;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\Custom_Fonts;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Revert_Runner extends Revert_Runner_Base {
public static function get_name(): string {
return 'custom-fonts';
}
public function should_revert( array $data ): bool {
return (
isset( $data['runners'] ) &&
array_key_exists( static::get_name(), $data['runners'] )
);
}
public function revert( array $data ) {
$metadata = $data['runners'][ static::get_name() ] ?? [];
if ( empty( $metadata ) ) {
return;
}
$this->revert_imported_fonts( $metadata );
$this->revert_attachments( $data['session_id'] );
$custom_fonts = new Custom_Fonts();
$custom_fonts->get_fonts( true );
}
private function revert_imported_fonts( $metadata ) {
$imported_fonts = $metadata['imported_fonts'] ?? [];
foreach ( $imported_fonts as $font ) {
$this->delete_font_and_attachments( $font['id'] );
}
}
private function revert_attachments( $session_id ) {
$attachments_query = new \WP_Query( [
'post_type' => 'attachment',
'post_status' => 'inherit',
'posts_per_page' => -1,
'meta_query' => [
[
'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID,
'value' => $session_id,
'compare' => '=',
],
],
] );
if ( ! $attachments_query->have_posts() ) {
return;
}
foreach ( $attachments_query->posts as $attachment ) {
wp_delete_attachment( $attachment->ID, true );
}
}
private function delete_font_and_attachments( $font_id ) {
$attachments = get_attached_media( '', $font_id );
foreach ( $attachments as $attachment ) {
wp_delete_attachment( $attachment->ID, true );
}
wp_delete_post( $font_id, true );
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\ImportExport;
use Elementor\App\Modules\ImportExport\Runners\Export\Export_Runner_Base;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\Custom_Fonts;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Export_Runner extends Export_Runner_Base {
public static function get_name(): string {
return 'custom-fonts';
}
public function should_export( array $data ) {
return (
isset( $data['include'] ) &&
in_array( 'settings', $data['include'], true )
);
}
public function export( array $data ) {
$custom_fonts = new Custom_Fonts();
$fonts = $custom_fonts->get_fonts( true );
if ( empty( $fonts ) ) {
return [
'manifest' => [],
'files' => [],
];
}
$fonts_data = [];
$manifest_data = [];
foreach ( $fonts as $font_family => $font_type ) {
$font_data = $this->prepare_font_data( $font_family );
if ( $font_data ) {
$fonts_data[] = $font_data;
$manifest['custom-fonts'][ $font_family ] = $font_data;
}
}
return [
'files' => [
'path' => Import_Export::FILE_NAME,
'data' => $fonts_data,
],
'manifest' => [
$manifest_data,
],
];
}
private function prepare_font_data( $font_family ) {
$font_query = new \WP_Query( [
'post_type' => Fonts_Manager::CPT,
'post_status' => 'any',
'posts_per_page' => 1,
'title' => $font_family,
] );
if ( ! $font_query->have_posts() ) {
return null;
}
$font_post = $font_query->posts[0];
$custom_fonts = new Custom_Fonts();
$font_files = get_post_meta( $font_post->ID, Custom_Fonts::FONT_META_KEY, true );
$font_face = get_post_meta( $font_post->ID, Custom_Fonts::FONT_FACE_META_KEY, true );
$font_type = $custom_fonts->get_font_family_type( $font_post->ID, $font_post->post_title );
$is_font_variable = 'variable' === $font_type;
$font_variables = [];
$font_variable_ranges = [];
if ( $is_font_variable ) {
$font_data = $custom_fonts->get_font_data( $font_post->ID, $font_post->post_title );
$font_variables = $font_data[ $font_post->post_title ]['variables'] ?? null;
$font_variable_ranges = $font_data[ $font_post->post_title ]['variable_ranges'] ?? null;
}
$attachments = get_attached_media( '', $font_post->ID );
$attachments_data = [];
foreach ( $attachments as $attachment ) {
$attachments_data[] = [
'id' => $attachment->ID,
'title' => $attachment->post_title,
'url' => wp_get_attachment_url( $attachment->ID ),
];
}
return [
'ID' => $font_post->ID,
'post_title' => $font_post->post_title,
'post_content' => $font_post->post_content,
'post_status' => $font_post->post_status,
'font_type' => $font_type,
'font_files' => $font_files,
'font_face' => $font_face,
'font_variables' => $font_variables,
'font_variable_ranges' => $font_variable_ranges,
'attachments' => $attachments_data,
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\ImportExport;
use Elementor\App\Modules\ImportExport\Processes\Export;
use Elementor\App\Modules\ImportExport\Processes\Import;
use Elementor\App\Modules\ImportExport\Processes\Revert;
class Import_Export {
const FILE_NAME = 'custom-fonts';
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() );
} );
add_action( 'elementor/import-export/revert-kit', function ( Revert $revert ) {
$revert->register( new Revert_Runner() );
} );
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\ImportExport;
use Elementor\App\Modules\ImportExport\Runners\Import\Import_Runner_Base;
use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\Custom_Fonts;
use ElementorPro\Modules\AssetsManager\AssetTypes\ImportExport\Traits\External_Attachment_Trait;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Import_Runner extends Import_Runner_Base {
use External_Attachment_Trait;
private $session_id;
private $imported_fonts = [];
public static function get_name(): string {
return 'custom-fonts';
}
public function should_import( array $data ) {
return (
isset( $data['include'] ) &&
in_array( 'settings', $data['include'], true )
);
}
public function import( array $data, array $imported_data ) {
$this->session_id = $data['session_id'];
$custom_fonts = new Custom_Fonts();
$custom_fonts->get_fonts( true );
$result = [];
$custom_fonts_file_path = $data['extracted_directory_path'] . Import_Export::FILE_NAME;
$fonts_data = ImportExportUtils::read_json_file( $custom_fonts_file_path );
if ( empty( $fonts_data ) ) {
return $result;
}
foreach ( $fonts_data as $font_data ) {
$this->import_font( $font_data );
}
if ( empty( $this->imported_fonts ) ) {
return $result;
}
$result['site-settings']['custom-fonts'] = $this->imported_fonts;
return $result;
}
public function get_import_session_metadata(): array {
return [
'imported_fonts' => $this->imported_fonts,
];
}
private function import_font( $font_data ) {
$existing_font = $this->get_existing_font( $font_data['post_title'] );
if ( $existing_font ) {
return [];
}
$font_id = $this->create_font( $font_data );
if ( $font_id ) {
$this->imported_fonts[] = [
'id' => $font_id,
'title' => $font_data['post_title'],
];
}
}
private function get_existing_font( $font_title ) {
$font_query = new \WP_Query( [
'post_type' => Fonts_Manager::CPT,
'post_status' => 'publish',
'posts_per_page' => 1,
'title' => $font_title,
] );
if ( $font_query->have_posts() ) {
$font_post = $font_query->posts[0];
return [
'id' => $font_post->ID,
'title' => $font_post->post_title,
];
}
return null;
}
private function create_font( $font_data ) {
$font_id = wp_insert_post( [
'post_title' => $font_data['post_title'],
'post_content' => $font_data['post_content'],
'post_status' => $font_data['post_status'],
'post_type' => Fonts_Manager::CPT,
] );
if ( is_wp_error( $font_id ) ) {
return false;
}
$this->set_session_post_meta( $font_id, $this->session_id );
if ( ! empty( $font_data['font_files'] ) ) {
update_post_meta( $font_id, Custom_Fonts::FONT_META_KEY, $font_data['font_files'] );
}
if ( ! empty( $font_data['font_face'] ) ) {
update_post_meta( $font_id, Custom_Fonts::FONT_FACE_META_KEY, $font_data['font_face'] );
}
if ( ! empty( $font_data['attachments'] ) ) {
$this->import_font_attachments( $font_id, $font_data['attachments'] );
}
return $font_id;
}
private function import_font_attachments( $font_id, $attachments_data ) {
$this->create_attachments_from_urls( $font_id, $attachments_data );
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\ImportExport;
use Elementor\App\Modules\ImportExport\Runners\Revert\Revert_Runner_Base;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\Custom_Fonts;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Revert_Runner extends Revert_Runner_Base {
public static function get_name(): string {
return 'custom-fonts';
}
public function should_revert( array $data ): bool {
return (
isset( $data['runners'] ) &&
array_key_exists( static::get_name(), $data['runners'] )
);
}
public function revert( array $data ) {
$metadata = $data['runners'][ static::get_name() ] ?? [];
if ( empty( $metadata ) ) {
return;
}
$this->revert_imported_fonts( $metadata );
$this->revert_attachments( $data['session_id'] );
$custom_fonts = new Custom_Fonts();
$custom_fonts->get_fonts( true );
}
private function revert_imported_fonts( $metadata ) {
$imported_fonts = $metadata['imported_fonts'] ?? [];
foreach ( $imported_fonts as $font ) {
$this->delete_font_and_attachments( $font['id'] );
}
}
private function revert_attachments( $session_id ) {
$attachments_query = new \WP_Query( [
'post_type' => 'attachment',
'post_status' => 'inherit',
'posts_per_page' => -1,
'meta_query' => [
[
'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID,
'value' => $session_id,
'compare' => '=',
],
],
] );
if ( ! $attachments_query->have_posts() ) {
return;
}
foreach ( $attachments_query->posts as $attachment ) {
wp_delete_attachment( $attachment->ID, true );
}
}
private function delete_font_and_attachments( $font_id ) {
$attachments = get_attached_media( '', $font_id );
foreach ( $attachments as $attachment ) {
wp_delete_attachment( $attachment->ID, true );
}
wp_delete_post( $font_id, true );
}
}

View File

@@ -0,0 +1,262 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Fonts;
use ElementorPro\Core\Utils;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts_Manager;
use ElementorPro\Modules\AssetsManager\Classes\Font_Base;
use Elementor\Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Typekit_Fonts extends Font_Base {
const TYPEKIT_KIT_ID_OPTION_NAME = 'typekit-kit-id';
const TYPEKIT_FONTS_OPTION_NAME = 'elementor_typekit-data';
const TYPEKIT_FONTS_LINK = 'https://use.typekit.net/%s.css';
protected $kit_enqueued = false;
protected $error = '';
private $api_base = 'https://typekit.com/api/v1/json/kits';
private function get_typekit_fonts() {
return get_option( self::TYPEKIT_FONTS_OPTION_NAME, false );
}
private function get_typekit_kit_id() {
return get_option( 'elementor_' . self::TYPEKIT_KIT_ID_OPTION_NAME, false );
}
public function get_name() {
return esc_html__( 'Adobe Fonts (TypeKit)', 'elementor-pro' );
}
public function get_type() {
return 'typekit';
}
private function fetch_typekit_data() {
$kit_id = $this->get_typekit_kit_id();
if ( ! $kit_id ) {
return false;
}
$response = wp_remote_get( $this->api_base . '/' . $kit_id . '/published' );
// Response is a WP_Error object
if ( is_wp_error( $response ) ) {
return false;
}
// Response code is not success
$response_code = (int) wp_remote_retrieve_response_code( $response );
$response_body = json_decode( wp_remote_retrieve_body( $response ) );
if ( 200 !== $response_code ) {
switch ( $response_code ) {
case 404:
$this->error = esc_html__( 'Project not found.', 'elementor-pro' );
break;
default:
$this->error = $response_code;
if ( isset( $response_body->errors ) ) {
$this->error .= ': ' . implode( ', ', $response_body->errors );
}
break;
}
return false;
}
if ( ! $response_body ) {
$this->error = esc_html__( 'No project data was returned.', 'elementor-pro' );
return false;
}
/*
* Expected Json response example
* {
* "kit": {
* "id": "nmm7qvq",
* "families": [
* {
* "id": "hmqz",
* "name": "Adobe Caslon Pro",
* "slug": "adobe-caslon-pro",
* "css_names": [
* "adobe-caslon-pro"
* ],
* "css_stack": "\"adobe-caslon-pro\",serif",
* "variations": [ "n6","i6","i7" ]
* }
* ]
* }
* }
*/
if ( ! is_object( $response_body ) || ! isset( $response_body->kit ) || ! isset( $response_body->kit->families ) || ! is_array( $response_body->kit->families ) ) {
return false;
}
$families = [];
foreach ( $response_body->kit->families as $font_family ) {
$font_css = isset( $font_family->css_names[0] ) ? $font_family->css_names[0] : $font_family->slug;
$families[ $font_css ] = $this->get_type();
}
update_option( self::TYPEKIT_FONTS_OPTION_NAME, $families );
return $families;
}
private function get_kit_fonts() {
$typekit_fonts = $this->get_typekit_fonts();
if ( ! $typekit_fonts ) {
$typekit_fonts = $this->fetch_typekit_data();
}
return $typekit_fonts;
}
/**
* @param array $data
*
* @return array
* @throws \Exception
*/
public function handle_panel_request( array $data ) {
$font_family = sanitize_text_field( $data['font'] );
$typekit_fonts = $this->get_kit_fonts();
if ( ! $typekit_fonts || ! is_array( $typekit_fonts ) ) {
throw new \Exception( 'Error with TypeKit fonts.' );
}
if ( ! in_array( $font_family, array_keys( $typekit_fonts ) ) ) {
throw new \Exception( 'Font missing in Project.' );
}
$kit_id = $this->get_typekit_kit_id();
return [ 'font_url' => sprintf( self::TYPEKIT_FONTS_LINK, $kit_id ) ];
}
public function sanitize_kit_id_settings( $input ) {
if ( empty( $input ) ) {
delete_option( self::TYPEKIT_FONTS_OPTION_NAME );
}
return $input;
}
public function register_admin_fields( Settings $settings ) {
$fonts = $this->get_typekit_fonts();
$button_label = esc_html__( 'Get Project ID', 'elementor-pro' );
$found_label = '<span class="elementor-pro-typekit-count">{{count}}</span> ' . esc_html__( 'Fonts Families Found in project. Please note that typekit takes a few minutes to sync once you publish or update a project.', 'elementor-pro' );
if ( $fonts && is_array( $fonts ) ) {
$button_label = esc_html__( 'Sync Project', 'elementor-pro' );
}
$settings->add_section( Settings::TAB_INTEGRATIONS, 'typekit', [
'callback' => function() {
echo '<hr><h2>' . esc_html__( 'Adobe Fonts (TypeKit)', 'elementor-pro' ) . '</h2>';
echo esc_html__( 'TypeKit partners with the worlds leading type foundries to bring thousands of beautiful fonts to designers every day.', 'elementor-pro' );
},
'fields' => [
self::TYPEKIT_KIT_ID_OPTION_NAME => [
'label' => esc_html__( 'Project ID', 'elementor-pro' ),
'field_args' => [
'type' => 'text',
'desc' => sprintf(
/* translators: 1: Link opening tag, 2: Link closing tag. */
esc_html__( 'Enter Your %1$sTypeKit Project ID%2$s.', 'elementor-pro' ),
'<a href="https://fonts.adobe.com/typekit" target="_blank">',
'</a>'
),
],
'setting_args' => [
'sanitize_callback' => [ $this, 'sanitize_kit_id_settings' ],
],
],
'validate_api_data' => [
'field_args' => [
'type' => 'raw_html',
'html' => sprintf( '<button data-found="%s" data-action="%s" data-nonce="%s" class="button elementor-button-spinner" id="elementor_pro_typekit_validate_button">%s</button><br><p><span class="elementor-pro-typekit-data hidden"></span></p>',
esc_html( $found_label ),
self::TYPEKIT_KIT_ID_OPTION_NAME . '_fetch',
wp_create_nonce( self::TYPEKIT_KIT_ID_OPTION_NAME ),
$button_label
),
],
],
],
] );
}
public function register_fonts_in_control( $fonts ) {
$typekit_fonts = $this->get_kit_fonts();
if ( $typekit_fonts ) {
return array_merge( $typekit_fonts, $fonts );
}
return $fonts;
}
public function print_font_link( $font ) {
if ( $this->kit_enqueued ) {
return;
}
if ( $this->is_font_in_kit( $font ) ) {
$kit_url = sprintf( self::TYPEKIT_FONTS_LINK, $this->get_typekit_kit_id() );
echo '<link rel="stylesheet" type="text/css" href="' . esc_url( $kit_url ) . '">';
$this->kit_enqueued = true;
}
}
private function is_font_in_kit( $font ) {
$kit_fonts = $this->get_kit_fonts();
if ( ! $kit_fonts || ! is_array( $kit_fonts ) ) {
return false;
}
return in_array( $font, array_keys( $kit_fonts ) );
}
public function integrations_admin_ajax_handler() {
check_ajax_referer( self::TYPEKIT_KIT_ID_OPTION_NAME, '_nonce' );
if ( ! current_user_can( Fonts_Manager::CAPABILITY ) ) {
wp_send_json_error( 'Permission denied' );
}
$kit_id = Utils::_unstable_get_super_global_value( $_POST, 'kit_id' );
if ( ! $kit_id ) {
wp_send_json_error();
}
$fonts = [];
try {
update_option( 'elementor_' . self::TYPEKIT_KIT_ID_OPTION_NAME, sanitize_text_field( $kit_id ) );
$fonts = $this->fetch_typekit_data();
} catch ( \Exception $exception ) {
wp_send_json_error();
}
wp_send_json_success( [
'fonts' => $fonts,
'count' => count( $fonts ),
] );
}
protected function actions() {
parent::actions();
if ( is_admin() ) {
add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 100 );
}
add_filter( 'elementor/fonts/additional_fonts', [ $this, 'register_fonts_in_control' ] );
add_action( 'elementor/fonts/print_font_links/' . $this->get_type(), [ $this, 'print_font_link' ] );
add_action( 'wp_ajax_elementor_pro_admin_fetch_fonts', [ $this, 'integrations_admin_ajax_handler' ] );
}
}

View File

@@ -0,0 +1,276 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes;
use Elementor\Core\Admin\Menu\Admin_Menu_Manager;
use Elementor\Modules\EditorOne\Classes\Menu_Data_Provider;
use Elementor\Plugin;
use ElementorPro\Base\Editor_One_Trait;
use ElementorPro\Core\Behaviors\Feature_Lock;
use ElementorPro\License\API;
use ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems\Custom_Icons_Menu_Item;
use ElementorPro\Modules\AssetsManager\AssetTypes\AdminMenuItems\Custom_Icons_Promotion_Menu_Item;
use ElementorPro\Modules\AssetsManager\AssetTypes\EditorOneMenuItems\Editor_One_Icons_Menu_Item;
use ElementorPro\Modules\AssetsManager\AssetTypes\EditorOneMenuItems\Editor_One_Icons_Promotion;
use ElementorPro\Modules\AssetsManager\Classes;
use Elementor\Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Icons_Manager {
use Editor_One_Trait;
const CAPABILITY = 'manage_options';
const CPT = 'elementor_icons';
const MENU_SLUG = 'edit.php?post_type=' . self::CPT;
const PROMOTION_MENU_SLUG = 'e-custom-icons';
private $post_type_object;
private $enqueued_fonts = [];
protected $icon_types = [];
private $has_icons = null;
/**
* get a font type object for a given type
*
* @param null $type
*
* @return array|bool|\ElementorPro\Modules\AssetsManager\Classes\Font_Base
*/
public function get_icon_type_object( $type = null ) {
if ( null === $type ) {
return $this->icon_types;
}
if ( isset( $this->icon_types[ $type ] ) ) {
return $this->icon_types[ $type ];
}
return false;
}
/**
* Add a font type to the font manager
*
* @param string $icon_type
* @param Classes\Assets_Base $instance
*/
public function add_icon_type( $icon_type, $instance ) {
$this->icon_types[ $icon_type ] = $instance;
}
/**
* Register elementor icon set custom post type
*/
public function register_post_type() {
$labels = [
'name' => _x( 'Custom Icons', 'CPT Name', 'elementor-pro' ),
'singular_name' => _x( 'Icon Set', 'CPT Singular Name', 'elementor-pro' ),
'add_new' => esc_html__( 'Add New', 'elementor-pro' ),
'add_new_item' => esc_html__( 'Add New Icon Set', 'elementor-pro' ),
'edit_item' => esc_html__( 'Edit Icon Set', 'elementor-pro' ),
'new_item' => esc_html__( 'New Icon Set', 'elementor-pro' ),
'all_items' => esc_html__( 'All Icons', 'elementor-pro' ),
'view_item' => esc_html__( 'View Icon', 'elementor-pro' ),
'search_items' => esc_html__( 'Search Icon Set', 'elementor-pro' ),
'not_found' => esc_html__( 'No icons found', 'elementor-pro' ),
'not_found_in_trash' => esc_html__( 'No icons found in trash', 'elementor-pro' ),
'parent_item_colon' => '',
'menu_name' => _x( 'Custom Icons', 'CPT Menu Name', 'elementor-pro' ),
];
$args = [
'labels' => $labels,
'public' => false,
'rewrite' => false,
'show_ui' => true,
'show_in_menu' => false,
'show_in_nav_menus' => false,
'exclude_from_search' => true,
'capability_type' => 'post',
'hierarchical' => false,
'supports' => [ 'title' ],
];
$this->post_type_object = register_post_type( self::CPT, $args );
}
public function post_updated_messages( $messages ) {
$messages[ self::CPT ] = [
0 => '', // Unused. Messages start at index 1.
1 => esc_html__( 'Icon Set updated.', 'elementor-pro' ),
2 => esc_html__( 'Custom field updated.', 'elementor-pro' ),
3 => esc_html__( 'Custom field deleted.', 'elementor-pro' ),
4 => esc_html__( 'Icon Set updated.', 'elementor-pro' ),
/* translators: %s: Date and time of the revision. */
5 => isset( $_GET['revision'] ) ? sprintf( esc_html__( 'Icon Set restored to revision from %s', 'elementor-pro' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
6 => esc_html__( 'Icon Set saved.', 'elementor-pro' ),
7 => esc_html__( 'Icon Set saved.', 'elementor-pro' ),
8 => esc_html__( 'Icon Set submitted.', 'elementor-pro' ),
9 => esc_html__( 'Icon Set updated.', 'elementor-pro' ),
10 => esc_html__( 'Icon Set draft updated.', 'elementor-pro' ),
];
return $messages;
}
/**
* Add Font manager link to admin menu
*/
private function register_admin_menu( Admin_Menu_Manager $admin_menu_manager ) {
if ( $this->can_use_custom_icons() ) {
$admin_menu_manager->register( static::MENU_SLUG, new Custom_Icons_Menu_Item() );
} else {
$admin_menu_manager->register( static::PROMOTION_MENU_SLUG, new Custom_Icons_Promotion_Menu_Item() );
}
}
private function can_use_custom_icons() {
return ( API::is_license_active() || $this->has_icons() );
}
private function has_icons() {
if ( null !== $this->has_icons ) {
return $this->has_icons;
}
$existing_icons = new \WP_Query( [
'post_type' => static::CPT,
'posts_per_page' => 1,
] );
$this->has_icons = $existing_icons->post_count > 0;
return $this->has_icons;
}
public function redirect_admin_old_page_to_new() {
if ( ! empty( $_GET['page'] ) && 'elementor_custom_icons' === $_GET['page'] ) {
wp_safe_redirect( admin_url( static::MENU_SLUG ) );
die;
}
}
/**
* Clean up admin Font manager admin listing
*/
public function clean_admin_listing_page() {
global $typenow;
if ( self::CPT !== $typenow ) {
return;
}
add_filter( 'months_dropdown_results', '__return_empty_array' );
add_filter( 'screen_options_show_screen', '__return_false' );
}
public function post_row_actions( $actions, $post ) {
if ( self::CPT !== $post->post_type ) {
return $actions;
}
unset( $actions['inline hide-if-no-js'] );
return $actions;
}
public function add_finder_item( array $categories ) {
$categories['settings']['items']['custom-icons'] = [
'title' => esc_html__( 'Custom Icons', 'elementor-pro' ),
'icon' => 'favorite',
'url' => admin_url( static::MENU_SLUG ),
'keywords' => [ 'custom', 'icons', 'elementor' ],
];
if ( ! $this->can_use_custom_icons() ) {
$lock = new Feature_Lock( [ 'type' => 'custom-icon' ] );
$categories['settings']['items']['custom-icons']['lock'] = $lock->get_config();
}
return $categories;
}
protected function actions() {
add_action( 'init', [ $this, 'register_post_type' ] );
if ( is_admin() ) {
add_action( 'init', [ $this, 'redirect_admin_old_page_to_new' ] );
add_action( 'elementor/admin/menu/register', function ( Admin_Menu_Manager $admin_menu_manager ) {
if ( $this->is_editor_one_active() ) {
return;
}
$this->register_admin_menu( $admin_menu_manager );
} );
// TODO: BC - Remove after `Admin_Menu_Manager` will be the standard.
add_action( 'admin_menu', function () {
if ( did_action( 'elementor/admin/menu/register' ) ) {
return;
}
$menu_title = _x( 'Custom Icons', 'Elementor Font', 'elementor-pro' );
add_submenu_page(
Settings::PAGE_ID,
$menu_title,
$menu_title,
self::CAPABILITY,
static::MENU_SLUG
);
}, 50 );
add_action( 'admin_head', [ $this, 'clean_admin_listing_page' ] );
add_action( 'elementor/editor-one/menu/register', function ( Menu_Data_Provider $menu_data_provider ) {
if ( $this->can_use_custom_icons() ) {
$menu_data_provider->register_menu( new Editor_One_Icons_Menu_Item() );
} else {
$menu_data_provider->register_menu( new Editor_One_Icons_Promotion() );
}
} );
}
// TODO: Maybe just ignore all of those when the user can't use custom icons?
add_filter( 'post_updated_messages', [ $this, 'post_updated_messages' ] );
add_filter( 'post_row_actions', [ $this, 'post_row_actions' ], 10, 2 );
add_filter( 'elementor/finder/categories', [ $this, 'add_finder_item' ] );
add_filter( 'elementor/editor-one/admin-edit-post-types', function ( array $post_types ) {
$post_types[] = self::CPT;
return $post_types;
} );
/**
* Elementor icons manager loaded.
*
* Fires after the icons manager was fully loaded and instantiated.
*
* @since 2.0.0
*
* @param Fonts_Manager $this An instance of icons manager.
*/
do_action( 'elementor_pro/icons_manager_loaded', $this );
}
/**
* Fonts_Manager constructor.
*/
public function __construct() {
$this->actions();
$this->add_icon_type( 'custom', new Icons\Custom_Icons() );
$this->add_icon_type( 'font-awesome-pro', new Icons\Font_Awesome_Pro() );
}
}

View File

@@ -0,0 +1,503 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons;
use Elementor\Core\Utils\Exceptions;
use ElementorPro\Core\Utils;
use ElementorPro\Modules\AssetsManager\Classes\Assets_Base;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons_Manager;
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Custom_Icons extends Assets_Base {
const META_KEY = 'elementor_custom_icon_set_config';
const OPTION_NAME = 'elementor_custom_icon_sets_config';
public $current_post_id = 0;
public function get_name() {
return esc_html__( 'Custom Icons', 'elementor-pro' );
}
public function get_type() {
return 'custom-icons';
}
public function add_meta_box() {
add_meta_box(
'elementor-custom-icons-metabox',
__( 'Icon Set', 'elementor-pro' ),
[ $this, 'render_metabox' ],
Icons_Manager::CPT,
'normal',
'default'
);
}
public static function get_icon_set_config( $id ) {
return get_post_meta( $id, self::META_KEY, true );
}
public function render_metabox( $post ) {
wp_enqueue_media();
$save_data = self::get_icon_set_config( $post->ID );
$fields = [
[
'id' => 'open_div',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'elementor-custom-icons-metabox',
],
],
[
'id' => 'zip_upload',
'field_type' => 'dropzone',
'accept' => 'zip,application/octet-stream,application/zip,application/x-zip,application/x-zip-compressed',
'label' => false,
'sub-label' => esc_html__( 'Your Fontello, IcoMoon or Fontastic .zip file', 'elementor-pro' ),
],
[
'id' => 'close_div',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
],
[
'id' => self::META_KEY,
'name' => self::META_KEY,
'field_type' => 'input',
'input_type' => 'hidden',
'label' => false,
'value' => $save_data,
'saved' => $save_data,
],
[
'id' => Icons_Manager::CPT . '_nonce',
'name' => Icons_Manager::CPT . '_nonce',
'field_type' => 'input',
'input_type' => 'hidden',
'label' => false,
'value' => wp_create_nonce( Icons_Manager::CPT ),
],
];
foreach ( $fields as $field ) {
$field['saved'] = isset( $field['saved'] ) ? $field['saved'] : '';
}
$this->print_metabox( $fields );
}
public function save_post_meta( $post_id, $post, $update ) {
// If this is an autosave, our form has not been submitted,
// so we don't want to do anything.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
// Check the user's permissions.
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
// Check if our nonce is set.
if ( ! isset( $_POST[ Icons_Manager::CPT . '_nonce' ] ) ) {
return $post_id;
}
// Verify that the nonce is valid.
if ( ! wp_verify_nonce(
Utils::_unstable_get_super_global_value( $_POST, Icons_Manager::CPT . '_nonce' ),
Icons_Manager::CPT
) ) {
return $post_id;
}
if ( ! isset( $_POST[ self::META_KEY ] ) ) {
return delete_post_meta( $post_id, self::META_KEY );
}
// PHPCS - It will be sanitized in the next line.
$json = json_decode( stripslashes_deep( $_POST[ self::META_KEY ] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
foreach ( $json as $property => $value ) {
$json[ $property ] = $this->sanitize_text_field_recursive( $value );
}
// All good save the files array
update_post_meta( $post_id, self::META_KEY, json_encode( $json ) );
// Force refresh of list in Options Table
self::clear_icon_list_option();
}
public static function get_supported_icon_sets() {
$icon_sets = [
'fontastic' => __NAMESPACE__ . '\IconSets\Fontastic',
'fontello' => __NAMESPACE__ . '\IconSets\Fontello',
'icomoon' => __NAMESPACE__ . '\IconSets\Icomoon',
];
$additional_icon_sets = [];
/**
* Additional icon sets.
*
* Filters the icon types supported by Elementor Pro.
*
* By default Elementor Pro supports 'fontastic', 'fontello' and 'icomoon'.
* This hook allows developers to add additional icon sets.
*
* @param array $additional_icon_sets Additional icon sets.
*/
$additional_icon_sets = apply_filters( 'elementor_pro/icons_manager/custom_icons/additional_supported_types', $additional_icon_sets );
return array_merge( $additional_icon_sets, $icon_sets );
}
private function get_active_icon_sets() {
$icons = new \WP_Query( [
'post_type' => Icons_Manager::CPT,
'posts_per_page' => -1,
] );
$custom_icon_sets = [];
foreach ( $icons->posts as $icon_set ) {
$set_config = json_decode( self::get_icon_set_config( $icon_set->ID ), true );
$set_config['custom_icon_post_id'] = $icon_set->ID;
$set_config['label'] = $icon_set->post_title;
$custom_icon_sets[ $set_config['name'] ] = $set_config;
}
return $custom_icon_sets;
}
/**
* get_wp_filesystem
* @return \WP_Filesystem_Base
*/
public static function get_wp_filesystem() {
global $wp_filesystem;
if ( empty( $wp_filesystem ) ) {
require_once ABSPATH . '/wp-admin/includes/file.php';
WP_Filesystem();
}
return $wp_filesystem;
}
private function upload() {
$file = Utils::_unstable_get_super_global_value( $_FILES, 'zip_upload' );
$filename = $file['name'];
$ext = pathinfo( $filename, PATHINFO_EXTENSION );
if ( 'zip' !== $ext ) {
unlink( $filename );
return new \WP_Error( 'unsupported_file', esc_html__( 'Only zip files are allowed', 'elementor-pro' ) );
}
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
// Handler upload archive file.
$upload_result = wp_handle_upload( $file, [ 'test_form' => false ] );
if ( isset( $upload_result['error'] ) ) {
unlink( $filename );
return new \WP_Error( 'upload_error', $upload_result['error'] );
}
return $upload_result['file'];
}
private function extract_zip( $file, $to ) {
// TODO: Move to core as a util.
$valid_field_types = [
'css',
'eot',
'html',
'json',
'otf',
'svg',
'ttf',
'txt',
'woff',
'woff2',
];
$zip = new \ZipArchive();
$zip->open( $file );
$valid_entries = [];
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
for ( $i = 0; $i < $zip->numFiles; $i++ ) {
$zipped_file_name = $zip->getNameIndex( $i );
$dirname = pathinfo( $zipped_file_name, PATHINFO_DIRNAME );
// Skip the OS X-created __MACOSX directory.
if ( '__MACOSX/' === substr( $dirname, 0, 9 ) ) {
continue;
}
$zipped_extension = pathinfo( $zipped_file_name, PATHINFO_EXTENSION );
// Skip files with transversal paths.
if ( strpos( $zipped_file_name, '..' ) !== false ) {
continue;
}
if ( in_array( $zipped_extension, $valid_field_types, true ) ) {
$valid_entries[] = $zipped_file_name;
}
}
$unzip_result = false;
if ( ! empty( $valid_entries ) ) {
$unzip_result = $zip->extractTo( $to, $valid_entries );
}
if ( ! $unzip_result ) {
$unzip_result = new \WP_Error( 'error', esc_html__( 'Could not unzip or empty archive.', 'elementor-pro' ) );
}
@unlink( $file );
return $unzip_result; // TRUE | WP_Error instance.
}
private function upload_and_extract_zip() {
$zip_file = $this->upload();
if ( is_wp_error( $zip_file ) ) {
return $zip_file;
}
$filesystem = self::get_wp_filesystem();
$extract_to = trailingslashit( get_temp_dir() . pathinfo( $zip_file, PATHINFO_FILENAME ) );
$unzipped = $this->extract_zip( $zip_file, $extract_to );
if ( is_wp_error( $unzipped ) ) {
return $unzipped;
}
// Find the right folder.
$source_files = array_keys( $filesystem->dirlist( $extract_to ) );
if ( count( $source_files ) === 0 ) {
return new \WP_Error( 'incompatible_archive', esc_html__( 'Incompatible archive', 'elementor-pro' ) );
}
if ( 1 === count( $source_files ) && $filesystem->is_dir( $extract_to . $source_files[0] ) ) {
$directory = $extract_to . trailingslashit( $source_files[0] );
} else {
$directory = $extract_to;
}
return [
'directory' => $directory,
'extracted_to' => $extract_to,
];
}
public function custom_icons_upload_handler( $data ) {
if ( ! current_user_can( Icons_Manager::CAPABILITY ) ) {
return new \WP_Error( Exceptions::FORBIDDEN, 'Access denied.' );
}
$this->current_post_id = $data['post_id'];
$results = $this->upload_and_extract_zip();
if ( is_wp_error( $results ) ) {
return $results;
}
$supported_icon_sets = self::get_supported_icon_sets();
foreach ( $supported_icon_sets as $key => $handler ) {
/**
* @var IconSets\Icon_Set_Base $icon_set_handler
*/
$icon_set_handler = new $handler( $results['directory'] );
if ( ! $icon_set_handler ) {
continue;
}
if ( ! $icon_set_handler->is_valid() ) {
continue;
}
$icon_set_handler->handle_new_icon_set();
$icon_set_handler->move_files( $this->current_post_id );
$config = $icon_set_handler->build_config();
// Notify about duplicate prefix
if ( self::icon_set_prefix_exists( $config['prefix'] ) ) {
$config['duplicate_prefix'] = true;
}
return [
'config' => $config,
];
}
return new \WP_Error( 'unsupported_zip_format', esc_html__( 'The zip file provided is not supported!', 'elementor-pro' ) );
}
public function handle_delete_icon_set( $post_id ) {
if ( Icons_Manager::CPT !== get_post_type( $post_id ) ) {
return;
}
// remove all assets related to this icon set
$attachments = get_attached_media( '', $post_id );
foreach ( $attachments as $attachment ) {
wp_delete_attachment( $attachment->ID, 'true' );
}
// remove icon set assets directory
$icon_set_dir = get_post_meta( $post_id, '_elementor_icon_set_path', true );
if ( ! empty( $icon_set_dir ) && is_dir( $icon_set_dir ) ) {
$this::get_wp_filesystem()->rmdir( $icon_set_dir, true );
}
// Force refresh of list in Options Table
self::clear_icon_list_option();
}
public static function clear_icon_list_option() {
delete_option( self::OPTION_NAME );
}
public function display_post_states( $post_states, $post ) {
if ( 'publish' !== $post->post_status || Icons_Manager::CPT !== $post->post_type ) {
return $post_states;
}
$data = json_decode( self::get_icon_set_config( $post->ID ) );
if ( ! empty( $data->count ) ) {
echo sprintf( '<span class="font-variations-count">%d</span>', esc_html( $data->count ) );
}
return $post_states;
}
/**
* Render preview column in font manager admin listing
*
* @param $column
* @param $post_id
*/
public function render_columns( $column, $post_id ) {
if ( 'icons_prefix' === $column ) {
$data = json_decode( self::get_icon_set_config( $post_id ) );
if ( ! empty( $data->prefix ) ) {
echo '<pre>' . esc_html( '.' . $data->prefix ) . '</pre>';
}
}
}
/**
* Define which columns to display in font manager admin listing
*
* @param $columns
*
* @return array
*/
public function manage_columns( $columns ) {
return [
'cb' => '<input type="checkbox" />',
'title' => esc_html__( 'Icon Set', 'elementor-pro' ),
'icons_prefix' => esc_html__( 'CSS Prefix', 'elementor-pro' ),
];
}
public function update_enter_title_here( $title, $post ) {
if ( isset( $post->post_type ) && Icons_Manager::CPT === $post->post_type ) {
return esc_html__( 'Enter Icon Set Name', 'elementor-pro' );
}
return $title;
}
public function register_ajax_actions( Ajax $ajax ) {
$ajax->register_ajax_action( 'pro_assets_manager_custom_icon_upload', [ $this, 'custom_icons_upload_handler' ] );
}
public function register_icon_libraries_control( $additional_sets ) {
return array_replace( $additional_sets, self::get_custom_icons_config() );
}
public function add_custom_icon_templates( $current_screen ) {
if ( 'elementor_icons' !== $current_screen->id || 'post' !== $current_screen->base ) {
return;
}
Plugin::elementor()->common->add_template( __DIR__ . '/templates.php' );
}
public function add_custom_icons_url( $config ) {
$config['customIconsURL'] = admin_url( 'edit.php?post_type=' . Icons_Manager::CPT );
return $config;
}
public static function get_custom_icons_config() {
$config = get_option( self::OPTION_NAME, false );
if ( false === $config ) {
$icons = new \WP_Query( [
'post_type' => Icons_Manager::CPT,
'posts_per_page' => -1,
'post_status' => 'publish',
] );
$config = [];
foreach ( $icons->posts as $icon_set ) {
$set_config = json_decode( self::get_icon_set_config( $icon_set->ID ), true );
$set_config['custom_icon_post_id'] = $icon_set->ID;
$set_config['label'] = $icon_set->post_title;
if ( isset( $set_config['fetchJson'] ) ) {
unset( $set_config['icons'] );
}
$config[ $set_config['name'] ] = $set_config;
}
update_option( self::OPTION_NAME, $config );
}
return $config;
}
public static function icon_set_prefix_exists( $prefix ) {
$config = self::get_custom_icons_config();
if ( empty( $config ) ) {
return false;
}
foreach ( $config as $icon_set_name => $icon_config ) {
if ( $prefix === $icon_config['prefix'] ) {
return true;
}
}
return false;
}
public function transition_post_status( $new_status, $old_status, $post ) {
if ( Icons_Manager::CPT !== $post->post_type ) {
return;
}
if ( 'publish' === $old_status && 'publish' !== $new_status ) {
$this->clear_icon_list_option();
}
}
protected function actions() {
parent::actions();
if ( is_admin() ) {
add_action( 'add_meta_boxes_' . Icons_Manager::CPT, [ $this, 'add_meta_box' ] );
add_action( 'save_post_' . Icons_Manager::CPT, [ $this, 'save_post_meta' ], 10, 3 );
add_filter( 'display_post_states', [ $this, 'display_post_states' ], 10, 2 );
add_action( 'manage_' . Icons_Manager::CPT . '_posts_custom_column', [ $this, 'render_columns' ], 10, 2 );
add_filter( 'enter_title_here', [ $this, 'update_enter_title_here' ], 10, 2 );
add_filter( 'manage_' . Icons_Manager::CPT . '_posts_columns', [ $this, 'manage_columns' ], 100 );
add_action( 'current_screen', [ $this, 'add_custom_icon_templates' ] );
}
add_action( 'transition_post_status', [ $this, 'transition_post_status' ], 10, 3 );
add_action( 'before_delete_post', [ $this, 'handle_delete_icon_set' ] );
add_filter( 'elementor/icons_manager/additional_tabs', [ $this, 'register_icon_libraries_control' ] );
add_filter( 'elementor/editor/localize_settings', [ $this, 'add_custom_icons_url' ] );
// Ajax.
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons;
use ElementorPro\Modules\AssetsManager\Classes\Assets_Base;
use Elementor\Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Font_Awesome_Pro extends Assets_Base {
const FA_KIT_ID_OPTION_NAME = 'font_awesome_pro_kit_id';
const FA_KIT_SCRIPT_LINK = 'https://kit.fontawesome.com/%s.js';
public function get_name() {
return esc_html__( 'Font Awesome Pro', 'elementor-pro' );
}
public function get_type() {
return 'font-awesome-pro';
}
private function get_kit_id() {
return get_option( 'elementor_' . self::FA_KIT_ID_OPTION_NAME, false );
}
public function replace_font_awesome_pro( $settings ) {
$json_url = ELEMENTOR_PRO_ASSETS_URL . 'lib/font-awesome-pro/%s.js';
$icons['fa-regular'] = [
'name' => 'fa-regular',
'label' => esc_html__( 'Font Awesome - Regular Pro', 'elementor-pro' ),
'url' => false,
'enqueue' => false,
'prefix' => 'fa-',
'displayPrefix' => 'far',
'labelIcon' => 'fab fa-font-awesome-alt',
'ver' => '5.15.1-pro',
'fetchJson' => sprintf( $json_url, 'regular' ),
'native' => true,
];
$icons['fa-solid'] = [
'name' => 'fa-solid',
'label' => esc_html__( 'Font Awesome - Solid Pro', 'elementor-pro' ),
'url' => false,
'enqueue' => false,
'prefix' => 'fa-',
'displayPrefix' => 'fas',
'labelIcon' => 'fab fa-font-awesome',
'ver' => '5.15.1-pro',
'fetchJson' => sprintf( $json_url, 'solid' ),
'native' => true,
];
$icons['fa-brands'] = [
'name' => 'fa-brands',
'label' => esc_html__( 'Font Awesome - Brands Pro', 'elementor-pro' ),
'url' => false,
'enqueue' => false,
'prefix' => 'fa-',
'displayPrefix' => 'fab',
'labelIcon' => 'fab fa-font-awesome-flag',
'ver' => '5.15.1-pro',
'fetchJson' => sprintf( $json_url, 'brands' ),
'native' => true,
];
$icons['fa-light'] = [
'name' => 'fa-light',
'label' => esc_html__( 'Font Awesome - Light Pro', 'elementor-pro' ),
'url' => false,
'enqueue' => false,
'prefix' => 'fa-',
'displayPrefix' => 'fal',
'labelIcon' => 'fal fa-flag',
'ver' => '5.15.1-pro',
'fetchJson' => sprintf( $json_url, 'light' ),
'native' => true,
];
$icons['fa-duotone'] = [
'name' => 'fa-duotone',
'label' => esc_html__( 'Font Awesome - Duotone Pro', 'elementor-pro' ),
'url' => false,
'enqueue' => false,
'prefix' => 'fa-',
'displayPrefix' => 'fad',
'labelIcon' => 'fad fa-flag',
'ver' => '5.15.1-pro',
'fetchJson' => sprintf( $json_url, 'duotone' ),
'native' => true,
];
// remove Free
unset(
$settings['fa-solid'],
$settings['fa-regular'],
$settings['fa-brands']
);
return array_merge( $icons, $settings );
}
public function register_admin_fields( Settings $settings ) {
$settings->add_section( Settings::TAB_INTEGRATIONS, 'font_awesome_pro', [
'callback' => function() {
echo '<hr><h2>' . esc_html__( 'Font Awesome Pro', 'elementor-pro' ) . '</h2>';
echo esc_html__( 'Font Awesome, the web\'s most popular icon set and toolkit, Pro Integration', 'elementor-pro' );
},
'fields' => [
self::FA_KIT_ID_OPTION_NAME => [
'label' => esc_html__( 'Kit ID', 'elementor-pro' ),
'field_args' => [
'type' => 'text',
'desc' => sprintf(
/* translators: 1: Link opening tag, 2: Link closing tag. */
esc_html__( 'Enter Your %1$sFont Awesome Pro Kit ID%2$s.', 'elementor-pro' ),
'<a href="https://fontawesome.com/kits" target="_blank">',
'</a>'
),
],
'setting_args' => [
'sanitize_callback' => [ $this, 'sanitize_kit_id_settings' ],
],
],
'validate_api_data' => [
'field_args' => [
'type' => 'raw_html',
'html' => sprintf( '<button data-action="%s" data-nonce="%s" class="button elementor-button-spinner" id="elementor_pro_fa_pro_validate_button">%s</button><br><p><span class="elementor-pro-fa_pro_data hidden"></span></p>',
self::FA_KIT_ID_OPTION_NAME . '_fetch',
wp_create_nonce( self::FA_KIT_ID_OPTION_NAME ),
__( 'Validate Kit ID', 'elementor-pro' )
),
],
],
],
] );
}
public function enqueue_kit_js() {
wp_enqueue_script( 'font-awesome-pro', sprintf( self::FA_KIT_SCRIPT_LINK, $this->get_kit_id() ), [], ELEMENTOR_PRO_VERSION );
}
public function sanitize_kit_id_settings( $input ) {
if ( empty( $input ) ) {
delete_option( 'elementor_' . self::FA_KIT_ID_OPTION_NAME );
}
return $input;
}
protected function actions() {
parent::actions();
if ( is_admin() ) {
add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 100 );
}
if ( $this->get_kit_id() ) {
add_filter( 'elementor/icons_manager/native', [ $this, 'replace_font_awesome_pro' ] );
add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_kit_js' ] );
add_action( 'elementor/frontend/after_enqueue_scripts', [ $this, 'enqueue_kit_js' ] );
}
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\IconSets;
use ElementorPro\Core\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Fontastic extends Icon_Set_Base {
protected $data = '';
protected $data_file = 'icons-reference.html';
protected $stylesheet_file = 'styles.css';
protected $allowed_zipped_files = [ 'icons-reference.html', 'styles.css', 'fonts/' ];
protected $allowed_webfont_extensions = [ 'woff', 'ttf', 'svg', 'eot' ];
protected function prepare() {
$this->data = Utils::_unstable_file_get_contents( $this->directory . $this->stylesheet_file );
$this->dir_name = $this->get_unique_name();
}
public function get_type() {
return esc_html__( 'Fontastic', 'elementor-pro' );
}
public function is_valid() {
if ( ! file_exists( $this->directory . $this->data_file ) ) {
return false; // missing data file
}
return true;
}
protected function extract_icon_list() {
$pattern = '/\.' . $this->get_prefix() . '(.*)\:before\s\{/';
preg_match_all( $pattern, $this->data, $icons_matches );
if ( empty( $icons_matches[1] ) ) {
return false; // missing icons list
}
$icons = [];
foreach ( $icons_matches[1] as $icon ) {
$icons[] = $icon;
}
return $icons;
}
protected function get_prefix() {
static $set_prefix = null;
if ( null === $set_prefix ) {
$pattern = '/class\^="(.*)?"/';
preg_match_all( $pattern, $this->data, $prefix );
if ( ! isset( $prefix[1][0] ) ) {
return false; // missing css_prefix_text
}
$set_prefix = $prefix[1][0];
}
return $set_prefix;
}
public function get_name() {
static $set_name = null;
if ( null === $set_name ) {
$pattern = '/font-family: "(.*)"/';
preg_match_all( $pattern, $this->data, $name );
if ( ! isset( $name[1][0] ) ) {
return false; // missing name
}
$set_name = $name[1][0];
}
return $set_name;
}
protected function get_stylesheet( $unique_name = '' ) {
return $this->get_url() . '/' . $this->stylesheet_file;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\IconSets;
use ElementorPro\Core\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Fontello extends Icon_Set_Base {
protected $data_file = 'config.json';
protected $stylesheet_file = '';
protected $allowed_zipped_files = [ 'config.json', 'demo.html', 'README.txt', 'LICENSE.txt', 'css/', 'font/' ];
protected $allowed_webfont_extensions = [ 'woff', 'woff2', 'ttf', 'svg', 'otf' ];
protected function prepare() {
$this->remove_fontello_styling();
$this->dir_name = $this->get_unique_name();
}
public function get_type() {
return esc_html__( 'Fontello', 'elementor-pro' );
}
public function is_valid() {
if ( ! file_exists( $this->directory . $this->data_file ) ) {
return false; // missing data file
}
return true;
}
private function remove_fontello_styling() {
$filename = $this->directory . 'css/' . $this->get_name() . '.css';
$stylesheet = Utils::_unstable_file_get_contents( $filename );
$stylesheet = str_replace( [ 'margin-left: .2em;', 'margin-right: .2em;' ], [ '', '' ], $stylesheet );
file_put_contents( $filename, $stylesheet );
}
private function get_json() {
return json_decode( Utils::_unstable_file_get_contents( $this->directory . $this->data_file ) );
}
protected function extract_icon_list() {
$config = $this->get_json();
if ( ! isset( $config->glyphs ) ) {
return false; // missing icons list
}
$icons = [];
foreach ( $config->glyphs as $icon ) {
$icons[] = $icon->css;
}
return $icons;
}
protected function get_prefix() {
$config = $this->get_json();
if ( ! isset( $config->css_prefix_text ) ) {
return false; // missing css_prefix_text
}
return $config->css_prefix_text;
}
public function get_name() {
$config = $this->get_json();
if ( ! isset( $config->name ) ) {
return false; // missing name
}
return $config->name;
}
protected function get_stylesheet() {
$name = $this->get_name();
if ( ! $name ) {
return false; // missing name
}
return $this->get_url() . '/css/' . $name . '.css';
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\IconSets;
use ElementorPro\Core\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Icomoon extends Icon_Set_Base {
protected $data_file = 'selection.json';
protected $stylesheet_file = 'style.css';
protected $allowed_zipped_files = [ 'selection.json', 'demo.html', 'Read Mw.txt', 'demo-files/', 'fonts/' ];
protected $allowed_webfont_extensions = [ 'woff', 'ttf', 'svg', 'eot' ];
protected function prepare() {
$this->dir_name = $this->get_unique_name();
return [];
}
public function get_type() {
return esc_html__( 'Icomoon', 'elementor-pro' );
}
public function is_valid() {
if ( ! file_exists( $this->directory . $this->data_file ) ) {
return false; // missing data file
}
return true;
}
private function get_json() {
return json_decode( Utils::_unstable_file_get_contents( $this->directory . $this->data_file ) );
}
protected function extract_icon_list() {
$config = $this->get_json();
if ( ! isset( $config->icons ) ) {
return false; // missing icons list
}
$icons = [];
foreach ( $config->icons as $icon ) {
$icons[] = $icon->properties->name;
}
return $icons;
}
protected function get_prefix() {
$config = $this->get_json();
if ( ! isset( $config->preferences->fontPref->prefix ) ) {
return false; // missing css_prefix_text
}
return $config->preferences->fontPref->prefix;
}
protected function get_display_prefix() {
$config = $this->get_json();
if ( ! isset( $config->preferences->fontPref->classSelector ) ) {
return false; // missing css_prefix_text
}
return str_replace( '.', '', $config->preferences->fontPref->classSelector );
}
public function get_name() {
$config = $this->get_json();
if ( ! isset( $config->metadata->name ) ) {
return false; // missing name
}
return $config->metadata->name;
}
protected function get_stylesheet() {
return $this->get_url( '/' . $this->stylesheet_file );
}
}

View File

@@ -0,0 +1,254 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\IconSets;
use ElementorPro\Core\Utils;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons\Custom_Icons;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
abstract class Icon_Set_Base {
protected $dir_name = '';
protected $directory = '';
protected $data_file = '';
protected $stylesheet_file = '';
protected $allowed_zipped_files = [];
protected $files_to_save = [];
/**
* Webfont extensions.
*
* @var array
*/
protected $allowed_webfont_extensions = [ 'woff', 'woff2', 'ttf', 'svg', 'otf', 'eot' ];
abstract protected function extract_icon_list();
abstract protected function prepare();
abstract protected function get_type();
abstract public function get_name();
private function is_path_dir( $path ) {
return '/' === substr( $path, -1 );
}
private function is_file_allowed( $path_name ) {
$check = $this->directory . $path_name;
if ( ! file_exists( $check ) ) {
return false;
}
if ( $this->is_path_dir( $path_name ) ) {
return is_dir( $check );
}
return true;
}
/**
* is icon set
*
* validate that the current uploaded zip is in this icon set format
* @return bool
*/
public function is_icon_set() {
foreach ( $this->allowed_zipped_files as $file ) {
if ( ! $this->is_file_allowed( $file ) ) {
return false;
}
}
return true;
}
public function is_valid() {
return false;
}
protected function get_display_prefix() {
return '';
}
protected function get_prefix() {
return '';
}
public function handle_new_icon_set() {
return $this->prepare();
}
/**
* cleanup_temp_files
* @param \WP_Filesystem_Base $wp_filesystem
*/
protected function cleanup_temp_files( $wp_filesystem ) {
$wp_filesystem->rmdir( $this->directory, true );
}
/**
* Gets the URL to uploaded file.
*
* @param $file_name
*
* @return string
*/
protected function get_file_url( $file_name ) {
$wp_upload_dir = wp_upload_dir();
$url = $wp_upload_dir['baseurl'] . '/elementor/custom-icons/' . $file_name;
/**
* Upload file URL.
*
* Filters the URL to a file uploaded using custom icons.
*
* By default URL to a file uploaded is set to `/elementor/custom-icons/{file_name}`
* inside the WordPress uploads folder. This hook allows developers to change this URL.
*
* @since 1.0.0
*
* @param string $url File URL.
* @param string $file_name File name.
*/
$url = apply_filters( 'elementor_pro/icons_manager/custom_icons/url', $url, $file_name );
return $url;
}
protected function get_icon_sets_dir() {
$wp_upload_dir = wp_upload_dir();
$path = $wp_upload_dir['basedir'] . '/elementor/custom-icons';
/**
* Upload file path.
*
* Filters the path to a folder uploaded using custom icons.
*
* By default the folder path to custom icon files is set to `/elementor/custom-icons`
* inside the WordPress uploads folder. This hook allows developers to change this path.
*
* @param string $path Path to custom icons uploads directory.
*/
$path = apply_filters( 'elementor_pro/icons_manager/custom_icons/dir', $path );
Utils::get_ensure_upload_dir( $path );
return $path;
}
protected function get_ensure_upload_dir( $dir = '' ) {
$path = $this->get_icon_sets_dir();
if ( ! empty( $dir ) ) {
$path .= '/' . $dir;
}
return Utils::get_ensure_upload_dir( $path );
}
public function move_files( $post_id ) {
// @todo: save only needed files
$wp_filesystem = Custom_Icons::get_wp_filesystem();
$to = $this->get_ensure_upload_dir( $this->dir_name ) . '/';
foreach ( $wp_filesystem->dirlist( $this->directory, false, true ) as $file ) {
$full_path = $this->directory . $file['name'];
if ( $wp_filesystem->is_dir( $full_path ) ) {
$wp_filesystem->mkdir( $to . $file['name'] );
foreach ( $file['files'] as $filename => $sub_file ) {
$new_path = $to . $file['name'] . DIRECTORY_SEPARATOR . $filename;
$wp_filesystem->move( $full_path . DIRECTORY_SEPARATOR . $filename, $new_path );
$this->insert_attachment( $this->get_url() . '/' . $file['name'] . '/' . $filename, $new_path, $post_id );
}
} else {
$new_path = $to . $file['name'];
$wp_filesystem->move( $full_path, $new_path );
$this->insert_attachment( $this->get_url() . '/' . $file['name'], $new_path, $post_id );
}
}
$this->cleanup_temp_files( $wp_filesystem );
update_post_meta( $post_id, '_elementor_icon_set_path', $to );
$this->directory = $to;
}
private function insert_attachment( $file_url, $filename, $post_id = 0 ) {
$attachment = [
'file' => $filename,
'guid' => $file_url,
'post_parent' => $post_id,
'post_type' => 'attachment',
];
$id = wp_insert_attachment( $attachment );
return $id;
}
public function get_unique_name() {
$name = $this->get_name();
$basename = $name;
$counter = 1;
while ( ! $this->is_name_unique( $name ) ) {
$name = $basename . '-' . $counter;
$counter++;
}
return $name;
}
private function is_name_unique( $name ) {
return ! is_dir( $this->get_icon_sets_dir() . '/' . $name );
}
protected function get_url( $filename = '' ) {
return $this->get_file_url( $this->dir_name . $filename );
}
protected function get_stylesheet() {
return '';
}
protected function get_version() {
return '1.0.0';
}
protected function get_enqueue() {
return false;
}
public function build_config() {
$icon_set_config = [
'name' => $this->dir_name,
'label' => ucwords( str_replace( [ '-', '_' ], ' ', $this->dir_name ) ),
'url' => $this->get_stylesheet(),
'enqueue' => $this->get_enqueue(),
'prefix' => $this->get_prefix(),
'displayPrefix' => $this->get_display_prefix(),
'labelIcon' => 'eicon eicon-folder',
'ver' => $this->get_version(),
'custom_icon_type' => $this->get_type(),
];
$icons = $this->extract_icon_list();
$icon_set_config['count'] = count( $icons );
$icon_set_config['icons'] = $icons;
if ( 25 < $icon_set_config['count'] ) {
$icon_set_config['fetchJson'] = $this->store_icon_list_json( $icons );
}
return $icon_set_config;
}
private function store_icon_list_json( $icons ) {
$wp_filesystem = Custom_Icons::get_wp_filesystem();
$json_file = $this->get_ensure_upload_dir( $this->dir_name ) . '/e_icons.js';
$wp_filesystem->put_contents( $json_file, json_encode( [ 'icons' => $icons ] ) );
return $this->get_url() . '/e_icons.js';
}
/**
* Icon Set Base constructor.
*
* @param $directory
*/
public function __construct( $directory ) {
$this->directory = $directory;
return $this->is_icon_set() ? $this : false;
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\ImportExportCustomization;
use Elementor\App\Modules\ImportExportCustomization\Runners\Export\Export_Runner_Base;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons_Manager;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons\Custom_Icons;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Export_Runner extends Export_Runner_Base {
public static function get_name(): string {
return 'custom-icons';
}
public function should_export( array $data ) {
return (
isset( $data['include'] ) &&
in_array( 'settings', $data['include'], true )
);
}
public function export( array $data ) {
$icon_sets = $this->get_custom_icon_sets();
$include_custom_icons = $data['customization']['settings']['customIcons'] ?? true;
if ( empty( $icon_sets ) || ! $include_custom_icons ) {
return [
'manifest' => [],
'files' => [],
];
}
$icon_sets_data = [];
$manifest = [];
foreach ( $icon_sets as $icon_set ) {
$icon_set_data = $this->prepare_icon_set_data( $icon_set );
if ( $icon_set_data ) {
$icon_sets_data[] = $icon_set_data;
$manifest['custom-icons'][ $icon_set->ID ] = $icon_set_data;
}
}
return [
'files' => [
'path' => Import_Export_Customization::FILE_NAME,
'data' => $icon_sets_data,
],
'manifest' => [
$manifest,
],
];
}
private function get_custom_icon_sets() {
return get_posts( [
'post_type' => Icons_Manager::CPT,
'posts_per_page' => -1,
'post_status' => 'publish',
] );
}
private function prepare_icon_set_data( $icon_set ) {
$icon_set_config = Custom_Icons::get_icon_set_config( $icon_set->ID );
$icon_set_path = get_post_meta( $icon_set->ID, '_elementor_icon_set_path', true );
$icon_type = '';
if ( $icon_set_config ) {
$config_data = json_decode( $icon_set_config, true );
$icon_type = $config_data['custom_icon_type'] ?? '';
}
$attachments = get_attached_media( '', $icon_set->ID );
$attachments_data = [];
foreach ( $attachments as $attachment ) {
$url = wp_get_attachment_url( $attachment->ID );
$attachments_data[] = [
'id' => $attachment->ID,
'title' => $attachment->post_title,
'url' => $url,
];
do_action( 'elementor/templates/collect_media_url', $url, (array) $attachment );
}
return [
'ID' => $icon_set->ID,
'post_title' => $icon_set->post_title,
'post_content' => $icon_set->post_content,
'post_status' => $icon_set->post_status,
'icon_set_config' => $icon_set_config,
'icon_set_path' => $icon_set_path,
'icon_type' => $icon_type,
'attachments' => $attachments_data,
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\ImportExportCustomization;
use Elementor\App\Modules\ImportExportCustomization\Processes\Export;
use Elementor\App\Modules\ImportExportCustomization\Processes\Import;
use Elementor\App\Modules\ImportExportCustomization\Processes\Revert;
class Import_Export_Customization {
const FILE_NAME = 'custom-icons';
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() );
} );
add_action( 'elementor/import-export-customization/revert-kit', function ( Revert $revert ) {
$revert->register( new Revert_Runner() );
} );
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\ImportExportCustomization;
use Elementor\App\Modules\ImportExportCustomization\Runners\Import\Import_Runner_Base;
use Elementor\App\Modules\ImportExportCustomization\Utils as ImportExportUtils;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons_Manager;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons\Custom_Icons;
use ElementorPro\Modules\AssetsManager\AssetTypes\ImportExport\Traits\External_Attachment_Trait;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Import_Runner extends Import_Runner_Base {
use External_Attachment_Trait;
private $session_id;
private $imported_icon_sets = [];
public static function get_name(): string {
return 'custom-icons';
}
public function should_import( array $data ) {
return (
isset( $data['include'] ) &&
in_array( 'settings', $data['include'], true )
);
}
public function import( array $data, array $imported_data ) {
$this->session_id = $data['session_id'];
$result = [];
$custom_icons_file_path = $data['extracted_directory_path'] . Import_Export_Customization::FILE_NAME;
$icon_sets_data = ImportExportUtils::read_json_file( $custom_icons_file_path );
$include_custom_icons = $data['customization']['settings']['customIcons'] ?? true;
if ( empty( $icon_sets_data ) || ! $include_custom_icons ) {
return $result;
}
foreach ( $icon_sets_data as $icon_set_data ) {
$this->import_icon_set( $icon_set_data );
}
if ( empty( $this->imported_icon_sets ) ) {
return $result;
}
$result['site-settings']['custom-icons'] = $this->imported_icon_sets;
return $result;
}
public function get_import_session_metadata(): array {
return [
'imported_icon_sets' => $this->imported_icon_sets,
];
}
private function import_icon_set( $icon_set_data ) {
$existing_icon_set = $this->get_existing_icon_set( $icon_set_data['post_title'] );
if ( $existing_icon_set ) {
return;
}
$icon_set_id = $this->create_icon_set( $icon_set_data );
if ( $icon_set_id ) {
$this->imported_icon_sets[] = [
'id' => $icon_set_id,
'title' => $icon_set_data['post_title'],
];
}
}
private function get_existing_icon_set( $icon_set_title ) {
$icon_set_query = new \WP_Query( [
'post_type' => Icons_Manager::CPT,
'post_status' => 'publish',
'posts_per_page' => 1,
'title' => $icon_set_title,
] );
if ( $icon_set_query->have_posts() ) {
$icon_set_post = $icon_set_query->posts[0];
return [
'id' => $icon_set_post->ID,
'title' => $icon_set_post->post_title,
];
}
return null;
}
private function create_icon_set( $icon_set_data ) {
$icon_set_id = wp_insert_post( [
'post_title' => $icon_set_data['post_title'],
'post_content' => $icon_set_data['post_content'],
'post_status' => $icon_set_data['post_status'],
'post_type' => Icons_Manager::CPT,
] );
if ( is_wp_error( $icon_set_id ) ) {
return false;
}
$this->set_session_post_meta( $icon_set_id, $this->session_id );
if ( ! empty( $icon_set_data['icon_set_config'] ) ) {
update_post_meta( $icon_set_id, Custom_Icons::META_KEY, $icon_set_data['icon_set_config'] );
}
if ( ! empty( $icon_set_data['icon_set_path'] ) ) {
update_post_meta( $icon_set_id, '_elementor_icon_set_path', $icon_set_data['icon_set_path'] );
}
if ( ! empty( $icon_set_data['attachments'] ) ) {
$this->handle_icon_set_attachments( $icon_set_id, $icon_set_data['attachments'] );
}
Custom_Icons::clear_icon_list_option();
return $icon_set_id;
}
private function handle_icon_set_attachments( $icon_set_id, $attachments_data ) {
$this->create_attachments_from_urls( $icon_set_id, $attachments_data );
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\ImportExportCustomization;
use Elementor\App\Modules\ImportExportCustomization\Runners\Revert\Revert_Runner_Base;
use Elementor\Plugin;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons\Custom_Icons;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Revert_Runner extends Revert_Runner_Base {
public static function get_name(): string {
return 'custom-icons';
}
public function should_revert( array $data ): bool {
return (
isset( $data['runners'] ) &&
array_key_exists( static::get_name(), $data['runners'] )
);
}
public function revert( array $data ) {
$metadata = $data['runners'][ static::get_name() ] ?? [];
if ( empty( $metadata ) ) {
return;
}
$this->revert_imported_icon_sets( $metadata );
$this->revert_attachments( $data['session_id'] );
Custom_Icons::clear_icon_list_option();
}
private function revert_imported_icon_sets( $metadata ) {
$imported_icon_sets = $metadata['imported_icon_sets'] ?? [];
foreach ( $imported_icon_sets as $icon_set ) {
$this->delete_icon_set_and_attachments( $icon_set['id'] );
}
}
private function revert_attachments( $session_id ) {
$attachments_query = new \WP_Query( [
'post_type' => 'attachment',
'post_status' => 'inherit',
'posts_per_page' => -1,
'meta_query' => [
[
'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID,
'value' => $session_id,
'compare' => '=',
],
],
] );
if ( ! $attachments_query->have_posts() ) {
return;
}
foreach ( $attachments_query->posts as $attachment ) {
wp_delete_attachment( $attachment->ID, true );
}
}
private function delete_icon_set_and_attachments( $icon_set_id ) {
$icon_set_path = get_post_meta( $icon_set_id, '_elementor_icon_set_path', true );
if ( $icon_set_path && is_dir( $icon_set_path ) ) {
Plugin::$instance->uploads_manager->remove_file_or_dir( $icon_set_path );
}
$attachments = get_attached_media( '', $icon_set_id );
foreach ( $attachments as $attachment ) {
wp_delete_attachment( $attachment->ID, true );
}
wp_delete_post( $icon_set_id, true );
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\ImportExport;
use Elementor\App\Modules\ImportExport\Runners\Export\Export_Runner_Base;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons_Manager;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons\Custom_Icons;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Export_Runner extends Export_Runner_Base {
public static function get_name(): string {
return 'custom-icons';
}
public function should_export( array $data ) {
return (
isset( $data['include'] ) &&
in_array( 'settings', $data['include'], true )
);
}
public function export( array $data ) {
$icon_sets = $this->get_custom_icon_sets();
if ( empty( $icon_sets ) ) {
return [
'manifest' => [],
'files' => [],
];
}
$icon_sets_data = [];
$manifest = [];
foreach ( $icon_sets as $icon_set ) {
$icon_set_data = $this->prepare_icon_set_data( $icon_set );
if ( $icon_set_data ) {
$icon_sets_data[] = $icon_set_data;
$manifest['custom-icons'][ $icon_set->ID ] = $icon_set_data;
}
}
return [
'files' => [
'path' => Import_Export::FILE_NAME,
'data' => $icon_sets_data,
],
'manifest' => [
$manifest,
],
];
}
private function get_custom_icon_sets() {
return get_posts( [
'post_type' => Icons_Manager::CPT,
'posts_per_page' => -1,
'post_status' => 'publish',
] );
}
private function prepare_icon_set_data( $icon_set ) {
$icon_set_config = Custom_Icons::get_icon_set_config( $icon_set->ID );
$icon_set_path = get_post_meta( $icon_set->ID, '_elementor_icon_set_path', true );
$icon_type = '';
if ( $icon_set_config ) {
$config_data = json_decode( $icon_set_config, true );
$icon_type = $config_data['custom_icon_type'] ?? '';
}
$attachments = get_attached_media( '', $icon_set->ID );
$attachments_data = [];
foreach ( $attachments as $attachment ) {
$attachments_data[] = [
'id' => $attachment->ID,
'title' => $attachment->post_title,
'url' => wp_get_attachment_url( $attachment->ID ),
];
}
return [
'ID' => $icon_set->ID,
'post_title' => $icon_set->post_title,
'post_content' => $icon_set->post_content,
'post_status' => $icon_set->post_status,
'icon_set_config' => $icon_set_config,
'icon_set_path' => $icon_set_path,
'icon_type' => $icon_type,
'attachments' => $attachments_data,
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\ImportExport;
use Elementor\App\Modules\ImportExport\Processes\Export;
use Elementor\App\Modules\ImportExport\Processes\Import;
use Elementor\App\Modules\ImportExport\Processes\Revert;
class Import_Export {
const FILE_NAME = 'custom-icons';
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() );
} );
add_action( 'elementor/import-export/revert-kit', function ( Revert $revert ) {
$revert->register( new Revert_Runner() );
} );
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\ImportExport;
use Elementor\App\Modules\ImportExport\Runners\Import\Import_Runner_Base;
use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons_Manager;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons\Custom_Icons;
use ElementorPro\Modules\AssetsManager\AssetTypes\ImportExport\Traits\External_Attachment_Trait;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Import_Runner extends Import_Runner_Base {
use External_Attachment_Trait;
private $session_id;
private $imported_icon_sets = [];
public static function get_name(): string {
return 'custom-icons';
}
public function should_import( array $data ) {
return (
isset( $data['include'] ) &&
in_array( 'settings', $data['include'], true )
);
}
public function import( array $data, array $imported_data ) {
$this->session_id = $data['session_id'];
$result = [];
$custom_icons_file_path = $data['extracted_directory_path'] . Import_Export::FILE_NAME;
$icon_sets_data = ImportExportUtils::read_json_file( $custom_icons_file_path );
if ( empty( $icon_sets_data ) ) {
return $result;
}
foreach ( $icon_sets_data as $icon_set_data ) {
$this->import_icon_set( $icon_set_data );
}
if ( empty( $this->imported_icon_sets ) ) {
return $result;
}
$result['site-settings']['custom-icons'] = $this->imported_icon_sets;
return $result;
}
public function get_import_session_metadata(): array {
return [
'imported_icon_sets' => $this->imported_icon_sets,
];
}
private function import_icon_set( $icon_set_data ) {
$existing_icon_set = $this->get_existing_icon_set( $icon_set_data['post_title'] );
if ( $existing_icon_set ) {
return;
}
$icon_set_id = $this->create_icon_set( $icon_set_data );
if ( $icon_set_id ) {
$this->imported_icon_sets[] = [
'id' => $icon_set_id,
'title' => $icon_set_data['post_title'],
];
}
}
private function get_existing_icon_set( $icon_set_title ) {
$icon_set_query = new \WP_Query( [
'post_type' => Icons_Manager::CPT,
'post_status' => 'publish',
'posts_per_page' => 1,
'title' => $icon_set_title,
] );
if ( $icon_set_query->have_posts() ) {
$icon_set_post = $icon_set_query->posts[0];
return [
'id' => $icon_set_post->ID,
'title' => $icon_set_post->post_title,
];
}
return null;
}
private function create_icon_set( $icon_set_data ) {
$icon_set_id = wp_insert_post( [
'post_title' => $icon_set_data['post_title'],
'post_content' => $icon_set_data['post_content'],
'post_status' => $icon_set_data['post_status'],
'post_type' => Icons_Manager::CPT,
] );
if ( is_wp_error( $icon_set_id ) ) {
return false;
}
$this->set_session_post_meta( $icon_set_id, $this->session_id );
if ( ! empty( $icon_set_data['icon_set_config'] ) ) {
update_post_meta( $icon_set_id, Custom_Icons::META_KEY, $icon_set_data['icon_set_config'] );
}
if ( ! empty( $icon_set_data['icon_set_path'] ) ) {
update_post_meta( $icon_set_id, '_elementor_icon_set_path', $icon_set_data['icon_set_path'] );
}
if ( ! empty( $icon_set_data['attachments'] ) ) {
$this->handle_icon_set_attachments( $icon_set_id, $icon_set_data['attachments'] );
}
Custom_Icons::clear_icon_list_option();
return $icon_set_id;
}
private function handle_icon_set_attachments( $icon_set_id, $attachments_data ) {
$this->create_attachments_from_urls( $icon_set_id, $attachments_data );
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\Icons\ImportExport;
use Elementor\App\Modules\ImportExport\Runners\Revert\Revert_Runner_Base;
use Elementor\Plugin;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons\Custom_Icons;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Revert_Runner extends Revert_Runner_Base {
public static function get_name(): string {
return 'custom-icons';
}
public function should_revert( array $data ): bool {
return (
isset( $data['runners'] ) &&
array_key_exists( static::get_name(), $data['runners'] )
);
}
public function revert( array $data ) {
$metadata = $data['runners'][ static::get_name() ] ?? [];
if ( empty( $metadata ) ) {
return;
}
$this->revert_imported_icon_sets( $metadata );
$this->revert_attachments( $data['session_id'] );
Custom_Icons::clear_icon_list_option();
}
private function revert_imported_icon_sets( $metadata ) {
$imported_icon_sets = $metadata['imported_icon_sets'] ?? [];
foreach ( $imported_icon_sets as $icon_set ) {
$this->delete_icon_set_and_attachments( $icon_set['id'] );
}
}
private function revert_attachments( $session_id ) {
$attachments_query = new \WP_Query( [
'post_type' => 'attachment',
'post_status' => 'inherit',
'posts_per_page' => -1,
'meta_query' => [
[
'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID,
'value' => $session_id,
'compare' => '=',
],
],
] );
if ( ! $attachments_query->have_posts() ) {
return;
}
foreach ( $attachments_query->posts as $attachment ) {
wp_delete_attachment( $attachment->ID, true );
}
}
private function delete_icon_set_and_attachments( $icon_set_id ) {
$icon_set_path = get_post_meta( $icon_set_id, '_elementor_icon_set_path', true );
if ( $icon_set_path && is_dir( $icon_set_path ) ) {
Plugin::$instance->uploads_manager->remove_file_or_dir( $icon_set_path );
}
$attachments = get_attached_media( '', $icon_set_id );
foreach ( $attachments as $attachment ) {
wp_delete_attachment( $attachment->ID, true );
}
wp_delete_post( $icon_set_id, true );
}
}

View File

@@ -0,0 +1,21 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
?>
<script type="text/template" id="elementor-custom-icons-template-footer">
<div class="elementor-icon-set-footer"><?php echo esc_html__( 'Created on', 'elementor-pro' ); ?>: {{day}}/{{mm}}/{{year}}, {{hour}}:{{minute}}</div>
</script>
<script type="text/template" id="elementor-custom-icons-template-header">
<div class="elementor-icon-set-header">
<div><span class="elementor-icon-set-header-meta"><?php echo esc_html__( 'Name', 'elementor-pro' ); ?>: </span><span class="elementor-icon-set-header-meta-value">{{name}}</span></div>
<div><span class="elementor-icon-set-header-meta"><?php echo esc_html__( 'CSS Prefix', 'elementor-pro' ); ?>: </span><span class="elementor-icon-set-header-meta-value">{{prefix}}</span></div>
<div><span class="elementor-icon-set-header-meta"><?php echo esc_html__( 'Icons Count', 'elementor-pro' ); ?>: </span><span class="elementor-icon-set-header-meta-value">{{count}}</span></div>
</div>
</script>
<script type="text/template" id="elementor-custom-icons-template-duplicate-prefix">
<div class="elementor-icon-set-duplicate-prefix"><?php echo esc_html__( 'The Icon Set prefix already exists in your site. In order to avoid conflicts we recommend to use a unique prefix per Icon Set.', 'elementor-pro' ); ?></div>
</script>

View File

@@ -0,0 +1,76 @@
<?php
namespace ElementorPro\Modules\AssetsManager\AssetTypes\ImportExport\Traits;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
trait External_Attachment_Trait {
private function create_attachment_from_url( $parent_id, $attachment_data ) {
$local_file_path = \Elementor\TemplateLibrary\Classes\Media_Mapper::get_local_file_path( $attachment_data['url'] );
$imported_attachment = false;
if ( $local_file_path !== $attachment_data['url'] && file_exists( $local_file_path ) ) {
$imported_attachment = Plugin::$instance->templates_manager->get_import_images_instance()->import_local_file( $local_file_path, $parent_id );
}
if ( $imported_attachment ) {
wp_update_post( [
'ID' => $imported_attachment['id'],
'post_title' => $attachment_data['title'],
'post_content' => '',
'post_status' => 'inherit',
'post_parent' => $parent_id,
'post_mime_type' => 'application/octet-stream',
] );
return $imported_attachment['id'];
}
$attachment_id = wp_insert_attachment( [
'post_title' => $attachment_data['title'],
'post_content' => '',
'post_status' => 'inherit',
'post_parent' => $parent_id,
'post_mime_type' => 'application/octet-stream',
], '' );
if ( is_wp_error( $attachment_id ) ) {
return false;
}
update_post_meta( $attachment_id, '_external_url', $attachment_data['url'] );
$this->set_session_post_meta( $attachment_id, $this->session_id );
$this->add_external_url_filter();
return $attachment_id;
}
public function add_external_url_filter() {
add_filter( 'wp_get_attachment_url', function( $url, $attachment_id ) {
$external_url = get_post_meta( $attachment_id, '_external_url', true );
if ( $external_url ) {
return $external_url;
}
return $url;
}, 10, 2 );
}
public function create_attachments_from_urls( $parent_id, $attachments_data ): array {
$attachment_ids = [];
foreach ( $attachments_data as $attachment_data ) {
$attachment_id = $this->create_attachment_from_url( $parent_id, $attachment_data );
if ( $attachment_id ) {
$attachment_ids[] = $attachment_id;
}
}
return $attachment_ids;
}
}

View File

@@ -0,0 +1,394 @@
<?php
namespace ElementorPro\Modules\AssetsManager\Classes;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
abstract class Assets_Base {
abstract public function get_name();
abstract public function get_type();
protected function actions() { }
public function print_metabox( $fields ) {
?>
<div class="elementor-metabox-content">
<?php
foreach ( $fields as $field ) :
$field['saved'] = isset( $field['saved'] ) ? $field['saved'] : '';
// PHPCS - admin fields
echo $this->get_metabox_field_html( $field, $field['saved'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
endforeach;
?>
</div>
<?php
}
public function get_metabox_field_html( $field, $saved ) {
$html = '';
switch ( $field['field_type'] ) {
case 'html':
$html = $this->get_html_field( $field );
return $html;
break;
case 'html_tag':
$html = $this->get_html_tag( $field );
return $html;
break;
case 'toolbar':
$html = $this->get_repeater_tools( $field );
break;
case 'input':
$html = $this->get_input_field( $field, $saved );
break;
case 'select':
$html = $this->get_select_field( $field, $saved );
break;
case 'textarea':
$html = $this->get_textarea_field( $field, $saved );
break;
case 'file':
$html = $this->get_file_field( $field, $saved );
break;
case 'repeater':
$html = $this->get_repeater_field( $field, $saved );
break;
case 'dropzone':
$html = $this->get_dropzone_field( $field, $saved );
break;
case 'checkbox':
$html = $this->get_checkbox_field( $field, $saved );
break;
default:
$method = 'get_' . $field['field_type'] . 'field';
if ( method_exists( $this, $method ) ) {
$html = call_user_func( [ $this, $method ], $field, $saved );
}
break;
}
return $this->get_field_row( $field, $html );
}
public function get_field_label( $field ) {
if ( ! isset( $field['label'] ) || false === $field['label'] ) {
return '';
}
$id = $field['id'];
if ( 'file' === $field['field_type'] ) {
$id .= $field['field_type'];
}
return '<label class="elementor-field-label" for="' . esc_attr( $id ) . '">' . $field['label'] . '</label>';
}
public function get_input_field( $attributes, $saved = '' ) {
if ( isset( $attributes['input_type'] ) ) {
$attributes['type'] = $attributes['input_type'];
unset( $attributes['input_type'], $attributes['field_type'] );
}
if ( 'checkbox' === $attributes['type'] && ! empty( $saved ) ) {
$attributes['checked'] = 'checked';
} else {
if ( empty( $attributes['value'] ) && ! empty( $saved ) ) {
$attributes['value'] = $saved;
}
}
if ( empty( $attributes['name'] ) ) {
$attributes['name'] = $attributes['id'];
}
$input = '<input ' . $this->get_attribute_string( $attributes ) . '>';
return $input;
}
public function get_attribute_string( $attributes, $field = [] ) {
if ( isset( $field['extra_attributes'] ) && is_array( $field['extra_attributes'] ) ) {
$attributes = array_merge( $attributes, $field['extra_attributes'] );
}
$attributes_array = [];
foreach ( $attributes as $name => $value ) {
$attributes_array[] = sprintf( '%s="%s"', $name, esc_attr( $value ) );
}
return implode( ' ', $attributes_array );
}
public function get_select_field( $field, $selected = '' ) {
$input = '<select ';
$input .= $this->get_attribute_string( [
'name' => $field['id'],
'id' => $field['id'],
], $field );
$input .= '>' . "\n";
foreach ( $field['options'] as $value => $label ) {
$input .= '<option value="' . $value . '" ' . selected( $selected, $value, false ) . '>' . esc_attr( $label ) . '</option>' . PHP_EOL;
}
return $input . '</select>';
}
public function get_textarea_field( $field, $html ) {
$input = '<textarea ';
$input .= $this->get_attribute_string( [
'name' => $field['id'],
'id' => $field['id'],
], $field );
$input .= '>' . esc_textarea( $html ) . '</textarea>';
return $input;
}
public function get_file_field( $field, $saved ) {
$value = [
'id' => '',
'url' => '',
];
if ( isset( $saved['id'] ) && isset( $saved['url'] ) ) {
$value = $saved;
}
$html = '<ul></ul>';
$html .= $this->get_input_field(
[
'type' => 'hidden',
'name' => $field['id'] . '[id]',
'value' => $value['id'],
]
);
$html .= $this->get_input_field(
[
'type' => 'text',
'name' => $field['id'] . '[url]',
'value' => $value['url'],
'placeholder' => $field['description'],
'class' => 'elementor-field-input',
]
);
$html .= $this->get_input_field(
[
'type' => 'button',
'class' => 'button elementor-upload-btn',
'name' => $field['id'],
'id' => $field['id'],
'value' => '',
'data-preview_anchor' => isset( $field['preview_anchor'] ) ? $field['preview_anchor'] : 'none',
'data-mime_type' => isset( $field['mine'] ) ? $field['mine'] : '',
'data-ext' => isset( $field['ext'] ) ? $field['ext'] : '',
'data-upload_text' => esc_html__( 'Upload', 'elementor-pro' ),
'data-remove_text' => esc_html__( 'Delete', 'elementor-pro' ),
'data-box_title' => isset( $field['box_title'] ) ? $field['box_title'] : '',
'data-box_action' => isset( $field['box_action'] ) ? $field['box_action'] : '',
]
);
return $html;
}
public function get_html_field( $field ) {
return $field['raw_html'];
}
public function get_dropzone_field( $field ) {
ob_start();
$input_attributes = [
'type' => 'file',
'name' => $field['id'],
'id' => $field['id'],
'accept' => $field['accept'],
'class' => 'box__file',
];
if ( ! empty( $field['multiple'] ) ) {
$input_attributes['multiple'] = true;
}
$input_html = $this->get_input_field( $input_attributes );
$field['label'] = '<h4><span class="box__dragndrop">' . esc_html__( 'Drag & Drop to Upload', 'elementor-pro' ) . '</span></h4>';
if ( ! empty( $field['sub-label'] ) ) {
$field['label'] .= '<h5>' . $field['sub-label'] . '</h5>';
}
?>
<div class="elementor-dropzone-field">
<div class="box__input">
<div class="elementor--dropzone--upload__icon">
<i class="eicon-library-upload"></i>
</div>
<?php echo $input_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo $this->get_field_label( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<div class="elementor-button elementor--dropzone--upload__browse">
<span><?php echo esc_html__( 'Click here to browse', 'elementor-pro' ); ?></span>
</div>
</div>
<div class="box__uploading"><?php echo esc_html__( 'Uploading&hellip;', 'elementor-pro' ); ?></div>
<div class="box__success"><?php echo esc_html__( 'Done!', 'elementor-pro' ); ?></div>
<div class="box__error"><?php echo esc_html__( 'Error!', 'elementor-pro' ); ?> <span></span>.</div>
</div>
<?php
return ob_get_clean();
}
public function get_repeater_field( $field, $saved ) {
$id = $field['id'];
$js_id = 'repeater_' . Utils::generate_random_string();
$add_label = isset( $field['add_label'] ) ? $field['add_label'] : esc_html__( 'Add item', 'elementor-pro' );
$row_label = isset( $field['row_label'] ) ? $field['row_label'] : esc_html__( 'Row', 'elementor-pro' );
$row_label_html_args = [
'id' => 'row_label_' . $js_id,
'class' => 'repeater-title hidden',
];
if ( is_array( $row_label ) ) {
$label = $row_label['default'];
$row_label_html_args['data-default'] = $row_label['default'];
$row_label_html_args['data-selector'] = $row_label['selector'];
} else {
$label = $row_label;
$row_label_html_args['data-default'] = $row_label;
}
$row_label_html = '<span ' . $this->get_attribute_string( $row_label_html_args ) . '>' . $label . '</span>';
ob_start();
?>
<script type="text/template" id="<?php echo esc_attr( $js_id . '_block' ); ?>">
<div class="repeater-block block-visible">
<?php
echo $row_label_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->get_repeater_tools( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>
<div class="repeater-content form-table">
<?php
foreach ( $field['fields'] as $sub_field ) {
$sub_field['real_id'] = $sub_field['id'];
$sub_field['id'] = $id . '[__counter__][' . $sub_field['id'] . ']';
echo $this->get_metabox_field_html( $sub_field, '' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
</div>
</div>
</script>
<?php
$counter = 0;
$row_label_html_args['class'] = 'repeater-title';
$row_label_html = '<span ' . $this->get_attribute_string( $row_label_html_args ) . '>' . $label . '</span>';
if ( is_array( $saved ) && count( $saved ) > 0 ) {
foreach ( (array) $saved as $key => $item ) {
echo '<div class="repeater-block">';
echo $row_label_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->get_repeater_tools( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="repeater-content hidden form-table">';
foreach ( $field['fields'] as $sub_field ) {
$default = isset( $sub_field['default'] ) ? $sub_field['default'] : '';
$item_meta = isset( $item[ $sub_field['id'] ] ) ? $item[ $sub_field['id'] ] : $default;
$sub_field['real_id'] = $sub_field['id'];
$sub_field['id'] = $id . '[' . $counter . '][' . $sub_field['id'] . ']';
echo $this->get_metabox_field_html( $sub_field, $item_meta ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>'; // end table
echo '</div>';
$counter++;
}
}
echo '<input type="button" class="button button-primary add-repeater-row" value="' . esc_attr( $add_label ) . '" data-template-id="' . esc_html( $js_id ) . '_block">';
return ob_get_clean();
}
public function get_checkbox_field( $field, $saved ) {
Utils::print_unescaped_internal_string( $this->get_field_row( $field, '' ) );
$html = '<div id="' . esc_attr( $field['id'] ) . '" class="elementor-field-checkboxes">';
foreach ( $field['options'] as $checkbox_key => $label ) {
$name = $field['id'] . '_' . $checkbox_key;
$checked = ! empty( $saved ) && in_array( $checkbox_key, $saved, true ) ? 'checked' : '';
$html .= '<input name="' . esc_attr( $name ) . '" type="checkbox" ' . esc_attr( $checked ) . '><span class="label">' . esc_html( $label ) . '</span></input>';
}
$html .= '</div>';
return $html;
}
private function get_html_tag( $field ) {
$tag = isset( $field['tag'] ) ? $field['tag'] : 'div';
if ( isset( $field['close'] ) && true === $field['close'] ) {
return '</' . $tag . '>';
}
return '<' . $tag . ' ' . $this->get_attribute_string( $field['attributes'] ) . '>';
}
private function get_repeater_tools( $field ) {
$confirm = isset( $field['confirm'] ) ? $field['confirm'] : esc_html__( 'Are you sure?', 'elementor-pro' );
$remove_title = isset( $field['remove_title'] ) ? $field['remove_title'] : esc_html__( 'Delete', 'elementor-pro' );
$toggle_title = isset( $field['toggle_title'] ) ? $field['toggle_title'] : esc_html__( 'Edit', 'elementor-pro' );
$close_title = isset( $field['close_title'] ) ? $field['close_title'] : esc_html__( 'Close', 'elementor-pro' );
return '<span class="elementor-repeater-tool-btn close-repeater-row" title="' . esc_attr( $close_title ) . '">
<i class="eicon-close" aria-hidden="true"></i>' . $close_title . '
</span>
<span class="elementor-repeater-tool-btn toggle-repeater-row" title="' . esc_attr( $toggle_title ) . '">
<i class="eicon-edit" aria-hidden="true"></i>' . $toggle_title . '
</span>
<span class="elementor-repeater-tool-btn remove-repeater-row" data-confirm="' . $confirm . '" title="' . esc_attr( $remove_title ) . '">
<i class="eicon-trash" aria-hidden="true"></i>' . $remove_title . '
</span>';
}
public function get_field_row( $field, $field_html ) {
$description = '';
$css_id = isset( $field['id'] ) ? ' ' . $field['id'] : '';
if ( isset( $field['real_id'] ) ) {
$css_id = ' ' . $field['real_id'];
}
$css_id .= ' elementor-field-' . $field['field_type'];
return '<div class="elementor-field' . $css_id . '">' . $this->get_field_label( $field ) . $field_html . $description . '</div>';
}
public function sanitize_text_field_recursive( $data ) {
if ( is_array( $data ) ) {
foreach ( $data as $key => $value ) {
$data[ $key ] = $this->sanitize_text_field_recursive( $value );
}
return $data;
}
return sanitize_text_field( $data );
}
public function __construct() {
$this->actions();
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace ElementorPro\Modules\AssetsManager\Classes;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Font_Base extends Assets_Base {
const FONTS_OPTION_NAME = 'elementor_fonts_manager_fonts';
protected $font_preview_phrase = '';
protected function actions() {}
public function __construct() {
parent::__construct();
$this->font_preview_phrase = esc_html__( 'Elementor Is Making the Web Beautiful', 'elementor-pro' );
}
public function get_name() {
return '';
}
public function get_type() {
return '';
}
public function handle_panel_request( array $data ) {
return [];
}
public function get_fonts( $force = false ) {}
public function enqueue_font( $font_family, $font_data, $post_css ) {}
public function get_font_family_type( $post_id, $post_title ) {}
public function get_font_data( $post_id, $post_title ) {}
public function render_preview_column( $post_id ) {}
public function render_type_column( $post_id ) {}
public function get_font_variations_count( $post_id ) {}
public function save_meta( $post_id, $data ) {}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace ElementorPro\Modules\AssetsManager;
use ElementorPro\Base\Module_Base;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\ImportExport\Import_Export as Fonts_Import_Export;
use ElementorPro\Modules\AssetsManager\AssetTypes\Fonts\ImportExportCustomization\Import_Export_Customization as Fonts_Import_Export_Customization;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons\ImportExport\Import_Export as Icons_Import_Export;
use ElementorPro\Modules\AssetsManager\AssetTypes\Icons\ImportExportCustomization\Import_Export_Customization as Icons_Import_Export_Customization;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
private $asset_managers = [];
public function get_name() {
return 'assets-manager';
}
public function add_asset_manager( $name, $instance ) {
$this->asset_managers[ $name ] = $instance;
}
public function get_assets_manager( $id = null ) {
if ( $id ) {
if ( ! isset( $this->asset_managers[ $id ] ) ) {
return null;
}
return $this->asset_managers[ $id ];
}
return $this->asset_managers;
}
public function __construct() {
parent::__construct();
$this->add_asset_manager( 'font', new AssetTypes\Fonts_Manager() );
$this->add_asset_manager( 'icon', new AssetTypes\Icons_Manager() );
$this->register_import_export();
}
private function register_import_export() {
( new Fonts_Import_Export() )->register_hooks();
( new Fonts_Import_Export_Customization() )->register_hooks();
( new Icons_Import_Export() )->register_hooks();
( new Icons_Import_Export_Customization() )->register_hooks();
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class Action_Base {
/**
* Get the action type identifier.
*
* @return string Action type (e.g., 'email', 'webhook', 'collect-submissions')
*/
abstract public function get_type(): string;
/**
* Execute the action with the provided form data and widget settings.
*
* @param array $form_data Sanitized form data submitted by the user.
* Example: ['name' => 'John', 'email' => 'john@example.com']
* @param array $widget_settings Full widget settings - action extracts what it needs.
* Example: ['email_to' => 'admin@site.com', 'email_subject' => 'New form', ...]
* @param array $context Additional context (post_id, form_id, form_name).
* Example: ['post_id' => 123, 'form_id' => 'contact', 'form_name' => 'Contact Form']
* @return array Result array with 'status' and optional data.
* Success: ['status' => 'success', 'message' => '...', ...]
* Failure: ['status' => 'failed', 'error' => '...', ...]
*/
abstract public function execute( array $form_data, array $widget_settings, array $context ): array;
/**
* Validate widget settings for this action.
*
* @param array $widget_settings Widget settings to validate.
* @return bool|\WP_Error True if valid, WP_Error otherwise.
*/
protected function validate_settings( array $widget_settings ) {
return true;
}
/**
* Format a success result.
*
* @param string $message Success message.
* @param array $additional_data Additional data to include.
* @return array
*/
protected function success( string $message, array $additional_data = [] ): array {
return array_merge(
[
'status' => 'success',
'message' => $message,
],
$additional_data
);
}
/**
* Format a failure result.
*
* @param string $error Error message.
* @param array $additional_data Additional data to include.
* @return array
*/
protected function failure( string $error, array $additional_data = [] ): array {
return array_merge(
[
'status' => 'failed',
'error' => $error,
],
$additional_data
);
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Action_Runner {
/**
* Registered actions.
*
* @var Action_Base[]
*/
private static $actions = [];
/**
* Register an action.
*
* @param Action_Base $action Action instance to register.
* @return void
*/
public static function register_action( Action_Base $action ): void {
self::$actions[ $action->get_type() ] = $action;
}
/**
* Create an action instance by type.
*
* @param string $type Action type.
* @return Action_Base|null Action instance or null if not found.
*/
public static function create_action( string $type ): ?Action_Base {
if ( ! isset( self::$actions[ $type ] ) ) {
return null;
}
return self::$actions[ $type ];
}
/**
* Get all registered actions.
*
* @return Action_Base[] Array of registered actions.
*/
public static function get_registered_actions(): array {
return self::$actions;
}
/**
* Check if an action type is registered.
*
* @param string $type Action type.
* @return bool
*/
public static function has_action( string $type ): bool {
return isset( self::$actions[ $type ] );
}
/**
* Execute multiple actions and gather results.
*
* @param string[] $actions Array of action type strings.
* @param array $form_data Sanitized form data.
* @param array $widget_settings Full widget settings for actions to extract what they need.
* @param array $context Form context (post_id, form_id, form_name).
* @return array Results containing actionResults, allActionsSucceeded, failedActions, and optional submissionId.
*/
public static function execute_actions( array $actions, array $form_data, array $widget_settings, array $context ): array {
$action_results = [];
$failed_actions = [];
foreach ( $actions as $action_type ) {
if ( ! Action_Type::is_valid( $action_type ) ) {
$action_results[] = [
'type' => $action_type,
'status' => 'failed',
'error' => sprintf( __( 'Invalid action type: %s', 'elementor-pro' ), $action_type ),
];
$failed_actions[] = $action_type;
continue;
}
try {
$action = self::create_action( $action_type );
if ( ! $action ) {
throw new \Exception( sprintf( __( 'Could not create action: %s', 'elementor-pro' ), $action_type ) );
}
$result = $action->execute( $form_data, $widget_settings, $context );
$action_results[] = array_merge(
[ 'type' => $action_type ],
$result
);
} catch ( \Exception $e ) {
$action_results[] = [
'type' => $action_type,
'status' => 'failed',
'error' => $e->getMessage(),
];
$failed_actions[] = $action_type;
}
}
$all_actions_succeeded = empty( $failed_actions );
$response = [
'actionResults' => $action_results,
'allActionsSucceeded' => $all_actions_succeeded,
'failedActions' => $failed_actions,
];
return $response;
}
/**
* Initialize default actions.
*
* @return void
*/
public static function init(): void {
self::register_action( new Email_Action() );
self::register_action( new Collect_Submissions_Action() );
self::register_action( new Webhook_Action() );
/**
* Allow registering custom actions.
*
* @param Action_Factory $factory The action factory instance.
*/
do_action( 'elementor_pro/atomic_forms/actions/register', __CLASS__ );
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Action_Type {
const EMAIL = 'email';
const COLLECT_SUBMISSIONS = 'collect-submissions';
const WEBHOOK = 'webhook';
/**
* Get all registered action types.
*
* @return array
*/
public static function get_all_types(): array {
return [
self::EMAIL,
self::COLLECT_SUBMISSIONS,
self::WEBHOOK,
];
}
/**
* Check if an action type is valid.
*
* @param string $type Action type to validate.
* @return bool
*/
public static function is_valid( string $type ): bool {
return in_array( $type, self::get_all_types(), true );
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
use ElementorPro\Core\Utils;
use ElementorPro\Modules\Forms\Submissions\Database\Query;
use ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository;
use Elementor\Utils as ElementorUtils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Collect_Submissions_Action extends Action_Base {
public function get_type(): string {
return Action_Type::COLLECT_SUBMISSIONS;
}
public function execute( array $form_data, array $widget_settings, array $context ): array {
$metadata_keys = $this->normalize_metadata_keys( $widget_settings['submissions_metadata'] ?? [] );
$field_metadata = $context['field_metadata'] ?? [];
$fields = $this->prepare_fields( $form_data, $field_metadata );
$submission_id = Query::get_instance()->add_submission(
[
'main_meta_id' => 0,
'post_id' => $context['post_id'],
'referer' => $this->get_referer(),
'referer_title' => $this->get_referer_title(),
'element_id' => $context['form_id'],
'form_name' => $context['form_name'],
'campaign_id' => 0,
'user_id' => get_current_user_id(),
'user_ip' => in_array( 'remote_ip', $metadata_keys, true ) ? Utils::get_client_ip() : '',
'user_agent' => in_array( 'user_agent', $metadata_keys, true ) ? $this->get_user_agent() : '',
'actions_count' => 0,
'actions_succeeded_count' => 0,
'meta' => wp_json_encode( [] ),
],
$fields
);
if ( ! $submission_id ) {
return $this->failure( __( 'Failed to save submission to database', 'elementor-pro' ) );
}
$this->store_form_snapshot( $context, $fields );
return $this->success(
__( 'Submission saved successfully', 'elementor-pro' ),
[ 'submissionId' => $submission_id ]
);
}
private function normalize_metadata_keys( array $raw ): array {
$allowed = [ 'remote_ip', 'user_agent' ];
return array_values( array_intersect( $raw, $allowed ) );
}
private function prepare_fields( array $form_data, array $field_metadata = [] ): array {
$fields = [];
foreach ( $form_data as $key => $value ) {
$meta = $field_metadata[ $key ] ?? [];
$label = ! empty( $meta['label'] ) ? $meta['label'] : ucwords( str_replace( [ '_', '-' ], ' ', $key ) );
$type = ! empty( $meta['type'] ) ? $meta['type'] : $this->guess_field_type( $key, $value );
$fields[] = [
'id' => $key,
'type' => $type,
'label' => $label,
'value' => is_array( $value ) ? implode( ', ', $value ) : $value,
];
}
return $fields;
}
private function guess_field_type( string $key, $value ): string {
$key_lower = strtolower( $key );
if ( strpos( $key_lower, 'email' ) !== false ) {
return 'email';
}
if ( strpos( $key_lower, 'phone' ) !== false || strpos( $key_lower, 'tel' ) !== false ) {
return 'tel';
}
if ( is_array( $value ) ) {
return 'checkbox';
}
if ( strpos( $key_lower, 'message' ) !== false || ( is_string( $value ) && strlen( $value ) > 100 ) ) {
return 'textarea';
}
if ( strpos( $key_lower, 'url' ) !== false || strpos( $key_lower, 'website' ) !== false ) {
return 'url';
}
return 'text';
}
private function store_form_snapshot( array $context, array $fields ): void {
$snapshot_fields = array_map(
function ( $field ) {
return [
'id' => $field['id'],
'type' => $field['type'],
'label' => $field['label'],
];
},
$fields
);
Form_Snapshot_Repository::instance()->create_or_update(
$context['post_id'],
$context['form_id'],
[
'name' => $context['form_name'],
'fields' => $snapshot_fields,
]
);
}
private function get_referer(): string {
$referer = ElementorUtils::get_super_global_value( $_SERVER, 'HTTP_REFERER' );
if ( $referer ) {
return esc_url_raw( wp_unslash( $referer ) );
}
return '';
}
private function get_referer_title(): string {
// For now, return empty as we don't have access to the frontend page title
return '';
}
private function get_user_agent(): string {
$user_agent = ElementorUtils::get_super_global_value( $_SERVER, 'HTTP_USER_AGENT' );
if ( $user_agent ) {
return sanitize_textarea_field( wp_unslash( $user_agent ) );
}
return '';
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
use ElementorPro\Modules\AtomicForm\Actions\Email_Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Email_Action extends Action_Base {
public function get_type(): string {
return Action_Type::EMAIL;
}
public function execute( array $form_data, array $widget_settings, array $context ): array {
$validation = $this->validate_settings( $widget_settings );
if ( is_wp_error( $validation ) ) {
return $this->failure( $validation->get_error_message() );
}
$email_settings = new Email_Settings( $widget_settings );
$to = $email_settings->to();
$from = $email_settings->from();
$from_name = $email_settings->from_name();
$message = $email_settings->message();
$subject = $email_settings->subject();
$reply_to = $email_settings->reply_to();
$cc = $email_settings->cc();
$bcc = $email_settings->bcc();
$content_type = $email_settings->content_type();
$field_metadata = $context['field_metadata'] ?? [];
$message = $this->replace_shortcodes( $message, $form_data, 'html' === $content_type, $field_metadata );
$headers = [];
$headers[] = sprintf( 'From: %s <%s>', $from_name, $from );
$headers[] = sprintf( 'Reply-To: %s', $reply_to );
if ( 'html' === $content_type ) {
$headers[] = 'Content-Type: text/html; charset=UTF-8';
}
if ( ! empty( $cc ) ) {
$headers[] = sprintf( 'Cc: %s', $cc );
}
if ( ! empty( $bcc ) ) {
$headers[] = sprintf( 'Bcc: %s', $bcc );
}
/**
* Filter email headers for atomic forms.
*
* @param array $headers Email headers.
* @param array $form_data Form data.
* @param array $widget_settings Widget settings.
*/
$headers = apply_filters( 'elementor_pro/atomic_forms/email_headers', $headers, $form_data, $widget_settings );
/**
* Filter email message for atomic forms.
*
* @param string $message Email message.
* @param array $form_data Form data.
* @param array $widget_settings Widget settings.
*/
$message = apply_filters( 'elementor_pro/atomic_forms/email_message', $message, $form_data, $widget_settings );
$email_sent = wp_mail( $to, $subject, $message, $headers );
if ( ! $email_sent ) {
return $this->failure( __( 'Failed to send email', 'elementor-pro' ) );
}
return $this->success( __( 'Email sent successfully', 'elementor-pro' ) );
}
protected function validate_settings( array $widget_settings ) {
$email_settings = new Email_Settings( $widget_settings );
$email_to = $email_settings->to();
if ( ! empty( $email_to ) && ! is_email( $email_to ) ) {
$emails = array_map( 'trim', explode( ',', $email_to ) );
foreach ( $emails as $email ) {
if ( ! is_email( $email ) ) {
return new \WP_Error(
'invalid_email',
sprintf(
/* translators: %s: Invalid email address. */
__( 'Invalid email address: %s', 'elementor-pro' ),
$email
)
);
}
}
}
return true;
}
private function replace_shortcodes( string $message, array $form_data, bool $is_html, array $field_metadata = [] ): string {
$line_break = $is_html ? '<br>' : "\n";
if ( strpos( $message, '[all-fields]' ) !== false ) {
$all_fields_text = '';
foreach ( $form_data as $key => $value ) {
$meta = $field_metadata[ $key ] ?? [];
$formatted_key = ! empty( $meta['label'] ) ? $meta['label'] : ucwords( str_replace( [ '_', '-' ], ' ', $key ) );
$formatted_value = is_array( $value ) ? implode( ', ', $value ) : $value;
if ( $is_html ) {
$formatted_key = esc_html( $formatted_key );
if ( is_string( $formatted_value ) ) {
$formatted_value = nl2br( esc_html( $formatted_value ) );
}
}
$all_fields_text .= sprintf(
'%s: %s%s',
$formatted_key,
$formatted_value,
$line_break
);
}
$message = str_replace( '[all-fields]', $all_fields_text, $message );
}
$message = preg_replace_callback(
'/\[field[^\]]*id=["\']([^"\']+)["\'][^\]]*\]/',
function ( $matches ) use ( $form_data ) {
$field_id = $matches[1];
if ( isset( $form_data[ $field_id ] ) ) {
$value = $form_data[ $field_id ];
return is_array( $value ) ? implode( ', ', $value ) : $value;
}
return '';
},
$message
);
return $message;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
use ElementorPro\Core\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Email_Settings {
private $email_settings;
public function __construct( array $widget_settings ) {
$this->email_settings = $widget_settings['email'] ?? [];
}
public function to() {
return $this->email_settings['to'] ?? get_option( 'admin_email' );
}
public function from() {
return $this->email_settings['from'] ?? 'noreply@' . Utils::get_site_domain();
}
public function from_name() {
return $this->email_settings['from-name'] ?? get_bloginfo( 'name' );
}
public function subject() {
return $this->email_settings['subject'] ?? sprintf(
/* translators: %s: Site title. */
__( 'New message from "%s"', 'elementor-pro' ),
get_bloginfo( 'name' )
);
}
public function message() {
return $this->email_settings['message'] ?? '[all-fields]';
}
public function reply_to() {
return $this->email_settings['reply-to'] ?? $this->from();
}
public function cc() {
return $this->email_settings['cc'] ?? '';
}
public function bcc() {
return $this->email_settings['bcc'] ?? '';
}
public function content_type() {
return $this->email_settings['send-as'] ?? 'html';
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Webhook_Action extends Action_Base {
public function get_type(): string {
return Action_Type::WEBHOOK;
}
public function execute( array $form_data, array $widget_settings, array $context ): array {
$validation = $this->validate_settings( $widget_settings );
if ( is_wp_error( $validation ) ) {
return $this->failure( $validation->get_error_message() );
}
$url = $widget_settings['webhook_url'];
$method = strtoupper( $widget_settings['webhook_method'] ?? 'POST' );
$timeout = isset( $widget_settings['webhook_timeout'] ) ? absint( $widget_settings['webhook_timeout'] ) : 30;
$payload = [
'formData' => $form_data,
'postId' => $context['post_id'],
'formId' => $context['form_id'],
'formName' => $context['form_name'],
'timestamp' => current_time( 'mysql' ),
'siteUrl' => get_site_url(),
];
/**
* Filter webhook payload for atomic forms.
*
* @param array $payload Webhook payload.
* @param array $form_data Form data.
* @param array $widget_settings Widget settings.
* @param array $context Form context.
*/
$payload = apply_filters(
'elementor_pro/atomic_forms/webhook_payload',
$payload,
$form_data,
$widget_settings,
$context
);
$args = [
'method' => $method,
'timeout' => $timeout,
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'Elementor Pro Atomic Forms/' . ELEMENTOR_PRO_VERSION,
],
'body' => wp_json_encode( $payload ),
];
if ( ! empty( $widget_settings['webhook_headers'] ) && is_array( $widget_settings['webhook_headers'] ) ) {
$args['headers'] = array_merge( $args['headers'], $widget_settings['webhook_headers'] );
}
$response = wp_remote_request( $url, $args );
if ( is_wp_error( $response ) ) {
return $this->failure(
sprintf(
/* translators: %s: Error message. */
__( 'Webhook request failed: %s', 'elementor-pro' ),
$response->get_error_message()
)
);
}
$response_code = wp_remote_retrieve_response_code( $response );
$response_body = wp_remote_retrieve_body( $response );
if ( $response_code >= 200 && $response_code < 300 ) {
return $this->success(
__( 'Webhook delivered successfully', 'elementor-pro' ),
[
'responseCode' => $response_code,
'responseBody' => $response_body,
]
);
}
return $this->failure(
sprintf(
/* translators: %d: HTTP status code. */
__( 'Webhook returned error status code: %d', 'elementor-pro' ),
$response_code
),
[
'responseCode' => $response_code,
'responseBody' => $response_body,
]
);
}
protected function validate_settings( array $widget_settings ) {
if ( empty( $widget_settings['webhook_url'] ) ) {
return new \WP_Error(
'missing_url',
__( 'Webhook URL is required', 'elementor-pro' )
);
}
if ( ! filter_var( $widget_settings['webhook_url'], FILTER_VALIDATE_URL ) ) {
return new \WP_Error(
'invalid_url',
__( 'Invalid webhook URL', 'elementor-pro' )
);
}
if ( isset( $widget_settings['webhook_method'] ) ) {
$allowed_methods = [ 'GET', 'POST', 'PUT', 'PATCH', 'DELETE' ];
if ( ! in_array( strtoupper( $widget_settings['webhook_method'] ), $allowed_methods, true ) ) {
return new \WP_Error(
'invalid_method',
__( 'Invalid HTTP method', 'elementor-pro' )
);
}
}
return true;
}
}

View File

@@ -0,0 +1,248 @@
<?php
namespace ElementorPro\Modules\AtomicForm;
use Elementor\Utils as ElementorUtils;
use ElementorPro\Modules\AtomicForm\Actions\Action_Runner;
use ElementorPro\Modules\AtomicWidgets\Settings_Resolver;
use ElementorPro\Modules\Forms\Classes\Ajax_Handler;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Atomic_Form_Controller {
const NONCE_ACTION = 'elementor_pro_atomic_forms_send_form';
public static function is_form_submitted(): bool {
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is validated in ajax_send_form.
return wp_doing_ajax()
&& 'elementor_pro_atomic_forms_send_form' === ElementorUtils::get_super_global_value( $_POST, 'action' );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
public function ajax_send_form(): void {
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is validated below.
$post_data = [
'_nonce' => ElementorUtils::get_super_global_value( $_POST, '_nonce' ),
'post_id' => ElementorUtils::get_super_global_value( $_POST, 'post_id' ),
'form_id' => ElementorUtils::get_super_global_value( $_POST, 'form_id' ),
'form_name' => ElementorUtils::get_super_global_value( $_POST, 'form_name' ),
'form_fields' => ElementorUtils::get_super_global_value( $_POST, 'form_fields' ) ?? [],
];
// phpcs:enable WordPress.Security.NonceVerification.Missing
if ( ! $this->is_nonce_valid( $post_data ) ) {
$this->send_invalid_form_response();
}
$post_id = absint( $post_data['post_id'] ?? 0 );
$form_id = sanitize_text_field( $post_data['form_id'] ?? '' );
$form_fields = $post_data['form_fields'] ?? [];
if ( ! $post_id || ! $form_id || empty( $form_fields ) ) {
$this->send_invalid_form_response();
}
$form_data = $this->convert_form_fields_to_data( $form_fields );
if ( empty( $form_data ) ) {
$this->send_invalid_form_response();
}
$field_metadata = $this->extract_field_metadata( $form_fields );
$widget_settings = $this->get_widget_settings( $post_id, $form_id );
if ( is_wp_error( $widget_settings ) ) {
$this->send_error_response( $widget_settings->get_error_message() );
}
$posted_form_name = sanitize_text_field( $post_data['form_name'] ?? '' );
$form_name = $this->resolve_form_name( $posted_form_name, $form_id );
$spam_check = apply_filters(
'elementor_pro/atomic_forms/spam_check',
false,
$form_fields,
$widget_settings,
$post_id
);
if ( $spam_check ) {
$this->send_error_response(
__( 'Your submission was flagged as spam. Please try again or contact the site administrator.', 'elementor-pro' )
);
}
$actions = $widget_settings['actions-after-submit'] ?? [];
if ( empty( $actions ) ) {
$this->send_error_response( __( 'No actions configured for this form', 'elementor-pro' ) );
}
$results = Action_Runner::execute_actions(
$actions,
$form_data,
$widget_settings,
[
'post_id' => $post_id,
'form_id' => $form_id,
'form_name' => $form_name,
'field_metadata' => $field_metadata,
]
);
$this->send_response(
$results['actionResults'],
$results['allActionsSucceeded'],
$results['failedActions']
);
}
private function is_nonce_valid( array $post_data ): bool {
$nonce = $post_data['_nonce'] ?? '';
if ( ! $nonce ) {
return false;
}
return wp_verify_nonce( $nonce, self::NONCE_ACTION );
}
private function convert_form_fields_to_data( array $form_fields ): array {
$form_data = [];
foreach ( $form_fields as $field ) {
if ( ! is_array( $field ) ) {
continue;
}
$id = sanitize_text_field( $field['id'] ?? '' );
$value = $field['value'] ?? '';
if ( ! $id ) {
continue;
}
if ( is_array( $value ) ) {
$form_data[ $id ] = array_map( 'sanitize_text_field', $value );
} else {
$type = sanitize_text_field( $field['type'] ?? 'text' );
if ( 'textarea' === $type ) {
$form_data[ $id ] = sanitize_textarea_field( $value );
} else {
$form_data[ $id ] = sanitize_text_field( $value );
}
}
}
return $form_data;
}
private function resolve_form_name( string $posted_form_name, string $form_id ): string {
return ! empty( $posted_form_name ) ? $posted_form_name : $form_id;
}
private function extract_field_metadata( array $form_fields ): array {
$metadata = [];
foreach ( $form_fields as $field ) {
if ( ! is_array( $field ) ) {
continue;
}
$id = sanitize_text_field( $field['id'] ?? '' );
if ( ! $id ) {
continue;
}
$metadata[ $id ] = [
'label' => sanitize_text_field( $field['label'] ?? '' ),
'type' => sanitize_text_field( $field['type'] ?? '' ),
];
}
return $metadata;
}
private function get_widget_settings( int $post_id, string $form_id ) {
$document = Plugin::elementor()->documents->get( $post_id );
if ( ! $document ) {
return new \WP_Error(
'document_not_found',
__( 'Document not found', 'elementor-pro' )
);
}
$element_data = $document->get_elements_data();
$form_element = ElementorUtils::find_element_recursive( $element_data, $form_id );
if ( empty( $form_element ) ) {
return new \WP_Error(
'form_not_found',
__( 'Form element not found', 'elementor-pro' )
);
}
$settings = $form_element['settings'] ?? [];
$resolved = Settings_Resolver::resolve( $settings );
if ( ! isset( $resolved['actions-after-submit'] ) && isset( $resolved['email'] ) ) {
$resolved['actions-after-submit'] = [ 'email' ];
}
return $resolved;
}
private function send_invalid_form_response(): void {
wp_send_json_error( [
'message' => Ajax_Handler::get_default_message( Ajax_Handler::INVALID_FORM, [] ),
] );
}
private function send_error_response( string $message = '' ): void {
wp_send_json_error( [
'message' => $message ?? Ajax_Handler::get_default_message( Ajax_Handler::ERROR, [] ),
] );
}
private function send_response( array $action_results, bool $all_actions_succeeded, array $failed_actions ): void {
$response_data = [
'actionResults' => $action_results,
'allActionsSucceeded' => $all_actions_succeeded,
'failedActions' => $failed_actions,
];
if ( $all_actions_succeeded ) {
wp_send_json_success( [
'message' => Ajax_Handler::get_default_message( Ajax_Handler::SUCCESS, [] ),
'data' => $response_data,
] );
} else {
$has_success = ! empty( $action_results ) && count( $failed_actions ) < count( $action_results );
if ( $has_success ) {
wp_send_json_success( [
'message' => Ajax_Handler::get_default_message( Ajax_Handler::SUCCESS, [] ),
'data' => $response_data,
] );
} else {
wp_send_json_error( [
'message' => Ajax_Handler::get_default_message( Ajax_Handler::ERROR, [] ),
'data' => $response_data,
] );
}
}
}
public function __construct() {
add_action( 'wp_ajax_elementor_pro_atomic_forms_send_form', [ $this, 'ajax_send_form' ] );
add_action( 'wp_ajax_nopriv_elementor_pro_atomic_forms_send_form', [ $this, 'ajax_send_form' ] );
}
}

View File

@@ -0,0 +1,22 @@
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') | trim %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% set interactions_attribute = interactions is not empty ? 'data-interactions=' ~ interactions | json_encode | e('html_attr') : '' %}
{% set placeholder_attribute = settings.placeholder is not empty ? 'placeholder=' ~ settings.placeholder | e('html_attr') : '' %}
{% set required_attribute = settings.required ? 'required' : '' %}
{% set checked_attribute = settings.checked ? 'checked' : '' %}
{% set name = settings.name is not empty ? settings.name : settings._cssid is not empty ? 'checkbox_' ~ settings._cssid : 'checkbox_' ~ id %}
{% set name_attribute = 'name=' ~ name | e('html_attr') %}
{% set value_attribute = settings.value is not empty ? 'value=' ~ settings.value | e('html_attr') : '' %}
<input
{{ id_attribute }}
{{ name_attribute }}
{{ value_attribute }}
class="{{ classes }}"
type="checkbox"
data-interaction-id="{{ interaction_id | default(id) }}"
{{ settings.attributes | raw }}
{{ interactions_attribute }}
{{ placeholder_attribute | raw }}
{{ required_attribute }}
{{ checked_attribute }}
/>

View File

@@ -0,0 +1,121 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Checkbox;
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\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\Primitives\Boolean_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Checkbox extends Atomic_Widget_Base {
use Has_Template;
public static $widget_description = 'Display a checkbox input with required, readonly, and attributes.';
public static function get_element_type(): string {
return 'e-form-checkbox';
}
public function get_title(): string {
return esc_html__( 'Checkbox', 'elementor-pro' );
}
public function get_icon(): string {
return 'eicon-atomic-checkbox';
}
public function get_categories(): array {
return [ 'atomic-form' ];
}
public function get_keywords() {
return [ 'atomic', 'form', 'checkbox' ];
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'name' => String_Prop_Type::make()
->default( '' ),
'value' => String_Prop_Type::make()
->default( '' ),
'required' => Boolean_Prop_Type::make()
->default( false ),
'checked' => Boolean_Prop_Type::make()
->default( false ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Content', 'elementor-pro' ) )
->set_items( [
Text_Control::bind_to( 'name' )
->set_label( __( 'Group name', 'elementor-pro' ) )
->set_placeholder( __( 'Enter checkbox group name', 'elementor-pro' ) )
->set_meta( [
'layout' => 'two-columns',
] ),
Text_Control::bind_to( 'value' )
->set_label( __( 'Choice value', 'elementor-pro' ) )
->set_placeholder( __( 'Enter choice value', 'elementor-pro' ) )
->set_meta( [
'layout' => 'two-columns',
] ),
Switch_Control::bind_to( 'required' )
->set_label( __( 'Required', 'elementor-pro' ) ),
Switch_Control::bind_to( 'checked' )
->set_label( __( 'Checked', 'elementor-pro' ) ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor-pro' ) )
->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-pro' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function get_templates(): array {
return [
'checkbox' => __DIR__ . '/checkbox.html.twig',
];
}
protected function define_base_styles(): array {
return [];
}
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
protected function define_atomic_pseudo_states(): array {
return [
Style_States::get_pseudo_states_map()['checked'],
];
}
}

View File

@@ -0,0 +1,192 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Classes;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Atomic_Form;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
use ElementorPro\Core\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Akismet {
public function __construct() {
add_filter( 'elementor/atomic-widgets/props-schema', [ $this, 'register_props' ] );
add_filter( 'elementor/atomic-widgets/controls', [ $this, 'register_controls' ], 10, 2 );
add_filter( 'elementor_pro/atomic_forms/spam_check', [ $this, 'is_spam' ], 10, 4 );
}
public function register_props( array $schema ): array {
if ( ! isset( $schema['actions-after-submit'] ) ) {
return $schema;
}
if ( ! $this->is_akismet_active() ) {
return $schema;
}
$schema['akismet-enabled'] = Boolean_Prop_Type::make()->default( false );
return $schema;
}
public function register_controls( array $controls, $element ): array {
if ( ! ( $element instanceof Atomic_Form ) ) {
return $controls;
}
if ( ! $this->is_akismet_active() ) {
return $controls;
}
$controls[] = Section::make()
->set_label( __( 'Akismet', 'elementor-pro' ) )
->set_items( [
Switch_Control::bind_to( 'akismet-enabled' )
->set_label( __( 'Akismet spam protection', 'elementor-pro' ) ),
] );
return $controls;
}
public function is_akismet_active(): bool {
if ( ! class_exists( '\Akismet' ) ) {
return false;
}
$akismet_key = \Akismet::get_api_key();
return ! empty( $akismet_key );
}
public function is_spam( bool $is_spam, array $form_fields, array $widget_settings, int $post_id = 0 ): bool {
if ( $is_spam ) {
return true;
}
if ( empty( $widget_settings['akismet-enabled'] ) ) {
return false;
}
if ( ! $this->is_akismet_active() ) {
return false;
}
$mapped = $this->map_fields( $form_fields );
$params = $this->build_params( $mapped, $post_id );
return $this->remote_check_is_spam( $params );
}
private function map_fields( array $form_fields ): array {
$mapped = [
'comment_author' => '',
'comment_author_email' => '',
'comment_author_url' => '',
'comment_content' => '',
];
$text_fields = [];
$textarea_fields = [];
foreach ( $form_fields as $field ) {
if ( ! is_array( $field ) ) {
continue;
}
$type = sanitize_text_field( $field['type'] ?? 'text' );
$value = sanitize_text_field( $field['value'] ?? '' );
$label = sanitize_text_field( $field['label'] ?? '' );
switch ( $type ) {
case 'email':
if ( empty( $mapped['comment_author_email'] ) ) {
$mapped['comment_author_email'] = $value;
}
break;
case 'url':
if ( empty( $mapped['comment_author_url'] ) ) {
$mapped['comment_author_url'] = $value;
}
break;
case 'textarea':
$textarea_fields[] = [
'label' => $label,
'value' => sanitize_textarea_field( $field['value'] ?? '' ),
];
break;
case 'text':
$text_fields[] = [
'label' => $label,
'value' => $value,
];
break;
}
}
$mapped['comment_author'] = $this->concatenate_fields( $text_fields );
$mapped['comment_content'] = $this->concatenate_fields( $textarea_fields );
return $mapped;
}
private function concatenate_fields( array $fields ): string {
if ( empty( $fields ) ) {
return '';
}
if ( 1 === count( $fields ) ) {
return $fields[0]['value'];
}
$parts = [];
foreach ( $fields as $field ) {
$label = ! empty( $field['label'] ) ? $field['label'] : 'Field';
$parts[] = $label . ': ' . $field['value'];
}
return implode( ' | ', $parts );
}
private function build_params( array $mapped, int $post_id = 0 ): array {
$params = $mapped;
$params['blog'] = get_option( 'home' );
$params['blog_lang'] = get_locale();
$params['blog_charset'] = get_option( 'blog_charset' );
if ( $post_id ) {
$params['permalink'] = get_permalink( $post_id );
}
$params['user_ip'] = Utils::get_client_ip();
$params['referrer'] = wp_get_referer();
if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
$params['user_agent'] = sanitize_textarea_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) );
}
$params['comment_type'] = 'contact-form';
$ignore = [ 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' ];
foreach ( $_SERVER as $key => $value ) {
if ( ! in_array( $key, $ignore, true ) && is_string( $value ) ) {
$params[ $key ] = sanitize_text_field( wp_unslash( $value ) );
}
}
return $params;
}
private function remote_check_is_spam( array $params ): bool {
$response = \Akismet::http_post( _http_build_query( $params, '', '&' ), 'comment-check' );
return 'true' === $response[1];
}
}

View File

@@ -0,0 +1,20 @@
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') | trim %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% set interactions_attribute = interactions is not empty ? 'data-interactions=' ~ interactions | json_encode | e('html_attr') : '' %}
{% set placeholder_attribute = settings.placeholder is not empty ? 'placeholder=' ~ settings.placeholder | e('html_attr') : '' %}
{% set required_attribute = settings.required ? 'required' : '' %}
{% set readonly_attribute = settings.readonly ? 'readonly' : '' %}
{% set name = settings.name is not empty ? settings.name : settings._cssid is not empty ? settings._cssid : id %}
{% set name_attribute = 'name=' ~ name | e('html_attr') %}
<input
{{ id_attribute }}
{{ name_attribute }}
class="{{ classes }}"
type="{{ settings.type }}"
data-interaction-id="{{ interaction_id | default(id) }}"
{{ settings.attributes | raw }}
{{ interactions_attribute }}
{{ placeholder_attribute | raw }}
{{ required_attribute }}
{{ readonly_attribute }}
/>

View File

@@ -0,0 +1,178 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Input;
use Elementor\Modules\AtomicWidgets\Controls\Section;
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\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\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\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Input extends Atomic_Widget_Base {
use Has_Template;
public static $widget_description = 'Display a text input with customizable type, placeholder, default value, required, readonly, and attributes.';
public static function get_element_type(): string {
return 'e-form-input';
}
public function get_title(): string {
return esc_html__( 'Input', 'elementor-pro' );
}
public function get_icon(): string {
return 'eicon-atomic-input';
}
public function get_categories(): array {
return [ 'atomic-form' ];
}
public function get_keywords() {
return [ 'atomic', 'form', 'input', 'text', 'email', 'number', 'tel', 'password' ];
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'placeholder' => String_Prop_Type::make()
->default( '' ),
'type' => String_Prop_Type::make()
->default( 'text' )
->enum( [ 'text', 'email', 'number', 'tel', 'password' ] ),
'required' => Boolean_Prop_Type::make()
->default( false ),
'readonly' => Boolean_Prop_Type::make()
->default( false ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Content', 'elementor-pro' ) )
->set_items( [
Text_Control::bind_to( 'placeholder' )
->set_placeholder( 'Enter placeholder text' )
->set_label( __( 'Input placeholder', 'elementor-pro' ) ),
Select_Control::bind_to( 'type' )
->set_label( __( 'Type', 'elementor-pro' ) )
->set_options( [
[
'label' => __( 'Text', 'elementor-pro' ),
'value' => 'text',
],
[
'label' => __( 'Email', 'elementor-pro' ),
'value' => 'email',
],
[
'label' => __( 'Number', 'elementor-pro' ),
'value' => 'number',
],
[
'label' => __( 'Tel', 'elementor-pro' ),
'value' => 'tel',
],
[
'label' => __( 'Password', 'elementor-pro' ),
'value' => 'password',
],
] ),
Switch_Control::bind_to( 'required' )
->set_label( __( 'Required', 'elementor-pro' ) ),
Switch_Control::bind_to( 'readonly' )
->set_label( __( 'Read only', 'elementor-pro' ) ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor-pro' ) )
->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-pro' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function get_templates(): array {
return [
'input' => __DIR__ . '/input.html.twig',
];
}
protected function define_base_styles(): array {
$border_radius_value = Size_Prop_Type::generate( [
'size' => 0,
'unit' => 'px',
] );
$height_value = Size_Prop_Type::generate( [
'size' => 36,
'unit' => 'px',
] );
$border_color_value = Color_Prop_Type::generate( '#D6D5D5' );
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( [
'border-radius' => $border_radius_value,
'height' => $height_value,
'border-color' => $border_color_value,
'font-family' => String_Prop_Type::generate( 'Poppins' ),
'font-size' => Size_Prop_Type::generate( [
'size' => 12,
'unit' => 'px',
] ),
] ),
)
->add_variant(
Style_Variant::make()
->set_state( Style_States::FOCUS )
->add_props( [
'border-color' => Color_Prop_Type::generate( '#706F6F' ),
'outline-style' => String_Prop_Type::generate( 'none' ),
] ),
),
'base::placeholder' => Style_Definition::make() // this should be changed once we support placeholder/pseudo-elements styles in the styles system.
->add_variant(
Style_Variant::make()
->add_props( [
'color' => Color_Prop_Type::generate( '#9DA5AE' ),
] ),
),
];
}
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
}

View File

@@ -0,0 +1,13 @@
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') | trim %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% set for_attribute = settings['input-id'] is not empty ? 'for=' ~ settings['input-id'] | e('html_attr') : '' %}
{% set interactions_attribute = interactions is not empty ? 'data-interactions=' ~ interactions | json_encode | e('html_attr') : '' %}
{% set allowed_tags = '<b><strong><sup><sub><s><em><i><u><a><del><span><br>' %}
<label
{{ id_attribute }}
class="{{ classes }}"
{{ for_attribute }}
data-interaction-id="{{ interaction_id | default(id) }}"
{{ settings.attributes | raw }}
{{ interactions_attribute }}
>{{ settings.text | striptags(allowed_tags) | raw }}</label>

View File

@@ -0,0 +1,127 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Label;
use Elementor\Modules\AtomicWidgets\Controls\Section;
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\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\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Label extends Atomic_Widget_Base {
use Has_Template;
public static $widget_description = 'Display a label with customizable text and for attribute.';
public static function get_element_type(): string {
return 'e-form-label';
}
public function get_title(): string {
return esc_html__( 'Label', 'elementor-pro' );
}
public function get_icon(): string {
return 'eicon-atomic-label';
}
public function get_categories(): array {
return [ 'atomic-form' ];
}
public function get_keywords() {
return [ 'atomic', 'form', 'label', 'text' ];
}
protected static function define_props_schema(): array {
return [
'tag' => String_Prop_Type::make()
->default( 'label' ),
'classes' => Classes_Prop_Type::make()
->default( [] ),
'text' => Html_V3_Prop_Type::make()
->default( [
'content' => String_Prop_Type::generate( 'Form label' ),
'children' => [],
] ),
'input-id' => String_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( __( 'Content', 'elementor-pro' ) )
->set_items( [
Inline_Editing_Control::bind_to( 'text' )
->set_label( __( 'Label text', 'elementor-pro' ) ),
Text_Control::bind_to( 'input-id' )
->set_label( __( 'Connected to input ID', 'elementor-pro' ) )
->set_meta( [
'layout' => 'two-columns',
] ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor-pro' ) )
->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-pro' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function get_templates(): array {
return [
'label' => __DIR__ . '/label.html.twig',
];
}
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
protected function define_base_styles(): array {
$text_color_value = Color_Prop_Type::generate( '#0c0d0e' );
$font_size_value = Size_Prop_Type::generate( [
'size' => 14,
'unit' => 'px',
] );
$font_family_value = String_Prop_Type::generate( 'Poppins' );
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( [
'color' => $text_color_value,
'font-family' => $font_family_value,
'font-size' => $font_size_value,
] ),
),
];
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace ElementorPro\Modules\AtomicForm;
use Elementor\Core\Experiments\Manager as ExperimentsManager;
use Elementor\Modules\AtomicWidgets\Module as AtomicWidgetsModule;
use Elementor\Widgets_Manager;
use ElementorPro\Base\Module_Base;
use ElementorPro\License\API;
use ElementorPro\Modules\AtomicForm\Actions\Action_Runner;
use ElementorPro\Modules\AtomicForm\Classes\Akismet;
use ElementorPro\Modules\AtomicForm\Input\Input;
use ElementorPro\Modules\AtomicForm\Label\Label;
use ElementorPro\Modules\AtomicForm\Textarea\Textarea;
use ElementorPro\Modules\AtomicForm\Submit_Button\Submit_Button;
use ElementorPro\Modules\AtomicForm\Checkbox\Checkbox;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
const MODULE_NAME = 'e-atomic-form';
const EXPERIMENT_NAME = 'e_pro_atomic_form';
const AKISMET_LICENSE_FEATURE_NAME = 'akismet';
public function get_name() {
return self::MODULE_NAME;
}
public static function get_experimental_data(): array {
return [
'name' => self::EXPERIMENT_NAME,
'title' => esc_html__( 'Atomic Form', 'elementor-pro' ),
'description' => esc_html__( 'Atomic form widgets. Note: This feature requires the "Atomic Widgets" experiment to be enabled.', 'elementor-pro' ),
'hidden' => true,
'default' => ExperimentsManager::STATE_ACTIVE,
'release_status' => ExperimentsManager::RELEASE_STATUS_DEV,
];
}
public function __construct() {
parent::__construct();
if ( ! $this->is_experiment_active() ) {
return;
}
if ( class_exists( '\Akismet' ) && API::is_licence_has_feature( static::AKISMET_LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) {
$this->add_component( 'akismet', new Akismet() );
}
add_filter(
'elementor/widgets/register',
fn( $widgets_manager ) => $this->register_widgets( $widgets_manager )
);
add_action( 'elementor/frontend/after_enqueue_styles', fn () => $this->add_inline_styles() );
add_action( 'init', fn() => Action_Runner::init() );
if ( Atomic_Form_Controller::is_form_submitted() ) {
$this->add_component( 'atomic_ajax_handler', new Atomic_Form_Controller() );
do_action( 'elementor_pro/atomic_forms/form_submitted', $this );
}
}
private function is_experiment_active(): bool {
return version_compare( ELEMENTOR_VERSION, '4.0', '>=' )
&& Plugin::elementor()->experiments->is_feature_active( self::EXPERIMENT_NAME )
&& Plugin::elementor()->experiments->is_feature_active( AtomicWidgetsModule::EXPERIMENT_NAME );
}
private function register_widgets( Widgets_Manager $widgets_manager ) {
$widgets_manager->register( new Input() );
$widgets_manager->register( new Label() );
$widgets_manager->register( new Textarea() );
$widgets_manager->register( new Submit_Button() );
$widgets_manager->register( new Checkbox() );
}
private function add_inline_styles() {
$inline_styles = [
Textarea::get_inline_styles(),
Submit_Button::get_inline_styles(),
];
wp_add_inline_style( 'elementor-frontend', implode( ' ', $inline_styles ) );
}
}

View File

@@ -0,0 +1,11 @@
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') | trim %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% set interactions_attribute = interactions is not empty ? 'data-interactions=' ~ interactions | json_encode | e('html_attr') : '' %}
<button
{{ id_attribute }}
class="{{ classes }}"
type="submit"
data-interaction-id="{{ interaction_id | default(id) }}"
{{ settings.attributes | raw }}
{{ interactions_attribute }}
>{{ settings.label | e }}</button>

View File

@@ -0,0 +1,155 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Submit_Button;
use Elementor\Modules\AtomicWidgets\Controls\Section;
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\Background_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_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;
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Submit_Button extends Atomic_Widget_Base {
use Has_Template;
public static $widget_description = 'Display a submit button with customizable label text.';
private static $button_background_color_hover = '#323232';
public static function get_element_type(): string {
return 'e-form-submit-button';
}
public function get_title(): string {
return esc_html__( 'Submit button', 'elementor-pro' );
}
public function get_icon(): string {
return 'eicon-atomic-submit-button';
}
public function get_categories(): array {
return [ 'atomic-form' ];
}
public function get_keywords() {
return [ 'atomic', 'form', 'button', 'submit', 'send' ];
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'label' => String_Prop_Type::make()
->default( 'Submit' ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Content', 'elementor-pro' ) )
->set_items( [
Text_Control::bind_to( 'label' )
->set_label( __( 'Button Text', 'elementor-pro' ) )
->set_placeholder( 'Submit' ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor-pro' ) )
->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-pro' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function get_templates(): array {
return [
'submit_button' => __DIR__ . '/submit-button.html.twig',
];
}
protected function define_base_styles(): array {
$background_color_value = Background_Prop_Type::generate( [
'color' => Color_Prop_Type::generate( '#000' ),
] );
$text_color_value = Color_Prop_Type::generate( '#fff' );
$display_value = String_Prop_Type::generate( 'flex' );
$padding_value = Dimensions_Prop_Type::generate( [
'block-start' => Size_Prop_Type::generate( [
'size' => 10,
'unit' => 'px',
]),
'inline-end' => Size_Prop_Type::generate( [
'size' => 30,
'unit' => 'px',
]),
'block-end' => Size_Prop_Type::generate( [
'size' => 10,
'unit' => 'px',
]),
'inline-start' => Size_Prop_Type::generate( [
'size' => 28,
'unit' => 'px',
]),
]);
$justify_content_value = String_Prop_Type::generate( 'center' );
$align_items_value = String_Prop_Type::generate( 'center' );
$background_color_hover_value = Background_Prop_Type::generate( [
'color' => Color_Prop_Type::generate( self::$button_background_color_hover ),
] );
$border_base_size = Size_Prop_Type::generate( [
'size' => 0,
'unit' => 'px',
] );
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'background', $background_color_value )
->add_prop( 'color', $text_color_value )
->add_prop( 'display', $display_value )
->add_prop( 'padding', $padding_value )
->add_prop( 'justify-content', $justify_content_value )
->add_prop( 'align-items', $align_items_value )
->add_prop( 'border-radius', $border_base_size )
->add_prop( 'border-width', $border_base_size )
)
->add_variant(
Style_Variant::make()
->set_state( Style_States::HOVER )
->add_prop( 'background', $background_color_hover_value )
),
];
}
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
public static function get_inline_styles(): string {
$base_class = self::get_element_type() . '-base';
return ".{$base_class} { cursor: pointer; box-sizing: border-box; }";
}
}

View File

@@ -0,0 +1,27 @@
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') | trim %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% set interactions_attribute = interactions is not empty ? 'data-interactions=' ~ interactions | json_encode | e('html_attr') : '' %}
{% set placeholder_attribute = settings.placeholder is not empty ? 'placeholder=' ~ settings.placeholder | e('html_attr') : '' %}
{% set required_attribute = settings.required ? 'required' : '' %}
{% set readonly_attribute = settings.readonly ? 'readonly' : '' %}
{% set rows_attribute = settings.rows is not empty ? 'rows=' ~ settings.rows | e('html_attr') : '' %}
{% set minlength_attribute = settings.minlength is not empty ? 'minlength=' ~ settings.minlength | e('html_attr') : '' %}
{% set maxlength_attribute = settings.maxlength is not empty ? 'maxlength=' ~ settings.maxlength | e('html_attr') : '' %}
{% set resizable_attribute = settings.resizable ? 'data-resizable' : '' %}
{% set name = settings.name is not empty ? settings.name : settings._cssid is not empty ? settings._cssid : id %}
{% set name_attribute = 'name=' ~ name | e('html_attr') %}
<textarea
{{ id_attribute }}
{{ name_attribute }}
class="{{ classes }}"
data-interaction-id="{{ interaction_id | default(id) }}"
{{ settings.attributes | raw }}
{{ interactions_attribute }}
{{ placeholder_attribute | raw }}
{{ required_attribute }}
{{ readonly_attribute }}
{{ rows_attribute }}
{{ minlength_attribute }}
{{ maxlength_attribute }}
{{ resizable_attribute }}
></textarea>

View File

@@ -0,0 +1,173 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Textarea;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Number_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_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\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\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Textarea extends Atomic_Widget_Base {
use Has_Template;
public static $widget_description = 'Display a text area with customizable type, placeholder, default value, required, readonly, and attributes.';
public static function get_element_type(): string {
return 'e-form-textarea';
}
public function get_title(): string {
return esc_html__( 'Text area', 'elementor-pro' );
}
public function get_icon(): string {
return 'eicon-atomic-text-area';
}
public function get_categories(): array {
return [ 'atomic-form' ];
}
public function get_keywords() {
return [ 'atomic', 'form', 'textarea', 'text', 'email' ];
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'placeholder' => String_Prop_Type::make()
->default( '' ),
'rows' => Number_Prop_Type::make()
->default( 4 ),
'required' => Boolean_Prop_Type::make()
->default( false ),
'readonly' => Boolean_Prop_Type::make()
->default( false ),
'resizable' => Boolean_Prop_Type::make()
->default( true ),
'minlength' => Number_Prop_Type::make(),
'maxlength' => Number_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-pro' ) )
->set_items( [
Text_Control::bind_to( 'placeholder' )
->set_placeholder( 'Enter placeholder text' )
->set_label( __( 'Text area placeholder', 'elementor-pro' ) ),
Number_Control::bind_to( 'rows' )
->set_label( __( 'Rows', 'elementor-pro' ) )
->set_min( 1 )
->set_step( 1 ),
Switch_Control::bind_to( 'required' )
->set_label( __( 'Required', 'elementor-pro' ) ),
Switch_Control::bind_to( 'readonly' )
->set_label( __( 'Read only', 'elementor-pro' ) ),
Switch_Control::bind_to( 'resizable' )
->set_label( __( 'Resizable', 'elementor-pro' ) ),
Number_Control::bind_to( 'minlength' )
->set_label( __( 'Min length', 'elementor-pro' ) )
->set_min( 0 )
->set_step( 1 ),
Number_Control::bind_to( 'maxlength' )
->set_label( __( 'Max length', 'elementor-pro' ) )
->set_min( 0 )
->set_step( 1 ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor-pro' ) )
->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-pro' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function get_templates(): array {
return [
'textarea' => __DIR__ . '/textarea.html.twig',
];
}
protected function define_base_styles(): array {
$border_radius_value = Size_Prop_Type::generate( [
'size' => 0,
'unit' => 'px',
] );
$border_color_value = Color_Prop_Type::generate( '#D6D5D5' );
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( [
'border-radius' => $border_radius_value,
'border-color' => $border_color_value,
'font-family' => String_Prop_Type::generate( 'Poppins' ),
'font-size' => Size_Prop_Type::generate( [
'size' => 12,
'unit' => 'px',
] ),
] ),
)
->add_variant(
Style_Variant::make()
->set_state( Style_States::FOCUS )
->add_props( [
'border-color' => Color_Prop_Type::generate( '#706F6F' ),
'outline-style' => String_Prop_Type::generate( 'none' ),
] ),
),
'base::placeholder' => Style_Definition::make() // this should be changed once we support placeholder/pseudo-elements styles in the styles system.
->add_variant(
Style_Variant::make()
->add_props( [
'color' => Color_Prop_Type::generate( '#9DA5AE' ),
] ),
),
];
}
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
public static function get_inline_styles(): string {
$base_class = self::get_element_type() . '-base';
// Default html textarea is resizable, but we want control over it from settings.
$inline_css = ".{$base_class}:not([data-resizable]) { resize: none; }";
return $inline_css;
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace ElementorPro\Modules\AtomicWidgets;
use ElementorPro\Base\Module_Base;
use ElementorPro\Plugin;
use Elementor\Modules\AtomicWidgets\Module as AtomicWidgetsModule;
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers_Registry;
use ElementorPro\Modules\AtomicWidgets\PropTypes\Display_Conditions\Display_Conditions_Prop_Type;
use ElementorPro\Modules\AtomicWidgets\PropTypes\Display_Conditions\Condition_Group_Prop_Type;
use ElementorPro\Modules\AtomicWidgets\Transformers\Display_Conditions as Display_Conditions_Transformer;
use ElementorPro\Modules\AtomicWidgets\Transformers\Condition_Group as Condition_Group_Transformer;
use ElementorPro\License\API;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
public function get_name() {
return 'atomic';
}
public function __construct() {
parent::__construct();
if ( ! Plugin::elementor()->experiments->is_feature_active( AtomicWidgetsModule::EXPERIMENT_NAME ) ) {
return;
}
add_filter(
'elementor/atomic-widgets/props-schema',
fn( $schema ) => $this->inject_props_schema( $schema ),
10,
1
);
add_action(
'elementor/atomic-widgets/settings/transformers/register',
fn ( $transformers ) => $this->register_settings_transformers( $transformers ),
);
add_filter(
'elementor/atomic_widgets/editor_data/element_styles',
fn( $styles_without_custom_css, $styles ) => $this->get_license_based_custom_css_value( $styles_without_custom_css, $styles ),
10,
2
);
}
private function inject_props_schema( $schema ) {
$display_conditions_prop_type = Display_Conditions_Prop_Type::make();
$components_module = 'Elementor\\Modules\\Components\\Module';
$overridable_prop_type = 'Elementor\\Modules\\Components\\PropTypes\\Overridable_Prop_Type';
$is_components_experiment_active = false;
if ( class_exists( $components_module ) ) {
$is_components_experiment_active = Plugin::elementor()->experiments->is_feature_active( $components_module::EXPERIMENT_NAME );
}
if (
$is_components_experiment_active &&
class_exists( $overridable_prop_type )
) {
$display_conditions_prop_type->meta( $overridable_prop_type::ignore() );
}
$schema[ Display_Conditions_Prop_Type::get_key() ] = $display_conditions_prop_type;
return $schema;
}
private function register_settings_transformers( Transformers_Registry $transformers ): Transformers_Registry {
$transformers->register( Display_Conditions_Prop_Type::get_key(), new Display_Conditions_Transformer() );
$transformers->register( Condition_Group_Prop_Type::get_key(), new Condition_Group_Transformer() );
return $transformers;
}
private function get_license_based_custom_css_value( $styles_without_custom_css, $styles ) {
if ( $this->has_custom_css_feature_in_license() ) {
return $styles;
}
return $styles_without_custom_css;
}
private function has_custom_css_feature_in_license() {
return API::is_license_active() && API::is_licence_has_feature( 'atomic-custom-css' );
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace ElementorPro\Modules\AtomicWidgets\PropTypes\Display_Conditions;
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 Condition_Group_Prop_Type extends Array_Prop_Type {
public static function get_key(): string {
return 'condition-group';
}
protected function define_item_type(): Prop_Type {
return String_Prop_Type::make();
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace ElementorPro\Modules\AtomicWidgets\PropTypes\Display_Conditions;
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 Display_Conditions_Prop_Type extends Array_Prop_Type {
public static function get_key(): string {
return 'display-conditions';
}
protected function define_item_type(): Prop_Type {
return Condition_Group_Prop_Type::make();
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace ElementorPro\Modules\AtomicWidgets\PropTypes\Display_Conditions;
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 Page_Title_Condition_Prop_Type extends Object_Prop_Type {
public static function get_key(): string {
return 'page-title-condition';
}
protected function validate_value( $value ): bool {
return true;
}
protected function define_shape(): array {
return [
'operator' => String_Prop_Type::make()
->enum( [ '==', '!=' ] )
->default( '==' )
->required(),
'value' => String_Prop_Type::make()
->required(),
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace ElementorPro\Modules\AtomicWidgets\PropTypes\Display_Conditions;
use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Date_Time_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Time_Of_Day_Condition_Prop_Type extends Object_Prop_Type {
public static function get_key(): string {
return 'time-of-day-condition';
}
protected function validate_value( $value ): bool {
return true;
}
protected function define_shape(): array {
return [
'operator' => String_Prop_Type::make()
->enum( [ '==', '!=', '>', '<', '>=', '<=' ] )
->default( '==' )
->required(),
'server_time' => Boolean_Prop_Type::make()
->default( true )
->required(),
'value' => Date_Time_Prop_Type::make()
->required(),
];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace ElementorPro\Modules\AtomicWidgets;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Settings_Resolver {
public static function resolve( array $settings ): array {
$resolved = [];
foreach ( $settings as $key => $value ) {
$resolved[ $key ] = static::resolve_value( $value );
}
return $resolved;
}
private static function resolve_value( $value ) {
if ( ! is_array( $value ) ) {
return $value;
}
if ( ! empty( $value['$$type'] ) && array_key_exists( 'value', $value ) ) {
return static::resolve_value( $value['value'] );
}
return array_map( [ static::class, 'resolve_value' ], $value );
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace ElementorPro\Modules\AtomicWidgets\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 Condition_Group extends Transformer_Base {
public function transform( $conditions, Props_Resolver_Context $context ) {
if ( ! is_array( $conditions ) || empty( $conditions ) ) {
return null;
}
return array_map( function( $condition ) {
return json_decode( $condition, true );
}, $conditions );
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace ElementorPro\Modules\AtomicWidgets\Transformers;
use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context;
use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver;
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base;
use ElementorPro\Modules\AtomicWidgets\PropTypes\Display_Conditions\Display_Conditions_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Display_Conditions extends Transformer_Base {
public function transform( $value, Props_Resolver_Context $context ) {
return ! is_array( $value ) || empty( $value )
? []
: $value;
}
public static function extract_from_settings( $settings ) {
$prop_key = Display_Conditions_Prop_Type::get_key();
if ( ! isset( $settings[ $prop_key ]['value'] ) ) {
return '[]';
}
$params = self::create_params_from_settings( $settings );
$resolved = Render_Props_Resolver::for_settings()->resolve( ...$params );
return [ json_encode( $resolved[ $prop_key ] ) ];
}
private static function create_params_from_settings( $settings ) {
$prop_key = Display_Conditions_Prop_Type::get_key();
$prop_type = Display_Conditions_Prop_Type::make();
$value = $settings[ $prop_key ];
$schema = [
$prop_key => $prop_type,
];
$props = [
$prop_key => $value,
];
return [ $schema, $props ];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace ElementorPro\Modules\Attributes\Controls;
use Elementor\Modules\AtomicWidgets\Controls\Types\Repeatable_Control as Core_Repeatable_Control;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Repeatable_Attributes_Control extends Core_Repeatable_Control {
private ?array $add_item_tooltip_props = null;
public function set_addItemTooltipProps( array $props ): self {
$this->add_item_tooltip_props = $props;
return $this;
}
public function get_props(): array {
$props = parent::get_props();
if ( null !== $this->add_item_tooltip_props ) {
$props['addItemTooltipProps'] = (object) $this->add_item_tooltip_props;
}
return $props;
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace ElementorPro\Modules\Attributes;
use ElementorPro\Base\Module_Base;
use ElementorPro\License\API;
use ElementorPro\Modules\Attributes\Controls\Repeatable_Attributes_Control;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use ElementorPro\Modules\Attributes\PropsResolver\Transformers\Settings\Pro_Attributes_Transformer;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
public function get_name() {
return 'attributes';
}
private function is_attributes_active() {
return API::is_licence_has_feature( 'atomic-custom-attributes' );
}
private function is_license_expired() {
return API::is_license_expired();
}
public function __construct() {
parent::__construct();
if ( $this->is_attributes_active() || $this->is_license_expired() ) {
add_action(
'elementor/atomic-widgets/settings/transformers/register',
function ( $transformers ) {
$attributes_key = Attributes_Prop_Type::get_key();
$transformers->register(
$attributes_key,
new Pro_Attributes_Transformer()
);
},
20
);
add_filter(
'elementor/atomic-widgets/controls',
fn( $element_controls, $atomic_element ) => $this->inject_attrs_control( $element_controls, $atomic_element ),
10,
2
);
}
}
private function inject_attrs_control( $element_controls, $atomic_element ) {
$schema = $atomic_element::get_props_schema();
if ( ! array_key_exists( 'attributes', $schema ) ) {
return $element_controls;
}
foreach ( $element_controls as $item ) {
if ( $item instanceof Section && $item->get_id() === 'settings' ) {
$is_empty_controls = empty( $item->get_items() );
$control = Repeatable_Attributes_Control::bind_to( 'attributes' )
->set_meta( [ 'topDivider' => ! $is_empty_controls ] )
->set_repeaterLabel( __( 'Attributes', 'elementor-pro' ) )
->set_initialValues(
[
'key' => [
'$$type' => 'string',
'value' => '',
],
'value' => [
'$$type' => 'string',
'value' => '',
],
]
)
->set_child_control_props( (object) [] )
->set_patternLabel( '${value.key.value}="${value.value.value}"' )
->set_placeholder( 'Empty attribute' )
->set_child_control_type( 'attributes' )
->hide_duplicate()
->hide_toggle();
if ( method_exists( $control, 'set_prop_key' ) ) {
$control->set_prop_key( 'attributes' );
}
if ( $this->is_license_expired() || ! $this->is_attributes_active() ) {
$control
->set_addItemTooltipProps( [
'disabled' => true,
] )
->set_child_control_props( (object) [ 'readOnly' => true ] );
}
$item->add_item( $control );
break;
}
}
return $element_controls;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace ElementorPro\Modules\Attributes\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 Pro_Attributes_Transformer extends Transformer_Base {
public function transform( $value, Props_Resolver_Context $context ) {
if ( ! is_array( $value ) ) {
return null;
}
$result = implode( ' ', array_map( function ( $item ) {
if ( ! $this->is_valid_attribute_item( $item ) ) {
return '';
}
return $item['key'] . '="' . $item['value'] . '"';
}, $value ) );
return $result;
}
private function is_valid_attribute_item( $item ): bool {
return isset( $item['key'], $item['value'] )
&& '' !== $item['key']
&& '' !== $item['value'];
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace ElementorPro\Modules\Blockquote;
use ElementorPro\Base\Module_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
public function __construct() {
parent::__construct();
add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] );
}
public function get_widgets() {
return [
'Blockquote',
];
}
public function get_name() {
return 'blockquote';
}
/**
* Get the base URL for assets.
*
* @return string
*/
public function get_assets_base_url(): string {
return ELEMENTOR_PRO_URL;
}
/**
* Register styles.
*
* At build time, Elementor compiles `/modules/blockquote/assets/scss/frontend.scss`
* to `/assets/css/widget-blockquote.min.css`.
*
* @return void
*/
public function register_styles() {
wp_register_style(
'widget-blockquote',
$this->get_css_assets_url( 'widget-blockquote', null, true, true ),
[ 'elementor-frontend' ],
ELEMENTOR_PRO_VERSION
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
<?php
namespace ElementorPro\Modules\CallToAction;
use ElementorPro\Base\Module_Base;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends Module_Base {
public function __construct() {
parent::__construct();
add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] );
add_action( 'elementor/preview/enqueue_styles', [ $this, 'enqueue_preview_styles' ] );
}
public function get_widgets() {
return [
'Call_To_Action',
];
}
public function get_name() {
return 'call-to-action';
}
/**
* Get the base URL for assets.
*
* @return string
*/
public function get_assets_base_url(): string {
return ELEMENTOR_PRO_URL;
}
/**
* Register styles.
*
* At build time, Elementor compiles `/modules/call-to-action/assets/scss/frontend.scss`
* to `/assets/css/widget-call-to-action.min.css`.
*
* @return void
*/
public function register_styles() {
$direction_suffix = is_rtl() ? '-rtl' : '';
$has_custom_breakpoints = Plugin::elementor()->breakpoints->has_custom_breakpoints();
wp_register_style(
'widget-call-to-action',
Plugin::get_frontend_file_url( "widget-call-to-action{$direction_suffix}.min.css", $has_custom_breakpoints ),
[ 'elementor-frontend' ],
$has_custom_breakpoints ? null : ELEMENTOR_PRO_VERSION
);
wp_register_style(
'e-transitions',
$this->get_css_assets_url( 'transitions', 'assets/css/conditionals/', true ),
[],
ELEMENTOR_PRO_VERSION
);
wp_register_style(
'e-ribbon',
$this->get_css_assets_url( 'ribbon', 'assets/css/conditionals/', true ),
[],
ELEMENTOR_PRO_VERSION
);
}
public function enqueue_preview_styles() {
wp_enqueue_style( 'e-ribbon' );
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace ElementorPro\Modules\Carousel;
use ElementorPro\Base\Module_Base;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
const WIDGET_HAS_CUSTOM_BREAKPOINTS = true;
public function __construct() {
parent::__construct();
add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] );
}
public function get_widgets() {
return [
'Media_Carousel',
'Testimonial_Carousel',
'Reviews',
];
}
public function get_name() {
return 'carousel';
}
/**
* Get the base URL for assets.
*
* @return string
*/
public function get_assets_base_url(): string {
return ELEMENTOR_PRO_URL;
}
/**
* Register styles.
*
* At build time, Elementor compiles `/modules/carousel/assets/scss/widgets/*.scss`
* to `/assets/css/widget-*.min.css`.
*
* @return void
*/
public function register_styles() {
$widgets = $this->get_widgets_style_list();
$direction_suffix = is_rtl() ? '-rtl' : '';
$has_custom_breakpoints = Plugin::elementor()->breakpoints->has_custom_breakpoints();
foreach ( $widgets 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,
Plugin::get_frontend_file_url( "{$widget_style_name}{$direction_suffix}.min.css", $should_load_responsive_css ),
[ 'elementor-frontend' ],
ELEMENTOR_PRO_VERSION
);
}
}
private function get_widgets_style_list(): array {
return [
'widget-media-carousel' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS,
'widget-testimonial-carousel' => self::WIDGET_HAS_CUSTOM_BREAKPOINTS,
'widget-reviews' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS,
'widget-carousel-module-base' => ! self::WIDGET_HAS_CUSTOM_BREAKPOINTS,
];
}
}

View File

@@ -0,0 +1,669 @@
<?php
namespace ElementorPro\Modules\Carousel\Widgets;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Image_Size;
use Elementor\Icons_Manager;
use Elementor\Repeater;
use ElementorPro\Base\Base_Widget;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class Base extends Base_Widget {
private $slide_prints_count = 0;
public function get_script_depends() {
return [ 'imagesloaded' ];
}
protected function is_dynamic_content(): bool {
return false;
}
abstract protected function add_repeater_controls( Repeater $repeater );
abstract protected function get_repeater_defaults();
abstract protected function print_slide( array $slide, array $settings, $element_key );
protected function register_controls() {
$this->start_controls_section(
'section_slides',
[
'label' => esc_html__( 'Slides', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'slides_name',
[
'label' => esc_html__( 'Slides Name', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'default' => esc_html__( 'Slides', 'elementor-pro' ),
]
);
$repeater = new Repeater();
$this->add_repeater_controls( $repeater );
$this->add_control(
'slides',
[
'label' => esc_html__( 'Slides', 'elementor-pro' ),
'type' => Controls_Manager::REPEATER,
'fields' => $repeater->get_controls(),
'default' => $this->get_repeater_defaults(),
'separator' => 'after',
]
);
$this->add_control(
'effect',
[
'type' => Controls_Manager::SELECT,
'label' => esc_html__( 'Effect', 'elementor-pro' ),
'default' => 'slide',
'options' => [
'slide' => esc_html__( 'Slide', 'elementor-pro' ),
'fade' => esc_html__( 'Fade', 'elementor-pro' ),
'cube' => esc_html__( 'Cube', 'elementor-pro' ),
],
'frontend_available' => true,
]
);
$slides_per_view = range( 1, 10 );
$slides_per_view = array_combine( $slides_per_view, $slides_per_view );
$this->add_responsive_control(
'slides_per_view',
[
'type' => Controls_Manager::SELECT,
'label' => esc_html__( 'Slides Per View', 'elementor-pro' ),
'options' => [ '' => esc_html__( 'Default', 'elementor-pro' ) ] + $slides_per_view,
'inherit_placeholders' => false,
'condition' => [
'effect' => 'slide',
],
'frontend_available' => true,
]
);
$this->add_responsive_control(
'slides_to_scroll',
[
'type' => Controls_Manager::SELECT,
'label' => esc_html__( 'Slides to Scroll', 'elementor-pro' ),
'description' => esc_html__( 'Set how many slides are scrolled per swipe.', 'elementor-pro' ),
'options' => [ '' => esc_html__( 'Default', 'elementor-pro' ) ] + $slides_per_view,
'inherit_placeholders' => false,
'condition' => [
'effect' => 'slide',
],
'frontend_available' => true,
]
);
$this->add_responsive_control(
'height',
[
'type' => Controls_Manager::SLIDER,
'label' => esc_html__( 'Height', 'elementor-pro' ),
'size_units' => [ 'px', 'em', 'rem', 'vh', 'custom' ],
'range' => [
'px' => [
'min' => 100,
'max' => 1000,
],
'em' => [
'min' => 10,
'max' => 100,
],
'rem' => [
'min' => 10,
'max' => 100,
],
'vh' => [
'min' => 20,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-main-swiper' => 'height: {{SIZE}}{{UNIT}};',
],
]
);
$this->add_responsive_control(
'width',
[
'type' => Controls_Manager::SLIDER,
'label' => esc_html__( 'Width', 'elementor-pro' ),
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'range' => [
'px' => [
'min' => 100,
'max' => 1140,
],
'%' => [
'min' => 50,
],
],
'default' => [
'unit' => '%',
],
'selectors' => [
'{{WRAPPER}} .elementor-main-swiper' => 'width: {{SIZE}}{{UNIT}};',
],
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_additional_options',
[
'label' => esc_html__( 'Additional Options', 'elementor-pro' ),
]
);
$this->add_control(
'show_arrows',
[
'type' => Controls_Manager::SWITCHER,
'label' => esc_html__( 'Arrows', 'elementor-pro' ),
'default' => 'yes',
'label_off' => esc_html__( 'Hide', 'elementor-pro' ),
'label_on' => esc_html__( 'Show', 'elementor-pro' ),
'prefix_class' => 'elementor-arrows-',
'render_type' => 'template',
'frontend_available' => true,
]
);
$this->add_control(
'pagination',
[
'label' => esc_html__( 'Pagination', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'bullets',
'options' => [
'' => esc_html__( 'None', 'elementor-pro' ),
'bullets' => esc_html__( 'Dots', 'elementor-pro' ),
'fraction' => esc_html__( 'Fraction', 'elementor-pro' ),
'progressbar' => esc_html__( 'Progress', 'elementor-pro' ),
],
'prefix_class' => 'elementor-pagination-type-',
'render_type' => 'template',
'frontend_available' => true,
]
);
$this->add_control(
'speed',
[
'label' => esc_html__( 'Transition Duration', 'elementor-pro' ),
'type' => Controls_Manager::NUMBER,
'default' => 500,
'render_type' => 'none',
'frontend_available' => true,
]
);
$this->add_control(
'autoplay',
[
'label' => esc_html__( 'Autoplay', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'separator' => 'before',
'render_type' => 'none',
'frontend_available' => true,
]
);
$this->add_control(
'autoplay_speed',
[
'label' => esc_html__( 'Autoplay Speed', 'elementor-pro' ),
'type' => Controls_Manager::NUMBER,
'default' => 5000,
'condition' => [
'autoplay' => 'yes',
],
'render_type' => 'none',
'frontend_available' => true,
]
);
$this->add_control(
'loop',
[
'label' => esc_html__( 'Infinite Loop', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'frontend_available' => true,
]
);
$this->add_control(
'pause_on_hover',
[
'label' => esc_html__( 'Pause on Hover', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'condition' => [
'autoplay' => 'yes',
],
'render_type' => 'none',
'frontend_available' => true,
]
);
$this->add_control(
'pause_on_interaction',
[
'label' => esc_html__( 'Pause on Interaction', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'condition' => [
'autoplay' => 'yes',
],
'render_type' => 'none',
'frontend_available' => true,
]
);
$this->add_group_control(
Group_Control_Image_Size::get_type(),
[
'name' => 'image_size',
'default' => 'full',
'separator' => 'before',
]
);
$this->add_control(
'lazyload',
[
'label' => esc_html__( 'Lazy Load', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'separator' => 'before',
'frontend_available' => true,
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_slides_style',
[
'label' => esc_html__( 'Slides', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$space_between_config = [
'label' => esc_html__( 'Space Between', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'range' => [
'px' => [
'max' => 50,
],
],
'render_type' => 'none',
'frontend_available' => true,
];
// TODO: Once Core 3.4.0 is out, get the active devices using Breakpoints/Manager::get_active_devices_list().
$active_breakpoint_instances = Plugin::elementor()->breakpoints->get_active_breakpoints();
// Devices need to be ordered from largest to smallest.
$active_devices = array_reverse( array_keys( $active_breakpoint_instances ) );
// Add desktop in the correct position.
if ( in_array( 'widescreen', $active_devices, true ) ) {
$active_devices = array_merge( array_slice( $active_devices, 0, 1 ), [ 'desktop' ], array_slice( $active_devices, 1 ) );
} else {
$active_devices = array_merge( [ 'desktop' ], $active_devices );
}
foreach ( $active_devices as $active_device ) {
$space_between_config[ $active_device . '_default' ] = [
'size' => 10,
];
}
$this->add_responsive_control(
'space_between',
$space_between_config
);
$this->add_control(
'slide_background_color',
[
'label' => esc_html__( 'Background Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-main-swiper .swiper-slide' => 'background-color: {{VALUE}}',
],
]
);
$this->add_control(
'slide_border_size',
[
'label' => esc_html__( 'Border Width', 'elementor-pro' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'selectors' => [
'{{WRAPPER}} .elementor-main-swiper .swiper-slide' => 'border-width: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}',
],
]
);
$this->add_control(
'slide_border_radius',
[
'label' => esc_html__( 'Border Radius', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
'range' => [
'%' => [
'max' => 50,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-main-swiper .swiper-slide' => 'border-radius: {{SIZE}}{{UNIT}}',
],
]
);
$this->add_control(
'slide_border_color',
[
'label' => esc_html__( 'Border Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-main-swiper .swiper-slide' => 'border-color: {{VALUE}}',
],
]
);
$this->add_control(
'slide_padding',
[
'label' => esc_html__( 'Padding', 'elementor-pro' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'selectors' => [
'{{WRAPPER}} .elementor-main-swiper .swiper-slide' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}',
],
'separator' => 'before',
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_navigation',
[
'label' => esc_html__( 'Navigation', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'heading_arrows',
[
'label' => esc_html__( 'Arrows', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
]
);
$this->add_responsive_control(
'arrows_size',
[
'label' => esc_html__( 'Size', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'default' => [
'size' => 20,
],
'range' => [
'px' => [
'max' => 100,
],
'em' => [
'max' => 10,
],
'rem' => [
'max' => 10,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-swiper-button' => 'font-size: {{SIZE}}{{UNIT}}',
],
]
);
$this->add_control(
'arrows_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-swiper-button' => 'color: {{VALUE}}',
'{{WRAPPER}} .elementor-swiper-button svg' => 'fill: {{VALUE}}',
],
]
);
$this->add_control(
'heading_pagination',
[
'label' => esc_html__( 'Pagination', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
'condition' => [
'pagination!' => '',
],
]
);
$this->add_control(
'pagination_position',
[
'label' => esc_html__( 'Position', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'outside',
'options' => [
'outside' => esc_html__( 'Outside', 'elementor-pro' ),
'inside' => esc_html__( 'Inside', 'elementor-pro' ),
],
'prefix_class' => 'elementor-pagination-position-',
'condition' => [
'pagination!' => '',
],
]
);
$this->add_responsive_control(
'pagination_gap',
[
'label' => esc_html__( 'Space Between Dots', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 50,
],
],
'selectors' => [
'{{WRAPPER}} .swiper-pagination-bullet' => '--swiper-pagination-bullet-horizontal-gap: {{SIZE}}{{UNIT}}; --swiper-pagination-bullet-vertical-gap: {{SIZE}}{{UNIT}};',
],
'condition' => [
'pagination' => 'bullets',
],
]
);
$this->add_responsive_control(
'pagination_size',
[
'label' => esc_html__( 'Size', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 100,
],
'em' => [
'max' => 10,
],
'rem' => [
'max' => 10,
],
],
'selectors' => [
'{{WRAPPER}} .swiper-pagination-bullet' => 'height: {{SIZE}}{{UNIT}}; width: {{SIZE}}{{UNIT}}',
'{{WRAPPER}} .swiper-horizontal .swiper-pagination-progressbar' => 'height: {{SIZE}}{{UNIT}}',
],
'condition' => [
'pagination!' => '',
],
]
);
$this->add_control(
'pagination_color_inactive',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
// The opacity property will override the default inactive dot color which is opacity 0.2.
'{{WRAPPER}} .swiper-pagination-bullet:not(.swiper-pagination-bullet-active)' => 'background-color: {{VALUE}}; opacity: 1;',
],
'condition' => [
'pagination!' => '',
],
]
);
$this->add_control(
'pagination_color',
[
'label' => esc_html__( 'Active Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .swiper-pagination-bullet-active, {{WRAPPER}} .swiper-pagination-progressbar-fill' => 'background-color: {{VALUE}}',
'{{WRAPPER}} .swiper-pagination-fraction' => 'color: {{VALUE}}',
],
'condition' => [
'pagination!' => '',
],
'control_type' => 'content',
]
);
$this->end_controls_section();
}
protected function print_slider( ?array $settings = null ) {
if ( null === $settings ) {
$settings = $this->get_settings_for_display();
}
$default_settings = [
'container_class' => 'elementor-main-swiper',
'video_play_icon' => true,
];
$settings = array_merge( $default_settings, $settings );
$slides_count = count( $settings['slides'] );
$optimized_markup = Plugin::elementor()->experiments->is_feature_active( 'e_optimized_markup' );
$this->add_render_attribute( [
'wrapper' => [
'class' => [ $settings['container_class'], 'swiper' ],
'role' => 'region',
'aria-roledescription' => 'carousel',
'aria-label' => $settings['slides_name'],
],
] );
?>
<?php if ( ! $optimized_markup ) : ?>
<div class="elementor-swiper">
<?php endif; ?>
<div <?php $this->print_render_attribute_string( 'wrapper' ); ?>>
<div class="swiper-wrapper">
<?php
foreach ( $settings['slides'] as $index => $slide ) :
$this->slide_prints_count++;
?>
<div class="swiper-slide" role="group" aria-roledescription="slide">
<?php $this->print_slide( $slide, $settings, 'slide-' . $index . '-' . $this->slide_prints_count ); ?>
</div>
<?php endforeach; ?>
</div>
<?php if ( 1 < $slides_count ) : ?>
<?php if ( $settings['show_arrows'] ) : ?>
<div class="elementor-swiper-button elementor-swiper-button-prev" role="button" tabindex="0" aria-label="<?php echo esc_attr__( 'Previous', 'elementor-pro' ); ?>">
<?php $this->render_swiper_button( 'previous' ); ?>
</div>
<div class="elementor-swiper-button elementor-swiper-button-next" role="button" tabindex="0" aria-label="<?php echo esc_attr__( 'Next', 'elementor-pro' ); ?>">
<?php $this->render_swiper_button( 'next' ); ?>
</div>
<?php endif; ?>
<?php if ( $settings['pagination'] ) : ?>
<div class="swiper-pagination"></div>
<?php endif; ?>
<?php endif; ?>
</div>
<?php if ( ! $optimized_markup ) : ?>
</div>
<?php endif; ?>
<?php
}
protected function get_slide_image_url( $slide, array $settings ) {
$image_url = Group_Control_Image_Size::get_attachment_image_src( $slide['image']['id'], 'image_size', $settings );
if ( ! $image_url ) {
$image_url = $slide['image']['url'];
}
return $image_url;
}
protected function get_slide_image_alt_attribute( $slide ) {
if ( ! empty( $slide['name'] ) ) {
return $slide['name'];
}
if ( ! empty( $slide['image']['alt'] ) ) {
return $slide['image']['alt'];
}
return '';
}
private function render_swiper_button( $type ) {
$direction = 'next' === $type ? 'right' : 'left';
if ( is_rtl() ) {
$direction = 'right' === $direction ? 'left' : 'right';
}
$icon_value = 'eicon-chevron-' . $direction;
Icons_Manager::render_icon( [
'library' => 'eicons',
'value' => $icon_value,
], [ 'aria-hidden' => 'true' ] );
}
}

View File

@@ -0,0 +1,865 @@
<?php
namespace ElementorPro\Modules\Carousel\Widgets;
use Elementor\Controls_Manager;
use Elementor\Control_Media;
use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
use Elementor\Embed;
use Elementor\Group_Control_Text_Shadow;
use Elementor\Group_Control_Typography;
use Elementor\Icons_Manager;
use Elementor\Repeater;
use Elementor\Utils;
use ElementorPro\Plugin;
use ElementorPro\Core\Utils as ProUtils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Media_Carousel extends Base {
/**
* @var int
*/
private $lightbox_slide_index;
public function get_name() {
return 'media-carousel';
}
public function get_title() {
return esc_html__( 'Media Carousel', 'elementor-pro' );
}
public function get_icon() {
return 'eicon-media-carousel';
}
public function get_keywords() {
return [ 'media', 'carousel', 'image', 'video', 'lightbox' ];
}
public function has_widget_inner_wrapper(): bool {
return ! Plugin::elementor()->experiments->is_feature_active( 'e_optimized_markup' );
}
/**
* 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 [ 'e-swiper', 'widget-media-carousel', 'widget-carousel-module-base' ];
}
/**
* Get script dependencies.
*
* Retrieve the list of script dependencies the widget requires.
*
* @since 3.27.0
* @access public
*
* @return array Widget script dependencies.
*/
public function get_script_depends(): array {
return [ 'swiper' ];
}
protected function render() {
$settings = $this->get_settings_for_display();
if ( $settings['overlay'] ) {
$this->add_render_attribute( 'image-overlay', 'class', [
'elementor-carousel-image-overlay',
'e-overlay-animation-' . $settings['overlay_animation'],
] );
}
$this->print_slider();
if ( 'slideshow' !== $settings['skin'] || count( $settings['slides'] ) <= 1 ) {
return;
}
$settings['thumbs_slider'] = true;
$settings['container_class'] = 'elementor-thumbnails-swiper';
$settings['show_arrows'] = false;
$this->print_slider( $settings );
}
protected function register_controls() {
parent::register_controls();
$this->start_controls_section(
'section_lightbox_style',
[
'label' => esc_html__( 'Lightbox', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'lightbox_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'#elementor-lightbox-slideshow-{{ID}}' => 'background-color: {{VALUE}};',
],
]
);
$this->add_control(
'lightbox_ui_color',
[
'label' => esc_html__( 'UI Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'#elementor-lightbox-slideshow-{{ID}} .dialog-lightbox-close-button, #elementor-lightbox-slideshow-{{ID}} .elementor-swiper-button' => 'color: {{VALUE}};',
],
]
);
$this->add_control(
'lightbox_ui_hover_color',
[
'label' => esc_html__( 'UI Hover Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'#elementor-lightbox-slideshow-{{ID}} .dialog-lightbox-close-button:hover, #elementor-lightbox-slideshow-{{ID}} .elementor-swiper-button:hover' => 'color: {{VALUE}};',
],
]
);
$this->add_control(
'lightbox_video_width',
[
'label' => esc_html__( 'Video Width', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'default' => [
'unit' => '%',
],
'range' => [
'%' => [
'min' => 50,
],
],
'selectors' => [
'#elementor-lightbox-slideshow-{{ID}} .elementor-video-container' => 'width: {{SIZE}}{{UNIT}};',
],
]
);
$this->end_controls_section();
$this->add_injections();
$this->update_controls();
}
protected function add_repeater_controls( Repeater $repeater ) {
$repeater->add_control(
'type',
[
'type' => Controls_Manager::CHOOSE,
'label' => esc_html__( 'Type', 'elementor-pro' ),
'default' => 'image',
'options' => [
'image' => [
'title' => esc_html__( 'Image', 'elementor-pro' ),
'icon' => 'eicon-image-bold',
],
'video' => [
'title' => esc_html__( 'Video', 'elementor-pro' ),
'icon' => 'eicon-video-camera',
],
],
'toggle' => false,
]
);
$repeater->add_control(
'image',
[
'label' => esc_html__( 'Image', 'elementor-pro' ),
'type' => Controls_Manager::MEDIA,
'dynamic' => [
'active' => true,
],
]
);
$repeater->add_control(
'image_link_to_type',
[
'label' => esc_html__( 'Link', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'options' => [
'' => esc_html__( 'None', 'elementor-pro' ),
'file' => esc_html__( 'Media File', 'elementor-pro' ),
'custom' => esc_html__( 'Custom URL', 'elementor-pro' ),
],
'condition' => [
'type' => 'image',
],
]
);
$repeater->add_control(
'image_link_to',
[
'type' => Controls_Manager::URL,
'dynamic' => [
'active' => true,
],
'show_external' => 'true',
'condition' => [
'type' => 'image',
'image_link_to_type' => 'custom',
],
'show_label' => false,
]
);
$repeater->add_control(
'video',
[
'label' => esc_html__( 'Video Link', 'elementor-pro' ),
'type' => Controls_Manager::URL,
'dynamic' => [
'active' => true,
],
'placeholder' => esc_html__( 'Enter your video link', 'elementor-pro' ),
'description' => esc_html__( 'YouTube or Vimeo link', 'elementor-pro' ),
'options' => false,
'condition' => [
'type' => 'video',
],
]
);
}
protected function get_default_slides_count() {
return 5;
}
protected function get_repeater_defaults() {
$placeholder_image_src = Utils::get_placeholder_image_src();
return array_fill( 0, $this->get_default_slides_count(), [
'image' => [
'url' => $placeholder_image_src,
],
] );
}
protected function get_image_caption( $slide ) {
$caption_type = $this->get_settings( 'caption' );
if ( empty( $caption_type ) ) {
return '';
}
$attachment_post = get_post( $slide['image']['id'] );
if ( ProUtils::has_invalid_post_permissions( $attachment_post ) ) {
return '';
}
if ( 'caption' === $caption_type ) {
return $attachment_post->post_excerpt;
}
if ( 'title' === $caption_type ) {
return $attachment_post->post_title;
}
return $attachment_post->post_content;
}
protected function get_image_link_to( $slide ) {
if ( ! empty( $slide['video']['url'] ) ) {
return $slide['image']['url'] ? $slide['image']['url'] : '#';
}
if ( ! $slide['image_link_to_type'] ) {
return '';
}
if ( 'custom' === $slide['image_link_to_type'] ) {
return $slide['image_link_to']['url'];
}
return $slide['image']['url'];
}
protected function print_slider( ?array $settings = null ) {
$this->lightbox_slide_index = 0;
parent::print_slider( $settings );
}
protected function print_slide( array $slide, array $settings, $element_key ) {
if ( ! empty( $settings['thumbs_slider'] ) ) {
$settings['video_play_icon'] = false;
}
$this->add_render_attribute( $element_key . '-image', [
'class' => 'elementor-carousel-image',
'role' => 'img',
'aria-label' => Control_Media::get_image_alt( $slide['image'] ),
] );
$img_src = $this->get_slide_image_url( $slide, $settings );
if ( 'yes' === $settings['lazyload'] ) {
$img_attribute['class'] = 'swiper-lazy';
$img_attribute['data-background'] = $img_src;
} else {
$img_attribute['style'] = "background-image: url('" . $img_src . "')";
}
$this->add_render_attribute( $element_key . '-image', $img_attribute );
$image_link_to = $this->get_image_link_to( $slide );
if ( $image_link_to && empty( $settings['thumbs_slider'] ) ) {
if ( 'custom' === $slide['image_link_to_type'] ) {
$this->add_link_attributes( $element_key . '_link', $slide['image_link_to'] );
} else {
$this->add_render_attribute( $element_key . '_link', 'href', esc_url( $image_link_to ) );
$this->add_lightbox_data_attributes( $element_key . '_link', $slide['image']['id'], 'yes', $this->get_id() );
if ( Plugin::elementor()->editor->is_edit_mode() ) {
$this->add_render_attribute( $element_key . '_link', 'class', 'elementor-clickable' );
}
$this->lightbox_slide_index++;
}
if ( 'video' === $slide['type'] && $slide['video']['url'] ) {
$embed_url_params = [
'autoplay' => 1,
'rel' => 0,
'controls' => 0,
];
$this->add_render_attribute( $element_key . '_link', 'data-elementor-lightbox-video', Embed::get_embed_url( $slide['video']['url'], $embed_url_params ) );
}
// PHPCS - the method get_render_attribute_string is safe.
echo '<a ' . $this->get_render_attribute_string( $element_key . '_link' ) . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$this->print_slide_image( $slide, $element_key, $settings );
if ( $image_link_to ) {
echo '</a>';
}
}
protected function print_slide_image( array $slide, $element_key, array $settings ) {
?>
<div <?php $this->print_render_attribute_string( $element_key . '-image' ); ?>>
<?php if ( 'yes' === $settings['lazyload'] ) : ?>
<div class="swiper-lazy-preloader"></div>
<?php endif; ?>
<?php if ( 'video' === $slide['type'] && $settings['video_play_icon'] ) : ?>
<div class="elementor-custom-embed-play">
<?php
Icons_Manager::render_icon( [
'library' => 'eicons',
'value' => 'eicon-play',
], [ 'aria-hidden' => 'true' ] );
?>
<span class="elementor-screen-only"><?php echo esc_html__( 'Play', 'elementor-pro' ); ?></span>
</div>
<?php endif; ?>
</div>
<?php if ( $settings['overlay'] ) : ?>
<div <?php $this->print_render_attribute_string( 'image-overlay' ); ?>>
<?php
if ( 'text' === $settings['overlay'] ) {
echo wp_kses_post( $this->get_image_caption( $slide ) );
} else {
$this->render_overlay_icon( $settings['icon'] );
}
?>
</div>
<?php
endif;
}
private function add_injections() {
$this->start_injection( [
'type' => 'section',
'at' => 'start',
'of' => 'section_slides',
] );
$this->add_control(
'skin',
[
'label' => esc_html__( 'Skin', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'carousel',
'options' => [
'carousel' => esc_html__( 'Carousel', 'elementor-pro' ),
'slideshow' => esc_html__( 'Slideshow', 'elementor-pro' ),
'coverflow' => esc_html__( 'Coverflow', 'elementor-pro' ),
],
'prefix_class' => 'elementor-skin-',
'render_type' => 'template',
'frontend_available' => true,
]
);
$this->end_injection();
$this->start_injection( [
'of' => 'image_size_custom_dimension',
] );
$this->add_control(
'image_fit',
[
'label' => esc_html__( 'Image Fit', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => '',
'options' => [
'' => esc_html__( 'Cover', 'elementor-pro' ),
'contain' => esc_html__( 'Contain', 'elementor-pro' ),
'auto' => esc_html__( 'Auto', 'elementor-pro' ),
],
'selectors' => [
'{{WRAPPER}} .elementor-main-swiper:not(.elementor-thumbnails-swiper) .elementor-carousel-image' => 'background-size: {{VALUE}}',
],
]
);
$this->end_injection();
$this->start_injection( [
'of' => 'pagination_color',
] );
$this->add_control(
'play_icon_title',
[
'label' => esc_html__( 'Play Icon', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
]
);
$this->add_control(
'play_icon_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-custom-embed-play i' => 'color: {{VALUE}}',
'{{WRAPPER}} .elementor-custom-embed-play svg' => 'fill: {{VALUE}}',
],
]
);
$this->add_responsive_control(
'play_icon_size',
[
'label' => esc_html__( 'Size', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'min' => 20,
'max' => 150,
],
'em' => [
'min' => 2,
'max' => 15,
],
'rem' => [
'min' => 2,
'max' => 15,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-custom-embed-play i' => 'font-size: {{SIZE}}{{UNIT}}',
],
]
);
$this->add_group_control(
Group_Control_Text_Shadow::get_type(),
[
'name' => 'play_icon_text_shadow',
'selector' => '{{WRAPPER}} .elementor-custom-embed-play i',
'fields_options' => [
'text_shadow_type' => [
'label' => esc_html__( 'Shadow', 'elementor-pro' ),
],
],
]
);
$this->end_injection();
$this->start_injection( [
'of' => 'pause_on_interaction',
] );
$this->add_control(
'overlay',
[
'label' => esc_html__( 'Overlay', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => '',
'options' => [
'' => esc_html__( 'None', 'elementor-pro' ),
'text' => esc_html__( 'Text', 'elementor-pro' ),
'icon' => esc_html__( 'Icon', 'elementor-pro' ),
],
'condition' => [
'skin!' => 'slideshow',
],
'separator' => 'before',
]
);
$this->add_control(
'caption',
[
'label' => esc_html__( 'Caption', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'title',
'options' => [
'title' => esc_html__( 'Title', 'elementor-pro' ),
'caption' => esc_html__( 'Caption', 'elementor-pro' ),
'description' => esc_html__( 'Description', 'elementor-pro' ),
],
'condition' => [
'skin!' => 'slideshow',
'overlay' => 'text',
],
]
);
$this->add_control(
'icon',
[
'label' => esc_html__( 'Icon', 'elementor-pro' ),
'type' => Controls_Manager::CHOOSE,
'default' => 'search-plus',
'options' => [
'search-plus' => [
'icon' => 'eicon-search-bold',
],
'plus-circle' => [
'icon' => 'eicon-plus-circle',
],
'eye' => [
'icon' => 'eicon-preview-medium',
],
'link' => [
'icon' => 'eicon-link',
],
],
'condition' => [
'skin!' => 'slideshow',
'overlay' => 'icon',
],
]
);
$this->add_control(
'overlay_animation',
[
'label' => esc_html__( 'Animation', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'fade',
'options' => [
'fade' => 'Fade',
'slide-up' => 'Slide Up',
'slide-down' => 'Slide Down',
'slide-right' => 'Slide Right',
'slide-left' => 'Slide Left',
'zoom-in' => 'Zoom In',
],
'condition' => [
'skin!' => 'slideshow',
'overlay!' => '',
],
]
);
$this->end_injection();
$this->start_injection( [
'type' => 'section',
'of' => 'section_navigation',
] );
$this->start_controls_section(
'section_overlay',
[
'label' => esc_html__( 'Overlay', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
'condition' => [
'skin!' => 'slideshow',
'overlay!' => '',
],
]
);
$this->add_control(
'overlay_background_color',
[
'label' => esc_html__( 'Background Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-carousel-image-overlay' => 'background-color: {{VALUE}};',
],
]
);
$this->add_control(
'overlay_color',
[
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-carousel-image-overlay' => '--e-carousel-image-overlay-color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'caption_typography',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_ACCENT,
],
'selector' => '{{WRAPPER}} .elementor-carousel-image-overlay',
'condition' => [
'overlay' => 'text',
],
]
);
$this->add_control(
'icon_size',
[
'label' => esc_html__( 'Icon Size', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'selectors' => [
'{{WRAPPER}} .elementor-carousel-image-overlay' => '--e-carousel-image-overlay-icon-size: {{SIZE}}{{UNIT}};',
],
'condition' => [
'overlay' => 'icon',
],
]
);
$this->end_controls_section();
$this->end_injection();
// Slideshow
$this->start_injection( [
'of' => 'effect',
] );
$this->add_responsive_control(
'slideshow_height',
[
'type' => Controls_Manager::SLIDER,
'label' => esc_html__( 'Height', 'elementor-pro' ),
'size_units' => [ 'px', 'em', 'rem', 'vh', 'custom' ],
'range' => [
'px' => [
'min' => 20,
'max' => 1000,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-main-swiper:not(.elementor-thumbnails-swiper)' => 'height: {{SIZE}}{{UNIT}};',
],
'condition' => [
'skin' => 'slideshow',
],
]
);
$this->add_control(
'thumbs_title',
[
'label' => esc_html__( 'Thumbnails', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
'condition' => [
'skin' => 'slideshow',
],
]
);
$this->end_injection();
$this->start_injection( [
'of' => 'slides_per_view',
] );
$this->add_control(
'thumbs_ratio',
[
'label' => esc_html__( 'Ratio', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'options' => [
'169' => '16:9',
'219' => '21:9',
'43' => '4:3',
'11' => '1:1',
],
'selectors_dictionary' => [
'169' => '16 / 9',
'219' => '21 / 9',
'43' => '4 / 3',
'11' => '1 / 1',
],
'default' => '219',
'condition' => [
'skin' => 'slideshow',
],
'selectors' => [
'{{WRAPPER}} .elementor-thumbnails-swiper .elementor-carousel-image' => 'aspect-ratio: {{VALUE}}',
],
]
);
$this->add_control(
'centered_slides',
[
'label' => esc_html__( 'Centered Slides', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'condition' => [
'skin' => 'slideshow',
],
'frontend_available' => true,
]
);
$this->end_injection();
$this->start_injection( [
'of' => 'slides_per_view',
] );
$slides_per_view = range( 1, 10 );
$slides_per_view = array_combine( $slides_per_view, $slides_per_view );
$this->add_responsive_control(
'slideshow_slides_per_view',
[
'type' => Controls_Manager::SELECT,
'label' => esc_html__( 'Slides Per View', 'elementor-pro' ),
'options' => [ '' => esc_html__( 'Default', 'elementor-pro' ) ] + $slides_per_view,
'condition' => [
'skin' => 'slideshow',
],
'frontend_available' => true,
]
);
$this->end_injection();
}
private function update_controls() {
$carousel_controls = [
'slides_to_scroll',
'pagination',
'heading_pagination',
'pagination_gap',
'pagination_size',
'pagination_position',
'pagination_color',
];
$carousel_responsive_controls = [
'width',
'height',
'slides_per_view',
];
foreach ( $carousel_controls as $control_id ) {
$this->update_control(
$control_id,
[
'condition' => [
'skin!' => 'slideshow',
],
],
[ 'recursive' => true ]
);
}
foreach ( $carousel_responsive_controls as $control_id ) {
$this->update_responsive_control(
$control_id,
[
'condition' => [
'skin!' => 'slideshow',
],
],
[ 'recursive' => true ]
);
}
$this->update_responsive_control(
'space_between',
[
'selectors' => [
'{{WRAPPER}}.elementor-skin-slideshow .elementor-main-swiper:not(.elementor-thumbnails-swiper)' => 'margin-bottom: {{SIZE}}{{UNIT}}',
],
'render_type' => 'ui',
]
);
$this->update_control(
'effect',
[
'condition' => [
'skin!' => 'coverflow',
],
]
);
}
public function get_group_name() {
return 'carousel';
}
private function render_overlay_icon( $icon_name ) {
$icon_value = 'fas fa-' . $icon_name;
$icon = [
'library' => 'fa-solid',
'value' => $icon_value,
];
Icons_Manager::render_icon( $icon );
}
}

View File

@@ -0,0 +1,921 @@
<?php
namespace ElementorPro\Modules\Carousel\Widgets;
use Elementor\Controls_Manager;
use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
use Elementor\Group_Control_Typography;
use Elementor\Icons_Manager;
use Elementor\Repeater;
use Elementor\Utils;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Reviews extends Base {
public function get_name() {
return 'reviews';
}
public function get_title() {
return esc_html__( 'Reviews', 'elementor-pro' );
}
public function get_icon() {
return 'eicon-review';
}
public function get_keywords() {
return [ 'reviews', 'social', 'rating', 'testimonial', 'carousel' ];
}
public function has_widget_inner_wrapper(): bool {
return ! Plugin::elementor()->experiments->is_feature_active( 'e_optimized_markup' );
}
/**
* 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 [ 'e-swiper', 'widget-testimonial-carousel', 'widget-reviews', 'widget-star-rating', 'widget-carousel-module-base' ];
}
/**
* Get script dependencies.
*
* Retrieve the list of script dependencies the widget requires.
*
* @since 3.27.0
* @access public
*
* @return array Widget script dependencies.
*/
public function get_script_depends(): array {
return [ 'swiper' ];
}
protected function register_controls() {
parent::register_controls();
$this->update_control(
'slide_padding',
[
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__header' => 'padding-top: {{TOP}}{{UNIT}}; padding-left: {{LEFT}}{{UNIT}}; padding-right: {{RIGHT}}{{UNIT}};',
'{{WRAPPER}} .elementor-testimonial__content' => 'padding-bottom: {{BOTTOM}}{{UNIT}}; padding-left: {{LEFT}}{{UNIT}}; padding-right: {{RIGHT}}{{UNIT}};',
],
]
);
$this->start_injection( [
'of' => 'slide_padding',
] );
$this->add_control(
'heading_header',
[
'label' => esc_html__( 'Header', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
'separator' => 'before',
]
);
$this->add_control(
'header_background_color',
[
'label' => esc_html__( 'Background Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__header' => 'background-color: {{VALUE}}',
],
]
);
$this->add_responsive_control(
'content_gap',
[
'label' => esc_html__( 'Gap', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 100,
],
'em' => [
'max' => 10,
],
'rem' => [
'max' => 10,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__header' => 'padding-block-end: calc( {{SIZE}}{{UNIT}} / 2 )',
'{{WRAPPER}} .elementor-testimonial__content' => 'padding-block-start: calc( {{SIZE}}{{UNIT}} / 2 )',
],
]
);
$this->add_control(
'show_separator',
[
'label' => esc_html__( 'Separator', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'label_off' => esc_html__( 'Hide', 'elementor-pro' ),
'label_on' => esc_html__( 'Show', 'elementor-pro' ),
'default' => 'has-separator',
'return_value' => 'has-separator',
'prefix_class' => 'elementor-review--',
'separator' => 'before',
]
);
$this->add_control(
'separator_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__header' => 'border-block-end-color: {{VALUE}}',
],
'condition' => [
'show_separator!' => '',
],
]
);
$this->add_control(
'separator_size',
[
'label' => esc_html__( 'Size', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 20,
],
'em' => [
'max' => 2,
],
'rem' => [
'max' => 2,
],
],
'condition' => [
'show_separator!' => '',
],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__header' => 'border-block-end-width: {{SIZE}}{{UNIT}}',
],
]
);
$this->end_injection();
$this->start_injection( [
'at' => 'before',
'of' => 'section_navigation',
] );
$this->start_controls_section(
'section_content_style',
[
'label' => esc_html__( 'Text', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'name_title_style',
[
'label' => esc_html__( 'Name', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
]
);
$this->add_control(
'name_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__name' => 'color: {{VALUE}}',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'name_typography',
'selector' => '{{WRAPPER}} .elementor-testimonial__header, {{WRAPPER}} .elementor-testimonial__name',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_PRIMARY,
],
]
);
$this->add_control(
'heading_title_style',
[
'label' => esc_html__( 'Title', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
'separator' => 'before',
]
);
$this->add_control(
'title_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__title' => 'color: {{VALUE}}',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'title_typography',
'selector' => '{{WRAPPER}} .elementor-testimonial__title',
]
);
$this->add_control(
'heading_review_style',
[
'label' => esc_html__( 'Review', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
'separator' => 'before',
]
);
$this->add_control(
'content_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__text' => 'color: {{VALUE}}',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'content_typography',
'selector' => '{{WRAPPER}} .elementor-testimonial__text',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_TEXT,
],
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_image_style',
[
'label' => esc_html__( 'Image', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'image_size',
[
'label' => esc_html__( 'Size', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 100,
],
'em' => [
'max' => 10,
],
'rem' => [
'max' => 10,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__image img' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}}',
],
]
);
$this->add_responsive_control(
'image_gap',
[
'label' => esc_html__( 'Gap', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 100,
],
'em' => [
'max' => 10,
],
'rem' => [
'max' => 10,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__image + cite' => 'margin-inline-start: {{SIZE}}{{UNIT}}; margin-inline-end: 0;',
],
]
);
$this->add_control(
'image_border_radius',
[
'label' => esc_html__( 'Border Radius', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__image img' => 'border-radius: {{SIZE}}{{UNIT}}',
],
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_icon_style',
[
'label' => esc_html__( 'Icon', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'icon_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'default',
'options' => [
'default' => esc_html__( 'Official', 'elementor-pro' ),
'custom' => esc_html__( 'Custom', 'elementor-pro' ),
],
]
);
$this->add_control(
'icon_custom_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'condition' => [
'icon_color' => 'custom',
],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__icon:not(.elementor-testimonial__rating)' => 'color: {{VALUE}};',
'{{WRAPPER}} .elementor-testimonial__icon:not(.elementor-testimonial__rating) svg' => 'fill: {{VALUE}};',
],
]
);
$this->add_responsive_control(
'icon_size',
[
'label' => esc_html__( 'Size', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 100,
],
'em' => [
'max' => 10,
],
'rem' => [
'max' => 10,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__icon' => 'font-size: {{SIZE}}{{UNIT}}',
'{{WRAPPER}} .elementor-testimonial__icon svg' => 'width: {{SIZE}}{{UNIT}}',
],
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_rating_style',
[
'label' => esc_html__( 'Rating', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'star_style',
[
'label' => esc_html__( 'Icon', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'options' => [
'star_fontawesome' => 'Font Awesome',
'star_unicode' => 'Unicode',
],
'default' => 'star_fontawesome',
'render_type' => 'template',
'prefix_class' => 'elementor--star-style-',
'separator' => 'before',
]
);
$this->add_control(
'unmarked_star_style',
[
'label' => esc_html__( 'Unmarked Style', 'elementor-pro' ),
'type' => Controls_Manager::CHOOSE,
'options' => [
'solid' => [
'title' => esc_html__( 'Solid', 'elementor-pro' ),
'icon' => 'eicon-star',
],
'outline' => [
'title' => esc_html__( 'Outline', 'elementor-pro' ),
'icon' => 'eicon-star-o',
],
],
'default' => 'solid',
]
);
$this->add_control(
'star_size',
[
'label' => esc_html__( 'Size', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 100,
],
'em' => [
'max' => 10,
],
'rem' => [
'max' => 10,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-star-rating' => 'font-size: {{SIZE}}{{UNIT}}',
],
'separator' => 'before',
]
);
$this->add_control(
'star_space',
[
'label' => esc_html__( 'Spacing', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 50,
],
'em' => [
'max' => 5,
],
'rem' => [
'max' => 5,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-star-rating i:not(:last-of-type)' => 'margin-inline-end: {{SIZE}}{{UNIT}}',
],
]
);
$this->add_control(
'stars_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-star-rating i:before' => 'color: {{VALUE}}',
],
'separator' => 'before',
]
);
$this->add_control(
'stars_unmarked_color',
[
'label' => esc_html__( 'Unmarked Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-star-rating i' => 'color: {{VALUE}}',
],
]
);
$this->end_controls_section();
$this->end_injection();
$this->update_responsive_control(
'width',
[
'selectors' => [
'{{WRAPPER}}.elementor-arrows-yes .elementor-main-swiper' => 'width: calc( {{SIZE}}{{UNIT}} - 40px )',
'{{WRAPPER}} .elementor-main-swiper' => 'width: {{SIZE}}{{UNIT}}',
],
]
);
$this->update_responsive_control(
'slides_per_view',
[
'condition' => null,
]
);
$this->update_control(
'slides_to_scroll',
[
'condition' => null,
]
);
$this->remove_control( 'effect' );
$this->remove_responsive_control( 'height' );
$this->remove_control( 'pagination_position' );
}
protected function add_repeater_controls( Repeater $repeater ) {
$repeater->add_control(
'image',
[
'label' => esc_html__( 'Image', 'elementor-pro' ),
'type' => Controls_Manager::MEDIA,
'default' => [
'url' => Utils::get_placeholder_image_src(),
],
'dynamic' => [
'active' => true,
],
]
);
$repeater->add_control(
'name',
[
'label' => esc_html__( 'Name', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'default' => esc_html__( 'John Doe', 'elementor-pro' ),
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
]
);
$repeater->add_control(
'title',
[
'label' => esc_html__( 'Title', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'default' => '@username',
'dynamic' => [
'active' => true,
],
]
);
$repeater->add_control(
'rating',
[
'label' => esc_html__( 'Rating', 'elementor-pro' ),
'type' => Controls_Manager::NUMBER,
'min' => 0,
'max' => 5,
'step' => 0.1,
'dynamic' => [
'active' => true,
],
]
);
$repeater->add_control(
'selected_social_icon',
[
'label' => esc_html__( 'Icon', 'elementor-pro' ),
'type' => Controls_Manager::ICONS,
'fa4compatibility' => 'social_icon',
'default' => [
'value' => 'fab fa-twitter',
'library' => 'fa-brands',
],
'recommended' => [
'fa-solid' => [
'rss',
'shopping-cart',
'thumbtack',
],
'fa-brands' => [
'android',
'apple',
'behance',
'bitbucket',
'codepen',
'delicious',
'digg',
'dribbble',
'envelope',
'facebook',
'flickr',
'foursquare',
'github',
'google-plus',
'houzz',
'instagram',
'jsfiddle',
'linkedin',
'medium',
'meetup',
'mix',
'mixcloud',
'odnoklassniki',
'pinterest',
'product-hunt',
'reddit',
'skype',
'slideshare',
'snapchat',
'soundcloud',
'spotify',
'stack-overflow',
'steam',
'telegram',
'threads',
'tripadvisor',
'tumblr',
'twitch',
'twitter',
'vimeo',
'fa-vk',
'weibo',
'weixin',
'whatsapp',
'wordpress',
'x-twitter',
'xing',
'yelp',
'youtube',
'500px',
],
],
]
);
$repeater->add_control(
'link',
[
'label' => esc_html__( 'Link', 'elementor-pro' ),
'type' => Controls_Manager::URL,
'dynamic' => [
'active' => true,
],
]
);
$repeater->add_control(
'content',
[
'label' => esc_html__( 'Review', 'elementor-pro' ),
'type' => Controls_Manager::TEXTAREA,
'default' => esc_html__( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.', 'elementor-pro' ),
'dynamic' => [
'active' => true,
],
]
);
}
protected function get_repeater_defaults() {
$placeholder_image_src = Utils::get_placeholder_image_src();
return [
[
'content' => esc_html__( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.', 'elementor-pro' ),
'name' => esc_html__( 'John Doe', 'elementor-pro' ),
'title' => '@username',
'image' => [
'url' => $placeholder_image_src,
],
],
[
'content' => esc_html__( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.', 'elementor-pro' ),
'name' => esc_html__( 'John Doe', 'elementor-pro' ),
'title' => '@username',
'image' => [
'url' => $placeholder_image_src,
],
],
[
'content' => esc_html__( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.', 'elementor-pro' ),
'name' => esc_html__( 'John Doe', 'elementor-pro' ),
'title' => '@username',
'image' => [
'url' => $placeholder_image_src,
],
],
];
}
private function print_cite( $slide, $settings ) {
if ( empty( $slide['name'] ) && empty( $slide['title'] ) ) {
return '';
}
$html = '<cite class="elementor-testimonial__cite">';
if ( ! empty( $slide['name'] ) ) {
$html .= '<span class="elementor-testimonial__name">' . $slide['name'] . '</span>';
}
if ( ! empty( $slide['rating'] ) ) {
$html .= $this->render_stars( $slide, $settings );
}
if ( ! empty( $slide['title'] ) ) {
$html .= '<span class="elementor-testimonial__title">' . $slide['title'] . '</span>';
}
$html .= '</cite>';
echo wp_kses_post( $html );
}
protected function render_stars( $slide, $settings ) {
$icon = '&#xE934;';
if ( 'star_fontawesome' === $settings['star_style'] ) {
if ( 'outline' === $settings['unmarked_star_style'] ) {
$icon = '&#xE933;';
}
} elseif ( 'star_unicode' === $settings['star_style'] ) {
$icon = '&#9733;';
if ( 'outline' === $settings['unmarked_star_style'] ) {
$icon = '&#9734;';
}
}
$rating = (float) $slide['rating'] > 5 ? 5 : $slide['rating'];
$floored_rating = (int) $rating;
$stars_html = '';
for ( $stars = 1; $stars <= 5; $stars++ ) {
if ( $stars <= $floored_rating ) {
$stars_html .= '<i class="elementor-star-full">' . $icon . '</i>';
} elseif ( $floored_rating + 1 === $stars && $rating !== $floored_rating ) {
$stars_html .= '<i class="elementor-star-' . ( $rating - $floored_rating ) * 10 . '">' . $icon . '</i>';
} else {
$stars_html .= '<i class="elementor-star-empty">' . $icon . '</i>';
}
}
return '<div class="elementor-star-rating">' . $stars_html . '</div>';
}
private function print_icon( $slide, $element_key ) {
$migration_allowed = Icons_Manager::is_migration_allowed();
if ( ! isset( $slide['social_icon'] ) && ! $migration_allowed ) {
// add old default
$slide['social_icon'] = 'fa fa-twitter';
}
if ( empty( $slide['social_icon'] ) && empty( $slide['selected_social_icon'] ) ) {
return '';
}
$migrated = isset( $slide['__fa4_migrated']['selected_social_icon'] );
$is_new = empty( $slide['social_icon'] ) && $migration_allowed;
$social = '';
if ( $is_new || $migrated ) {
ob_start();
Icons_Manager::render_icon( $slide['selected_social_icon'], [ 'aria-hidden' => 'true' ] );
$icon = ob_get_clean();
} else {
$icon = '<i class="' . esc_attr( $slide['social_icon'] ) . '" aria-hidden="true"></i>';
}
if ( ! empty( $slide['social_icon'] ) ) {
$social = str_replace( 'fa fa-', '', $slide['social_icon'] );
}
if ( ( $is_new || $migrated ) && 'svg' !== $slide['selected_social_icon']['library'] ) {
$social = explode( ' ', $slide['selected_social_icon']['value'], 2 );
if ( empty( $social[1] ) ) {
$social = '';
} else {
$social = str_replace( 'fa-', '', $social[1] );
}
}
if ( 'svg' === $slide['selected_social_icon']['library'] ) {
$social = '';
}
$this->add_render_attribute( 'icon_wrapper_' . $element_key, 'class', 'elementor-testimonial__icon elementor-icon' );
$this->add_render_attribute( 'icon_wrapper_' . $element_key, 'class', 'elementor-icon-' . $social );
$this->add_render_attribute( 'icon_wrapper_' . $element_key, 'aria-label', esc_attr__( 'Read More', 'elementor-pro' ) );
// Icon is escaped above, get_render_attribute_string() is safe
echo '<div ' . $this->get_render_attribute_string( 'icon_wrapper_' . $element_key ) . '>' . $icon . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
protected function print_slide( array $slide, array $settings, $element_key ) {
$lazyload = 'yes' === $this->get_settings( 'lazyload' );
$this->add_render_attribute( $element_key . '-testimonial', [
'class' => 'elementor-testimonial',
] );
$this->add_render_attribute( $element_key . '-testimonial', [
'class' => 'elementor-repeater-item-' . $slide['_id'],
] );
if ( ! empty( $slide['image']['url'] ) ) {
$img_src = $this->get_slide_image_url( $slide, $settings );
if ( $lazyload ) {
$img_attribute['class'] = 'swiper-lazy';
$img_attribute['data-src'] = $img_src;
} else {
$img_attribute['src'] = $img_src;
}
$img_attribute['alt'] = $this->get_slide_image_alt_attribute( $slide );
$this->add_render_attribute( $element_key . '-image', $img_attribute );
}
?>
<div <?php $this->print_render_attribute_string( $element_key . '-testimonial' ); ?>>
<?php if ( $slide['image']['url'] || ! empty( $slide['name'] ) || ! empty( $slide['title'] ) ) :
$link_url = empty( $slide['link']['url'] ) ? false : $slide['link']['url'];
$header_tag = ! empty( $link_url ) ? 'a' : 'div';
$header_element = 'header_' . $slide['_id'];
$this->add_render_attribute( $header_element, 'class', 'elementor-testimonial__header' );
if ( ! empty( $link_url ) ) {
$this->add_link_attributes( $header_element, $slide['link'] );
}
?>
<<?php Utils::print_validated_html_tag( $header_tag ); ?> <?php $this->print_render_attribute_string( $header_element ); ?>>
<?php if ( $slide['image']['url'] ) : ?>
<div class="elementor-testimonial__image">
<img <?php $this->print_render_attribute_string( $element_key . '-image' ); ?>>
<?php if ( $lazyload ) : ?>
<div class="swiper-lazy-preloader"></div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php $this->print_cite( $slide, $settings ); ?>
<?php $this->print_icon( $slide, $element_key ); ?>
</<?php Utils::print_validated_html_tag( $header_tag ); ?>>
<?php endif; ?>
<?php if ( $slide['content'] ) : ?>
<div class="elementor-testimonial__content">
<div class="elementor-testimonial__text">
<?php
// Main content allowed
echo $slide['content']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
</div>
<?php endif; ?>
</div>
<?php
}
protected function render() {
$this->print_slider();
}
public function get_group_name() {
return 'carousel';
}
}

View File

@@ -0,0 +1,752 @@
<?php
namespace ElementorPro\Modules\Carousel\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_Typography;
use Elementor\Group_Control_Text_Stroke;
use Elementor\Group_Control_Text_Shadow;
use Elementor\Repeater;
use Elementor\Utils;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Testimonial_Carousel extends Base {
public function get_name() {
return 'testimonial-carousel';
}
public function get_title() {
return esc_html__( 'Testimonial Carousel', 'elementor-pro' );
}
public function get_icon() {
return 'eicon-testimonial-carousel';
}
public function get_keywords() {
return [ 'testimonial', 'carousel', 'image' ];
}
public function has_widget_inner_wrapper(): bool {
return ! Plugin::elementor()->experiments->is_feature_active( 'e_optimized_markup' );
}
/**
* 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 [ 'e-swiper', 'widget-testimonial-carousel', 'widget-carousel-module-base' ];
}
/**
* Get script dependencies.
*
* Retrieve the list of script dependencies the widget requires.
*
* @since 3.27.0
* @access public
*
* @return array Widget script dependencies.
*/
public function get_script_depends(): array {
return [ 'swiper' ];
}
protected function register_controls() {
parent::register_controls();
$this->start_injection( [
'of' => 'slides',
] );
$this->add_control(
'skin',
[
'label' => esc_html__( 'Skin', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'default',
'options' => [
'default' => esc_html__( 'Default', 'elementor-pro' ),
'bubble' => esc_html__( 'Bubble', 'elementor-pro' ),
],
'prefix_class' => 'elementor-testimonial--skin-',
'render_type' => 'template',
]
);
$this->add_control(
'layout',
[
'label' => esc_html__( 'Layout', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'image_inline',
'options' => [
'image_inline' => esc_html__( 'Image Inline', 'elementor-pro' ),
'image_stacked' => esc_html__( 'Image Stacked', 'elementor-pro' ),
'image_above' => esc_html__( 'Image Above', 'elementor-pro' ),
'image_left' => esc_html__( 'Image Left', 'elementor-pro' ),
'image_right' => esc_html__( 'Image Right', 'elementor-pro' ),
],
'prefix_class' => 'elementor-testimonial--layout-',
'render_type' => 'template',
]
);
$this->add_responsive_control(
'alignment',
[
'label' => esc_html__( 'Alignment', 'elementor-pro' ),
'type' => Controls_Manager::CHOOSE,
'default' => 'center',
'options' => [
'left' => [
'title' => esc_html__( 'Left', 'elementor-pro' ),
'icon' => 'eicon-text-align-left',
],
'center' => [
'title' => esc_html__( 'Center', 'elementor-pro' ),
'icon' => 'eicon-text-align-center',
],
'right' => [
'title' => esc_html__( 'Right', 'elementor-pro' ),
'icon' => 'eicon-text-align-right',
],
],
'prefix_class' => 'elementor-testimonial-%s-align-',
]
);
$this->end_injection();
$this->start_controls_section(
'section_skin_style',
[
'label' => esc_html__( 'Bubble', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
'condition' => [
'skin' => 'bubble',
],
]
);
$this->add_control(
'background_color',
[
'label' => esc_html__( 'Background Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'alpha' => false,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__content, {{WRAPPER}} .elementor-testimonial__content:after' => 'background-color: {{VALUE}}',
],
]
);
$this->add_responsive_control(
'text_padding',
[
'label' => esc_html__( 'Padding', 'elementor-pro' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'default' => [
'top' => '20',
'bottom' => '20',
'left' => '20',
'right' => '20',
'unit' => 'px',
],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__content' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}',
'{{WRAPPER}}.elementor-testimonial--layout-image_left .elementor-testimonial__footer,
{{WRAPPER}}.elementor-testimonial--layout-image_right .elementor-testimonial__footer' => 'padding-top: {{TOP}}{{UNIT}}',
'{{WRAPPER}}.elementor-testimonial--layout-image_above .elementor-testimonial__footer,
{{WRAPPER}}.elementor-testimonial--layout-image_inline .elementor-testimonial__footer,
{{WRAPPER}}.elementor-testimonial--layout-image_stacked .elementor-testimonial__footer' => 'padding: 0 {{RIGHT}}{{UNIT}} 0 {{LEFT}}{{UNIT}}',
],
]
);
$this->add_responsive_control(
'border_radius',
[
'label' => esc_html__( 'Border Radius', 'elementor-pro' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__content' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->add_control(
'border',
[
'label' => esc_html__( 'Border', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__content, {{WRAPPER}} .elementor-testimonial__content:after' => 'border-style: solid',
],
]
);
$this->add_control(
'border_color',
[
'label' => esc_html__( 'Border Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'default' => '#000',
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__content' => 'border-color: {{VALUE}}',
'{{WRAPPER}} .elementor-testimonial__content:after' => 'border-color: transparent {{VALUE}} {{VALUE}} transparent',
],
'condition' => [
'border' => 'yes',
],
]
);
$this->add_responsive_control(
'border_width',
[
'label' => esc_html__( 'Border Width', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'range' => [
'px' => [
'max' => 20,
],
'em' => [
'max' => 2,
],
'rem' => [
'max' => 2,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__content, {{WRAPPER}} .elementor-testimonial__content:after' => 'border-width: {{SIZE}}{{UNIT}}',
'{{WRAPPER}}.elementor-testimonial--layout-image_stacked .elementor-testimonial__content:after,
{{WRAPPER}}.elementor-testimonial--layout-image_inline .elementor-testimonial__content:after' => 'margin-top: -{{SIZE}}{{UNIT}}',
'{{WRAPPER}}.elementor-testimonial--layout-image_above .elementor-testimonial__content:after' => 'margin-bottom: -{{SIZE}}{{UNIT}}',
],
'condition' => [
'border' => 'yes',
],
]
);
$this->end_controls_section();
$this->start_injection( [
'at' => 'before',
'of' => 'section_navigation',
] );
$this->start_controls_section(
'section_content_style',
[
'label' => esc_html__( 'Content', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_responsive_control(
'content_gap',
[
'label' => esc_html__( 'Gap', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 100,
],
'em' => [
'max' => 10,
],
'rem' => [
'max' => 10,
],
],
'selectors' => [
'{{WRAPPER}}.elementor-testimonial--layout-image_inline .elementor-testimonial__footer,
{{WRAPPER}}.elementor-testimonial--layout-image_stacked .elementor-testimonial__footer' => 'margin-top: {{SIZE}}{{UNIT}}',
'{{WRAPPER}}.elementor-testimonial--layout-image_above .elementor-testimonial__footer' => 'margin-bottom: {{SIZE}}{{UNIT}}',
'{{WRAPPER}}.elementor-testimonial--layout-image_left .elementor-testimonial__footer' => 'padding-right: {{SIZE}}{{UNIT}}',
'{{WRAPPER}}.elementor-testimonial--layout-image_right .elementor-testimonial__footer' => 'padding-left: {{SIZE}}{{UNIT}}',
],
]
);
$this->add_control(
'content_color',
[
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__text' => 'color: {{VALUE}}',
],
'global' => [
'default' => Global_Colors::COLOR_TEXT,
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'content_typography',
'selector' => '{{WRAPPER}} .elementor-testimonial__text',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_TEXT,
],
]
);
$this->add_group_control(
Group_Control_Text_Stroke::get_type(),
[
'name' => 'text_stroke',
'selector' => '{{WRAPPER}} .elementor-testimonial__text',
]
);
$this->add_group_control(
Group_Control_Text_Shadow::get_type(),
[
'name' => 'content_text_shadow',
'selector' => '{{WRAPPER}} .elementor-testimonial__text',
]
);
$this->add_control(
'name_title_style',
[
'label' => esc_html__( 'Name', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
'separator' => 'before',
]
);
$this->add_control(
'name_color',
[
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__name' => 'color: {{VALUE}}',
],
'global' => [
'default' => Global_Colors::COLOR_TEXT,
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'name_typography',
'selector' => '{{WRAPPER}} .elementor-testimonial__name',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_PRIMARY,
],
]
);
$this->add_control(
'heading_title_style',
[
'label' => esc_html__( 'Title', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
'separator' => 'before',
]
);
$this->add_control(
'title_color',
[
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__title' => 'color: {{VALUE}}',
],
'global' => [
'default' => Global_Colors::COLOR_PRIMARY,
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'title_typography',
'selector' => '{{WRAPPER}} .elementor-testimonial__title',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_SECONDARY,
],
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_image_style',
[
'label' => esc_html__( 'Image', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_responsive_control(
'image_size',
[
'label' => esc_html__( 'Size', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 200,
],
'em' => [
'max' => 20,
],
'rem' => [
'max' => 20,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__image img' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}}',
'{{WRAPPER}}.elementor-testimonial--layout-image_left .elementor-testimonial__content:after,
{{WRAPPER}}.elementor-testimonial--layout-image_right .elementor-testimonial__content:after' => 'top: calc( {{text_padding.TOP}}{{text_padding.UNIT}} + ({{SIZE}}{{UNIT}} / 2) - 8px );',
'body:not(.rtl) {{WRAPPER}}.elementor-testimonial--layout-image_stacked:not(.elementor-testimonial--align-center):not(.elementor-testimonial--align-right) .elementor-testimonial__content:after,
body:not(.rtl) {{WRAPPER}}.elementor-testimonial--layout-image_inline:not(.elementor-testimonial--align-center):not(.elementor-testimonial--align-right) .elementor-testimonial__content:after,
{{WRAPPER}}.elementor-testimonial--layout-image_stacked.elementor-testimonial--align-left .elementor-testimonial__content:after,
{{WRAPPER}}.elementor-testimonial--layout-image_inline.elementor-testimonial--align-left .elementor-testimonial__content:after' => 'left: calc( {{text_padding.LEFT}}{{text_padding.UNIT}} + ({{SIZE}}{{UNIT}} / 2) - 8px ); right:auto;',
'body.rtl {{WRAPPER}}.elementor-testimonial--layout-image_stacked:not(.elementor-testimonial--align-center):not(.elementor-testimonial--align-left) .elementor-testimonial__content:after,
body.rtl {{WRAPPER}}.elementor-testimonial--layout-image_inline:not(.elementor-testimonial--align-center):not(.elementor-testimonial--align-left) .elementor-testimonial__content:after,
{{WRAPPER}}.elementor-testimonial--layout-image_stacked.elementor-testimonial--align-right .elementor-testimonial__content:after,
{{WRAPPER}}.elementor-testimonial--layout-image_inline.elementor-testimonial--align-right .elementor-testimonial__content:after' => 'right: calc( {{text_padding.RIGHT}}{{text_padding.UNIT}} + ({{SIZE}}{{UNIT}} / 2) - 8px ); left:auto;',
'body:not(.rtl) {{WRAPPER}}.elementor-testimonial--layout-image_above:not(.elementor-testimonial--align-center):not(.elementor-testimonial--align-right) .elementor-testimonial__content:after,
{{WRAPPER}}.elementor-testimonial--layout-image_above.elementor-testimonial--align-left .elementor-testimonial__content:after' => 'left: calc( {{text_padding.LEFT}}{{text_padding.UNIT}} + ({{SIZE}}{{UNIT}} / 2) - 8px ); right:auto;',
'body.rtl {{WRAPPER}}.elementor-testimonial--layout-image_above:not(.elementor-testimonial--align-center):not(.elementor-testimonial--align-left) .elementor-testimonial__content:after,
{{WRAPPER}}.elementor-testimonial--layout-image_above.elementor-testimonial--align-right .elementor-testimonial__content:after' => 'right: calc( {{text_padding.RIGHT}}{{text_padding.UNIT}} + ({{SIZE}}{{UNIT}} / 2) - 8px ); left:auto;',
],
]
);
$this->add_responsive_control(
'image_gap',
[
'label' => esc_html__( 'Gap', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'range' => [
'px' => [
'max' => 100,
],
'em' => [
'max' => 10,
],
'rem' => [
'max' => 10,
],
],
'selectors' => [
'body.rtl {{WRAPPER}}.elementor-testimonial--layout-image_inline.elementor-testimonial--align-left .elementor-testimonial__image + cite,
body.rtl {{WRAPPER}}.elementor-testimonial--layout-image_above.elementor-testimonial--align-left .elementor-testimonial__image + cite,
body:not(.rtl) {{WRAPPER}}.elementor-testimonial--layout-image_inline .elementor-testimonial__image + cite,
body:not(.rtl) {{WRAPPER}}.elementor-testimonial--layout-image_above .elementor-testimonial__image + cite' => 'margin-left: {{SIZE}}{{UNIT}}; margin-right: 0;',
'body:not(.rtl) {{WRAPPER}}.elementor-testimonial--layout-image_inline.elementor-testimonial--align-right .elementor-testimonial__image + cite,
body:not(.rtl) {{WRAPPER}}.elementor-testimonial--layout-image_above.elementor-testimonial--align-right .elementor-testimonial__image + cite,
body.rtl {{WRAPPER}}.elementor-testimonial--layout-image_inline .elementor-testimonial__image + cite,
body.rtl {{WRAPPER}}.elementor-testimonial--layout-image_above .elementor-testimonial__image + cite' => 'margin-right: {{SIZE}}{{UNIT}}; margin-left:0;',
'{{WRAPPER}}.elementor-testimonial--layout-image_stacked .elementor-testimonial__image + cite,
{{WRAPPER}}.elementor-testimonial--layout-image_left .elementor-testimonial__image + cite,
{{WRAPPER}}.elementor-testimonial--layout-image_right .elementor-testimonial__image + cite' => 'margin-top: {{SIZE}}{{UNIT}}',
],
]
);
$this->add_control(
'image_border',
[
'label' => esc_html__( 'Border', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__image img' => 'border-style: solid',
],
]
);
$this->add_control(
'image_border_color',
[
'label' => esc_html__( 'Border Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'default' => '#000',
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__image img' => 'border-color: {{VALUE}}',
],
'condition' => [
'image_border' => 'yes',
],
]
);
$this->add_responsive_control(
'image_border_width',
[
'label' => esc_html__( 'Border Width', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'range' => [
'px' => [
'max' => 20,
],
'em' => [
'max' => 2,
],
'rem' => [
'max' => 2,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__image img' => 'border-width: {{SIZE}}{{UNIT}}',
],
'condition' => [
'image_border' => 'yes',
],
]
);
$this->add_control(
'image_border_radius',
[
'label' => esc_html__( 'Border Radius', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
'selectors' => [
'{{WRAPPER}} .elementor-testimonial__image img' => 'border-radius: {{SIZE}}{{UNIT}}',
],
]
);
$this->end_controls_section();
$this->end_injection();
$this->update_responsive_control(
'width',
[
'selectors' => [
'{{WRAPPER}}.elementor-arrows-yes .elementor-main-swiper' => 'width: calc( {{SIZE}}{{UNIT}} - 40px )',
'{{WRAPPER}} .elementor-main-swiper' => 'width: {{SIZE}}{{UNIT}}',
],
]
);
$this->update_responsive_control(
'slides_per_view',
[
'condition' => null,
]
);
$this->update_responsive_control(
'slides_to_scroll',
[
'condition' => null,
]
);
$this->remove_control( 'effect' );
$this->remove_responsive_control( 'height' );
$this->remove_control( 'pagination_position' );
}
protected function add_repeater_controls( Repeater $repeater ) {
$repeater->add_control(
'content',
[
'label' => esc_html__( 'Content', 'elementor-pro' ),
'type' => Controls_Manager::TEXTAREA,
'dynamic' => [
'active' => true,
],
]
);
$repeater->add_control(
'image',
[
'label' => esc_html__( 'Image', 'elementor-pro' ),
'type' => Controls_Manager::MEDIA,
'dynamic' => [
'active' => true,
],
]
);
$repeater->add_control(
'name',
[
'label' => esc_html__( 'Name', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'default' => esc_html__( 'John Doe', 'elementor-pro' ),
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
]
);
$repeater->add_control(
'title',
[
'label' => esc_html__( 'Title', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'default' => esc_html__( 'CEO', 'elementor-pro' ),
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
]
);
}
protected function get_repeater_defaults() {
$placeholder_image_src = Utils::get_placeholder_image_src();
return [
[
'content' => esc_html__( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.', 'elementor-pro' ),
'name' => esc_html__( 'John Doe', 'elementor-pro' ),
'title' => esc_html__( 'CEO', 'elementor-pro' ),
'image' => [
'url' => $placeholder_image_src,
],
],
[
'content' => esc_html__( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.', 'elementor-pro' ),
'name' => esc_html__( 'John Doe', 'elementor-pro' ),
'title' => esc_html__( 'CEO', 'elementor-pro' ),
'image' => [
'url' => $placeholder_image_src,
],
],
[
'content' => esc_html__( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.', 'elementor-pro' ),
'name' => esc_html__( 'John Doe', 'elementor-pro' ),
'title' => esc_html__( 'CEO', 'elementor-pro' ),
'image' => [
'url' => $placeholder_image_src,
],
],
];
}
private function print_cite( $slide, $location ) {
if ( empty( $slide['name'] ) && empty( $slide['title'] ) ) {
return '';
}
$skin = $this->get_settings( 'skin' );
$layout = 'bubble' === $skin ? 'image_inline' : $this->get_settings( 'layout' );
$locations_outside = [ 'image_above', 'image_right', 'image_left' ];
$locations_inside = [ 'image_inline', 'image_stacked' ];
$print_outside = ( 'outside' === $location && in_array( $layout, $locations_outside ) );
$print_inside = ( 'inside' === $location && in_array( $layout, $locations_inside ) );
$html = '';
if ( $print_outside || $print_inside ) {
$html = '<cite class="elementor-testimonial__cite">';
if ( ! empty( $slide['name'] ) ) {
$html .= '<span class="elementor-testimonial__name">' . $slide['name'] . '</span>';
}
if ( ! empty( $slide['title'] ) ) {
$html .= '<span class="elementor-testimonial__title">' . $slide['title'] . '</span>';
}
$html .= '</cite>';
}
// PHPCS - the main text of a widget should not be escaped.
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
protected function print_slide( array $slide, array $settings, $element_key ) {
$lazyload = 'yes' === $this->get_settings( 'lazyload' );
$this->add_render_attribute( $element_key . '-testimonial', [
'class' => 'elementor-testimonial',
] );
if ( ! empty( $slide['image']['url'] ) ) {
$img_src = $this->get_slide_image_url( $slide, $settings );
if ( $lazyload ) {
$img_attribute['class'] = 'swiper-lazy';
$img_attribute['data-src'] = $img_src;
} else {
$img_attribute['src'] = $img_src;
}
$img_attribute['alt'] = ! empty( $slide['image']['alt'] ) ? $slide['image']['alt'] : $slide['name'];
$this->add_render_attribute( $element_key . '-image', $img_attribute );
}
?>
<div <?php $this->print_render_attribute_string( $element_key . '-testimonial' ); ?>>
<?php if ( $slide['content'] ) : ?>
<div class="elementor-testimonial__content">
<div class="elementor-testimonial__text">
<?php // PHPCS - the main text of a widget should not be escaped.
echo $slide['content']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<?php $this->print_cite( $slide, 'outside' ); ?>
</div>
<?php endif; ?>
<div class="elementor-testimonial__footer">
<?php if ( $slide['image']['url'] ) : ?>
<div class="elementor-testimonial__image">
<img <?php $this->print_render_attribute_string( $element_key . '-image' ); ?>>
<?php if ( $lazyload ) : ?>
<div class="swiper-lazy-preloader"></div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php $this->print_cite( $slide, 'inside' ); ?>
</div>
</div>
<?php
}
protected function render() {
$this->print_slider();
}
public function get_group_name() {
return 'carousel';
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace ElementorPro\Modules\Checklist;
use Elementor\Core\Isolation\Kit_Adapter_Interface;
use ElementorPro\Base\Module_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Module extends Module_Base {
private $module;
private ?Wordpress_Adapter_Interface $wordpress_adapter;
private ?Kit_Adapter_Interface $kit_adapter;
public function __construct( ?Wordpress_Adapter_Interface $wordpress_adapter = null, ?Kit_Adapter_Interface $kit_adapter = null ) {
if ( ! class_exists( 'Elementor\\Modules\\Checklist\\Module' ) ) {
return;
}
$this->module = \Elementor\Modules\Checklist\Module::instance();
$this->wordpress_adapter = $wordpress_adapter;
$this->kit_adapter = $kit_adapter;
add_filter( 'elementor/checklist/steps', [ $this, 'replace_steps' ], 1 );
}
public function get_name() : string {
return 'e-checklist';
}
/**
* @param array $steps
* @return \Elementor\Modules\Checklist\Steps\Step_Base[]
*/
public function replace_steps( array $steps ) : array {
$formatted_steps = [];
foreach ( $steps as $step_id => $step ) {
if ( \Elementor\Modules\Checklist\Steps\Setup_Header::STEP_ID !== $step_id ) {
$formatted_steps[ $step_id ] = $step;
continue;
}
$header_step = new \ElementorPro\Modules\Checklist\Steps\Setup_Header( $this->module, $this->wordpress_adapter, $this->kit_adapter );
$formatted_steps[ $header_step->get_id() ] = $header_step;
}
return $formatted_steps;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace ElementorPro\Modules\Checklist\Steps;
use ElementorPro\License\API;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) || ! class_exists( '\\Elementor\\Modules\\Checklist\\Steps\\Setup_Header' ) ) {
exit;
}
class Setup_Header extends \Elementor\Modules\Checklist\Steps\Setup_Header {
const STEP_ID = 'setup_header_pro';
public function __construct( $module, $wordpress_adapter = null, $kit_adapter = null ) {
$promotion_data = ! $this->does_license_support_header()
? [
'url' => 'http://go.elementor.com/app-website-checklist-header-article',
'text' => esc_html__( 'Upgrade Now', 'elementor-pro' ),
'icon' => 'default',
]
: null;
parent::__construct( $module, $wordpress_adapter, $kit_adapter, $promotion_data );
}
public function get_id() : string {
return self::STEP_ID;
}
public function is_visible() : bool {
return true;
}
public function get_cta_url() : string {
$base_create_url = Plugin::elementor()->documents->get_create_new_post_url( 'elementor_library' );
return add_query_arg( [ 'template_type' => 'header' ], $base_create_url );
}
private function does_license_support_header() : bool {
return API::is_licence_has_feature( 'theme-builder' );
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace ElementorPro\Modules\CloudLibrary;
use ElementorPro\Base\Module_Base;
use ElementorPro\Plugin;
use ElementorPro\Core\Connect\Apps\Activate;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends Module_Base {
public function get_name(): string {
return 'cloud-library';
}
public function __construct() {
parent::__construct();
add_action( 'elementor/init', function () {
$this->set_cloud_library_settings();
}, 13 /** after elementor core */ );
}
private function set_cloud_library_settings() {
if ( ! Plugin::elementor()->common ) {
return;
}
/** @var ConnectModule $connect */
$connect = Plugin::elementor()->common->get_component( 'connect' );
/** @var Activate $activate */
$activate = $connect->get_app( 'activate' );
if ( ! $activate ) {
return;
}
Plugin::elementor()->app->set_settings( 'cloud-library', [
'library_connect_url' => esc_url( $activate->get_admin_url( 'authorize', [
'utm_source' => 'template-library',
'utm_medium' => 'wp-dash',
'utm_campaign' => 'connect-and-activate-license',
'utm_content' => 'cloud-library',
'source' => 'cloud-library',
] ) ),
'library_connect_title_copy' => esc_html__( 'Connect to your Elementor account', 'elementor-pro' ),
'library_connect_sub_title_copy' => esc_html__( 'This includes activating your Elementor Pro license on a specific site.', 'elementor-pro' ) . '<br>' . esc_html__( 'Then you can find all your templates in one convenient library.', 'elementor-pro' ),
'library_connect_button_copy' => esc_html__( 'Connect & Activate', 'elementor-pro' ),
] );
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace ElementorPro\Modules\CodeHighlight;
use ElementorPro\Base\Module_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
public function __construct() {
parent::__construct();
add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] );
add_action( 'elementor/frontend/before_register_scripts', [ $this, 'register_frontend_scripts' ] );
}
public function get_name() {
return 'code-highlight';
}
public function get_widgets() {
return [
'Code_Highlight',
];
}
/**
* Get the base URL for assets.
*
* @return string
*/
public function get_assets_base_url(): string {
return ELEMENTOR_PRO_URL;
}
/**
* Register styles.
*
* At build time, Elementor compiles `/modules/code-highlight/assets/scss/frontend.scss`
* to `/assets/css/widget-code-highlight.min.css`.
*
* @return void
*/
public function register_styles() {
wp_register_style(
'widget-code-highlight',
$this->get_css_assets_url( 'widget-code-highlight', null, true, true ),
[ 'elementor-frontend' ],
ELEMENTOR_PRO_VERSION
);
}
public function register_frontend_scripts() {
$base_url = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0';
wp_register_script( 'prismjs_core', $base_url . '/components/prism-core.min.js', [], '1.23.0', true );
wp_register_script( 'prismjs_loader', $base_url . '/plugins/autoloader/prism-autoloader.min.js', [ 'prismjs_core' ], '1.23.0', true );
wp_register_script( 'prismjs_normalize', $base_url . '/plugins/normalize-whitespace/prism-normalize-whitespace.min.js', [ 'prismjs_core' ], '1.23.0', true );
wp_register_script( 'prismjs_line_numbers', $base_url . '/plugins/line-numbers/prism-line-numbers.min.js', [ 'prismjs_normalize' ], '1.23.0', true );
wp_register_script( 'prismjs_line_highlight', $base_url . '/plugins/line-highlight/prism-line-highlight.min.js', [ 'prismjs_normalize' ], '1.23.0', true );
wp_register_script( 'prismjs_toolbar', $base_url . '/plugins/toolbar/prism-toolbar.min.js', [ 'prismjs_normalize' ], '1.23.0', true );
wp_register_script( 'prismjs_copy_to_clipboard', $base_url . '/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js', [ 'prismjs_toolbar' ], '1.23.0', true );
}
}

View File

@@ -0,0 +1,316 @@
<?php
namespace ElementorPro\Modules\CodeHighlight\Widgets;
use Elementor\Modules\DynamicTags\Module as TagsModule;
use Elementor\Controls_Manager;
use ElementorPro\Plugin;
use ElementorPro\Base\Base_Widget;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Code_Highlight extends Base_Widget {
public function get_name() {
return 'code-highlight';
}
public function get_title() {
return esc_html__( 'Code Highlight', 'elementor-pro' );
}
public function get_icon() {
return 'eicon-code-highlight';
}
public function get_keywords() {
return [ 'code', 'highlight', 'syntax', 'highlighter', 'javascript', 'css', 'php', 'html', 'java', 'js' ];
}
public function get_style_depends() {
return [ 'widget-code-highlight' ];
}
protected function is_dynamic_content(): bool {
return false;
}
public function has_widget_inner_wrapper(): bool {
return ! Plugin::elementor()->experiments->is_feature_active( 'e_optimized_markup' );
}
public function get_script_depends() {
$depends = [
'prismjs_core' => true,
'prismjs_loader' => true,
'prismjs_normalize' => true,
'highlight_handler' => true,
'prismjs_line_numbers' => true,
'prismjs_line_highlight' => true,
'prismjs_copy_to_clipboard' => true,
];
if ( ! Plugin::elementor()->preview->is_preview_mode() ) {
$settings = $this->get_settings_for_display();
if ( ! $settings['line_numbers'] ) {
unset( $depends['prismjs_line_numbers'] );
}
if ( ! $settings['highlight_lines'] ) {
unset( $depends['prismjs_line_highlight'] );
}
if ( ! $settings['copy_to_clipboard'] ) {
unset( $depends['prismjs_copy_to_clipboard'] );
}
}
return array_keys( $depends );
}
protected function register_controls() {
$this->start_controls_section(
'section_content',
[
'label' => esc_html__( 'Code Highlight', 'elementor-pro' ),
]
);
$language_option = [
'markup' => 'Markup',
'html' => 'HTML',
'css' => 'CSS',
'sass' => 'Sass (Sass)',
'scss' => 'Sass (Scss)',
'less' => 'Less',
'javascript' => 'JavaScript',
'typescript' => 'TypeScript',
'jsx' => 'React JSX',
'tsx' => 'React TSX',
'php' => 'PHP',
'ruby' => 'Ruby',
'json' => 'JSON + Web App Manifest',
'http' => 'HTTP',
'xml' => 'XML',
'svg' => 'SVG',
'rust' => 'Rust',
'csharp' => 'C#',
'dart' => 'Dart',
'git' => 'Git',
'java' => 'Java',
'sql' => 'SQL',
'go' => 'Go',
'kotlin' => 'Kotlin + Kotlin Script',
'julia' => 'Julia',
'python' => 'Python',
'swift' => 'Swift',
'bash' => 'Bash + Shell',
'scala' => 'Scala',
'haskell' => 'Haskell',
'perl' => 'Perl',
'objectivec' => 'Objective-C',
'visual-basic,' => 'Visual Basic + VBA',
'r' => 'R',
'c' => 'C',
'cpp' => 'C++',
'aspnet' => 'ASP.NET (C#)',
];
/**
* Code highlight languages.
*
* Filters the available programming languages in the code highlight.
*
* By default supports a code list of programming languages. This hook
* allows developers to add or remove languages.
*
* @param array $language_option An array of languages.
*/
$language_option = apply_filters( 'elementor_pro/code_highlight/languages', $language_option );
$this->add_control(
'language',
[
'label' => esc_html__( 'Language', 'elementor-pro' ),
'type' => Controls_Manager::SELECT2,
'multiple' => false,
'options' => $language_option,
'default' => 'javascript',
]
);
$this->add_control(
'code',
[
'label' => esc_html__( 'Code', 'elementor-pro' ),
'type' => Controls_Manager::CODE,
'default' => 'console.log( \'Code is Poetry\' );',
'dynamic' => [
'active' => true,
'categories' => [
TagsModule::TEXT_CATEGORY,
],
],
]
);
$this->add_control(
'line_numbers',
[
'label' => esc_html__( 'Line Numbers', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'return_value' => 'line-numbers',
'default' => 'line-numbers',
]
);
$this->add_control(
'copy_to_clipboard',
[
'label' => esc_html__( 'Copy to Clipboard', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'label_on' => esc_html__( 'On', 'elementor-pro' ),
'label_off' => esc_html__( 'Off', 'elementor-pro' ),
'return_value' => 'copy-to-clipboard',
'default' => 'copy-to-clipboard',
]
);
$this->add_control(
'highlight_lines',
[
'label' => esc_html__( 'Highlight Lines', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'default' => '',
'placeholder' => '1, 3-6',
'dynamic' => [
'active' => true,
],
]
);
$this->add_control(
'word_wrap',
[
'label' => esc_html__( 'Word Wrap', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'label_on' => esc_html__( 'On', 'elementor-pro' ),
'label_off' => esc_html__( 'Off', 'elementor-pro' ),
'return_value' => 'word-wrap',
'default' => '',
]
);
$this->add_control(
'theme',
[
'label' => esc_html__( 'Theme', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'default' => 'default',
'options' => [
'default' => 'Solid',
'dark' => 'Dark',
'okaidia' => 'Okaidia',
'solarizedlight' => 'Solarizedlight',
'tomorrow' => 'Tomorrow',
'twilight' => 'Twilight',
],
'separator' => 'before',
]
);
$this->add_responsive_control(
'height',
[
'label' => esc_html__( 'Height', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'vh', 'custom' ],
'range' => [
'px' => [
'min' => 115,
'max' => 1000,
],
'em' => [
'min' => 6,
'max' => 50,
],
'rem' => [
'min' => 6,
'max' => 50,
],
],
'selectors' => [
'{{WRAPPER}} .highlight-height' => 'height: {{SIZE}}{{UNIT}};',
],
]
);
$this->add_responsive_control(
'font_size',
[
'label' => esc_html__( 'Font Size', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'vw', 'custom' ],
'range' => [
'px' => [
'min' => 1,
'max' => 200,
],
'em' => [
'max' => 20,
],
'rem' => [
'max' => 20,
],
'vw' => [
'min' => 0.1,
'max' => 10,
'step' => 0.1,
],
],
'responsive' => true,
'selectors' => [
'{{WRAPPER}} pre, {{WRAPPER}} code, {{WRAPPER}} .line-numbers .line-numbers-rows' => 'font-size: {{SIZE}}{{UNIT}};',
],
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
if ( empty( $settings['code'] ) ) {
return;
}
?>
<div class="<?php echo 'prismjs-' . esc_attr( $settings['theme'] ); ?> <?php echo esc_attr( $settings['copy_to_clipboard'] ); ?> <?php echo esc_attr( $settings['word_wrap'] ); ?>">
<pre data-line="<?php echo esc_attr( $settings['highlight_lines'] ); ?>" class="highlight-height language-<?php echo esc_attr( $settings['language'] ); ?> <?php echo esc_attr( $settings['line_numbers'] ); ?>">
<code readonly="true" class="language-<?php echo esc_attr( $settings['language'] ); ?>">
<xmp><?php $this->print_unescaped_setting( 'code' ); ?></xmp>
</code>
</pre>
</div>
<?php
}
protected function content_template() {
?>
<#
if ( '' === settings.code ) {
return;
}
#>
<div class="prismjs-{{ settings.theme }} {{ settings.copy_to_clipboard }} {{ settings.word_wrap }}">
<pre data-line="{{ settings.highlight_lines }}" class="highlight-height language-{{ settings.language }} {{ settings.line_numbers }}">
<code readonly="true" class="language-{{ settings.language }}">
<xmp>{{{ settings.code }}}</xmp>
</code>
</pre>
</div>
<?php
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace ElementorPro\Modules\CompatibilityTag;
use Elementor\Modules\CompatibilityTag\Base_Module as Compatibility_Tag_Base_Module;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Compatibility_Tag_Component extends Compatibility_Tag_Base_Module {
/**
* This is the header used by extensions to show testing.
*
* @var string
*/
const PLUGIN_VERSION_TESTED_HEADER = 'Elementor Pro tested up to';
/**
* @return string
*/
protected function get_plugin_header() {
return self::PLUGIN_VERSION_TESTED_HEADER;
}
/**
* @return string
*/
protected function get_plugin_label() {
return esc_html__( 'Elementor Pro', 'elementor-pro' );
}
/**
* @return string
*/
protected function get_plugin_name() {
return ELEMENTOR_PRO_PLUGIN_BASE;
}
/**
* @return string
*/
protected function get_plugin_version() {
return ELEMENTOR_PRO_VERSION;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace ElementorPro\Modules\CompatibilityTag;
use ElementorPro\Base\Module_Base;
use Elementor\Modules\CompatibilityTag\Base_Module as Compatibility_Tag_Base_Module;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends Module_Base {
const MODULE_NAME = 'compatibility-tag-pro';
/**
* Checks if elementor core compatibility module is exists before
* activate this module
*
* @return bool
*/
public static function is_active() {
return class_exists( Compatibility_Tag_Base_Module::class );
}
/**
* @return string
*/
public function get_name() {
return self::MODULE_NAME;
}
/**
* Module constructor.
*/
public function __construct() {
parent::__construct();
$this->add_component( 'compatibility-tag-pro-handler', new Compatibility_Tag_Component() );
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace ElementorPro\Modules\Countdown;
use ElementorPro\Base\Module_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
public function __construct() {
parent::__construct();
add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] );
}
public function get_widgets() {
return [
'Countdown',
];
}
public function get_name() {
return 'countdown';
}
/**
* Get the base URL for assets.
*
* @return string
*/
public function get_assets_base_url(): string {
return ELEMENTOR_PRO_URL;
}
/**
* Register styles.
*
* At build time, Elementor compiles `/modules/countdown/assets/scss/frontend.scss`
* to `/assets/css/widget-countdown.min.css`.
*
* @return void
*/
public function register_styles() {
wp_register_style(
'widget-countdown',
$this->get_css_assets_url( 'widget-countdown', null, true, true ),
[ 'elementor-frontend' ],
ELEMENTOR_PRO_VERSION
);
}
}

View File

@@ -0,0 +1,884 @@
<?php
namespace ElementorPro\Modules\Countdown\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_Border;
use Elementor\Group_Control_Box_Shadow;
use Elementor\Group_Control_Typography;
use Elementor\Group_Control_Text_Shadow;
use Elementor\Group_Control_Text_Stroke;
use Elementor\Utils;
use ElementorPro\Base\Base_Widget;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Countdown extends Base_Widget {
public function get_name() {
return 'countdown';
}
public function get_title() {
return esc_html__( 'Countdown', 'elementor-pro' );
}
public function get_icon() {
return 'eicon-countdown';
}
public function get_keywords() {
return [ 'countdown', 'number', 'timer', 'time', 'date', 'evergreen' ];
}
protected function is_dynamic_content(): bool {
return false;
}
public function has_widget_inner_wrapper(): bool {
return ! Plugin::elementor()->experiments->is_feature_active( 'e_optimized_markup' );
}
/**
* 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-countdown' ];
}
protected function register_controls() {
$this->start_controls_section(
'section_countdown',
[
'label' => esc_html__( 'Countdown', 'elementor-pro' ),
]
);
$this->add_control(
'countdown_type',
[
'label' => esc_html__( 'Type', 'elementor-pro' ),
'type' => Controls_Manager::SELECT,
'options' => [
'due_date' => esc_html__( 'Due Date', 'elementor-pro' ),
'evergreen' => esc_html__( 'Evergreen Timer', 'elementor-pro' ),
],
'default' => 'due_date',
]
);
$this->add_control(
'due_date',
[
'label' => esc_html__( 'Due Date', 'elementor-pro' ),
'type' => Controls_Manager::DATE_TIME,
'default' => gmdate( 'Y-m-d H:i', strtotime( '+1 month' ) + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ),
/* translators: %s: Time zone. */
'description' => sprintf( esc_html__( 'Date set according to your timezone: %s.', 'elementor-pro' ), Utils::get_timezone_string() ),
'condition' => [
'countdown_type' => 'due_date',
],
'dynamic' => [
'active' => true,
],
]
);
$this->add_control(
'evergreen_counter_hours',
[
'label' => esc_html__( 'Hours', 'elementor-pro' ),
'type' => Controls_Manager::NUMBER,
'default' => 47,
'placeholder' => esc_html__( 'Hours', 'elementor-pro' ),
'condition' => [
'countdown_type' => 'evergreen',
],
'dynamic' => [
'active' => true,
],
]
);
$this->add_control(
'evergreen_counter_minutes',
[
'label' => esc_html__( 'Minutes', 'elementor-pro' ),
'type' => Controls_Manager::NUMBER,
'default' => 59,
'placeholder' => esc_html__( 'Minutes', 'elementor-pro' ),
'condition' => [
'countdown_type' => 'evergreen',
],
'dynamic' => [
'active' => true,
],
]
);
$this->add_control(
'show_days',
[
'label' => esc_html__( 'Days', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'label_on' => esc_html__( 'Show', 'elementor-pro' ),
'label_off' => esc_html__( 'Hide', 'elementor-pro' ),
'default' => 'yes',
]
);
$this->add_control(
'show_hours',
[
'label' => esc_html__( 'Hours', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'label_on' => esc_html__( 'Show', 'elementor-pro' ),
'label_off' => esc_html__( 'Hide', 'elementor-pro' ),
'default' => 'yes',
]
);
$this->add_control(
'show_minutes',
[
'label' => esc_html__( 'Minutes', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'label_on' => esc_html__( 'Show', 'elementor-pro' ),
'label_off' => esc_html__( 'Hide', 'elementor-pro' ),
'default' => 'yes',
]
);
$this->add_control(
'show_seconds',
[
'label' => esc_html__( 'Seconds', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'label_on' => esc_html__( 'Show', 'elementor-pro' ),
'label_off' => esc_html__( 'Hide', 'elementor-pro' ),
'default' => 'yes',
]
);
$this->add_control(
'show_labels',
[
'label' => esc_html__( 'Show Label', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'label_on' => esc_html__( 'Show', 'elementor-pro' ),
'label_off' => esc_html__( 'Hide', 'elementor-pro' ),
'default' => 'yes',
'separator' => 'before',
]
);
$this->add_control(
'custom_labels',
[
'label' => esc_html__( 'Custom Label', 'elementor-pro' ),
'type' => Controls_Manager::SWITCHER,
'condition' => [
'show_labels!' => '',
],
]
);
$this->add_control(
'label_days',
[
'label' => esc_html__( 'Days', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'default' => esc_html__( 'Days', 'elementor-pro' ),
'placeholder' => esc_html__( 'Days', 'elementor-pro' ),
'condition' => [
'show_labels!' => '',
'custom_labels!' => '',
'show_days' => 'yes',
],
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
]
);
$this->add_control(
'label_hours',
[
'label' => esc_html__( 'Hours', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'default' => esc_html__( 'Hours', 'elementor-pro' ),
'placeholder' => esc_html__( 'Hours', 'elementor-pro' ),
'condition' => [
'show_labels!' => '',
'custom_labels!' => '',
'show_hours' => 'yes',
],
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
]
);
$this->add_control(
'label_minutes',
[
'label' => esc_html__( 'Minutes', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'default' => esc_html__( 'Minutes', 'elementor-pro' ),
'placeholder' => esc_html__( 'Minutes', 'elementor-pro' ),
'condition' => [
'show_labels!' => '',
'custom_labels!' => '',
'show_minutes' => 'yes',
],
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
]
);
$this->add_control(
'label_seconds',
[
'label' => esc_html__( 'Seconds', 'elementor-pro' ),
'type' => Controls_Manager::TEXT,
'default' => esc_html__( 'Seconds', 'elementor-pro' ),
'placeholder' => esc_html__( 'Seconds', 'elementor-pro' ),
'condition' => [
'show_labels!' => '',
'custom_labels!' => '',
'show_seconds' => 'yes',
],
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
]
);
$this->add_control(
'expire_actions',
[
'label' => esc_html__( 'Actions After Expire', 'elementor-pro' ),
'type' => Controls_Manager::SELECT2,
'options' => [
'redirect' => esc_html__( 'Redirect', 'elementor-pro' ),
'hide' => esc_html__( 'Hide', 'elementor-pro' ),
'message' => esc_html__( 'Show Message', 'elementor-pro' ),
],
'label_block' => true,
'separator' => 'before',
'render_type' => 'none',
'multiple' => true,
]
);
$this->add_control(
'message_after_expire',
[
'label' => esc_html__( 'Message', 'elementor-pro' ),
'type' => Controls_Manager::TEXTAREA,
'separator' => 'before',
'dynamic' => [
'active' => true,
],
'condition' => [
'expire_actions' => 'message',
],
]
);
$this->add_control(
'expire_redirect_url',
[
'label' => esc_html__( 'Redirect URL', 'elementor-pro' ),
'type' => Controls_Manager::URL,
'separator' => 'before',
'options' => false,
'dynamic' => [
'active' => true,
],
'condition' => [
'expire_actions' => 'redirect',
],
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_countdown_style',
[
'label' => esc_html__( 'Countdown', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'heading_container',
[
'label' => esc_html__( 'Container', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
]
);
$this->add_control(
'label_display',
[
'label' => esc_html__( 'Layout', 'elementor-pro' ),
'type' => Controls_Manager::CHOOSE,
'options' => [
'block' => [
'title' => esc_html__( 'Block', 'elementor-pro' ),
'icon' => 'eicon-grow',
],
'inline' => [
'title' => esc_html__( 'Inline', 'elementor-pro' ),
'icon' => 'eicon-shrink',
],
],
'default' => 'block',
'prefix_class' => 'elementor-countdown--label-',
]
);
$this->add_responsive_control(
'container_width',
[
'label' => esc_html__( 'Container Width', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'default' => [
'unit' => '%',
'size' => 100,
],
'tablet_default' => [
'unit' => '%',
],
'mobile_default' => [
'unit' => '%',
],
'range' => [
'px' => [
'max' => 2000,
],
'em' => [
'max' => 200,
],
'rem' => [
'max' => 200,
],
],
'selectors' => [
'{{WRAPPER}} .elementor-countdown-wrapper' => 'max-width: {{SIZE}}{{UNIT}};',
],
'condition' => [
'label_display' => 'block',
],
]
);
$this->add_control(
'boxes_alignment',
[
'label' => esc_html__( 'Alignment', 'elementor-pro' ),
'type' => Controls_Manager::CHOOSE,
'options' => [
'start' => [
'title' => esc_html__( 'Start', 'elementor-pro' ),
'icon' => 'eicon-text-align-left',
],
'center' => [
'title' => esc_html__( 'Center', 'elementor-pro' ),
'icon' => 'eicon-text-align-center',
],
'end' => [
'title' => esc_html__( 'End', 'elementor-pro' ),
'icon' => 'eicon-text-align-right',
],
],
'classes' => 'elementor-control-start-end',
'selectors' => [
'{{WRAPPER}} .elementor-countdown-wrapper' => 'text-align: {{VALUE}};',
],
'condition' => [
'label_display' => 'inline',
],
]
);
$this->add_responsive_control(
'box_spacing',
[
'label' => esc_html__( 'Space Between', 'elementor-pro' ),
'type' => Controls_Manager::SLIDER,
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
'default' => [
'size' => 10,
],
'range' => [
'px' => [
'max' => 100,
],
'em' => [
'max' => 10,
],
'rem' => [
'max' => 10,
],
],
'selectors' => [
'body:not(.rtl) {{WRAPPER}} .elementor-countdown-item:not(:first-of-type)' => 'margin-left: calc( {{SIZE}}{{UNIT}}/2 );',
'body:not(.rtl) {{WRAPPER}} .elementor-countdown-item:not(:last-of-type)' => 'margin-right: calc( {{SIZE}}{{UNIT}}/2 );',
'body.rtl {{WRAPPER}} .elementor-countdown-item:not(:first-of-type)' => 'margin-right: calc( {{SIZE}}{{UNIT}}/2 );',
'body.rtl {{WRAPPER}} .elementor-countdown-item:not(:last-of-type)' => 'margin-left: calc( {{SIZE}}{{UNIT}}/2 );',
],
]
);
$this->add_control(
'heading_boxes',
[
'label' => esc_html__( 'Boxes', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
'separator' => 'before',
]
);
$this->add_responsive_control(
'box_padding',
[
'label' => esc_html__( 'Padding', 'elementor-pro' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'selectors' => [
'{{WRAPPER}} .elementor-countdown-item' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->add_control(
'box_background_color',
[
'label' => esc_html__( 'Background Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'global' => [
'default' => Global_Colors::COLOR_PRIMARY,
],
'selectors' => [
'{{WRAPPER}} .elementor-countdown-item' => 'background-color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => 'box_border',
'selector' => '{{WRAPPER}} .elementor-countdown-item',
]
);
$this->add_control(
'box_border_radius',
[
'label' => esc_html__( 'Border Radius', 'elementor-pro' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
'selectors' => [
'{{WRAPPER}} .elementor-countdown-item' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => 'box_shadow',
'selector' => '{{WRAPPER}} .elementor-countdown-item',
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_content_style',
[
'label' => esc_html__( 'Content', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'heading_digits',
[
'label' => esc_html__( 'Digits', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
]
);
$this->add_control(
'digits_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-countdown-digits' => 'color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'digits_typography',
'selector' => '{{WRAPPER}} .elementor-countdown-digits',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_TEXT,
],
]
);
$this->add_group_control(
Group_Control_Text_Shadow::get_type(),
[
'name' => 'digits_text_shadow',
'selector' => '{{WRAPPER}} .elementor-countdown-digits',
]
);
$this->add_control(
'heading_label',
[
'label' => esc_html__( 'Label', 'elementor-pro' ),
'type' => Controls_Manager::HEADING,
'separator' => 'before',
'condition' => [
'show_labels!' => '',
],
]
);
$this->add_control(
'label_color',
[
'label' => esc_html__( 'Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .elementor-countdown-label' => 'color: {{VALUE}};',
],
'condition' => [
'show_labels!' => '',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'label_typography',
'selector' => '{{WRAPPER}} .elementor-countdown-label',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_SECONDARY,
],
'condition' => [
'show_labels!' => '',
],
]
);
$this->add_group_control(
Group_Control_Text_Shadow::get_type(),
[
'name' => 'label_text_shadow',
'selector' => '{{WRAPPER}} .elementor-countdown-label',
'condition' => [
'show_labels!' => '',
],
]
);
$this->add_group_control(
Group_Control_Text_Stroke::get_type(),
[
'name' => 'text_stroke',
'selector' => '{{WRAPPER}} .elementor-countdown-label',
'condition' => [
'show_labels!' => '',
],
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_expire_message_style',
[
'label' => esc_html__( 'Message', 'elementor-pro' ),
'tab' => Controls_Manager::TAB_STYLE,
'condition' => [
'expire_actions' => 'message',
],
]
);
$this->add_responsive_control(
'align',
[
'label' => esc_html__( 'Alignment', 'elementor-pro' ),
'type' => Controls_Manager::CHOOSE,
'options' => [
'start' => [
'title' => esc_html__( 'Start', 'elementor-pro' ),
'icon' => 'eicon-text-align-left',
],
'center' => [
'title' => esc_html__( 'Center', 'elementor-pro' ),
'icon' => 'eicon-text-align-center',
],
'end' => [
'title' => esc_html__( 'End', 'elementor-pro' ),
'icon' => 'eicon-text-align-right',
],
],
'classes' => 'elementor-control-start-end',
'selectors_dictionary' => [
'left' => is_rtl() ? 'end' : 'start',
'right' => is_rtl() ? 'start' : 'end',
],
'selectors' => [
'{{WRAPPER}} .elementor-countdown-expire--message' => 'text-align: {{VALUE}};',
],
]
);
$this->add_control(
'text_color',
[
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
'{{WRAPPER}} .elementor-countdown-expire--message' => 'color: {{VALUE}};',
],
'global' => [
'default' => Global_Colors::COLOR_TEXT,
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'typography',
'global' => [
'default' => Global_Typography::TYPOGRAPHY_TEXT,
],
'selector' => '{{WRAPPER}} .elementor-countdown-expire--message',
]
);
$this->add_group_control(
Group_Control_Text_Shadow::get_type(),
[
'name' => 'message_text_shadow',
'selector' => '{{WRAPPER}} .elementor-countdown-expire--message',
]
);
$this->add_responsive_control(
'message_padding',
[
'label' => esc_html__( 'Padding', 'elementor-pro' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
'selectors' => [
'{{WRAPPER}} .elementor-countdown-expire--message' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->end_controls_section();
}
private function get_strftime( $instance ) {
$string = '';
if ( $instance['show_days'] ) {
$string .= $this->render_countdown_item( $instance, 'label_days', 'elementor-countdown-days' );
}
if ( $instance['show_hours'] ) {
$string .= $this->render_countdown_item( $instance, 'label_hours', 'elementor-countdown-hours' );
}
if ( $instance['show_minutes'] ) {
$string .= $this->render_countdown_item( $instance, 'label_minutes', 'elementor-countdown-minutes' );
}
if ( $instance['show_seconds'] ) {
$string .= $this->render_countdown_item( $instance, 'label_seconds', 'elementor-countdown-seconds' );
}
return $string;
}
private $_default_countdown_labels;
private function init_default_countdown_labels() {
$this->_default_countdown_labels = [
'label_months' => esc_html__( 'Months', 'elementor-pro' ),
'label_weeks' => esc_html__( 'Weeks', 'elementor-pro' ),
'label_days' => esc_html__( 'Days', 'elementor-pro' ),
'label_hours' => esc_html__( 'Hours', 'elementor-pro' ),
'label_minutes' => esc_html__( 'Minutes', 'elementor-pro' ),
'label_seconds' => esc_html__( 'Seconds', 'elementor-pro' ),
];
}
public function get_default_countdown_labels() {
if ( ! $this->_default_countdown_labels ) {
$this->init_default_countdown_labels();
}
return $this->_default_countdown_labels;
}
private function render_countdown_item( $instance, $label, $part_class ) {
$string = '<div class="elementor-countdown-item"><span class="elementor-countdown-digits ' . $part_class . '"></span>';
if ( $instance['show_labels'] ) {
$default_labels = $this->get_default_countdown_labels();
$label = ( $instance['custom_labels'] ) ? $instance[ $label ] : $default_labels[ $label ];
$string .= ' <span class="elementor-countdown-label">' . $label . '</span>';
}
$string .= '</div>';
return $string;
}
private function get_evergreen_interval( $instance ) {
$hours = empty( $instance['evergreen_counter_hours'] ) ? 0 : ( $instance['evergreen_counter_hours'] * HOUR_IN_SECONDS );
$minutes = empty( $instance['evergreen_counter_minutes'] ) ? 0 : ( $instance['evergreen_counter_minutes'] * MINUTE_IN_SECONDS );
$evergreen_interval = $hours + $minutes;
return $evergreen_interval;
}
private function get_actions( $settings ) {
if ( empty( $settings['expire_actions'] ) || ! is_array( $settings['expire_actions'] ) ) {
return false;
}
$actions = [];
foreach ( $settings['expire_actions'] as $action ) {
$action_to_run = [ 'type' => $action ];
if ( 'redirect' === $action ) {
if ( empty( $settings['expire_redirect_url']['url'] ) ) {
continue;
}
$action_to_run['redirect_url'] = esc_url( $settings['expire_redirect_url']['url'] );
}
$actions[] = $action_to_run;
}
return $actions;
}
private function is_valid_url( $url ) {
return ! preg_match( '/\bjavascript\b/i', $url ) && filter_var( $url, FILTER_VALIDATE_URL );
}
private function sanitize_action( $key, $value ) {
if ( 'redirect_url' === $key && is_string( $value ) ) {
return $this->is_valid_url( $value ) ? esc_url( $value ) : null;
}
return esc_html( $value );
}
private function map_sanitized_action( $action ) {
$sanitized_action = [];
foreach ( $action as $key => $value ) {
$sanitized_action[ $key ] = $this->sanitize_action( $key, $value );
}
return $sanitized_action;
}
private function sanitize_redirect_url( $actions ) {
return array_map( function ( $action ) {
return $this->map_sanitized_action( $action );
}, $actions );
}
protected function render() {
$instance = $this->get_settings_for_display();
$due_date = $instance['due_date'];
$string = $this->get_strftime( $instance );
if ( 'evergreen' === $instance['countdown_type'] ) {
$this->add_render_attribute( 'div', 'data-evergreen-interval', $this->get_evergreen_interval( $instance ) );
} else {
$wp_timezone = new \DateTimeZone( wp_timezone_string() );
$due_date = new \DateTime( $due_date, $wp_timezone );
$due_date = $due_date->getTimestamp();
}
$actions = false;
if ( ! Plugin::elementor()->editor->is_edit_mode() ) {
$actions = $this->get_actions( $instance );
}
if ( $actions ) {
$sanitized_actions = $this->sanitize_redirect_url( $actions );
$this->add_render_attribute( 'div', 'data-expire-actions', wp_json_encode( $sanitized_actions ) );
}
$this->add_render_attribute( 'div', [
'class' => 'elementor-countdown-wrapper',
'data-date' => $due_date,
] );
?>
<div <?php $this->print_render_attribute_string( 'div' ); ?>>
<?php echo wp_kses_post( $string ); ?>
</div>
<?php
if ( $actions && is_array( $actions ) ) {
foreach ( $actions as $action ) {
if ( 'message' !== $action['type'] ) {
continue;
} ?>
<div class="elementor-countdown-expire--message">
<?php echo esc_html( $instance['message_after_expire'] ); ?>
</div>
<?php
}
}
}
}

View File

@@ -0,0 +1,185 @@
<?php
namespace ElementorPro\Modules\CustomAttributes;
use Elementor\Controls_Stack;
use Elementor\Controls_Manager;
use Elementor\Element_Base;
use Elementor\Utils;
use ElementorPro\Base\Module_Base;
use ElementorPro\License\API;
use ElementorPro\Modules\Tiers\Module as Tiers;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
const LICENSE_FEATURE_NAME = 'custom-attributes';
public function __construct() {
parent::__construct();
$this->add_actions();
}
public function get_name() {
return 'custom-attributes';
}
private function get_black_list_attributes() {
static $black_list = null;
if ( null === $black_list ) {
$black_list = [ 'id', 'class', 'data-id', 'data-settings', 'data-element_type', 'data-widget_type', 'data-model-cid' ];
/**
* Elementor attributes black list.
*
* Filters the attributes that won't be rendered in the wrapper element.
*
* By default Elementor doesn't render some attributes to prevent things
* from breaking down. This hook allows developers to alter this list of
* attributes.
*
* @since 2.2.0
*
* @param array $black_list A black list of attributes.
*/
$black_list = apply_filters( 'elementor_pro/element/attributes/black_list', $black_list );
}
return $black_list;
}
/**
* @param Element_Base $element
*/
public function replace_go_pro_custom_attributes_controls( Element_Base $element ) {
$old_section = Plugin::elementor()->controls_manager->get_control_from_stack(
$element->get_unique_name(),
'section_custom_attributes_pro'
);
Plugin::elementor()->controls_manager->remove_control_from_stack( $element->get_unique_name(), [ 'section_custom_attributes_pro', 'custom_attributes_pro' ] );
$this->register_custom_attributes_controls( $element, $old_section['tab'] );
}
public function register_custom_attributes_controls( Element_Base $element, $tab ) {
$element_name = $element->get_name();
$element->start_controls_section(
'_section_attributes',
[
'label' => esc_html__( 'Attributes', 'elementor-pro' ),
'tab' => $tab,
]
);
$element->add_control(
'_attributes',
[
'label' => esc_html__( 'Custom Attributes', 'elementor-pro' ),
'type' => Controls_Manager::TEXTAREA,
'dynamic' => [
'active' => true,
],
'ai' => [
'active' => false,
],
'placeholder' => esc_html__( 'key|value', 'elementor-pro' ),
'description' => sprintf(
/* translators: %s: The `|` separate char. */
esc_html__( 'Set custom attributes for the wrapper element. Each attribute in a separate line. Separate attribute key from the value using %s character.', 'elementor-pro' ),
'<code>|</code>'
),
'classes' => 'elementor-control-direction-ltr',
]
);
$element->end_controls_section();
}
/**
* @param $element Controls_Stack
* @param $section_id string
*/
public function register_controls( Controls_Stack $element, $section_id ) {
if ( ! $element instanceof Element_Base ) {
return;
}
// Remove Custom CSS Banner (From free version)
if ( 'section_custom_attributes_pro' !== $section_id ) {
return;
}
if ( ! API::is_licence_has_feature( self::LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) {
$this->replace_controls_with_upgrade_promotion( $element );
return;
}
$this->replace_go_pro_custom_attributes_controls( $element );
}
/**
* @param $element Element_Base
*/
public function render_attributes( Element_Base $element ) {
$settings = $element->get_settings_for_display();
if ( ! empty( $settings['_attributes'] ) ) {
$attributes = Utils::parse_custom_attributes( $settings['_attributes'], "\n" );
$black_list = $this->get_black_list_attributes();
foreach ( $attributes as $attribute => $value ) {
if ( ! in_array( $attribute, $black_list, true ) ) {
$element->add_render_attribute( '_wrapper', $attribute, $value );
}
}
}
}
protected function add_actions() {
add_action( 'elementor/element/after_section_end', [ $this, 'register_controls' ], 10, 2 );
if ( API::is_licence_has_feature( static::LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) {
add_action( 'elementor/element/after_add_attributes', [ $this, 'render_attributes' ] );
}
}
private function replace_controls_with_upgrade_promotion( Element_Base $element ) {
$old_section = Plugin::elementor()->controls_manager->get_control_from_stack(
$element->get_unique_name(),
'section_custom_attributes_pro'
);
Plugin::elementor()->controls_manager->remove_control_from_stack( $element->get_unique_name(), [ 'section_custom_attributes_pro', 'section_custom_attributes_pro' ] );
$element->start_controls_section(
'section_custom_attributes_promotion',
[
'label' => esc_html__( 'Attributes', 'elementor-pro' ),
'tab' => $old_section['tab'],
]
);
$element->add_control(
'custom_attributes_promotion',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => Tiers::get_promotion_template( [
'title' => esc_html__( 'Meet Our Attributes', 'elementor-pro' ),
'messages' => [
esc_html__( 'Add custom HTML attributes to any element.', 'elementor-pro' ),
],
'link' => 'https://go.elementor.com/go-pro-advanced-attributes/',
] ),
]
);
$element->end_controls_section();
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace ElementorPro\Modules\CustomCode\AdminMenuItems;
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item;
use Elementor\Settings;
use ElementorPro\Modules\CustomCode\Module as CustomCodeModule;
use ElementorPro\License\API;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Custom_Code_Menu_Item implements Admin_Menu_Item {
const LICENSE_FEATURE_NAME = 'custom_code';
public function get_capability(): string {
return CustomCodeModule::CAPABILITY;
}
public function get_label(): string {
return esc_html__( 'Custom Code', 'elementor-pro' );
}
public function get_parent_slug(): string {
return Settings::PAGE_ID;
}
public function get_position(): ?int {
return null;
}
public function is_visible(): bool {
return API::is_licence_has_feature( static::LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK );
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace ElementorPro\Modules\CustomCode\AdminMenuItems;
use ElementorPro\License\API;
use ElementorPro\Modules\Tiers\AdminMenuItems\Base_Promotion_Template;
use ElementorPro\Plugin;
use ElementorPro\Modules\CustomCode\Module as Custom_Code_Module;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Custom_Code_Promotion_Menu_Item extends Base_Promotion_Template {
public function get_name(): string {
return 'custom-code-promotion';
}
public function get_cta_url(): string {
if ( ! API::active_licence_has_feature( Custom_Code_Module::MODULE_NAME ) ) {
$upgrade_url = 'https://go.elementor.com/go-pro-advanced-custom-code/';
return $upgrade_url;
}
$connect_url = Plugin::instance()->license_admin->get_connect_url( [
'utm_source' => 'custom-code',
'utm_medium' => 'wp-dash',
'utm_campaign' => 'connect-and-activate-license',
] );
$renew_url = 'https://go.elementor.com/renew-custom-code/';
return API::is_license_expired()
? $renew_url
: $connect_url;
}
public function get_cta_text(): string {
if ( ! API::active_licence_has_feature( Custom_Code_Module::MODULE_NAME ) ) {
return esc_html__( 'Upgrade Now', 'elementor-pro' );
}
return API::is_license_expired()
? esc_html__( 'Renew now', 'elementor-pro' )
: esc_html__( 'Connect & Activate', 'elementor-pro' );
}
public function get_label(): string {
return $this->get_page_title();
}
public function get_page_title(): string {
return esc_html__( 'Custom Code', 'elementor-pro' );
}
public function get_promotion_title(): string {
return sprintf( esc_html__( 'Enjoy Creative Freedom %s with Custom Code', 'elementor-pro' ), '<br />' );
}
public function get_video_url(): string {
return 'https://www.youtube-nocookie.com/embed/IOovQd1hJUg?si=JLHk3UAexnvTfU1a';
}
public function get_promotion_description(): string {
return esc_html__(
'Add Custom Code snippets to your website.',
'elementor-pro'
);
}
public function get_side_note(): string {
return esc_html__( '* Requires an Advanced subscription or higher', 'elementor-pro' );
}
/**
* @deprecated use get_promotion_description instead
* @return void
*/
public function render_promotion_description() {
echo $this->get_promotion_description(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
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 pages performance*', 'elementor-pro' ),
esc_html__( 'Use Custom Code to create sophisticated custom interactions to engage visitors', 'elementor-pro' ),
esc_html__( 'Leverage Elementor AI to instantly generate Custom Code for Elementor', 'elementor-pro' ),
];
}
}

View File

@@ -0,0 +1,426 @@
<?php
namespace ElementorPro\Modules\CustomCode;
use Elementor\Utils;
use ElementorPro\Modules\AssetsManager\Classes\Assets_Base;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Custom_Code_Metabox extends Assets_Base {
const FIELD_LOCATION = 'location';
const FIELD_PRIORITY = 'priority';
const FILED_EXTRA_OPTIONS = 'extra_options';
const FIELD_CODE = 'code';
const OPTION_LOCATION_HEAD = 'elementor_head';
const OPTION_LOCATION_BODY_START = 'elementor_body_start';
const OPTION_LOCATION_BODY_END = 'elementor_body_end';
const OPTION_PRIORITY_LENGTH = 10;
const DEFAULT_PRIORITY = 1;
const DEFAULT_LOCATION = self::OPTION_LOCATION_HEAD;
const INPUT_OPTION_ENSURE_JQUERY = 'ensure_jquery';
const INPUT_FIELDS = [
self::FIELD_LOCATION,
self::FIELD_PRIORITY,
self::FIELD_CODE,
self::FILED_EXTRA_OPTIONS,
];
const INPUT_OPTIONS = [
self::INPUT_OPTION_ENSURE_JQUERY,
];
public function get_name() {
return Module::MODULE_NAME;
}
public function get_type() {
return Module::CPT;
}
public function get_field_label( $field ) {
$label = parent::get_field_label( $field );
if ( ! empty( $field['info'] ) ) {
$label = '<p class="elementor-field-label"><i data-info="' . $field['info'] . '" class="eicon-info-circle"></i>' . $label . '</p>';
}
return $label;
}
public function get_location_labels() {
return [
self::OPTION_LOCATION_HEAD => esc_html__( 'Head', 'elementor-pro' ),
self::OPTION_LOCATION_BODY_START => esc_html__( 'Body Start', 'elementor-pro' ),
self::OPTION_LOCATION_BODY_END => esc_html__( 'Body End', 'elementor-pro' ),
];
}
public function get_location_options() {
return [
self::OPTION_LOCATION_HEAD => '<head>',
self::OPTION_LOCATION_BODY_START => sprintf(
/* translators: %s: Body opening tag. */
esc_html__( '%s - Start', 'elementor-pro' ),
'<body>'
),
self::OPTION_LOCATION_BODY_END => sprintf(
/* translators: %s: Body closing tag. */
esc_html__( '%s - End', 'elementor-pro' ),
'</body>'
),
];
}
public function get_priority_options() {
$start = 1;
$result = range( $start, self::OPTION_PRIORITY_LENGTH );
$result = array_combine( $result, $result );
return $result;
}
/**
* Add script integrity.
*
* This is method is public, since its has to remove its own filter.
*
* @param string $html
* @param mixed $handle
*
* @return string
*/
public function add_script_integrity( $html, $handle ) {
if ( 'jshint' === $handle ) {
$html = str_replace( '></script>', ' integrity="sha512-qcoitUjhkmNyPmbIOlUV/zd8MJvrVcKrNqnveMWS3C6MYOl5+HLwliRKUm/Ae/dfIok6+E54hjgVrAeS+sBAGA==" crossorigin="anonymous"></script>', $html );
remove_filter( 'script_loader_tag', [ $this, 'add_script_integrity' ] );
}
return $html;
}
protected function actions() {
add_action( 'add_meta_boxes_' . Module::CPT, function () {
$this->add_meta_boxes();
} );
add_action( 'save_post_' . Module::CPT, function( $post_id, $post, $update ) {
return $this->save_post_meta( $post_id, $post );
}, 10, 3 );
add_action('post_submitbox_misc_actions', function ( $post ) {
$this->add_meta_publish_options( $post );
} );
}
private function get_fields() {
return [
[
'id' => 'open-div-meta-box',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'elementor-custom-code-meta-box',
],
],
[
'id' => 'open-div-panel',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'elementor-custom-code-panel',
],
],
[
'id' => 'open-div-placement',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'elementor-custom-code-panel-placement',
],
],
[
'id' => self::FIELD_LOCATION,
'field_type' => 'select',
'label' => esc_html__( 'Location', 'elementor-pro' ) . ':',
'options' => $this->get_location_options(),
'info' => esc_html__( 'Define where the Custom Code will appear', 'elementor-pro' ),
],
[
'id' => 'open-div-placement',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'elementor-custom-code-options-placement',
],
],
[
'id' => self::FILED_EXTRA_OPTIONS,
'field_type' => 'checkbox',
'options' => [
self::INPUT_OPTION_ENSURE_JQUERY => esc_html__( 'Always load jQuery', 'elementor-pro' ),
],
'info' => esc_html__( 'If your snippet includes jQuery, this will ensure it will work for all visitors. It may have a minor impact on loading speed.', 'elementor-pro' ),
],
[
'id' => 'close-div-placement',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
],
[
'id' => self::FIELD_PRIORITY,
'field_type' => 'select',
'label' => esc_html__( 'Priority', 'elementor-pro' ) . ':',
'options' => $this->get_priority_options(),
'info' => esc_html__( 'Define in which order the Custom Code will appear', 'elementor-pro' ),
],
[
'id' => 'close-div-placement',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
],
[
'id' => 'close-div-panel',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
],
[
'id' => 'close-div-meta-box',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
],
[
'id' => 'open-div-code-mirror-holder',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'elementor-custom-code-codemirror-holder',
],
],
[
'id' => 'open-div-code-mirror',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'attributes' => [
'class' => 'elementor-custom-code-codemirror',
],
],
[
'id' => self::FIELD_CODE,
'field_type' => 'textarea',
'label' => '',
'extra_attributes' => [
'class' => 'hidden',
],
],
[
'id' => 'close-div-code-mirror',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
],
[
'id' => 'close-div-code-mirror-holder',
'field_type' => 'html_tag',
'label' => false,
'tag' => 'div',
'close' => true,
],
];
}
private function get_code_editor_settings() {
// TODO: Handle `enqueue_code_editor_scripts` to work with `lint => 'true'`.
return [
'type' => 'text/html',
'codemirror' => [
'indentUnit' => 2,
'tabSize' => 2,
'gutters' => [ 'CodeMirror-lint-markers' ],
],
];
}
private function enqueue_code_editor_scripts( $field_code_id ) {
// Add integrity attribute to jshint.
add_filter( 'script_loader_tag', [ $this, 'add_script_integrity' ], 10, 2 );
wp_enqueue_script( 'htmlhint' );
wp_enqueue_script( 'csslint' );
wp_deregister_script( 'jshint' );
wp_enqueue_script( 'jshint',
'https://cdnjs.cloudflare.com/ajax/libs/jshint/2.12.0/jshint.min.js',
[],
'2.12.0'
);
/**
* Some of the plugins may load 'code-editor' for their needs and change the default behavior, so it should
* re-initialize the code editor with 'custom code' settings.
*/
if ( wp_script_is( 'code-editor' ) ) {
wp_add_inline_script( 'custom-code-metabox', sprintf( 'wp.codeEditor.initialize( jQuery( "#%s"), %s );', $field_code_id, wp_json_encode( wp_get_code_editor_settings( $this->get_code_editor_settings() ) ) ) );
} else {
wp_enqueue_code_editor( $this->get_code_editor_settings() );
wp_add_inline_script( 'code-editor', sprintf( 'wp.codeEditor.initialize( jQuery( "#%s") );', $field_code_id ) );
}
}
private function render_meta_box() {
$fields = $this->get_fields();
if ( ! empty( $_REQUEST['action'] ) && 'edit' == $_REQUEST['action'] ) {
$post = get_post( \ElementorPro\Core\Utils::_unstable_get_super_global_value( $_REQUEST, 'post' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
foreach ( self::INPUT_FIELDS as $input_field ) {
$field_meta = get_post_meta( $post->ID, "_elementor_$input_field", true );
if ( ! empty( $field_meta ) ) {
$key = array_search( $input_field, array_column( $fields, 'id' ) );
if ( false !== $key ) {
$fields[ $key ]['saved'] = $field_meta;
}
}
}
}
// The method, support fields only.
$this->print_metabox( $fields );
/**
* Elementor metabox render.
*
* Fires before custom scripts are enqueued, since enqueue depends on
* render handlers.
*
* @param Custom_Code_Metabox $this An instance of custom code metabox.
* @param int|false $id The ID of the current WordPress post.
* False if post is not set.
*/
do_action( 'elementor-pro/metabox/render', $this, get_the_ID() );
// Init codemirror.
$this->enqueue_code_editor_scripts( self::FIELD_CODE );
}
private function save_post_meta( $post_id, $post ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
// Check the user's permissions.
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
if ( get_post_status( $post->ID ) === 'auto-draft' ) {
return $post_id;
}
// PHPCS - Should not validate for nonce (already done in WordPress save_post).
$post_data = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing
foreach ( self::INPUT_FIELDS as $field ) {
if ( isset( $post_data[ $field ] ) && ! Utils::is_empty( $post_data[ $field ] ) ) {
if ( self::FIELD_CODE === $field ) {
$post_meta = $post_data[ $field ];
} else {
$post_meta = sanitize_text_field( $post_data[ $field ] );
}
if ( ! current_user_can( 'unfiltered_html' ) ) {
$post_meta = wp_kses_post( $post_meta );
}
update_post_meta( $post->ID, "_elementor_$field", $post_meta );
/** @var \ElementorPro\Modules\ThemeBuilder\Module $theme_builder */
$theme_builder = Plugin::instance()->modules_manager->get_modules( 'theme-builder' );
$theme_builder->get_conditions_manager()->get_cache()->regenerate();
} elseif ( self::FILED_EXTRA_OPTIONS === $field ) {
$input_options = [];
foreach ( self::INPUT_OPTIONS as $input_option ) {
$key = self::FILED_EXTRA_OPTIONS . '_' . $input_option;
$input_option_value = \ElementorPro\Core\Utils::_unstable_get_super_global_value( $post_data, $key );
if ( 'on' === $input_option_value ) {
$input_options [] = $input_option;
}
}
update_post_meta( $post->ID, "_elementor_$field", $input_options );
}
}
// Temporary workaround for applying conditions for draft custom code post.
if ( ! empty( $post_data['_conditions'] ) ) {
$conditions = (array) json_decode( wp_unslash( $post_data['_conditions'] ) );
foreach ( $conditions as $key => $item ) {
$item_assoc_array = (array) $item;
$conditions[ $key ] = [
$item_assoc_array['type'],
$item_assoc_array['name'],
$item_assoc_array['sub'],
$item_assoc_array['subId'],
];
}
/** @var \ElementorPro\Modules\ThemeBuilder\Module $theme_builder */
$theme_builder = Plugin::instance()->modules_manager->get_modules( 'theme-builder' );
$theme_builder->get_conditions_manager()->save_conditions( $post_id, $conditions );
}
}
private function add_meta_boxes() {
add_meta_box(
'elementor-custom-code',
__( 'Custom code', 'elementor-pro' ),
function() {
$this->render_meta_box();
},
module::CPT,
'normal',
'default'
);
}
private function add_meta_publish_options( $post ) {
if ( Module::CPT === $post->post_type ) {
?>
<div class="misc-pub-section misc-pub-post-conditions">
<i class="dashicons dashicons-networking" aria-hidden="true"></i>
<?php echo esc_html__( 'Conditions:', 'elementor-pro' ); ?>
<span class="post-conditions"></span>
</div>
<?php
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace ElementorPro\Modules\CustomCode;
use ElementorPro\Modules\ThemeBuilder\Documents\Theme_Document;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Document extends Theme_Document {
private static $applied_options = [];
public static function get_properties() {
$properties = parent::get_properties();
$properties['cpt'] = [ Module::CPT ];
$properties['admin_tab_group'] = '';
$properties['show_in_library'] = false;
$properties['support_site_editor'] = false;
return $properties;
}
public static function get_title() {
return esc_html__( 'Custom Code', 'elementor-pro' );
}
public static function get_type() {
return Module::DOCUMENT_TYPE;
}
public function get_name() {
return Module::DOCUMENT_TYPE;
}
public function print_content() {
$content = get_post_meta( $this->post->ID, '_elementor_' . Custom_Code_Metabox::FIELD_CODE, true ) . PHP_EOL;
$user_has_permission = current_user_can( Module::CAPABILITY );
$this->apply_snippet_options( get_post_meta( $this->get_id(), '_elementor_' . Custom_Code_Metabox::FILED_EXTRA_OPTIONS, true ) );
if ( $user_has_permission ) {
$this->print_snippet_with_elementor_comment( $content );
} else {
// PHPCS - the main content of custom code
echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
public static function get_create_url() {
$query_args = [
'post_type' => Module::CPT,
];
return add_query_arg( $query_args, admin_url( 'post-new.php' ) );
}
private function print_snippet_with_elementor_comment( $content ) {
echo implode( PHP_EOL, [
'',
'<!--',
'Title: ' . esc_html( $this->post->post_title ),
'Type: ' . Module::CPT, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'Author: ' . esc_html( get_the_author_meta( 'display_name', $this->post->post_author ) ),
'Last edited: ' . esc_html( $this->post->post_modified ),
'--- The comment is visible only for administrators ---',
'-->',
'',
] );
// PHPCS - the main content of custom code
echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo PHP_EOL . '<!-- End of snippet -->' . PHP_EOL;
}
private function apply_snippet_options( $options ) {
if ( ! is_array( $options ) || ! count( $options ) ) {
return;
}
foreach ( $options as $option ) {
if ( ! empty( self::$applied_options[ $option ] ) ) {
continue;
}
switch ( $option ) {
case Custom_Code_Metabox::INPUT_OPTION_ENSURE_JQUERY:
wp_enqueue_script( 'jquery' );
// Ensure jQuery will be first in order.
if ( 'wp_footer' === current_filter() ) {
wp_print_footer_scripts();
}
break;
}
self::$applied_options[ $option ] = true;
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace ElementorPro\Modules\CustomCode\EditorOneMenuItems;
use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface;
use Elementor\Modules\EditorOne\Classes\Menu_Config;
use ElementorPro\Modules\CustomCode\AdminMenuItems\Custom_Code_Menu_Item;
use ElementorPro\Modules\CustomCode\Module;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Editor_One_Custom_Code_Menu_Item extends Custom_Code_Menu_Item implements Menu_Item_Interface {
public function get_position(): int {
return 30;
}
public function get_slug(): string {
return Module::MENU_SLUG;
}
public function get_parent_slug(): string {
return Menu_Config::ELEMENTOR_MENU_SLUG;
}
public function get_label(): string {
return esc_html__( 'Code', 'elementor-pro' );
}
public function get_group_id(): string {
return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace ElementorPro\Modules\CustomCode\EditorOneMenuItems;
use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface;
use Elementor\Modules\EditorOne\Classes\Menu_Config;
use ElementorPro\Modules\CustomCode\AdminMenuItems\Custom_Code_Promotion_Menu_Item;
use ElementorPro\Modules\CustomCode\Module;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Editor_One_Custom_Code_Promotion extends Custom_Code_Promotion_Menu_Item implements Menu_Item_Interface {
public function get_position(): int {
return 30;
}
public function get_slug(): string {
return Module::PROMOTION_MENU_SLUG;
}
public function get_parent_slug(): string {
return Menu_Config::ELEMENTOR_MENU_SLUG;
}
public function get_label(): string {
return esc_html__( 'Code', 'elementor-pro' );
}
public function get_group_id(): string {
return Menu_Config::CUSTOM_ELEMENTS_GROUP_ID;
}
}

Some files were not shown because too many files have changed in this diff Show More