PluginProbe
Elementor Website Builder – more than just a page builder / 3.22.2
Elementor Website Builder – more than just a page builder v3.22.2
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 / widget-base.php

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

1,256 lines 33.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\Page_Assets\Data_Managers\Responsive_Widgets as Responsive_Widgets_Data_Manager;
5 use Elementor\Core\Page_Assets\Data_Managers\Widgets_Css as Widgets_Css_Data_Manager;
6 use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager;
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly.
10 }
11
12 /**
13 * Elementor widget base.
14 *
15 * An abstract class to register new Elementor widgets. It extended the
16 * `Element_Base` class to inherit its properties.
17 *
18 * This abstract class must be extended in order to register new widgets.
19 *
20 * @since 1.0.0
21 * @abstract
22 */
23 abstract class Widget_Base extends Element_Base {
24 /**
25 * Whether the widget has content.
26 *
27 * Used in cases where the widget has no content. When widgets uses only
28 * skins to display dynamic content generated on the server. For example the
29 * posts widget in Elementor Pro. Default is true, the widget has content
30 * template.
31 *
32 * @access protected
33 *
34 * @var bool
35 */
36 protected $_has_template_content = true;
37
38 private $is_first_section = true;
39
40 /**
41 * Registered Runtime Widgets.
42 *
43 * Registering in runtime all widgets that are being used on the page.
44 *
45 * @since 3.3.0
46 * @access public
47 * @static
48 *
49 * @var array
50 */
51 public static $registered_runtime_widgets = [];
52
53 public static $registered_inline_css_widgets = [];
54
55 private static $widgets_css_data_manager;
56
57 private static $responsive_widgets_data_manager;
58
59 /**
60 * Get element type.
61 *
62 * Retrieve the element type, in this case `widget`.
63 *
64 * @since 1.0.0
65 * @access public
66 * @static
67 *
68 * @return string The type.
69 */
70 public static function get_type() {
71 return 'widget';
72 }
73
74 /**
75 * Get widget icon.
76 *
77 * Retrieve the widget icon.
78 *
79 * @since 1.0.0
80 * @access public
81 *
82 * @return string Widget icon.
83 */
84 public function get_icon() {
85 return 'eicon-apps';
86 }
87
88 /**
89 * Get widget keywords.
90 *
91 * Retrieve the widget keywords.
92 *
93 * @since 1.0.10
94 * @access public
95 *
96 * @return array Widget keywords.
97 */
98 public function get_keywords() {
99 return [];
100 }
101
102 /**
103 * Get widget categories.
104 *
105 * Retrieve the widget categories.
106 *
107 * @since 1.0.10
108 * @access public
109 *
110 * @return array Widget categories.
111 */
112 public function get_categories() {
113 return [ 'general' ];
114 }
115
116 /**
117 * Get widget upsale data.
118 *
119 * Retrieve the widget promotion data.
120 *
121 * @since 3.18.0
122 * @access protected
123 *
124 * @return array|null Widget promotion data.
125 */
126 protected function get_upsale_data() {
127 return null;
128 }
129
130 /**
131 * Widget base constructor.
132 *
133 * Initializing the widget base class.
134 *
135 * @since 1.0.0
136 * @access public
137 *
138 * @throws \Exception If arguments are missing when initializing a full widget
139 * instance.
140 *
141 * @param array $data Widget data. Default is an empty array.
142 * @param array|null $args Optional. Widget default arguments. Default is null.
143 */
144 public function __construct( $data = [], $args = null ) {
145 parent::__construct( $data, $args );
146
147 $is_type_instance = $this->is_type_instance();
148
149 if ( ! $is_type_instance && null === $args ) {
150 throw new \Exception( 'An `$args` argument is required when initializing a full widget instance.' );
151 }
152
153 if ( $is_type_instance ) {
154 if ( $this->has_own_method( '_register_skins', self::class ) ) {
155 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_register_skins', '3.1.0', __CLASS__ . '::register_skins()' );
156
157 $this->_register_skins();
158 } else {
159 $this->register_skins();
160 }
161
162 $widget_name = $this->get_name();
163
164 /**
165 * Widget skin init.
166 *
167 * Fires when Elementor widget is being initialized.
168 *
169 * The dynamic portion of the hook name, `$widget_name`, refers to the widget name.
170 *
171 * @since 1.0.0
172 *
173 * @param Widget_Base $this The current widget.
174 */
175 do_action( "elementor/widget/{$widget_name}/skins_init", $this );
176 }
177 }
178
179 /**
180 * Get stack.
181 *
182 * Retrieve the widget stack of controls.
183 *
184 * @since 1.9.2
185 * @access public
186 *
187 * @param bool $with_common_controls Optional. Whether to include the common controls. Default is true.
188 *
189 * @return array Widget stack of controls.
190 */
191 public function get_stack( $with_common_controls = true ) {
192 $stack = parent::get_stack();
193
194 if ( $with_common_controls && 'common' !== $this->get_unique_name() ) {
195 /** @var Widget_Common $common_widget */
196 $common_widget = Plugin::$instance->widgets_manager->get_widget_types( 'common' );
197
198 $stack['controls'] = array_merge( $stack['controls'], $common_widget->get_controls() );
199
200 $stack['tabs'] = array_merge( $stack['tabs'], $common_widget->get_tabs_controls() );
201 }
202
203 return $stack;
204 }
205
206 /**
207 * Get widget controls pointer index.
208 *
209 * Retrieve widget pointer index where the next control should be added.
210 *
211 * While using injection point, it will return the injection point index. Otherwise index of the last control of the
212 * current widget itself without the common controls, plus one.
213 *
214 * @since 1.9.2
215 * @access public
216 *
217 * @return int Widget controls pointer index.
218 */
219 public function get_pointer_index() {
220 $injection_point = $this->get_injection_point();
221
222 if ( null !== $injection_point ) {
223 return $injection_point['index'];
224 }
225
226 return count( $this->get_stack( false )['controls'] );
227 }
228
229 /**
230 * Show in panel.
231 *
232 * Whether to show the widget in the panel or not. By default returns true.
233 *
234 * @since 1.0.0
235 * @access public
236 *
237 * @return bool Whether to show the widget in the panel or not.
238 */
239 public function show_in_panel() {
240 return true;
241 }
242
243 /**
244 * Hide on search.
245 *
246 * Whether to hide the widget on search in the panel or not. By default returns false.
247 *
248 * @access public
249 *
250 * @return bool Whether to hide the widget when searching for widget or not.
251 */
252 public function hide_on_search() {
253 return false;
254 }
255
256 /**
257 * Start widget controls section.
258 *
259 * Used to add a new section of controls to the widget. Regular controls and
260 * skin controls.
261 *
262 * Note that when you add new controls to widgets they must be wrapped by
263 * `start_controls_section()` and `end_controls_section()`.
264 *
265 * @since 1.0.0
266 * @access public
267 *
268 * @param string $section_id Section ID.
269 * @param array $args Section arguments Optional.
270 */
271 public function start_controls_section( $section_id, array $args = [] ) {
272 parent::start_controls_section( $section_id, $args );
273
274 if ( $this->is_first_section ) {
275 $this->register_skin_control();
276
277 $this->is_first_section = false;
278 }
279 }
280
281 /**
282 * Register the Skin Control if the widget has skins.
283 *
284 * An internal method that is used to add a skin control to the widget.
285 * Added at the top of the controls section.
286 *
287 * @since 2.0.0
288 * @access private
289 */
290 private function register_skin_control() {
291 $skins = $this->get_skins();
292 if ( ! empty( $skins ) ) {
293 $skin_options = [];
294
295 if ( $this->_has_template_content ) {
296 $skin_options[''] = esc_html__( 'Default', 'elementor' );
297 }
298
299 foreach ( $skins as $skin_id => $skin ) {
300 $skin_options[ $skin_id ] = $skin->get_title();
301 }
302
303 // Get the first item for default value
304 $default_value = array_keys( $skin_options );
305 $default_value = array_shift( $default_value );
306
307 if ( 1 >= count( $skin_options ) ) {
308 $this->add_control(
309 '_skin',
310 [
311 'label' => esc_html__( 'Skin', 'elementor' ),
312 'type' => Controls_Manager::HIDDEN,
313 'default' => $default_value,
314 ]
315 );
316 } else {
317 $this->add_control(
318 '_skin',
319 [
320 'label' => esc_html__( 'Skin', 'elementor' ),
321 'type' => Controls_Manager::SELECT,
322 'default' => $default_value,
323 'options' => $skin_options,
324 ]
325 );
326 }
327 }
328 }
329
330 /**
331 * Register widget skins - deprecated prefixed method
332 *
333 * @since 1.7.12
334 * @access protected
335 * @deprecated 3.1.0 Use `register_skins()` method instead.
336 */
337 protected function _register_skins() {
338 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', 'register_skins()' );
339
340 $this->register_skins();
341 }
342
343 /**
344 * Register widget skins.
345 *
346 * This method is activated while initializing the widget base class. It is
347 * used to assign skins to widgets with `add_skin()` method.
348 *
349 * Usage:
350 *
351 * protected function register_skins() {
352 * $this->add_skin( new Skin_Classic( $this ) );
353 * }
354 *
355 * @since 3.1.0
356 * @access protected
357 */
358 protected function register_skins() {}
359
360 /**
361 * Get initial config.
362 *
363 * Retrieve the current widget initial configuration.
364 *
365 * Adds more configuration on top of the controls list, the tabs assigned to
366 * the control, element name, type, icon and more. This method also adds
367 * widget type, keywords and categories.
368 *
369 * @since 2.9.0
370 * @access protected
371 *
372 * @return array The initial widget config.
373 */
374 protected function get_initial_config() {
375 $config = [
376 'widget_type' => $this->get_name(),
377 'keywords' => $this->get_keywords(),
378 'categories' => $this->get_categories(),
379 'html_wrapper_class' => $this->get_html_wrapper_class(),
380 'show_in_panel' => $this->show_in_panel(),
381 'hide_on_search' => $this->hide_on_search(),
382 'upsale_data' => $this->get_upsale_data(),
383 'is_dynamic_content' => $this->is_dynamic_content(),
384 ];
385
386 if ( isset( $config['upsale_data'] ) && is_array( $config['upsale_data'] ) ) {
387 $filter_name = 'elementor/widgets/' . $this->get_name() . '/custom_promotion';
388 $config['upsale_data'] = Filtered_Promotions_Manager::get_filtered_promotion_data( $config['upsale_data'], $filter_name, 'upgrade_url' );
389 }
390
391 if ( isset( $config['upsale_data']['image'] ) ) {
392 $config['upsale_data']['image'] = esc_url( $config['upsale_data']['image'] );
393 }
394
395 $stack = Plugin::$instance->controls_manager->get_element_stack( $this );
396
397 if ( $stack ) {
398 $config['controls'] = $this->get_stack( false )['controls'];
399 $config['tabs_controls'] = $this->get_tabs_controls();
400 }
401
402 return array_replace_recursive( parent::get_initial_config(), $config );
403 }
404
405 /**
406 * @since 2.3.1
407 * @access protected
408 */
409 protected function should_print_empty() {
410 return false;
411 }
412
413 /**
414 * Print widget content template.
415 *
416 * Used to generate the widget content template on the editor, using a
417 * Backbone JavaScript template.
418 *
419 * @since 2.0.0
420 * @access protected
421 *
422 * @param string $template_content Template content.
423 */
424 protected function print_template_content( $template_content ) {
425 ?>
426 <div class="elementor-widget-container">
427 <?php
428 echo $template_content; // XSS ok.
429 ?>
430 </div>
431 <?php
432 }
433
434 /**
435 * Parse text editor.
436 *
437 * Parses the content from rich text editor with shortcodes, oEmbed and
438 * filtered data.
439 *
440 * @since 1.0.0
441 * @access protected
442 *
443 * @param string $content Text editor content.
444 *
445 * @return string Parsed content.
446 */
447 protected function parse_text_editor( $content ) {
448 /** This filter is documented in wp-includes/widgets/class-wp-widget-text.php */
449 $content = apply_filters( 'widget_text', $content, $this->get_settings() );
450
451 $content = shortcode_unautop( $content );
452 $content = do_shortcode( $content );
453 $content = wptexturize( $content );
454
455 if ( $GLOBALS['wp_embed'] instanceof \WP_Embed ) {
456 $content = $GLOBALS['wp_embed']->autoembed( $content );
457 }
458
459 return $content;
460 }
461
462 /**
463 * Safe print parsed text editor.
464 *
465 * @uses static::parse_text_editor.
466 *
467 * @access protected
468 *
469 * @param string $content Text editor content.
470 */
471 final protected function print_text_editor( $content ) {
472 // PHPCS - the method `parse_text_editor` is safe.
473 echo static::parse_text_editor( $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
474 }
475
476 /**
477 * Get HTML wrapper class.
478 *
479 * Retrieve the widget container class. Can be used to override the
480 * container class for specific widgets.
481 *
482 * @since 2.0.9
483 * @access protected
484 */
485 protected function get_html_wrapper_class() {
486 return 'elementor-widget-' . $this->get_name();
487 }
488
489 /**
490 * Add widget render attributes.
491 *
492 * Used to add attributes to the current widget wrapper HTML tag.
493 *
494 * @since 1.0.0
495 * @access protected
496 */
497 protected function add_render_attributes() {
498 parent::add_render_attributes();
499
500 $this->add_render_attribute(
501 '_wrapper', 'class', [
502 'elementor-widget',
503 $this->get_html_wrapper_class(),
504 ]
505 );
506
507 $settings = $this->get_settings();
508
509 $this->add_render_attribute( '_wrapper', 'data-widget_type', $this->get_name() . '.' . ( ! empty( $settings['_skin'] ) ? $settings['_skin'] : 'default' ) );
510 }
511
512 /**
513 * Add lightbox data to image link.
514 *
515 * Used to add lightbox data attributes to image link HTML.
516 *
517 * @since 2.9.1
518 * @access public
519 *
520 * @param string $link_html Image link HTML.
521 * @param string $id Attachment id.
522 *
523 * @return string Image link HTML with lightbox data attributes.
524 */
525 public function add_lightbox_data_to_image_link( $link_html, $id ) {
526 $settings = $this->get_settings_for_display();
527 $open_lightbox = isset( $settings['open_lightbox'] ) ? $settings['open_lightbox'] : null;
528
529 if ( Plugin::$instance->editor->is_edit_mode() ) {
530 $this->add_render_attribute( 'link', 'class', 'elementor-clickable', true );
531 }
532
533 $this->add_lightbox_data_attributes( 'link', $id, $open_lightbox, $this->get_id(), true );
534 return preg_replace( '/^<a/', '<a ' . $this->get_render_attribute_string( 'link' ), $link_html );
535 }
536
537 /**
538 * Add Light-Box attributes.
539 *
540 * Used to add Light-Box-related data attributes to links that open media files.
541 *
542 * @param array|string $element The link HTML element.
543 * @param int $id The ID of the image
544 * @param string $lightbox_setting_key The setting key that dictates weather to open the image in a lightbox
545 * @param string $group_id Unique ID for a group of lightbox images
546 * @param bool $overwrite Optional. Whether to overwrite existing
547 * attribute. Default is false, not to overwrite.
548 *
549 * @return Widget_Base Current instance of the widget.
550 * @since 2.9.0
551 * @access public
552 *
553 */
554 public function add_lightbox_data_attributes( $element, $id = null, $lightbox_setting_key = null, $group_id = null, $overwrite = false ) {
555 $kit = Plugin::$instance->kits_manager->get_active_kit();
556
557 $is_global_image_lightbox_enabled = 'yes' === $kit->get_settings( 'global_image_lightbox' );
558
559 if ( 'no' === $lightbox_setting_key ) {
560 if ( $is_global_image_lightbox_enabled ) {
561 $this->add_render_attribute( $element, 'data-elementor-open-lightbox', 'no', $overwrite );
562 }
563
564 return $this;
565 }
566
567 if ( 'yes' !== $lightbox_setting_key && ! $is_global_image_lightbox_enabled ) {
568 return $this;
569 }
570
571 $attributes['data-elementor-open-lightbox'] = 'yes';
572
573 $action_hash_params = [];
574
575 if ( $id ) {
576 $action_hash_params['id'] = $id;
577 $action_hash_params['url'] = wp_get_attachment_url( $id );
578 }
579
580 if ( $group_id ) {
581 $attributes['data-elementor-lightbox-slideshow'] = $group_id;
582
583 $action_hash_params['slideshow'] = $group_id;
584 }
585
586 if ( $id ) {
587 $lightbox_image_attributes = Plugin::$instance->images_manager->get_lightbox_image_attributes( $id );
588
589 if ( isset( $lightbox_image_attributes['title'] ) ) {
590 $attributes['data-elementor-lightbox-title'] = $lightbox_image_attributes['title'];
591 }
592
593 if ( isset( $lightbox_image_attributes['description'] ) ) {
594 $attributes['data-elementor-lightbox-description'] = $lightbox_image_attributes['description'];
595 }
596 }
597
598 $attributes['data-e-action-hash'] = Plugin::instance()->frontend->create_action_hash( 'lightbox', $action_hash_params );
599
600 $this->add_render_attribute( $element, $attributes, null, $overwrite );
601
602 return $this;
603 }
604
605 /**
606 * Render widget output on the frontend.
607 *
608 * Used to generate the final HTML displayed on the frontend.
609 *
610 * Note that if skin is selected, it will be rendered by the skin itself,
611 * not the widget.
612 *
613 * @since 1.0.0
614 * @access public
615 */
616 public function render_content() {
617 /**
618 * Before widget render content.
619 *
620 * Fires before Elementor widget is being rendered.
621 *
622 * @since 1.0.0
623 *
624 * @param Widget_Base $this The current widget.
625 */
626 do_action( 'elementor/widget/before_render_content', $this );
627
628 ob_start();
629
630 $skin = $this->get_current_skin();
631 if ( $skin ) {
632 $skin->set_parent( $this );
633 $skin->render_by_mode();
634 } else {
635 $this->render_by_mode();
636 }
637
638 $widget_content = ob_get_clean();
639
640 if ( empty( $widget_content ) ) {
641 return;
642 }
643 ?>
644 <div class="elementor-widget-container">
645 <?php
646 if ( $this->is_widget_first_render( $this->get_group_name() ) ) {
647 $this->register_runtime_widget( $this->get_group_name() );
648 }
649
650 $this->print_widget_css();
651
652 // get_name
653
654 /**
655 * Render widget content.
656 *
657 * Filters the widget content before it's rendered.
658 *
659 * @since 1.0.0
660 *
661 * @param string $widget_content The content of the widget.
662 * @param Widget_Base $this The widget.
663 */
664 $widget_content = apply_filters( 'elementor/widget/render_content', $widget_content, $this );
665
666 echo $widget_content; // XSS ok.
667 ?>
668 </div>
669 <?php
670 }
671
672 protected function is_widget_first_render( $widget_name ) {
673 return ! in_array( $widget_name, self::$registered_runtime_widgets, true );
674 }
675
676 /**
677 * Render widget plain content.
678 *
679 * Elementor saves the page content in a unique way, but it's not the way
680 * WordPress saves data. This method is used to save generated HTML to the
681 * database as plain content the WordPress way.
682 *
683 * When rendering plain content, it allows other WordPress plugins to
684 * interact with the content - to search, check SEO and other purposes. It
685 * also allows the site to keep working even if Elementor is deactivated.
686 *
687 * Note that if the widget uses shortcodes to display the data, the best
688 * practice is to return the shortcode itself.
689 *
690 * Also note that if the widget don't display any content it should return
691 * an empty string. For example Elementor Pro Form Widget uses this method
692 * to return an empty string because there is no content to return. This way
693 * if Elementor Pro will be deactivated there won't be any form to display.
694 *
695 * @since 1.0.0
696 * @access public
697 */
698 public function render_plain_content() {
699 $this->render_content();
700 }
701
702 /**
703 * Before widget rendering.
704 *
705 * Used to add stuff before the widget `_wrapper` element.
706 *
707 * @since 1.0.0
708 * @access public
709 */
710 public function before_render() {
711 ?>
712 <div <?php $this->print_render_attribute_string( '_wrapper' ); ?>>
713 <?php
714 }
715
716 /**
717 * After widget rendering.
718 *
719 * Used to add stuff after the widget `_wrapper` element.
720 *
721 * @since 1.0.0
722 * @access public
723 */
724 public function after_render() {
725 ?>
726 </div>
727 <?php
728 }
729
730 /**
731 * Get the element raw data.
732 *
733 * Retrieve the raw element data, including the id, type, settings, child
734 * elements and whether it is an inner element.
735 *
736 * The data with the HTML used always to display the data, but the Elementor
737 * editor uses the raw data without the HTML in order not to render the data
738 * again.
739 *
740 * @since 1.0.0
741 * @access public
742 *
743 * @param bool $with_html_content Optional. Whether to return the data with
744 * HTML content or without. Used for caching.
745 * Default is false, without HTML.
746 *
747 * @return array Element raw data.
748 */
749 public function get_raw_data( $with_html_content = false ) {
750 $data = parent::get_raw_data( $with_html_content );
751
752 unset( $data['isInner'] );
753
754 $data['widgetType'] = $this->get_data( 'widgetType' );
755
756 if ( $with_html_content ) {
757 ob_start();
758
759 $this->render_content();
760
761 $data['htmlCache'] = ob_get_clean();
762 }
763
764 return $data;
765 }
766
767 /**
768 * Print widget content.
769 *
770 * Output the widget final HTML on the frontend.
771 *
772 * @since 1.0.0
773 * @access protected
774 */
775 protected function print_content() {
776 $this->render_content();
777 }
778
779 /**
780 * Print a setting content without escaping.
781 *
782 * Script tags are allowed on frontend according to the WP theme securing policy.
783 *
784 * @param string $setting
785 * @param null $repeater_name
786 * @param null $index
787 */
788 final public function print_unescaped_setting( $setting, $repeater_name = null, $index = null ) {
789 if ( $repeater_name ) {
790 $repeater = $this->get_settings_for_display( $repeater_name );
791 $output = $repeater[ $index ][ $setting ];
792 } else {
793 $output = $this->get_settings_for_display( $setting );
794 }
795
796 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
797 }
798
799 /**
800 * Get default data.
801 *
802 * Retrieve the default widget data. Used to reset the data on initialization.
803 *
804 * @since 1.0.0
805 * @access protected
806 *
807 * @return array Default data.
808 */
809 protected function get_default_data() {
810 $data = parent::get_default_data();
811
812 $data['widgetType'] = '';
813
814 return $data;
815 }
816
817 /**
818 * Get default child type.
819 *
820 * Retrieve the widget child type based on element data.
821 *
822 * @since 1.0.0
823 * @access protected
824 *
825 * @param array $element_data Widget ID.
826 *
827 * @return array|false Child type or false if it's not a valid widget.
828 */
829 protected function _get_default_child_type( array $element_data ) {
830 return Plugin::$instance->elements_manager->get_element_types( 'section' );
831 }
832
833 /**
834 * Get repeater setting key.
835 *
836 * Retrieve the unique setting key for the current repeater item. Used to connect the current element in the
837 * repeater to it's settings model and it's control in the panel.
838 *
839 * PHP usage (inside `Widget_Base::render()` method):
840 *
841 * $tabs = $this->get_settings( 'tabs' );
842 * foreach ( $tabs as $index => $item ) {
843 * $tab_title_setting_key = $this->get_repeater_setting_key( 'tab_title', 'tabs', $index );
844 * $this->add_inline_editing_attributes( $tab_title_setting_key, 'none' );
845 * echo '<div ' . $this->get_render_attribute_string( $tab_title_setting_key ) . '>' . $item['tab_title'] . '</div>';
846 * }
847 *
848 * @since 1.8.0
849 * @access protected
850 *
851 * @param string $setting_key The current setting key inside the repeater item (e.g. `tab_title`).
852 * @param string $repeater_key The repeater key containing the array of all the items in the repeater (e.g. `tabs`).
853 * @param int $repeater_item_index The current item index in the repeater array (e.g. `3`).
854 *
855 * @return string The repeater setting key (e.g. `tabs.3.tab_title`).
856 */
857 protected function get_repeater_setting_key( $setting_key, $repeater_key, $repeater_item_index ) {
858 return implode( '.', [ $repeater_key, $repeater_item_index, $setting_key ] );
859 }
860
861 /**
862 * Add inline editing attributes.
863 *
864 * Define specific area in the element to be editable inline. The element can have several areas, with this method
865 * you can set the area inside the element that can be edited inline. You can also define the type of toolbar the
866 * user will see, whether it will be a basic toolbar or an advanced one.
867 *
868 * Note: When you use wysiwyg control use the advanced toolbar, with textarea control use the basic toolbar. Text
869 * control should not have toolbar.
870 *
871 * PHP usage (inside `Widget_Base::render()` method):
872 *
873 * $this->add_inline_editing_attributes( 'text', 'advanced' );
874 * echo '<div ' . $this->get_render_attribute_string( 'text' ) . '>' . $this->get_settings( 'text' ) . '</div>';
875 *
876 * @since 1.8.0
877 * @access protected
878 *
879 * @param string $key Element key.
880 * @param string $toolbar Optional. Toolbar type. Accepted values are `advanced`, `basic` or `none`. Default is
881 * `basic`.
882 */
883 protected function add_inline_editing_attributes( $key, $toolbar = 'basic' ) {
884 if ( ! Plugin::$instance->editor->is_edit_mode() ) {
885 return;
886 }
887
888 $this->add_render_attribute( $key, [
889 'class' => 'elementor-inline-editing',
890 'data-elementor-setting-key' => $key,
891 ] );
892
893 if ( 'basic' !== $toolbar ) {
894 $this->add_render_attribute( $key, [
895 'data-elementor-inline-editing-toolbar' => $toolbar,
896 ] );
897 }
898 }
899
900 /**
901 * Add new skin.
902 *
903 * Register new widget skin to allow the user to set custom designs. Must be
904 * called inside the `register_skins()` method.
905 *
906 * @since 1.0.0
907 * @access public
908 *
909 * @param Skin_Base $skin Skin instance.
910 */
911 public function add_skin( Skin_Base $skin ) {
912 Plugin::$instance->skins_manager->add_skin( $this, $skin );
913 }
914
915 /**
916 * Get single skin.
917 *
918 * Retrieve a single skin based on skin ID, from all the skin assigned to
919 * the widget. If the skin does not exist or not assigned to the widget,
920 * return false.
921 *
922 * @since 1.0.0
923 * @access public
924 *
925 * @param string $skin_id Skin ID.
926 *
927 * @return string|false Single skin, or false.
928 */
929 public function get_skin( $skin_id ) {
930 $skins = $this->get_skins();
931 if ( isset( $skins[ $skin_id ] ) ) {
932 return $skins[ $skin_id ];
933 }
934
935 return false;
936 }
937
938 /**
939 * Get current skin ID.
940 *
941 * Retrieve the ID of the current skin.
942 *
943 * @since 1.0.0
944 * @access public
945 *
946 * @return string Current skin.
947 */
948 public function get_current_skin_id() {
949 return $this->get_settings( '_skin' );
950 }
951
952 /**
953 * Get current skin.
954 *
955 * Retrieve the current skin, or if non exist return false.
956 *
957 * @since 1.0.0
958 * @access public
959 *
960 * @return Skin_Base|false Current skin or false.
961 */
962 public function get_current_skin() {
963 return $this->get_skin( $this->get_current_skin_id() );
964 }
965
966 /**
967 * Remove widget skin.
968 *
969 * Unregister an existing skin and remove it from the widget.
970 *
971 * @since 1.0.0
972 * @access public
973 *
974 * @param string $skin_id Skin ID.
975 *
976 * @return \WP_Error|true Whether the skin was removed successfully from the widget.
977 */
978 public function remove_skin( $skin_id ) {
979 return Plugin::$instance->skins_manager->remove_skin( $this, $skin_id );
980 }
981
982 /**
983 * Get widget skins.
984 *
985 * Retrieve all the skin assigned to the widget.
986 *
987 * @since 1.0.0
988 * @access public
989 *
990 * @return Skin_Base[]
991 */
992 public function get_skins() {
993 return Plugin::$instance->skins_manager->get_skins( $this );
994 }
995
996 /**
997 * Get group name.
998 *
999 * Some widgets need to use group names, this method allows you to create them.
1000 * By default it retrieves the regular name.
1001 *
1002 * @since 3.3.0
1003 * @access public
1004 *
1005 * @return string Unique name.
1006 */
1007 public function get_group_name() {
1008 return $this->get_name();
1009 }
1010
1011 /**
1012 * Get Inline CSS dependencies.
1013 *
1014 * Retrieve a list of inline CSS dependencies that the element requires.
1015 *
1016 * @since 3.3.0
1017 * @access public
1018 *
1019 * @return array.
1020 */
1021 public function get_inline_css_depends() {
1022 return [];
1023 }
1024
1025 /**
1026 * @param string $plugin_title Plugin's title
1027 * @param string $since Plugin version widget was deprecated
1028 * @param string $last Plugin version in which the widget will be removed
1029 * @param string $replacement Widget replacement
1030 */
1031 protected function deprecated_notice( $plugin_title, $since, $last = '', $replacement = '' ) {
1032 $this->start_controls_section(
1033 'Deprecated',
1034 [
1035 'label' => esc_html__( 'Deprecated', 'elementor' ),
1036 ]
1037 );
1038
1039 $this->add_control(
1040 'deprecated_notice',
1041 [
1042 'type' => Controls_Manager::DEPRECATED_NOTICE,
1043 'widget' => $this->get_title(),
1044 'since' => $since,
1045 'last' => $last,
1046 'plugin' => $plugin_title,
1047 'replacement' => $replacement,
1048 ]
1049 );
1050
1051 $this->end_controls_section();
1052 }
1053
1054 /**
1055 * Init controls.
1056 *
1057 * Reset the `is_first_section` flag to true, so when the Stacks are cleared
1058 * all the controls will be registered again with their skins and settings.
1059 *
1060 * @since 3.14.0
1061 * @access protected
1062 */
1063 protected function init_controls() {
1064 $this->is_first_section = true;
1065 parent::init_controls();
1066 }
1067
1068 public function register_runtime_widget( $widget_name ) {
1069 self::$registered_runtime_widgets[] = $widget_name;
1070 }
1071
1072 public function get_widget_css_config( $widget_name ) {
1073 $direction = is_rtl() ? '-rtl' : '';
1074
1075 $has_custom_breakpoints = $this->is_custom_breakpoints_widget();
1076
1077 $file_name = 'widget-' . $widget_name . $direction . '.min.css';
1078
1079 // The URL of the widget's external CSS file that is loaded in case that the CSS content is too large to be printed inline.
1080 $file_url = Plugin::$instance->frontend->get_frontend_file_url( $file_name, $has_custom_breakpoints );
1081
1082 // The local path of the widget's CSS file that is being read and saved in the DB when the CSS content should be printed inline.
1083 $file_path = Plugin::$instance->frontend->get_frontend_file_path( $file_name, $has_custom_breakpoints );
1084
1085 return [
1086 'key' => $widget_name,
1087 'version' => ELEMENTOR_VERSION,
1088 'file_path' => $file_path,
1089 'data' => [
1090 'file_url' => $file_url,
1091 ],
1092 ];
1093 }
1094
1095 public function get_css_config() {
1096 return $this->get_widget_css_config( $this->get_group_name() );
1097 }
1098
1099 public function get_responsive_widgets_config() {
1100 $responsive_widgets_data_manager = $this->get_responsive_widgets_data_manager();
1101
1102 return [
1103 'key' => $responsive_widgets_data_manager::RESPONSIVE_WIDGETS_DATABASE_KEY,
1104 'version' => ELEMENTOR_VERSION,
1105 'file_path' => ELEMENTOR_ASSETS_PATH . $responsive_widgets_data_manager::RESPONSIVE_WIDGETS_FILE_PATH,
1106 ];
1107 }
1108
1109 public function get_responsive_widgets() {
1110 $responsive_widgets_data_manager = $this->get_responsive_widgets_data_manager();
1111
1112 $config = $this->get_responsive_widgets_config();
1113
1114 return $responsive_widgets_data_manager->get_asset_data_from_config( $config );
1115 }
1116
1117 /**
1118 * Mark widget as deprecated.
1119 *
1120 * Use `get_deprecation_message()` method to print the message control at specific location in register_controls().
1121 *
1122 * @param $version string The version of Elementor that deprecated the widget.
1123 * @param $message string A message regarding the deprecation.
1124 * @param $replacement string The widget that should be used instead.
1125 */
1126 protected function add_deprecation_message( $version, $message, $replacement ) {
1127 // Expose the config for handling in JS.
1128 $this->set_config( 'deprecation', [
1129 'version' => $version,
1130 'message' => $message,
1131 'replacement' => $replacement,
1132 ] );
1133
1134 $this->add_control(
1135 'deprecation_message',
1136 [
1137 'type' => Controls_Manager::ALERT,
1138 'alert_type' => 'info',
1139 'content' => $message,
1140 'separator' => 'after',
1141 ]
1142 );
1143 }
1144
1145 /**
1146 * Get Responsive Widgets Data Manager.
1147 *
1148 * Retrieve the data manager that handles widgets that are using media queries for custom-breakpoints values.
1149 *
1150 * @since 3.5.0
1151 * @access protected
1152 *
1153 * @return Responsive_Widgets_Data_Manager
1154 */
1155 protected function get_responsive_widgets_data_manager() {
1156 if ( ! self::$responsive_widgets_data_manager ) {
1157 self::$responsive_widgets_data_manager = new Responsive_Widgets_Data_Manager();
1158 }
1159
1160 return self::$responsive_widgets_data_manager;
1161 }
1162
1163 /**
1164 * Is Custom Breakpoints Widget.
1165 *
1166 * Checking if there are active custom-breakpoints and if the widget use them.
1167 *
1168 * @since 3.5.0
1169 * @access protected
1170 *
1171 * @return boolean
1172 */
1173 protected function is_custom_breakpoints_widget() {
1174 $has_custom_breakpoints = Plugin::$instance->breakpoints->has_custom_breakpoints();
1175
1176 if ( $has_custom_breakpoints ) {
1177 $responsive_widgets = $this->get_responsive_widgets();
1178
1179 // The $widget_name can also represents a widgets group name, therefore we need to use the current widget name to check if it's responsive widget.
1180 $current_widget_name = $this->get_name();
1181
1182 // If the widget is not implementing custom-breakpoints media queries then it has no custom- css file.
1183 if ( ! isset( $responsive_widgets[ $current_widget_name ] ) ) {
1184 $has_custom_breakpoints = false;
1185 }
1186 }
1187
1188 return $has_custom_breakpoints;
1189 }
1190
1191 private function get_widget_css() {
1192 $widgets_css_data_manager = $this->get_widgets_css_data_manager();
1193
1194 $widgets_list = $this->get_inline_css_depends();
1195
1196 $widgets_list[] = $this->get_group_name();
1197
1198 $widget_css = '';
1199
1200 foreach ( $widgets_list as $widget_data ) {
1201 $widget_name = isset( $widget_data['name'] ) ? $widget_data['name'] : $widget_data;
1202
1203 if ( ! in_array( $widget_name, self::$registered_inline_css_widgets, true ) ) {
1204 if ( $this->get_group_name() === $widget_name ) {
1205 $config = $this->get_css_config();
1206 } else {
1207 /**
1208 * The core-dependency allowing to create a dependency specifically with the core widgets.
1209 * Otherwise, the config will be taken from the class that inherits from Widget_Base.
1210 */
1211 $is_core_dependency = isset( $widget_data['is_core_dependency'] ) ? true : false;
1212
1213 $config = $is_core_dependency ? self::get_widget_css_config( $widget_name ) : $this->get_widget_css_config( $widget_name );
1214 }
1215
1216 $widget_css .= $widgets_css_data_manager->get_asset_data_from_config( $config );
1217
1218 self::$registered_inline_css_widgets[] = $widget_name;
1219 }
1220 }
1221
1222 return $widget_css;
1223
1224 }
1225
1226 private function is_inline_css_mode() {
1227 static $is_active;
1228
1229 if ( null === $is_active ) {
1230 $is_edit_mode = Plugin::$instance->editor->is_edit_mode();
1231 $is_preview_mode = Plugin::$instance->preview->is_preview_mode();
1232 $is_optimized_mode = Plugin::$instance->experiments->is_feature_active( 'e_optimized_css_loading' );
1233
1234 $is_active = ( Utils::is_script_debug() || $is_edit_mode || $is_preview_mode || ! $is_optimized_mode ) ? false : true;
1235 }
1236
1237 return $is_active;
1238 }
1239
1240 private function print_widget_css() {
1241 if ( ! $this->is_inline_css_mode() ) {
1242 return;
1243 }
1244
1245 Utils::print_unescaped_internal_string( $this->get_widget_css() );
1246 }
1247
1248 private function get_widgets_css_data_manager() {
1249 if ( ! self::$widgets_css_data_manager ) {
1250 self::$widgets_css_data_manager = new Widgets_Css_Data_Manager();
1251 }
1252
1253 return self::$widgets_css_data_manager;
1254 }
1255 }
1256