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

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