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

1,084 lines 28.3 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' => $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 /**
701 * Before widget rendering.
702 *
703 * Used to add stuff before the widget `_wrapper` element.
704 *
705 * @since 1.0.0
706 * @access public
707 */
708 public function before_render() {
709 ?>
710 <div <?php $this->print_render_attribute_string( '_wrapper' ); ?>>
711 <?php
712 }
713
714 /**
715 * After widget rendering.
716 *
717 * Used to add stuff after the widget `_wrapper` element.
718 *
719 * @since 1.0.0
720 * @access public
721 */
722 public function after_render() {
723 ?>
724 </div>
725 <?php
726 }
727
728 /**
729 * Get the element raw data.
730 *
731 * Retrieve the raw element data, including the id, type, settings, child
732 * elements and whether it is an inner element.
733 *
734 * The data with the HTML used always to display the data, but the Elementor
735 * editor uses the raw data without the HTML in order not to render the data
736 * again.
737 *
738 * @since 1.0.0
739 * @access public
740 *
741 * @param bool $with_html_content Optional. Whether to return the data with
742 * HTML content or without. Used for caching.
743 * Default is false, without HTML.
744 *
745 * @return array Element raw data.
746 */
747 public function get_raw_data( $with_html_content = false ) {
748 $data = parent::get_raw_data( $with_html_content );
749
750 unset( $data['isInner'] );
751
752 $data['widgetType'] = $this->get_data( 'widgetType' );
753
754 if ( $with_html_content ) {
755 ob_start();
756
757 $this->render_content();
758
759 $data['htmlCache'] = ob_get_clean();
760 }
761
762 return $data;
763 }
764
765 /**
766 * Print widget content.
767 *
768 * Output the widget final HTML on the frontend.
769 *
770 * @since 1.0.0
771 * @access protected
772 */
773 protected function print_content() {
774 $this->render_content();
775 }
776
777 /**
778 * Print a setting content without escaping.
779 *
780 * Script tags are allowed on frontend according to the WP theme securing policy.
781 *
782 * @param string $setting
783 * @param null $repeater_name
784 * @param null $index
785 */
786 final public function print_unescaped_setting( $setting, $repeater_name = null, $index = null ) {
787 if ( $repeater_name ) {
788 $repeater = $this->get_settings_for_display( $repeater_name );
789 $output = $repeater[ $index ][ $setting ];
790 } else {
791 $output = $this->get_settings_for_display( $setting );
792 }
793
794 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
795 }
796
797 /**
798 * Get default data.
799 *
800 * Retrieve the default widget data. Used to reset the data on initialization.
801 *
802 * @since 1.0.0
803 * @access protected
804 *
805 * @return array Default data.
806 */
807 protected function get_default_data() {
808 $data = parent::get_default_data();
809
810 $data['widgetType'] = '';
811
812 return $data;
813 }
814
815 /**
816 * Get default child type.
817 *
818 * Retrieve the widget child type based on element data.
819 *
820 * @since 1.0.0
821 * @access protected
822 *
823 * @param array $element_data Widget ID.
824 *
825 * @return array|false Child type or false if it's not a valid widget.
826 */
827 protected function _get_default_child_type( array $element_data ) {
828 return Plugin::$instance->elements_manager->get_element_types( 'section' );
829 }
830
831 /**
832 * Get repeater setting key.
833 *
834 * Retrieve the unique setting key for the current repeater item. Used to connect the current element in the
835 * repeater to it's settings model and it's control in the panel.
836 *
837 * PHP usage (inside `Widget_Base::render()` method):
838 *
839 * $tabs = $this->get_settings( 'tabs' );
840 * foreach ( $tabs as $index => $item ) {
841 * $tab_title_setting_key = $this->get_repeater_setting_key( 'tab_title', 'tabs', $index );
842 * $this->add_inline_editing_attributes( $tab_title_setting_key, 'none' );
843 * echo '<div ' . $this->get_render_attribute_string( $tab_title_setting_key ) . '>' . $item['tab_title'] . '</div>';
844 * }
845 *
846 * @since 1.8.0
847 * @access protected
848 *
849 * @param string $setting_key The current setting key inside the repeater item (e.g. `tab_title`).
850 * @param string $repeater_key The repeater key containing the array of all the items in the repeater (e.g. `tabs`).
851 * @param int $repeater_item_index The current item index in the repeater array (e.g. `3`).
852 *
853 * @return string The repeater setting key (e.g. `tabs.3.tab_title`).
854 */
855 protected function get_repeater_setting_key( $setting_key, $repeater_key, $repeater_item_index ) {
856 return implode( '.', [ $repeater_key, $repeater_item_index, $setting_key ] );
857 }
858
859 /**
860 * Add inline editing attributes.
861 *
862 * Define specific area in the element to be editable inline. The element can have several areas, with this method
863 * you can set the area inside the element that can be edited inline. You can also define the type of toolbar the
864 * user will see, whether it will be a basic toolbar or an advanced one.
865 *
866 * Note: When you use wysiwyg control use the advanced toolbar, with textarea control use the basic toolbar. Text
867 * control should not have toolbar.
868 *
869 * PHP usage (inside `Widget_Base::render()` method):
870 *
871 * $this->add_inline_editing_attributes( 'text', 'advanced' );
872 * echo '<div ' . $this->get_render_attribute_string( 'text' ) . '>' . $this->get_settings( 'text' ) . '</div>';
873 *
874 * @since 1.8.0
875 * @access protected
876 *
877 * @param string $key Element key.
878 * @param string $toolbar Optional. Toolbar type. Accepted values are `advanced`, `basic` or `none`. Default is
879 * `basic`.
880 */
881 protected function add_inline_editing_attributes( $key, $toolbar = 'basic' ) {
882 if ( ! Plugin::$instance->editor->is_edit_mode() ) {
883 return;
884 }
885
886 $this->add_render_attribute( $key, [
887 'class' => 'elementor-inline-editing',
888 'data-elementor-setting-key' => $key,
889 ] );
890
891 if ( 'basic' !== $toolbar ) {
892 $this->add_render_attribute( $key, [
893 'data-elementor-inline-editing-toolbar' => $toolbar,
894 ] );
895 }
896 }
897
898 /**
899 * Add new skin.
900 *
901 * Register new widget skin to allow the user to set custom designs. Must be
902 * called inside the `register_skins()` method.
903 *
904 * @since 1.0.0
905 * @access public
906 *
907 * @param Skin_Base $skin Skin instance.
908 */
909 public function add_skin( Skin_Base $skin ) {
910 Plugin::$instance->skins_manager->add_skin( $this, $skin );
911 }
912
913 /**
914 * Get single skin.
915 *
916 * Retrieve a single skin based on skin ID, from all the skin assigned to
917 * the widget. If the skin does not exist or not assigned to the widget,
918 * return false.
919 *
920 * @since 1.0.0
921 * @access public
922 *
923 * @param string $skin_id Skin ID.
924 *
925 * @return string|false Single skin, or false.
926 */
927 public function get_skin( $skin_id ) {
928 $skins = $this->get_skins();
929 if ( isset( $skins[ $skin_id ] ) ) {
930 return $skins[ $skin_id ];
931 }
932
933 return false;
934 }
935
936 /**
937 * Get current skin ID.
938 *
939 * Retrieve the ID of the current skin.
940 *
941 * @since 1.0.0
942 * @access public
943 *
944 * @return string Current skin.
945 */
946 public function get_current_skin_id() {
947 return $this->get_settings( '_skin' );
948 }
949
950 /**
951 * Get current skin.
952 *
953 * Retrieve the current skin, or if non exist return false.
954 *
955 * @since 1.0.0
956 * @access public
957 *
958 * @return Skin_Base|false Current skin or false.
959 */
960 public function get_current_skin() {
961 return $this->get_skin( $this->get_current_skin_id() );
962 }
963
964 /**
965 * Remove widget skin.
966 *
967 * Unregister an existing skin and remove it from the widget.
968 *
969 * @since 1.0.0
970 * @access public
971 *
972 * @param string $skin_id Skin ID.
973 *
974 * @return \WP_Error|true Whether the skin was removed successfully from the widget.
975 */
976 public function remove_skin( $skin_id ) {
977 return Plugin::$instance->skins_manager->remove_skin( $this, $skin_id );
978 }
979
980 /**
981 * Get widget skins.
982 *
983 * Retrieve all the skin assigned to the widget.
984 *
985 * @since 1.0.0
986 * @access public
987 *
988 * @return Skin_Base[]
989 */
990 public function get_skins() {
991 return Plugin::$instance->skins_manager->get_skins( $this );
992 }
993
994 /**
995 * Get group name.
996 *
997 * Some widgets need to use group names, this method allows you to create them.
998 * By default it retrieves the regular name.
999 *
1000 * @since 3.3.0
1001 * @access public
1002 *
1003 * @return string Unique name.
1004 */
1005 public function get_group_name() {
1006 return $this->get_name();
1007 }
1008
1009 /**
1010 * @param string $plugin_title Plugin's title.
1011 * @param string $since Plugin version widget was deprecated.
1012 * @param string $last Plugin version in which the widget will be removed.
1013 * @param string $replacement Widget replacement.
1014 */
1015 protected function deprecated_notice( $plugin_title, $since, $last = '', $replacement = '' ) {
1016 $this->start_controls_section(
1017 'Deprecated',
1018 [
1019 'label' => esc_html__( 'Deprecated', 'elementor' ),
1020 ]
1021 );
1022
1023 $this->add_control(
1024 'deprecated_notice',
1025 [
1026 'type' => Controls_Manager::DEPRECATED_NOTICE,
1027 'widget' => $this->get_title(),
1028 'since' => $since,
1029 'last' => $last,
1030 'plugin' => $plugin_title,
1031 'replacement' => $replacement,
1032 ]
1033 );
1034
1035 $this->end_controls_section();
1036 }
1037
1038 /**
1039 * Init controls.
1040 *
1041 * Reset the `is_first_section` flag to true, so when the Stacks are cleared
1042 * all the controls will be registered again with their skins and settings.
1043 *
1044 * @since 3.14.0
1045 * @access protected
1046 */
1047 protected function init_controls() {
1048 $this->is_first_section = true;
1049 parent::init_controls();
1050 }
1051
1052 public function register_runtime_widget( $widget_name ) {
1053 self::$registered_runtime_widgets[] = $widget_name;
1054 }
1055
1056 /**
1057 * Mark widget as deprecated.
1058 *
1059 * Use `get_deprecation_message()` method to print the message control at specific location in register_controls().
1060 *
1061 * @param string $version The version of Elementor that deprecated the widget.
1062 * @param string $message A message regarding the deprecation.
1063 * @param string $replacement The widget that should be used instead.
1064 */
1065 protected function add_deprecation_message( $version, $message, $replacement ) {
1066 // Expose the config for handling in JS.
1067 $this->set_config( 'deprecation', [
1068 'version' => $version,
1069 'message' => $message,
1070 'replacement' => $replacement,
1071 ] );
1072
1073 $this->add_control(
1074 'deprecation_message',
1075 [
1076 'type' => Controls_Manager::ALERT,
1077 'alert_type' => 'info',
1078 'content' => $message,
1079 'separator' => 'after',
1080 ]
1081 );
1082 }
1083 }
1084