PluginProbe
Elementor Website Builder – more than just a page builder / 3.11.5
Elementor Website Builder – more than just a page builder v3.11.5
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / includes / base / element-base.php

element-base.php in Elementor Website Builder – more than just a page builder 3.11.5, at includes/base/element-base.php

1,487 lines 36.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor;
3
4 use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit; // Exit if accessed directly.
8 }
9
10 /**
11 * Elementor element base.
12 *
13 * An abstract class to register new Elementor elements. It extended the
14 * `Controls_Stack` class to inherit its properties.
15 *
16 * This abstract class must be extended in order to register new elements.
17 *
18 * @since 1.0.0
19 * @abstract
20 */
21 abstract class Element_Base extends Controls_Stack {
22
23 /**
24 * Child elements.
25 *
26 * Holds all the child elements of the element.
27 *
28 * @access private
29 *
30 * @var Element_Base[]
31 */
32 private $children;
33
34 /**
35 * Element default arguments.
36 *
37 * Holds all the default arguments of the element. Used to store additional
38 * data. For example WordPress widgets use this to store widget names.
39 *
40 * @access private
41 *
42 * @var array
43 */
44 private $default_args = [];
45
46 /**
47 * Is type instance.
48 *
49 * Whether the element is an instance of that type or not.
50 *
51 * @access private
52 *
53 * @var bool
54 */
55 private $is_type_instance = true;
56
57 /**
58 * Depended scripts.
59 *
60 * Holds all the element depended scripts to enqueue.
61 *
62 * @since 1.9.0
63 * @access private
64 *
65 * @var array
66 */
67 private $depended_scripts = [];
68
69 /**
70 * Depended styles.
71 *
72 * Holds all the element depended styles to enqueue.
73 *
74 * @since 1.9.0
75 * @access private
76 *
77 * @var array
78 */
79 private $depended_styles = [];
80
81 /**
82 * Add script depends.
83 *
84 * Register new script to enqueue by the handler.
85 *
86 * @since 1.9.0
87 * @access public
88 *
89 * @param string $handler Depend script handler.
90 */
91 public function add_script_depends( $handler ) {
92 $this->depended_scripts[] = $handler;
93 }
94
95 /**
96 * Add style depends.
97 *
98 * Register new style to enqueue by the handler.
99 *
100 * @since 1.9.0
101 * @access public
102 *
103 * @param string $handler Depend style handler.
104 */
105 public function add_style_depends( $handler ) {
106 $this->depended_styles[] = $handler;
107 }
108
109 /**
110 * Get script dependencies.
111 *
112 * Retrieve the list of script dependencies the element requires.
113 *
114 * @since 1.3.0
115 * @access public
116 *
117 * @return array Element scripts dependencies.
118 */
119 public function get_script_depends() {
120 return $this->depended_scripts;
121 }
122
123 /**
124 * Enqueue scripts.
125 *
126 * Registers all the scripts defined as element dependencies and enqueues
127 * them. Use `get_script_depends()` method to add custom script dependencies.
128 *
129 * @since 1.3.0
130 * @access public
131 */
132 final public function enqueue_scripts() {
133 $deprecated_scripts = [
134 //Insert here when you have a deprecated script
135 ];
136
137 foreach ( $this->get_script_depends() as $script ) {
138 if ( isset( $deprecated_scripts[ $script ] ) ) {
139 Utils::handle_deprecation( $script, $deprecated_scripts[ $script ]['version'], $deprecated_scripts[ $script ]['replacement'] );
140 }
141
142 wp_enqueue_script( $script );
143 }
144 }
145
146 /**
147 * Get style dependencies.
148 *
149 * Retrieve the list of style dependencies the element requires.
150 *
151 * @since 1.9.0
152 * @access public
153 *
154 * @return array Element styles dependencies.
155 */
156 public function get_style_depends() {
157 return $this->depended_styles;
158 }
159
160 /**
161 * Enqueue styles.
162 *
163 * Registers all the styles defined as element dependencies and enqueues
164 * them. Use `get_style_depends()` method to add custom style dependencies.
165 *
166 * @since 1.9.0
167 * @access public
168 */
169 final public function enqueue_styles() {
170 foreach ( $this->get_style_depends() as $style ) {
171 wp_enqueue_style( $style );
172 }
173 }
174
175 /**
176 * @since 1.0.0
177 * @deprecated 2.6.0
178 * @access public
179 * @static
180 */
181 final public static function add_edit_tool() {}
182
183 /**
184 * @since 2.2.0
185 * @deprecated 2.6.0
186 * @access public
187 * @static
188 */
189 final public static function is_edit_buttons_enabled() {
190 return get_option( 'elementor_edit_buttons' );
191 }
192
193 /**
194 * Get default child type.
195 *
196 * Retrieve the default child type based on element data.
197 *
198 * Note that not all elements support children.
199 *
200 * @since 1.0.0
201 * @access protected
202 * @abstract
203 *
204 * @param array $element_data Element data.
205 *
206 * @return Element_Base
207 */
208 abstract protected function _get_default_child_type( array $element_data );
209
210 /**
211 * Before element rendering.
212 *
213 * Used to add stuff before the element.
214 *
215 * @since 1.0.0
216 * @access public
217 */
218 public function before_render() {}
219
220 /**
221 * After element rendering.
222 *
223 * Used to add stuff after the element.
224 *
225 * @since 1.0.0
226 * @access public
227 */
228 public function after_render() {}
229
230 /**
231 * Get element title.
232 *
233 * Retrieve the element title.
234 *
235 * @since 1.0.0
236 * @access public
237 *
238 * @return string Element title.
239 */
240 public function get_title() {
241 return '';
242 }
243
244 /**
245 * Get element icon.
246 *
247 * Retrieve the element icon.
248 *
249 * @since 1.0.0
250 * @access public
251 *
252 * @return string Element icon.
253 */
254 public function get_icon() {
255 return 'eicon-columns';
256 }
257
258 public function get_help_url() {
259 return 'https://go.elementor.com/widget-' . $this->get_name();
260 }
261
262 public function get_custom_help_url() {
263 return '';
264 }
265
266 /**
267 * Whether the reload preview is required.
268 *
269 * Used to determine whether the reload preview is required or not.
270 *
271 * @since 1.0.0
272 * @access public
273 *
274 * @return bool Whether the reload preview is required.
275 */
276 public function is_reload_preview_required() {
277 return false;
278 }
279
280 /**
281 * @since 2.3.1
282 * @access protected
283 */
284 protected function should_print_empty() {
285 return true;
286 }
287
288 /**
289 * Get child elements.
290 *
291 * Retrieve all the child elements of this element.
292 *
293 * @since 1.0.0
294 * @access public
295 *
296 * @return Element_Base[] Child elements.
297 */
298 public function get_children() {
299 if ( null === $this->children ) {
300 $this->init_children();
301 }
302
303 return $this->children;
304 }
305
306 /**
307 * Get default arguments.
308 *
309 * Retrieve the element default arguments. Used to return all the default
310 * arguments or a specific default argument, if one is set.
311 *
312 * @since 1.0.0
313 * @access public
314 *
315 * @param array $item Optional. Default is null.
316 *
317 * @return array Default argument(s).
318 */
319 public function get_default_args( $item = null ) {
320 return self::get_items( $this->default_args, $item );
321 }
322
323 /**
324 * Add new child element.
325 *
326 * Register new child element to allow hierarchy.
327 *
328 * @since 1.0.0
329 * @access public
330 * @param array $child_data Child element data.
331 * @param array $child_args Child element arguments.
332 *
333 * @return Element_Base|false Child element instance, or false if failed.
334 */
335 public function add_child( array $child_data, array $child_args = [] ) {
336 if ( null === $this->children ) {
337 $this->init_children();
338 }
339
340 $child_type = $this->get_child_type( $child_data );
341
342 if ( ! $child_type ) {
343 return false;
344 }
345
346 $child = Plugin::$instance->elements_manager->create_element_instance( $child_data, $child_args, $child_type );
347
348 if ( $child ) {
349 $this->children[] = $child;
350 }
351
352 return $child;
353 }
354
355 /**
356 * Add link render attributes.
357 *
358 * Used to add link tag attributes to a specific HTML element.
359 *
360 * The HTML link tag is represented by the element parameter. The `url_control` parameter
361 * needs to be an array of link settings in the same format they are set by Elementor's URL control.
362 *
363 * Example usage:
364 *
365 * `$this->add_link_attributes( 'button', $settings['link'] );`
366 *
367 * @since 2.8.0
368 * @access public
369 *
370 * @param array|string $element The HTML element.
371 * @param array $url_control Array of link settings.
372 * @param bool $overwrite Optional. Whether to overwrite existing
373 * attribute. Default is false, not to overwrite.
374 *
375 * @return Element_Base Current instance of the element.
376 */
377
378 public function add_link_attributes( $element, array $url_control, $overwrite = false ) {
379 $attributes = [];
380
381 if ( ! empty( $url_control['url'] ) ) {
382 $allowed_protocols = array_merge( wp_allowed_protocols(), [ 'skype', 'viber' ] );
383
384 $attributes['href'] = esc_url( $url_control['url'], $allowed_protocols );
385 }
386
387 if ( ! empty( $url_control['is_external'] ) ) {
388 $attributes['target'] = '_blank';
389 }
390
391 if ( ! empty( $url_control['nofollow'] ) ) {
392 $attributes['rel'] = 'nofollow';
393 }
394
395 if ( ! empty( $url_control['custom_attributes'] ) ) {
396 // Custom URL attributes should come as a string of comma-delimited key|value pairs
397 $attributes = array_merge( $attributes, Utils::parse_custom_attributes( $url_control['custom_attributes'] ) );
398 }
399
400 if ( $attributes ) {
401 $this->add_render_attribute( $element, $attributes, null, $overwrite );
402 }
403
404 return $this;
405 }
406
407 /**
408 * Print element.
409 *
410 * Used to generate the element final HTML on the frontend and the editor.
411 *
412 * @since 1.0.0
413 * @access public
414 */
415 public function print_element() {
416 $element_type = $this->get_type();
417
418 /**
419 * Before frontend element render.
420 *
421 * Fires before Elementor element is rendered in the frontend.
422 *
423 * @since 2.2.0
424 *
425 * @param Element_Base $this The element.
426 */
427 do_action( 'elementor/frontend/before_render', $this );
428
429 /**
430 * Before frontend element render.
431 *
432 * Fires before Elementor element is rendered in the frontend.
433 *
434 * The dynamic portion of the hook name, `$element_type`, refers to the element type.
435 *
436 * @since 1.0.0
437 *
438 * @param Element_Base $this The element.
439 */
440 do_action( "elementor/frontend/{$element_type}/before_render", $this );
441
442 ob_start();
443
444 if ( $this->has_own_method( '_print_content', self::class ) ) {
445 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_print_content', '3.1.0', __CLASS__ . '::print_content()' );
446
447 $this->_print_content();
448 } else {
449 $this->print_content();
450 }
451
452 $content = ob_get_clean();
453
454 $should_render = ( ! empty( $content ) || $this->should_print_empty() );
455
456 /**
457 * Should the element be rendered for frontend
458 *
459 * Filters if the element should be rendered on frontend.
460 *
461 * @since 2.3.3
462 *
463 * @param bool true The element.
464 * @param Element_Base $this The element.
465 */
466 $should_render = apply_filters( "elementor/frontend/{$element_type}/should_render", $should_render, $this );
467
468 if ( $should_render ) {
469 if ( $this->has_own_method( '_add_render_attributes', self::class ) ) {
470 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_add_render_attributes', '3.1.0', __CLASS__ . '::add_render_attributes()' );
471
472 $this->_add_render_attributes();
473 } else {
474 $this->add_render_attributes();
475 }
476
477 $this->before_render();
478 // PHPCS - The content has already been escaped by the `render` method.
479 echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
480 $this->after_render();
481
482 $this->enqueue_scripts();
483 $this->enqueue_styles();
484 }
485
486 /**
487 * After frontend element render.
488 *
489 * Fires after Elementor element is rendered in the frontend.
490 *
491 * The dynamic portion of the hook name, `$element_type`, refers to the element type.
492 *
493 * @since 1.0.0
494 *
495 * @param Element_Base $this The element.
496 */
497 do_action( "elementor/frontend/{$element_type}/after_render", $this );
498
499 /**
500 * After frontend element render.
501 *
502 * Fires after Elementor element is rendered in the frontend.
503 *
504 * @since 2.3.0
505 *
506 * @param Element_Base $this The element.
507 */
508 do_action( 'elementor/frontend/after_render', $this );
509 }
510
511 /**
512 * Get the element raw data.
513 *
514 * Retrieve the raw element data, including the id, type, settings, child
515 * elements and whether it is an inner element.
516 *
517 * The data with the HTML used always to display the data, but the Elementor
518 * editor uses the raw data without the HTML in order not to render the data
519 * again.
520 *
521 * @since 1.0.0
522 * @access public
523 *
524 * @param bool $with_html_content Optional. Whether to return the data with
525 * HTML content or without. Used for caching.
526 * Default is false, without HTML.
527 *
528 * @return array Element raw data.
529 */
530 public function get_raw_data( $with_html_content = false ) {
531 $data = $this->get_data();
532
533 $elements = [];
534
535 foreach ( $this->get_children() as $child ) {
536 $elements[] = $child->get_raw_data( $with_html_content );
537 }
538
539 $raw_data = [
540 'id' => $this->get_id(),
541 'elType' => $data['elType'],
542 'settings' => $data['settings'],
543 'elements' => $elements,
544 'isInner' => $data['isInner'],
545 ];
546
547 if ( ! empty( $data['isLocked'] ) ) {
548 $raw_data['isLocked'] = $data['isLocked'];
549 }
550
551 return $raw_data;
552 }
553
554 public function get_data_for_save() {
555 $data = $this->get_raw_data();
556
557 $elements = [];
558
559 foreach ( $this->get_children() as $child ) {
560 $elements[] = $child->get_data_for_save();
561 }
562
563 if ( ! empty( $elements ) ) {
564 $data['elements'] = $elements;
565 }
566
567 if ( ! empty( $data['settings'] ) ) {
568 $data['settings'] = $this->on_save( $data['settings'] );
569 }
570
571 return $data;
572 }
573
574 /**
575 * Get unique selector.
576 *
577 * Retrieve the unique selector of the element. Used to set a unique HTML
578 * class for each HTML element. This way Elementor can set custom styles for
579 * each element.
580 *
581 * @since 1.0.0
582 * @access public
583 *
584 * @return string Unique selector.
585 */
586 public function get_unique_selector() {
587 return '.elementor-element-' . $this->get_id();
588 }
589
590 /**
591 * Is type instance.
592 *
593 * Used to determine whether the element is an instance of that type or not.
594 *
595 * @since 1.0.0
596 * @access public
597 *
598 * @return bool Whether the element is an instance of that type.
599 */
600 public function is_type_instance() {
601 return $this->is_type_instance;
602 }
603
604 /**
605 * On import update dynamic content (e.g. post and term IDs).
606 *
607 * @since 3.8.0
608 *
609 * @param array $config The config of the passed element.
610 * @param array $data The data that requires updating/replacement when imported.
611 * @param array|null $controls The available controls.
612 *
613 * @return array Element data.
614 */
615 public static function on_import_update_dynamic_content( array $config, array $data, $controls = null ) : array {
616 $tags_manager = Plugin::$instance->dynamic_tags;
617
618 if ( empty( $config['settings'][ $tags_manager::DYNAMIC_SETTING_KEY ] ) ) {
619 return $config;
620 }
621
622 foreach ( $config['settings'][ $tags_manager::DYNAMIC_SETTING_KEY ] as $dynamic_name => $dynamic_value ) {
623 $tag_config = $tags_manager->tag_text_to_tag_data( $dynamic_value );
624 $tag_instance = $tags_manager->create_tag( $tag_config['id'], $tag_config['name'], $tag_config['settings'] );
625
626 if ( is_null( $tag_instance ) ) {
627 continue;
628 }
629
630 if ( $tag_instance->has_own_method( 'on_import_replace_dynamic_content' ) ) {
631 // TODO: Remove this check in the future.
632 $tag_config = $tag_instance->on_import_replace_dynamic_content( $tag_config, $data['post_ids'] );
633 } else {
634 $tag_config = $tag_instance->on_import_update_dynamic_content( $tag_config, $data, $tag_instance->get_controls() );
635 }
636
637 $config['settings'][ $tags_manager::DYNAMIC_SETTING_KEY ][ $dynamic_name ] = $tags_manager->tag_data_to_tag_text( $tag_config['id'], $tag_config['name'], $tag_config['settings'] );
638 }
639
640 return $config;
641 }
642
643 /**
644 * Add render attributes.
645 *
646 * Used to add attributes to the current element wrapper HTML tag.
647 *
648 * @since 1.3.0
649 * @access protected
650 * @deprecated 3.1.0
651 */
652 protected function _add_render_attributes() {
653 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', __CLASS__ . '::add_render_attributes()' );
654
655 return $this->add_render_attributes();
656 }
657
658 /**
659 * Add render attributes.
660 *
661 * Used to add attributes to the current element wrapper HTML tag.
662 *
663 * @since 3.1.0
664 * @access protected
665 */
666 protected function add_render_attributes() {
667 $id = $this->get_id();
668
669 $settings = $this->get_settings_for_display();
670 $frontend_settings = $this->get_frontend_settings();
671 $controls = $this->get_controls();
672
673 $this->add_render_attribute( '_wrapper', [
674 'class' => [
675 'elementor-element',
676 'elementor-element-' . $id,
677 ],
678 'data-id' => $id,
679 'data-element_type' => $this->get_type(),
680 ] );
681
682 $class_settings = [];
683
684 foreach ( $settings as $setting_key => $setting ) {
685 if ( isset( $controls[ $setting_key ]['prefix_class'] ) ) {
686 $class_settings[ $setting_key ] = $setting;
687 }
688 }
689
690 foreach ( $class_settings as $setting_key => $setting ) {
691 if ( empty( $setting ) && '0' !== $setting ) {
692 continue;
693 }
694
695 $this->add_render_attribute( '_wrapper', 'class', $controls[ $setting_key ]['prefix_class'] . $setting );
696 }
697
698 $_animation = ! empty( $settings['_animation'] );
699 $animation = ! empty( $settings['animation'] );
700 $has_animation = $_animation && 'none' !== $settings['_animation'] || $animation && 'none' !== $settings['animation'];
701
702 if ( $has_animation ) {
703 $is_static_render_mode = Plugin::$instance->frontend->is_static_render_mode();
704
705 if ( ! $is_static_render_mode ) {
706 // Hide the element until the animation begins
707 $this->add_render_attribute( '_wrapper', 'class', 'elementor-invisible' );
708 }
709 }
710
711 if ( ! empty( $settings['_element_id'] ) ) {
712 $this->add_render_attribute( '_wrapper', 'id', trim( $settings['_element_id'] ) );
713 }
714
715 if ( $frontend_settings ) {
716 $this->add_render_attribute( '_wrapper', 'data-settings', wp_json_encode( $frontend_settings ) );
717 }
718
719 /**
720 * After element attribute rendered.
721 *
722 * Fires after the attributes of the element HTML tag are rendered.
723 *
724 * @since 2.3.0
725 *
726 * @param Element_Base $this The element.
727 */
728 do_action( 'elementor/element/after_add_attributes', $this );
729 }
730
731 /**
732 * Register the Transform controls in the advanced tab of the element.
733 *
734 * Previously registered under the Widget_Common class, but registered a more fundamental level now to enable access from other widgets.
735 *
736 * @since 3.9.0
737 * @access protected
738 * @return void
739 */
740 protected function register_transform_section( $element_selector = '' ) {
741 $default_unit_values_deg = [];
742 $default_unit_values_ms = [];
743
744 // Set the default unit sizes for all active breakpoints.
745 foreach ( Breakpoints_Manager::get_default_config() as $breakpoint_name => $breakpoint_config ) {
746 $default_unit_values_deg[ $breakpoint_name ] = [
747 'default' => [
748 'unit' => 'deg',
749 ],
750 ];
751
752 $default_unit_values_ms[ $breakpoint_name ] = [
753 'default' => [
754 'unit' => 'ms',
755 ],
756 ];
757 }
758
759 $this->start_controls_section(
760 '_section_transform',
761 [
762 'label' => esc_html__( 'Transform', 'elementor' ),
763 'tab' => Controls_Manager::TAB_ADVANCED,
764 ]
765 );
766
767 $this->start_controls_tabs( '_tabs_positioning' );
768
769 $transform_prefix_class = 'e-';
770 $transform_return_value = 'transform';
771 $transform_selector_class = ' > .elementor-widget-container';
772 $transform_css_modifier = '';
773
774 if ( 'con' === $element_selector ) {
775 $transform_selector_class = '.e-' . $element_selector;
776 $transform_css_modifier = $element_selector . '-';
777 }
778
779 foreach ( [ '', '_hover' ] as $tab ) {
780 $state = '_hover' === $tab ? ':hover' : '';
781
782 $this->start_controls_tab(
783 "_tab_positioning{$tab}",
784 [
785 'label' => '' === $tab ? esc_html__( 'Normal', 'elementor' ) : esc_html__( 'Hover', 'elementor' ),
786 ]
787 );
788
789 $this->add_control(
790 "_transform_rotate_popover{$tab}",
791 [
792 'label' => esc_html__( 'Rotate', 'elementor' ),
793 'type' => Controls_Manager::POPOVER_TOGGLE,
794 'prefix_class' => $transform_prefix_class,
795 'return_value' => $transform_return_value,
796 ]
797 );
798
799 $this->start_popover();
800
801 $this->add_responsive_control(
802 "_transform_rotateZ_effect{$tab}",
803 [
804 'label' => esc_html__( 'Rotate', 'elementor' ),
805 'type' => Controls_Manager::SLIDER,
806 'device_args' => $default_unit_values_deg,
807 'range' => [
808 'px' => [
809 'min' => -360,
810 'max' => 360,
811 ],
812 ],
813 'selectors' => [
814 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-rotateZ: {{SIZE}}deg',
815 ],
816 'condition' => [
817 "_transform_rotate_popover{$tab}!" => '',
818 ],
819 'frontend_available' => true,
820 ]
821 );
822
823 $this->add_control(
824 "_transform_rotate_3d{$tab}",
825 [
826 'label' => esc_html__( '3D Rotate', 'elementor' ),
827 'type' => Controls_Manager::SWITCHER,
828 'label_on' => esc_html__( 'On', 'elementor' ),
829 'label_off' => esc_html__( 'Off', 'elementor' ),
830 'selectors' => [
831 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-rotateX: 1{{UNIT}}; --e-' . $transform_css_modifier . 'transform-perspective: 20px;',
832 ],
833 'condition' => [
834 "_transform_rotate_popover{$tab}!" => '',
835 ],
836 ]
837 );
838
839 $this->add_responsive_control(
840 "_transform_rotateX_effect{$tab}",
841 [
842 'label' => esc_html__( 'Rotate X', 'elementor' ),
843 'type' => Controls_Manager::SLIDER,
844 'device_args' => $default_unit_values_deg,
845 'range' => [
846 'px' => [
847 'min' => -360,
848 'max' => 360,
849 ],
850 ],
851 'condition' => [
852 "_transform_rotate_3d{$tab}!" => '',
853 "_transform_rotate_popover{$tab}!" => '',
854 ],
855 'selectors' => [
856 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-rotateX: {{SIZE}}deg;',
857 ],
858 'frontend_available' => true,
859 ]
860 );
861
862 $this->add_responsive_control(
863 "_transform_rotateY_effect{$tab}",
864 [
865 'label' => esc_html__( 'Rotate Y', 'elementor' ),
866 'type' => Controls_Manager::SLIDER,
867 'device_args' => $default_unit_values_deg,
868 'range' => [
869 'px' => [
870 'min' => -360,
871 'max' => 360,
872 ],
873 ],
874 'condition' => [
875 "_transform_rotate_3d{$tab}!" => '',
876 "_transform_rotate_popover{$tab}!" => '',
877 ],
878 'selectors' => [
879 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-rotateY: {{SIZE}}deg;',
880 ],
881 'frontend_available' => true,
882 ]
883 );
884
885 $this->add_responsive_control(
886 "_transform_perspective_effect{$tab}",
887 [
888 'label' => esc_html__( 'Perspective', 'elementor' ),
889 'type' => Controls_Manager::SLIDER,
890 'range' => [
891 'px' => [
892 'min' => 0,
893 'max' => 1000,
894 ],
895 ],
896 'condition' => [
897 "_transform_rotate_popover{$tab}!" => '',
898 "_transform_rotate_3d{$tab}!" => '',
899 ],
900 'selectors' => [
901 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-perspective: {{SIZE}}px',
902 ],
903 'frontend_available' => true,
904 ]
905 );
906
907 $this->end_popover();
908
909 $this->add_control(
910 "_transform_translate_popover{$tab}",
911 [
912 'label' => esc_html__( 'Offset', 'elementor' ),
913 'type' => Controls_Manager::POPOVER_TOGGLE,
914 'prefix_class' => $transform_prefix_class,
915 'return_value' => $transform_return_value,
916 ]
917 );
918
919 $this->start_popover();
920
921 $this->add_responsive_control(
922 "_transform_translateX_effect{$tab}",
923 [
924 'label' => esc_html__( 'Offset X', 'elementor' ),
925 'type' => Controls_Manager::SLIDER,
926 'size_units' => [ '%', 'px' ],
927 'range' => [
928 '%' => [
929 'min' => -100,
930 'max' => 100,
931 ],
932 'px' => [
933 'min' => -1000,
934 'max' => 1000,
935 ],
936 ],
937 'condition' => [
938 "_transform_translate_popover{$tab}!" => '',
939 ],
940 'selectors' => [
941 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-translateX: {{SIZE}}{{UNIT}};',
942 ],
943 'frontend_available' => true,
944 ]
945 );
946
947 $this->add_responsive_control(
948 "_transform_translateY_effect{$tab}",
949 [
950 'label' => esc_html__( 'Offset Y', 'elementor' ),
951 'type' => Controls_Manager::SLIDER,
952 'size_units' => [ '%', 'px' ],
953 'range' => [
954 '%' => [
955 'min' => -100,
956 'max' => 100,
957 ],
958 'px' => [
959 'min' => -1000,
960 'max' => 1000,
961 ],
962 ],
963 'condition' => [
964 "_transform_translate_popover{$tab}!" => '',
965 ],
966 'selectors' => [
967 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-translateY: {{SIZE}}{{UNIT}};',
968 ],
969 'frontend_available' => true,
970 ]
971 );
972
973 $this->end_popover();
974
975 $this->add_control(
976 "_transform_scale_popover{$tab}",
977 [
978 'label' => esc_html__( 'Scale', 'elementor' ),
979 'type' => Controls_Manager::POPOVER_TOGGLE,
980 'prefix_class' => $transform_prefix_class,
981 'return_value' => $transform_return_value,
982 ]
983 );
984
985 $this->start_popover();
986
987 $this->add_control(
988 "_transform_keep_proportions{$tab}",
989 [
990 'label' => esc_html__( 'Keep Proportions', 'elementor' ),
991 'type' => Controls_Manager::SWITCHER,
992 'label_on' => esc_html__( 'On', 'elementor' ),
993 'label_off' => esc_html__( 'Off', 'elementor' ),
994 'default' => 'yes',
995 ]
996 );
997
998 $this->add_responsive_control(
999 "_transform_scale_effect{$tab}",
1000 [
1001 'label' => esc_html__( 'Scale', 'elementor' ),
1002 'type' => Controls_Manager::SLIDER,
1003 'range' => [
1004 'px' => [
1005 'min' => 0,
1006 'max' => 2,
1007 'step' => 0.1,
1008 ],
1009 ],
1010 'condition' => [
1011 "_transform_scale_popover{$tab}!" => '',
1012 "_transform_keep_proportions{$tab}!" => '',
1013 ],
1014 'selectors' => [
1015 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-scale: {{SIZE}};',
1016 ],
1017 'frontend_available' => true,
1018 ]
1019 );
1020
1021 $this->add_responsive_control(
1022 "_transform_scaleX_effect{$tab}",
1023 [
1024 'label' => esc_html__( 'Scale X', 'elementor' ),
1025 'type' => Controls_Manager::SLIDER,
1026 'range' => [
1027 'px' => [
1028 'min' => 0,
1029 'max' => 2,
1030 'step' => 0.1,
1031 ],
1032 ],
1033 'condition' => [
1034 "_transform_scale_popover{$tab}!" => '',
1035 "_transform_keep_proportions{$tab}" => '',
1036 ],
1037 'selectors' => [
1038 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-scaleX: {{SIZE}};',
1039 ],
1040 'frontend_available' => true,
1041 ]
1042 );
1043
1044 $this->add_responsive_control(
1045 "_transform_scaleY_effect{$tab}",
1046 [
1047 'label' => esc_html__( 'Scale Y', 'elementor' ),
1048 'type' => Controls_Manager::SLIDER,
1049 'range' => [
1050 'px' => [
1051 'min' => 0,
1052 'max' => 2,
1053 'step' => 0.1,
1054 ],
1055 ],
1056 'condition' => [
1057 "_transform_scale_popover{$tab}!" => '',
1058 "_transform_keep_proportions{$tab}" => '',
1059 ],
1060 'selectors' => [
1061 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-scaleY: {{SIZE}};',
1062 ],
1063 'frontend_available' => true,
1064 ]
1065 );
1066
1067 $this->end_popover();
1068
1069 $this->add_control(
1070 "_transform_skew_popover{$tab}",
1071 [
1072 'label' => esc_html__( 'Skew', 'elementor' ),
1073 'type' => Controls_Manager::POPOVER_TOGGLE,
1074 'prefix_class' => $transform_prefix_class,
1075 'return_value' => $transform_return_value,
1076 ]
1077 );
1078
1079 $this->start_popover();
1080
1081 $this->add_responsive_control(
1082 "_transform_skewX_effect{$tab}",
1083 [
1084 'label' => esc_html__( 'Skew X', 'elementor' ),
1085 'type' => Controls_Manager::SLIDER,
1086 'device_args' => $default_unit_values_deg,
1087 'range' => [
1088 'px' => [
1089 'min' => -360,
1090 'max' => 360,
1091 ],
1092 ],
1093 'condition' => [
1094 "_transform_skew_popover{$tab}!" => '',
1095 ],
1096 'selectors' => [
1097 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-skewX: {{SIZE}}deg;',
1098 ],
1099 'frontend_available' => true,
1100 ]
1101 );
1102
1103 $this->add_responsive_control(
1104 "_transform_skewY_effect{$tab}",
1105 [
1106 'label' => esc_html__( 'Skew Y', 'elementor' ),
1107 'type' => Controls_Manager::SLIDER,
1108 'device_args' => $default_unit_values_deg,
1109 'range' => [
1110 'px' => [
1111 'min' => -360,
1112 'max' => 360,
1113 ],
1114 ],
1115 'condition' => [
1116 "_transform_skew_popover{$tab}!" => '',
1117 ],
1118 'selectors' => [
1119 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-skewY: {{SIZE}}deg;',
1120 ],
1121 'frontend_available' => true,
1122 ]
1123 );
1124
1125 $this->end_popover();
1126
1127 $this->add_control(
1128 "_transform_flipX_effect{$tab}",
1129 [
1130 'label' => esc_html__( 'Flip Horizontal', 'elementor' ),
1131 'type' => Controls_Manager::CHOOSE,
1132 'options' => [
1133 'transform' => [
1134 'title' => esc_html__( 'Flip Horizontal', 'elementor' ),
1135 'icon' => 'eicon-flip eicon-tilted',
1136 ],
1137 ],
1138 'prefix_class' => $transform_prefix_class,
1139 'selectors' => [
1140 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-flipX: -1',
1141 ],
1142 'frontend_available' => true,
1143 ]
1144 );
1145
1146 $this->add_control(
1147 "_transform_flipY_effect{$tab}",
1148 [
1149 'label' => esc_html__( 'Flip Vertical', 'elementor' ),
1150 'type' => Controls_Manager::CHOOSE,
1151 'options' => [
1152 'transform' => [
1153 'title' => esc_html__( 'Flip Vertical', 'elementor' ),
1154 'icon' => 'eicon-flip',
1155 ],
1156 ],
1157 'prefix_class' => $transform_prefix_class,
1158 'selectors' => [
1159 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-flipY: -1',
1160 ],
1161 'frontend_available' => true,
1162 ]
1163 );
1164
1165 if ( '_hover' === $tab ) {
1166 $this->add_control(
1167 '_transform_transition_hover',
1168 [
1169 'label' => esc_html__( 'Transition Duration (ms)', 'elementor' ),
1170 'type' => Controls_Manager::SLIDER,
1171 'device_args' => $default_unit_values_ms,
1172 'range' => [
1173 'px' => [
1174 'min' => 100,
1175 'max' => 10000,
1176 ],
1177 ],
1178 'selectors' => [
1179 '{{WRAPPER}}' => '--e-' . $transform_css_modifier . 'transform-transition-duration: {{SIZE}}ms',
1180 ],
1181 ]
1182 );
1183 }
1184
1185 ${"transform_origin_conditions{$tab}"} = [
1186 [
1187 'name' => "_transform_scale_popover{$tab}",
1188 'operator' => '!=',
1189 'value' => '',
1190 ],
1191 [
1192 'name' => "_transform_rotate_popover{$tab}",
1193 'operator' => '!=',
1194 'value' => '',
1195 ],
1196 [
1197 'name' => "_transform_flipX_effect{$tab}",
1198 'operator' => '!=',
1199 'value' => '',
1200 ],
1201 [
1202 'name' => "_transform_flipY_effect{$tab}",
1203 'operator' => '!=',
1204 'value' => '',
1205 ],
1206 ];
1207
1208 $this->end_controls_tab();
1209 }
1210
1211 $this->end_controls_tabs();
1212
1213 $transform_origin_conditions = [
1214 'relation' => 'or',
1215 'terms' => array_merge( $transform_origin_conditions, $transform_origin_conditions_hover ),
1216 ];
1217
1218 // Will override motion effect transform-origin
1219 $this->add_responsive_control(
1220 'motion_fx_transform_x_anchor_point',
1221 [
1222 'label' => esc_html__( 'X Anchor Point', 'elementor' ),
1223 'type' => Controls_Manager::CHOOSE,
1224 'options' => [
1225 'left' => [
1226 'title' => esc_html__( 'Left', 'elementor' ),
1227 'icon' => 'eicon-h-align-left',
1228 ],
1229 'center' => [
1230 'title' => esc_html__( 'Center', 'elementor' ),
1231 'icon' => 'eicon-h-align-center',
1232 ],
1233 'right' => [
1234 'title' => esc_html__( 'Right', 'elementor' ),
1235 'icon' => 'eicon-h-align-right',
1236 ],
1237 ],
1238 'conditions' => $transform_origin_conditions,
1239 'separator' => 'before',
1240 'selectors' => [
1241 '{{WRAPPER}}' => '--e-' . $transform_css_modifier . 'transform-origin-x: {{VALUE}}',
1242 ],
1243 ]
1244 );
1245
1246 // Will override motion effect transform-origin
1247 $this->add_responsive_control(
1248 'motion_fx_transform_y_anchor_point',
1249 [
1250 'label' => esc_html__( 'Y Anchor Point', 'elementor' ),
1251 'type' => Controls_Manager::CHOOSE,
1252 'options' => [
1253 'top' => [
1254 'title' => esc_html__( 'Top', 'elementor' ),
1255 'icon' => 'eicon-v-align-top',
1256 ],
1257 'center' => [
1258 'title' => esc_html__( 'Center', 'elementor' ),
1259 'icon' => 'eicon-v-align-middle',
1260 ],
1261 'bottom' => [
1262 'title' => esc_html__( 'Bottom', 'elementor' ),
1263 'icon' => 'eicon-v-align-bottom',
1264 ],
1265 ],
1266 'conditions' => $transform_origin_conditions,
1267 'selectors' => [
1268 '{{WRAPPER}}' => '--e-' . $transform_css_modifier . 'transform-origin-y: {{VALUE}}',
1269 ],
1270 ]
1271 );
1272
1273 $this->end_controls_section();
1274 }
1275
1276 /**
1277 * Add Hidden Device Controls
1278 *
1279 * Adds controls for hiding elements within certain devices' viewport widths. Adds a control for each active device.
1280 *
1281 * @since 3.4.0
1282 * @access protected
1283 */
1284 protected function add_hidden_device_controls() {
1285 // The 'Hide On X' controls are displayed from largest to smallest, while the method returns smallest to largest.
1286 $active_devices = Plugin::$instance->breakpoints->get_active_devices_list( [ 'reverse' => true ] );
1287 $active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints();
1288
1289 foreach ( $active_devices as $breakpoint_key ) {
1290 $label = 'desktop' === $breakpoint_key ? esc_html__( 'Desktop', 'elementor' ) : $active_breakpoints[ $breakpoint_key ]->get_label();
1291
1292 $this->add_control(
1293 'hide_' . $breakpoint_key,
1294 [
1295 /* translators: %s: Device name. */
1296 'label' => sprintf( __( 'Hide On %s', 'elementor' ), $label ),
1297 'type' => Controls_Manager::SWITCHER,
1298 'default' => '',
1299 'prefix_class' => 'elementor-',
1300 'label_on' => esc_html__( 'Hide', 'elementor' ),
1301 'label_off' => esc_html__( 'Show', 'elementor' ),
1302 'return_value' => 'hidden-' . $breakpoint_key,
1303 ]
1304 );
1305 }
1306 }
1307
1308 /**
1309 * Get default data.
1310 *
1311 * Retrieve the default element data. Used to reset the data on initialization.
1312 *
1313 * @since 1.0.0
1314 * @access protected
1315 *
1316 * @return array Default data.
1317 */
1318 protected function get_default_data() {
1319 $data = parent::get_default_data();
1320
1321 return array_merge(
1322 $data, [
1323 'elements' => [],
1324 'isInner' => false,
1325 ]
1326 );
1327 }
1328
1329 /**
1330 * Print element content.
1331 *
1332 * Output the element final HTML on the frontend.
1333 *
1334 * @since 1.0.0
1335 * @access protected
1336 * @deprecated 3.1.0
1337 */
1338 protected function _print_content() {
1339 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', __CLASS__ . '::print_content()' );
1340
1341 $this->print_content();
1342 }
1343
1344 /**
1345 * Print element content.
1346 *
1347 * Output the element final HTML on the frontend.
1348 *
1349 * @since 3.1.0
1350 * @access protected
1351 */
1352 protected function print_content() {
1353 foreach ( $this->get_children() as $child ) {
1354 $child->print_element();
1355 }
1356 }
1357
1358 /**
1359 * Get initial config.
1360 *
1361 * Retrieve the current element initial configuration.
1362 *
1363 * Adds more configuration on top of the controls list and the tabs assigned
1364 * to the control. This method also adds element name, type, icon and more.
1365 *
1366 * @since 2.9.0
1367 * @access protected
1368 *
1369 * @return array The initial config.
1370 */
1371 protected function get_initial_config() {
1372 $config = [
1373 'name' => $this->get_name(),
1374 'elType' => $this->get_type(),
1375 'title' => $this->get_title(),
1376 'icon' => $this->get_icon(),
1377 'reload_preview' => $this->is_reload_preview_required(),
1378 ];
1379
1380 if ( preg_match( '/^' . __NAMESPACE__ . '(Pro)?\\\\/', get_called_class() ) ) {
1381 $config['help_url'] = $this->get_help_url();
1382 } else {
1383 $config['help_url'] = $this->get_custom_help_url();
1384 }
1385
1386 if ( ! $this->is_editable() ) {
1387 $config['editable'] = false;
1388 }
1389
1390 return $config;
1391 }
1392
1393 /**
1394 * A Base method for sanitizing the settings before save.
1395 * This method is meant to be overridden by the element.
1396 */
1397 protected function on_save( array $settings ) {
1398 return $settings;
1399 }
1400
1401 /**
1402 * Get child type.
1403 *
1404 * Retrieve the element child type based on element data.
1405 *
1406 * @since 2.0.0
1407 * @access private
1408 *
1409 * @param array $element_data Element ID.
1410 *
1411 * @return Element_Base|false Child type or false if type not found.
1412 */
1413 private function get_child_type( $element_data ) {
1414 $child_type = $this->_get_default_child_type( $element_data );
1415
1416 // If it's not a valid widget ( like a deactivated plugin )
1417 if ( ! $child_type ) {
1418 return false;
1419 }
1420
1421 /**
1422 * Element child type.
1423 *
1424 * Filters the child type of the element.
1425 *
1426 * @since 1.0.0
1427 *
1428 * @param Element_Base $child_type The child element.
1429 * @param array $element_data The original element ID.
1430 * @param Element_Base $this The original element.
1431 */
1432 $child_type = apply_filters( 'elementor/element/get_child_type', $child_type, $element_data, $this );
1433
1434 return $child_type;
1435 }
1436
1437 /**
1438 * Initialize children.
1439 *
1440 * Initializing the element child elements.
1441 *
1442 * @since 2.0.0
1443 * @access private
1444 */
1445 private function init_children() {
1446 $this->children = [];
1447
1448 $children_data = $this->get_data( 'elements' );
1449
1450 if ( ! $children_data ) {
1451 return;
1452 }
1453
1454 foreach ( $children_data as $child_data ) {
1455 if ( ! $child_data ) {
1456 continue;
1457 }
1458
1459 $this->add_child( $child_data );
1460 }
1461 }
1462
1463 /**
1464 * Element base constructor.
1465 *
1466 * Initializing the element base class using `$data` and `$args`.
1467 *
1468 * The `$data` parameter is required for a normal instance because of the
1469 * way Elementor renders data when initializing elements.
1470 *
1471 * @since 1.0.0
1472 * @access public
1473 *
1474 * @param array $data Optional. Element data. Default is an empty array.
1475 * @param array|null $args Optional. Element default arguments. Default is null.
1476 **/
1477 public function __construct( array $data = [], array $args = null ) {
1478 if ( $data ) {
1479 $this->is_type_instance = false;
1480 } elseif ( $args ) {
1481 $this->default_args = $args;
1482 }
1483
1484 parent::__construct( $data );
1485 }
1486 }
1487