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

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

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