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

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