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

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