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

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