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

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

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