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

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