PluginProbe
Elementor Website Builder – more than just a page builder / 3.20.2
Elementor Website Builder – more than just a page builder v3.20.2
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.20.2, at includes/base/element-base.php

1,508 lines 37.5 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 * Get panel presets.
325 *
326 * Used for displaying the widget in the panel multiple times, but with different defaults values,
327 * icon, title etc.
328 *
329 * @since 3.16.0
330 * @access public
331 *
332 * @return array
333 */
334 public function get_panel_presets() {
335 return [];
336 }
337
338 /**
339 * Add new child element.
340 *
341 * Register new child element to allow hierarchy.
342 *
343 * @since 1.0.0
344 * @access public
345 * @param array $child_data Child element data.
346 * @param array $child_args Child element arguments.
347 *
348 * @return Element_Base|false Child element instance, or false if failed.
349 */
350 public function add_child( array $child_data, array $child_args = [] ) {
351 if ( null === $this->children ) {
352 $this->init_children();
353 }
354
355 $child_type = $this->get_child_type( $child_data );
356
357 if ( ! $child_type ) {
358 return false;
359 }
360
361 $child = Plugin::$instance->elements_manager->create_element_instance( $child_data, $child_args, $child_type );
362
363 if ( $child ) {
364 $this->children[] = $child;
365 }
366
367 return $child;
368 }
369
370 /**
371 * Add link render attributes.
372 *
373 * Used to add link tag attributes to a specific HTML element.
374 *
375 * The HTML link tag is represented by the element parameter. The `url_control` parameter
376 * needs to be an array of link settings in the same format they are set by Elementor's URL control.
377 *
378 * Example usage:
379 *
380 * `$this->add_link_attributes( 'button', $settings['link'] );`
381 *
382 * @since 2.8.0
383 * @access public
384 *
385 * @param array|string $element The HTML element.
386 * @param array $url_control Array of link settings.
387 * @param bool $overwrite Optional. Whether to overwrite existing
388 * attribute. Default is false, not to overwrite.
389 *
390 * @return Element_Base Current instance of the element.
391 */
392
393 public function add_link_attributes( $element, array $url_control, $overwrite = false ) {
394 $attributes = [];
395
396 if ( ! empty( $url_control['url'] ) ) {
397 $allowed_protocols = array_merge( wp_allowed_protocols(), [ 'skype', 'viber' ] );
398
399 $attributes['href'] = esc_url( $url_control['url'], $allowed_protocols );
400 }
401
402 if ( ! empty( $url_control['is_external'] ) ) {
403 $attributes['target'] = '_blank';
404 }
405
406 if ( ! empty( $url_control['nofollow'] ) ) {
407 $attributes['rel'] = 'nofollow';
408 }
409
410 if ( ! empty( $url_control['custom_attributes'] ) ) {
411 // Custom URL attributes should come as a string of comma-delimited key|value pairs
412 $attributes = array_merge( $attributes, Utils::parse_custom_attributes( $url_control['custom_attributes'] ) );
413 }
414
415 if ( $attributes ) {
416 $this->add_render_attribute( $element, $attributes, null, $overwrite );
417 }
418
419 return $this;
420 }
421
422 /**
423 * Print element.
424 *
425 * Used to generate the element final HTML on the frontend and the editor.
426 *
427 * @since 1.0.0
428 * @access public
429 */
430 public function print_element() {
431 $element_type = $this->get_type();
432
433 /**
434 * Before frontend element render.
435 *
436 * Fires before Elementor element is rendered in the frontend.
437 *
438 * @since 2.2.0
439 *
440 * @param Element_Base $this The element.
441 */
442 do_action( 'elementor/frontend/before_render', $this );
443
444 /**
445 * Before frontend element render.
446 *
447 * Fires before Elementor element is rendered in the frontend.
448 *
449 * The dynamic portion of the hook name, `$element_type`, refers to the element type.
450 *
451 * @since 1.0.0
452 *
453 * @param Element_Base $this The element.
454 */
455 do_action( "elementor/frontend/{$element_type}/before_render", $this );
456
457 ob_start();
458
459 if ( $this->has_own_method( '_print_content', self::class ) ) {
460 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_print_content', '3.1.0', __CLASS__ . '::print_content()' );
461
462 $this->_print_content();
463 } else {
464 $this->print_content();
465 }
466
467 $content = ob_get_clean();
468
469 $should_render = ( ! empty( $content ) || $this->should_print_empty() );
470
471 /**
472 * Should the element be rendered for frontend
473 *
474 * Filters if the element should be rendered on frontend.
475 *
476 * @since 2.3.3
477 *
478 * @param bool true The element.
479 * @param Element_Base $this The element.
480 */
481 $should_render = apply_filters( "elementor/frontend/{$element_type}/should_render", $should_render, $this );
482
483 if ( $should_render ) {
484 if ( $this->has_own_method( '_add_render_attributes', self::class ) ) {
485 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_add_render_attributes', '3.1.0', __CLASS__ . '::add_render_attributes()' );
486
487 $this->_add_render_attributes();
488 } else {
489 $this->add_render_attributes();
490 }
491
492 $this->before_render();
493 // PHPCS - The content has already been escaped by the `render` method.
494 echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
495 $this->after_render();
496
497 $this->enqueue_scripts();
498 $this->enqueue_styles();
499 }
500
501 /**
502 * After frontend element render.
503 *
504 * Fires after Elementor element is rendered in the frontend.
505 *
506 * The dynamic portion of the hook name, `$element_type`, refers to the element type.
507 *
508 * @since 1.0.0
509 *
510 * @param Element_Base $this The element.
511 */
512 do_action( "elementor/frontend/{$element_type}/after_render", $this );
513
514 /**
515 * After frontend element render.
516 *
517 * Fires after Elementor element is rendered in the frontend.
518 *
519 * @since 2.3.0
520 *
521 * @param Element_Base $this The element.
522 */
523 do_action( 'elementor/frontend/after_render', $this );
524 }
525
526 /**
527 * Get the element raw data.
528 *
529 * Retrieve the raw element data, including the id, type, settings, child
530 * elements and whether it is an inner element.
531 *
532 * The data with the HTML used always to display the data, but the Elementor
533 * editor uses the raw data without the HTML in order not to render the data
534 * again.
535 *
536 * @since 1.0.0
537 * @access public
538 *
539 * @param bool $with_html_content Optional. Whether to return the data with
540 * HTML content or without. Used for caching.
541 * Default is false, without HTML.
542 *
543 * @return array Element raw data.
544 */
545 public function get_raw_data( $with_html_content = false ) {
546 $data = $this->get_data();
547
548 $elements = [];
549
550 foreach ( $this->get_children() as $child ) {
551 $elements[] = $child->get_raw_data( $with_html_content );
552 }
553
554 $raw_data = [
555 'id' => $this->get_id(),
556 'elType' => $data['elType'],
557 'settings' => $data['settings'],
558 'elements' => $elements,
559 'isInner' => $data['isInner'],
560 ];
561
562 if ( ! empty( $data['isLocked'] ) ) {
563 $raw_data['isLocked'] = $data['isLocked'];
564 }
565
566 return $raw_data;
567 }
568
569 public function get_data_for_save() {
570 $data = $this->get_raw_data();
571
572 $elements = [];
573
574 foreach ( $this->get_children() as $child ) {
575 $elements[] = $child->get_data_for_save();
576 }
577
578 if ( ! empty( $elements ) ) {
579 $data['elements'] = $elements;
580 }
581
582 if ( ! empty( $data['settings'] ) ) {
583 $data['settings'] = $this->on_save( $data['settings'] );
584 }
585
586 return $data;
587 }
588
589 /**
590 * Get unique selector.
591 *
592 * Retrieve the unique selector of the element. Used to set a unique HTML
593 * class for each HTML element. This way Elementor can set custom styles for
594 * each element.
595 *
596 * @since 1.0.0
597 * @access public
598 *
599 * @return string Unique selector.
600 */
601 public function get_unique_selector() {
602 return '.elementor-element-' . $this->get_id();
603 }
604
605 /**
606 * Is type instance.
607 *
608 * Used to determine whether the element is an instance of that type or not.
609 *
610 * @since 1.0.0
611 * @access public
612 *
613 * @return bool Whether the element is an instance of that type.
614 */
615 public function is_type_instance() {
616 return $this->is_type_instance;
617 }
618
619 /**
620 * On import update dynamic content (e.g. post and term IDs).
621 *
622 * @since 3.8.0
623 *
624 * @param array $config The config of the passed element.
625 * @param array $data The data that requires updating/replacement when imported.
626 * @param array|null $controls The available controls.
627 *
628 * @return array Element data.
629 */
630 public static function on_import_update_dynamic_content( array $config, array $data, $controls = null ) : array {
631 $tags_manager = Plugin::$instance->dynamic_tags;
632
633 if ( empty( $config['settings'][ $tags_manager::DYNAMIC_SETTING_KEY ] ) ) {
634 return $config;
635 }
636
637 foreach ( $config['settings'][ $tags_manager::DYNAMIC_SETTING_KEY ] as $dynamic_name => $dynamic_value ) {
638 $tag_config = $tags_manager->tag_text_to_tag_data( $dynamic_value );
639 $tag_instance = $tags_manager->create_tag( $tag_config['id'], $tag_config['name'], $tag_config['settings'] );
640
641 if ( is_null( $tag_instance ) ) {
642 continue;
643 }
644
645 if ( $tag_instance->has_own_method( 'on_import_replace_dynamic_content' ) ) {
646 // TODO: Remove this check in the future.
647 $tag_config = $tag_instance->on_import_replace_dynamic_content( $tag_config, $data['post_ids'] );
648 } else {
649 $tag_config = $tag_instance->on_import_update_dynamic_content( $tag_config, $data, $tag_instance->get_controls() );
650 }
651
652 $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'] );
653 }
654
655 return $config;
656 }
657
658 /**
659 * Add render attributes.
660 *
661 * Used to add attributes to the current element wrapper HTML tag.
662 *
663 * @since 1.3.0
664 * @access protected
665 * @deprecated 3.1.0 Use `add_render_attribute()` method instead.
666 */
667 protected function _add_render_attributes() {
668 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', 'add_render_attributes()' );
669
670 return $this->add_render_attributes();
671 }
672
673 /**
674 * Add render attributes.
675 *
676 * Used to add attributes to the current element wrapper HTML tag.
677 *
678 * @since 3.1.0
679 * @access protected
680 */
681 protected function add_render_attributes() {
682 $id = $this->get_id();
683
684 $settings = $this->get_settings_for_display();
685 $frontend_settings = $this->get_frontend_settings();
686 $controls = $this->get_controls();
687
688 $this->add_render_attribute( '_wrapper', [
689 'class' => [
690 'elementor-element',
691 'elementor-element-' . $id,
692 ],
693 'data-id' => $id,
694 'data-element_type' => $this->get_type(),
695 ] );
696
697 $class_settings = [];
698
699 foreach ( $settings as $setting_key => $setting ) {
700 if ( isset( $controls[ $setting_key ]['prefix_class'] ) ) {
701 if ( isset( $controls[ $setting_key ]['classes_dictionary'][ $setting ] ) ) {
702 $value = $controls[ $setting_key ]['classes_dictionary'][ $setting ];
703 } else {
704 $value = $setting;
705 }
706
707 $class_settings[ $setting_key ] = $value;
708 }
709 }
710
711 foreach ( $class_settings as $setting_key => $setting ) {
712 if ( empty( $setting ) && '0' !== $setting ) {
713 continue;
714 }
715
716 $this->add_render_attribute( '_wrapper', 'class', $controls[ $setting_key ]['prefix_class'] . $setting );
717 }
718
719 $_animation = ! empty( $settings['_animation'] );
720 $animation = ! empty( $settings['animation'] );
721 $has_animation = $_animation && 'none' !== $settings['_animation'] || $animation && 'none' !== $settings['animation'];
722
723 if ( $has_animation ) {
724 $is_static_render_mode = Plugin::$instance->frontend->is_static_render_mode();
725
726 if ( ! $is_static_render_mode ) {
727 // Hide the element until the animation begins
728 $this->add_render_attribute( '_wrapper', 'class', 'elementor-invisible' );
729 }
730 }
731
732 if ( ! empty( $settings['_element_id'] ) ) {
733 $this->add_render_attribute( '_wrapper', 'id', trim( $settings['_element_id'] ) );
734 }
735
736 if ( $frontend_settings ) {
737 $this->add_render_attribute( '_wrapper', 'data-settings', wp_json_encode( $frontend_settings ) );
738 }
739
740 /**
741 * After element attribute rendered.
742 *
743 * Fires after the attributes of the element HTML tag are rendered.
744 *
745 * @since 2.3.0
746 *
747 * @param Element_Base $this The element.
748 */
749 do_action( 'elementor/element/after_add_attributes', $this );
750 }
751
752 /**
753 * Register the Transform controls in the advanced tab of the element.
754 *
755 * Previously registered under the Widget_Common class, but registered a more fundamental level now to enable access from other widgets.
756 *
757 * @since 3.9.0
758 * @access protected
759 * @return void
760 */
761 protected function register_transform_section( $element_selector = '' ) {
762 $default_unit_values_deg = [];
763 $default_unit_values_ms = [];
764
765 // Set the default unit sizes for all active breakpoints.
766 foreach ( Breakpoints_Manager::get_default_config() as $breakpoint_name => $breakpoint_config ) {
767 $default_unit_values_deg[ $breakpoint_name ] = [
768 'default' => [
769 'unit' => 'deg',
770 ],
771 ];
772
773 $default_unit_values_ms[ $breakpoint_name ] = [
774 'default' => [
775 'unit' => 'ms',
776 ],
777 ];
778 }
779
780 $this->start_controls_section(
781 '_section_transform',
782 [
783 'label' => esc_html__( 'Transform', 'elementor' ),
784 'tab' => Controls_Manager::TAB_ADVANCED,
785 ]
786 );
787
788 $this->start_controls_tabs( '_tabs_positioning' );
789
790 $transform_prefix_class = 'e-';
791 $transform_return_value = 'transform';
792 $transform_selector_class = ' > .elementor-widget-container';
793 $transform_css_modifier = '';
794
795 if ( 'con' === $element_selector ) {
796 $transform_selector_class = '.e-' . $element_selector;
797 $transform_css_modifier = $element_selector . '-';
798 }
799
800 foreach ( [ '', '_hover' ] as $tab ) {
801 $state = '_hover' === $tab ? ':hover' : '';
802
803 $this->start_controls_tab(
804 "_tab_positioning{$tab}",
805 [
806 'label' => '' === $tab ? esc_html__( 'Normal', 'elementor' ) : esc_html__( 'Hover', 'elementor' ),
807 ]
808 );
809
810 $this->add_control(
811 "_transform_rotate_popover{$tab}",
812 [
813 'label' => esc_html__( 'Rotate', 'elementor' ),
814 'type' => Controls_Manager::POPOVER_TOGGLE,
815 'prefix_class' => $transform_prefix_class,
816 'return_value' => $transform_return_value,
817 ]
818 );
819
820 $this->start_popover();
821
822 $this->add_responsive_control(
823 "_transform_rotateZ_effect{$tab}",
824 [
825 'label' => esc_html__( 'Rotate', 'elementor' ) . ' (deg)',
826 'type' => Controls_Manager::SLIDER,
827 'device_args' => $default_unit_values_deg,
828 'range' => [
829 'px' => [
830 'min' => -360,
831 'max' => 360,
832 ],
833 ],
834 'selectors' => [
835 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-rotateZ: {{SIZE}}deg',
836 ],
837 'condition' => [
838 "_transform_rotate_popover{$tab}!" => '',
839 ],
840 'frontend_available' => true,
841 ]
842 );
843
844 $this->add_control(
845 "_transform_rotate_3d{$tab}",
846 [
847 'label' => esc_html__( '3D Rotate', 'elementor' ),
848 'type' => Controls_Manager::SWITCHER,
849 'label_on' => esc_html__( 'On', 'elementor' ),
850 'label_off' => esc_html__( 'Off', 'elementor' ),
851 'selectors' => [
852 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-rotateX: 1{{UNIT}}; --e-' . $transform_css_modifier . 'transform-perspective: 20px;',
853 ],
854 'condition' => [
855 "_transform_rotate_popover{$tab}!" => '',
856 ],
857 ]
858 );
859
860 $this->add_responsive_control(
861 "_transform_rotateX_effect{$tab}",
862 [
863 'label' => esc_html__( 'Rotate X', 'elementor' ) . ' (deg)',
864 'type' => Controls_Manager::SLIDER,
865 'device_args' => $default_unit_values_deg,
866 'range' => [
867 'px' => [
868 'min' => -360,
869 'max' => 360,
870 ],
871 ],
872 'condition' => [
873 "_transform_rotate_3d{$tab}!" => '',
874 "_transform_rotate_popover{$tab}!" => '',
875 ],
876 'selectors' => [
877 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-rotateX: {{SIZE}}deg;',
878 ],
879 'frontend_available' => true,
880 ]
881 );
882
883 $this->add_responsive_control(
884 "_transform_rotateY_effect{$tab}",
885 [
886 'label' => esc_html__( 'Rotate Y', 'elementor' ) . ' (deg)',
887 'type' => Controls_Manager::SLIDER,
888 'device_args' => $default_unit_values_deg,
889 'range' => [
890 'px' => [
891 'min' => -360,
892 'max' => 360,
893 ],
894 ],
895 'condition' => [
896 "_transform_rotate_3d{$tab}!" => '',
897 "_transform_rotate_popover{$tab}!" => '',
898 ],
899 'selectors' => [
900 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-rotateY: {{SIZE}}deg;',
901 ],
902 'frontend_available' => true,
903 ]
904 );
905
906 $this->add_responsive_control(
907 "_transform_perspective_effect{$tab}",
908 [
909 'label' => esc_html__( 'Perspective', 'elementor' ) . ' (px)',
910 'type' => Controls_Manager::SLIDER,
911 'range' => [
912 'px' => [
913 'max' => 1000,
914 ],
915 ],
916 'condition' => [
917 "_transform_rotate_popover{$tab}!" => '',
918 "_transform_rotate_3d{$tab}!" => '',
919 ],
920 'selectors' => [
921 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-perspective: {{SIZE}}px',
922 ],
923 'frontend_available' => true,
924 ]
925 );
926
927 $this->end_popover();
928
929 $this->add_control(
930 "_transform_translate_popover{$tab}",
931 [
932 'label' => esc_html__( 'Offset', 'elementor' ),
933 'type' => Controls_Manager::POPOVER_TOGGLE,
934 'prefix_class' => $transform_prefix_class,
935 'return_value' => $transform_return_value,
936 ]
937 );
938
939 $this->start_popover();
940
941 $this->add_responsive_control(
942 "_transform_translateX_effect{$tab}",
943 [
944 'label' => esc_html__( 'Offset X', 'elementor' ),
945 'type' => Controls_Manager::SLIDER,
946 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
947 'range' => [
948 '%' => [
949 'min' => -100,
950 'max' => 100,
951 ],
952 'px' => [
953 'min' => -1000,
954 'max' => 1000,
955 ],
956 ],
957 'condition' => [
958 "_transform_translate_popover{$tab}!" => '',
959 ],
960 'selectors' => [
961 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-translateX: {{SIZE}}{{UNIT}};',
962 ],
963 'frontend_available' => true,
964 ]
965 );
966
967 $this->add_responsive_control(
968 "_transform_translateY_effect{$tab}",
969 [
970 'label' => esc_html__( 'Offset Y', 'elementor' ),
971 'type' => Controls_Manager::SLIDER,
972 'size_units' => [ 'px', '%', 'em', 'rem', 'vh', 'custom' ],
973 'range' => [
974 '%' => [
975 'min' => -100,
976 'max' => 100,
977 ],
978 'px' => [
979 'min' => -1000,
980 'max' => 1000,
981 ],
982 ],
983 'condition' => [
984 "_transform_translate_popover{$tab}!" => '',
985 ],
986 'selectors' => [
987 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-translateY: {{SIZE}}{{UNIT}};',
988 ],
989 'frontend_available' => true,
990 ]
991 );
992
993 $this->end_popover();
994
995 $this->add_control(
996 "_transform_scale_popover{$tab}",
997 [
998 'label' => esc_html__( 'Scale', 'elementor' ),
999 'type' => Controls_Manager::POPOVER_TOGGLE,
1000 'prefix_class' => $transform_prefix_class,
1001 'return_value' => $transform_return_value,
1002 ]
1003 );
1004
1005 $this->start_popover();
1006
1007 $this->add_control(
1008 "_transform_keep_proportions{$tab}",
1009 [
1010 'label' => esc_html__( 'Keep Proportions', 'elementor' ),
1011 'type' => Controls_Manager::SWITCHER,
1012 'label_on' => esc_html__( 'On', 'elementor' ),
1013 'label_off' => esc_html__( 'Off', 'elementor' ),
1014 'default' => 'yes',
1015 ]
1016 );
1017
1018 $this->add_responsive_control(
1019 "_transform_scale_effect{$tab}",
1020 [
1021 'label' => esc_html__( 'Scale', 'elementor' ),
1022 'type' => Controls_Manager::SLIDER,
1023 'range' => [
1024 'px' => [
1025 'max' => 2,
1026 'step' => 0.1,
1027 ],
1028 ],
1029 'condition' => [
1030 "_transform_scale_popover{$tab}!" => '',
1031 "_transform_keep_proportions{$tab}!" => '',
1032 ],
1033 'selectors' => [
1034 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-scale: {{SIZE}};',
1035 ],
1036 'frontend_available' => true,
1037 ]
1038 );
1039
1040 $this->add_responsive_control(
1041 "_transform_scaleX_effect{$tab}",
1042 [
1043 'label' => esc_html__( 'Scale X', 'elementor' ),
1044 'type' => Controls_Manager::SLIDER,
1045 'range' => [
1046 'px' => [
1047 'max' => 2,
1048 'step' => 0.1,
1049 ],
1050 ],
1051 'condition' => [
1052 "_transform_scale_popover{$tab}!" => '',
1053 "_transform_keep_proportions{$tab}" => '',
1054 ],
1055 'selectors' => [
1056 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-scaleX: {{SIZE}};',
1057 ],
1058 'frontend_available' => true,
1059 ]
1060 );
1061
1062 $this->add_responsive_control(
1063 "_transform_scaleY_effect{$tab}",
1064 [
1065 'label' => esc_html__( 'Scale Y', 'elementor' ),
1066 'type' => Controls_Manager::SLIDER,
1067 'range' => [
1068 'px' => [
1069 'max' => 2,
1070 'step' => 0.1,
1071 ],
1072 ],
1073 'condition' => [
1074 "_transform_scale_popover{$tab}!" => '',
1075 "_transform_keep_proportions{$tab}" => '',
1076 ],
1077 'selectors' => [
1078 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-scaleY: {{SIZE}};',
1079 ],
1080 'frontend_available' => true,
1081 ]
1082 );
1083
1084 $this->end_popover();
1085
1086 $this->add_control(
1087 "_transform_skew_popover{$tab}",
1088 [
1089 'label' => esc_html__( 'Skew', 'elementor' ),
1090 'type' => Controls_Manager::POPOVER_TOGGLE,
1091 'prefix_class' => $transform_prefix_class,
1092 'return_value' => $transform_return_value,
1093 ]
1094 );
1095
1096 $this->start_popover();
1097
1098 $this->add_responsive_control(
1099 "_transform_skewX_effect{$tab}",
1100 [
1101 'label' => esc_html__( 'Skew X', 'elementor' ) . ' (deg)',
1102 'type' => Controls_Manager::SLIDER,
1103 'device_args' => $default_unit_values_deg,
1104 'range' => [
1105 'px' => [
1106 'min' => -360,
1107 'max' => 360,
1108 ],
1109 ],
1110 'condition' => [
1111 "_transform_skew_popover{$tab}!" => '',
1112 ],
1113 'selectors' => [
1114 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-skewX: {{SIZE}}deg;',
1115 ],
1116 'frontend_available' => true,
1117 ]
1118 );
1119
1120 $this->add_responsive_control(
1121 "_transform_skewY_effect{$tab}",
1122 [
1123 'label' => esc_html__( 'Skew Y', 'elementor' ) . ' (deg)',
1124 'type' => Controls_Manager::SLIDER,
1125 'device_args' => $default_unit_values_deg,
1126 'range' => [
1127 'px' => [
1128 'min' => -360,
1129 'max' => 360,
1130 ],
1131 ],
1132 'condition' => [
1133 "_transform_skew_popover{$tab}!" => '',
1134 ],
1135 'selectors' => [
1136 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-skewY: {{SIZE}}deg;',
1137 ],
1138 'frontend_available' => true,
1139 ]
1140 );
1141
1142 $this->end_popover();
1143
1144 $this->add_control(
1145 "_transform_flipX_effect{$tab}",
1146 [
1147 'label' => esc_html__( 'Flip Horizontal', 'elementor' ),
1148 'type' => Controls_Manager::CHOOSE,
1149 'options' => [
1150 'transform' => [
1151 'title' => esc_html__( 'Flip Horizontal', 'elementor' ),
1152 'icon' => 'eicon-flip eicon-tilted',
1153 ],
1154 ],
1155 'prefix_class' => $transform_prefix_class,
1156 'selectors' => [
1157 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-flipX: -1',
1158 ],
1159 'frontend_available' => true,
1160 ]
1161 );
1162
1163 $this->add_control(
1164 "_transform_flipY_effect{$tab}",
1165 [
1166 'label' => esc_html__( 'Flip Vertical', 'elementor' ),
1167 'type' => Controls_Manager::CHOOSE,
1168 'options' => [
1169 'transform' => [
1170 'title' => esc_html__( 'Flip Vertical', 'elementor' ),
1171 'icon' => 'eicon-flip',
1172 ],
1173 ],
1174 'prefix_class' => $transform_prefix_class,
1175 'selectors' => [
1176 "{{WRAPPER}}{$transform_selector_class}{$state}" => '--e-' . $transform_css_modifier . 'transform-flipY: -1',
1177 ],
1178 'frontend_available' => true,
1179 ]
1180 );
1181
1182 if ( '_hover' === $tab ) {
1183 $this->add_control(
1184 '_transform_transition_hover',
1185 [
1186 'label' => esc_html__( 'Transition Duration', 'elementor' ) . ' (ms)',
1187 'type' => Controls_Manager::SLIDER,
1188 'device_args' => $default_unit_values_ms,
1189 'range' => [
1190 'px' => [
1191 'min' => 0,
1192 'max' => 10000,
1193 'step' => 100,
1194 ],
1195 ],
1196 'selectors' => [
1197 '{{WRAPPER}}' => '--e-' . $transform_css_modifier . 'transform-transition-duration: {{SIZE}}ms',
1198 ],
1199 ]
1200 );
1201 }
1202
1203 ${"transform_origin_conditions{$tab}"} = [
1204 [
1205 'name' => "_transform_scale_popover{$tab}",
1206 'operator' => '!=',
1207 'value' => '',
1208 ],
1209 [
1210 'name' => "_transform_rotate_popover{$tab}",
1211 'operator' => '!=',
1212 'value' => '',
1213 ],
1214 [
1215 'name' => "_transform_flipX_effect{$tab}",
1216 'operator' => '!=',
1217 'value' => '',
1218 ],
1219 [
1220 'name' => "_transform_flipY_effect{$tab}",
1221 'operator' => '!=',
1222 'value' => '',
1223 ],
1224 ];
1225
1226 $this->end_controls_tab();
1227 }
1228
1229 $this->end_controls_tabs();
1230
1231 $transform_origin_conditions = [
1232 'relation' => 'or',
1233 'terms' => array_merge( $transform_origin_conditions, $transform_origin_conditions_hover ),
1234 ];
1235
1236 // Will override motion effect transform-origin
1237 $this->add_responsive_control(
1238 'motion_fx_transform_x_anchor_point',
1239 [
1240 'label' => esc_html__( 'X Anchor Point', 'elementor' ),
1241 'type' => Controls_Manager::CHOOSE,
1242 'options' => [
1243 'left' => [
1244 'title' => esc_html__( 'Left', 'elementor' ),
1245 'icon' => 'eicon-h-align-left',
1246 ],
1247 'center' => [
1248 'title' => esc_html__( 'Center', 'elementor' ),
1249 'icon' => 'eicon-h-align-center',
1250 ],
1251 'right' => [
1252 'title' => esc_html__( 'Right', 'elementor' ),
1253 'icon' => 'eicon-h-align-right',
1254 ],
1255 ],
1256 'conditions' => $transform_origin_conditions,
1257 'separator' => 'before',
1258 'selectors' => [
1259 '{{WRAPPER}}' => '--e-' . $transform_css_modifier . 'transform-origin-x: {{VALUE}}',
1260 ],
1261 ]
1262 );
1263
1264 // Will override motion effect transform-origin
1265 $this->add_responsive_control(
1266 'motion_fx_transform_y_anchor_point',
1267 [
1268 'label' => esc_html__( 'Y Anchor Point', 'elementor' ),
1269 'type' => Controls_Manager::CHOOSE,
1270 'options' => [
1271 'top' => [
1272 'title' => esc_html__( 'Top', 'elementor' ),
1273 'icon' => 'eicon-v-align-top',
1274 ],
1275 'center' => [
1276 'title' => esc_html__( 'Center', 'elementor' ),
1277 'icon' => 'eicon-v-align-middle',
1278 ],
1279 'bottom' => [
1280 'title' => esc_html__( 'Bottom', 'elementor' ),
1281 'icon' => 'eicon-v-align-bottom',
1282 ],
1283 ],
1284 'conditions' => $transform_origin_conditions,
1285 'selectors' => [
1286 '{{WRAPPER}}' => '--e-' . $transform_css_modifier . 'transform-origin-y: {{VALUE}}',
1287 ],
1288 ]
1289 );
1290
1291 $this->end_controls_section();
1292 }
1293
1294 /**
1295 * Add Hidden Device Controls
1296 *
1297 * Adds controls for hiding elements within certain devices' viewport widths. Adds a control for each active device.
1298 *
1299 * @since 3.4.0
1300 * @access protected
1301 */
1302 protected function add_hidden_device_controls() {
1303 // The 'Hide On X' controls are displayed from largest to smallest, while the method returns smallest to largest.
1304 $active_devices = Plugin::$instance->breakpoints->get_active_devices_list( [ 'reverse' => true ] );
1305 $active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints();
1306
1307 foreach ( $active_devices as $breakpoint_key ) {
1308 $label = 'desktop' === $breakpoint_key ? esc_html__( 'Desktop', 'elementor' ) : $active_breakpoints[ $breakpoint_key ]->get_label();
1309
1310 $this->add_control(
1311 'hide_' . $breakpoint_key,
1312 [
1313 'label' => sprintf(
1314 /* translators: %s: Device name. */
1315 esc_html__( 'Hide On %s', 'elementor' ),
1316 $label
1317 ),
1318 'type' => Controls_Manager::SWITCHER,
1319 'default' => '',
1320 'prefix_class' => 'elementor-',
1321 'label_on' => esc_html__( 'Hide', 'elementor' ),
1322 'label_off' => esc_html__( 'Show', 'elementor' ),
1323 'return_value' => 'hidden-' . $breakpoint_key,
1324 ]
1325 );
1326 }
1327 }
1328
1329 /**
1330 * Get default data.
1331 *
1332 * Retrieve the default element data. Used to reset the data on initialization.
1333 *
1334 * @since 1.0.0
1335 * @access protected
1336 *
1337 * @return array Default data.
1338 */
1339 protected function get_default_data() {
1340 $data = parent::get_default_data();
1341
1342 return array_merge(
1343 $data, [
1344 'elements' => [],
1345 'isInner' => false,
1346 ]
1347 );
1348 }
1349
1350 /**
1351 * Print element content.
1352 *
1353 * Output the element final HTML on the frontend.
1354 *
1355 * @since 1.0.0
1356 * @access protected
1357 * @deprecated 3.1.0 Use `print_content()` method instead.
1358 */
1359 protected function _print_content() {
1360 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', 'print_content()' );
1361
1362 $this->print_content();
1363 }
1364
1365 /**
1366 * Print element content.
1367 *
1368 * Output the element final HTML on the frontend.
1369 *
1370 * @since 3.1.0
1371 * @access protected
1372 */
1373 protected function print_content() {
1374 foreach ( $this->get_children() as $child ) {
1375 $child->print_element();
1376 }
1377 }
1378
1379 /**
1380 * Get initial config.
1381 *
1382 * Retrieve the current element initial configuration.
1383 *
1384 * Adds more configuration on top of the controls list and the tabs assigned
1385 * to the control. This method also adds element name, type, icon and more.
1386 *
1387 * @since 2.9.0
1388 * @access protected
1389 *
1390 * @return array The initial config.
1391 */
1392 protected function get_initial_config() {
1393 $config = [
1394 'name' => $this->get_name(),
1395 'elType' => $this->get_type(),
1396 'title' => $this->get_title(),
1397 'icon' => $this->get_icon(),
1398 'reload_preview' => $this->is_reload_preview_required(),
1399 ];
1400
1401 if ( preg_match( '/^' . __NAMESPACE__ . '(Pro)?\\\\/', get_called_class() ) ) {
1402 $config['help_url'] = $this->get_help_url();
1403 } else {
1404 $config['help_url'] = $this->get_custom_help_url();
1405 }
1406
1407 if ( ! $this->is_editable() ) {
1408 $config['editable'] = false;
1409 }
1410
1411 return $config;
1412 }
1413
1414 /**
1415 * A Base method for sanitizing the settings before save.
1416 * This method is meant to be overridden by the element.
1417 */
1418 protected function on_save( array $settings ) {
1419 return $settings;
1420 }
1421
1422 /**
1423 * Get child type.
1424 *
1425 * Retrieve the element child type based on element data.
1426 *
1427 * @since 2.0.0
1428 * @access private
1429 *
1430 * @param array $element_data Element ID.
1431 *
1432 * @return Element_Base|false Child type or false if type not found.
1433 */
1434 private function get_child_type( $element_data ) {
1435 $child_type = $this->_get_default_child_type( $element_data );
1436
1437 // If it's not a valid widget ( like a deactivated plugin )
1438 if ( ! $child_type ) {
1439 return false;
1440 }
1441
1442 /**
1443 * Element child type.
1444 *
1445 * Filters the child type of the element.
1446 *
1447 * @since 1.0.0
1448 *
1449 * @param Element_Base $child_type The child element.
1450 * @param array $element_data The original element ID.
1451 * @param Element_Base $this The original element.
1452 */
1453 $child_type = apply_filters( 'elementor/element/get_child_type', $child_type, $element_data, $this );
1454
1455 return $child_type;
1456 }
1457
1458 /**
1459 * Initialize children.
1460 *
1461 * Initializing the element child elements.
1462 *
1463 * @since 2.0.0
1464 * @access private
1465 */
1466 private function init_children() {
1467 $this->children = [];
1468
1469 $children_data = $this->get_data( 'elements' );
1470
1471 if ( ! $children_data ) {
1472 return;
1473 }
1474
1475 foreach ( $children_data as $child_data ) {
1476 if ( ! $child_data ) {
1477 continue;
1478 }
1479
1480 $this->add_child( $child_data );
1481 }
1482 }
1483
1484 /**
1485 * Element base constructor.
1486 *
1487 * Initializing the element base class using `$data` and `$args`.
1488 *
1489 * The `$data` parameter is required for a normal instance because of the
1490 * way Elementor renders data when initializing elements.
1491 *
1492 * @since 1.0.0
1493 * @access public
1494 *
1495 * @param array $data Optional. Element data. Default is an empty array.
1496 * @param array|null $args Optional. Element default arguments. Default is null.
1497 **/
1498 public function __construct( array $data = [], array $args = null ) {
1499 if ( $data ) {
1500 $this->is_type_instance = false;
1501 } elseif ( $args ) {
1502 $this->default_args = $args;
1503 }
1504
1505 parent::__construct( $data );
1506 }
1507 }
1508