PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0-beta2
Elementor Website Builder – more than just a page builder v4.3.0-beta2
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.3.0-beta2, at includes/base/widget-base.php

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