PluginProbe
Elementor Website Builder – more than just a page builder / 4.1.3
Elementor Website Builder – more than just a page builder v4.1.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 / widgets / heading.php

heading.php in Elementor Website Builder – more than just a page builder 4.1.3, at includes/widgets/heading.php

604 lines 15.4 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 if ( ! defined( 'ABSPATH' ) ) {
5 exit; // Exit if accessed directly.
6 }
7
8 use Elementor\Core\Kits\Documents\Tabs\Global_Colors;
9 use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
10 use Elementor\Modules\ContentSanitizer\Interfaces\Sanitizable;
11 use Elementor\Core\Utils\Hints;
12 use Elementor\Core\Admin\Admin_Notices;
13 use Elementor\Modules\Promotions\Controls\Promotion_Control;
14
15 /**
16 * Elementor heading widget.
17 *
18 * Elementor widget that displays an eye-catching headlines.
19 *
20 * @since 1.0.0
21 */
22 class Widget_Heading extends Widget_Base implements Sanitizable {
23
24 /**
25 * Get widget name.
26 *
27 * Retrieve heading widget name.
28 *
29 * @since 1.0.0
30 * @access public
31 *
32 * @return string Widget name.
33 */
34 public function get_name() {
35 return 'heading';
36 }
37
38 /**
39 * Get widget title.
40 *
41 * Retrieve heading widget title.
42 *
43 * @since 1.0.0
44 * @access public
45 *
46 * @return string Widget title.
47 */
48 public function get_title() {
49 return esc_html__( 'Heading', 'elementor' );
50 }
51
52 /**
53 * Get widget icon.
54 *
55 * Retrieve heading widget icon.
56 *
57 * @since 1.0.0
58 * @access public
59 *
60 * @return string Widget icon.
61 */
62 public function get_icon() {
63 return 'eicon-t-letter';
64 }
65
66 /**
67 * Get widget categories.
68 *
69 * Retrieve the list of categories the heading widget belongs to.
70 *
71 * Used to determine where to display the widget in the editor.
72 *
73 * @since 2.0.0
74 * @access public
75 *
76 * @return array Widget categories.
77 */
78 public function get_categories() {
79 return [ 'basic' ];
80 }
81
82 /**
83 * Get widget keywords.
84 *
85 * Retrieve the list of keywords the widget belongs to.
86 *
87 * @since 2.1.0
88 * @access public
89 *
90 * @return array Widget keywords.
91 */
92 public function get_keywords() {
93 return [ 'heading', 'title', 'text' ];
94 }
95
96 protected function is_dynamic_content(): bool {
97 return false;
98 }
99
100 /**
101 * Get style dependencies.
102 *
103 * Retrieve the list of style dependencies the widget requires.
104 *
105 * @since 3.24.0
106 * @access public
107 *
108 * @return array Widget style dependencies.
109 */
110 public function get_style_depends(): array {
111 return [ 'widget-heading' ];
112 }
113
114 public function has_widget_inner_wrapper(): bool {
115 return ! Plugin::$instance->experiments->is_feature_active( 'e_optimized_markup' );
116 }
117
118 /**
119 * Remove data attributes from the html.
120 *
121 * @param string $content Heading title.
122 * @return string
123 */
124 public function sanitize( $content ): string {
125 $allowed_tags = wp_kses_allowed_html( 'post' );
126 $allowed_tags_for_heading = [];
127 $non_allowed_tags = [ 'img' ];
128
129 foreach ( $allowed_tags as $tag => $attributes ) {
130 if ( in_array( $tag, $non_allowed_tags, true ) ) {
131 continue;
132 }
133
134 $filtered_attributes = array_filter( $attributes, function( $attribute ) {
135 return ! substr( $attribute, 0, 5 ) === 'data-';
136 }, ARRAY_FILTER_USE_KEY );
137
138 $allowed_tags_for_heading[ $tag ] = $filtered_attributes;
139 }
140
141 return wp_kses( $content, $allowed_tags_for_heading );
142 }
143
144 /**
145 * Get widget upsale data.
146 *
147 * Retrieve the widget promotion data.
148 *
149 * @since 3.18.0
150 * @access protected
151 *
152 * @return array Widget promotion data.
153 */
154 protected function get_upsale_data() {
155 return [
156 'condition' => ! Utils::has_pro(),
157 'image' => esc_url( ELEMENTOR_ASSETS_URL . 'images/go-pro.svg' ),
158 'image_alt' => esc_attr__( 'Upgrade', 'elementor' ),
159 'description' => esc_html__( 'Create captivating headings that rotate with the Animated Headline Widget.', 'elementor' ),
160 'upgrade_url' => esc_url( 'https://go.elementor.com/go-pro-heading-widget/' ),
161 'upgrade_text' => esc_html__( 'Upgrade Now', 'elementor' ),
162 ];
163 }
164
165 /**
166 * Register heading widget controls.
167 *
168 * Adds different input fields to allow the user to change and customize the widget settings.
169 *
170 * @since 3.1.0
171 * @access protected
172 */
173 protected function register_controls() {
174 $this->start_controls_section(
175 'section_title',
176 [
177 'label' => esc_html__( 'Heading', 'elementor' ),
178 ]
179 );
180
181 $this->add_control(
182 'title',
183 [
184 'label' => esc_html__( 'Title', 'elementor' ),
185 'type' => Controls_Manager::TEXTAREA,
186 'ai' => [
187 'type' => 'text',
188 ],
189 'dynamic' => [
190 'active' => true,
191 ],
192 'placeholder' => esc_html__( 'Enter your title', 'elementor' ),
193 'default' => esc_html__( 'Add Your Heading Text Here', 'elementor' ),
194 ]
195 );
196
197 $this->add_control(
198 'link',
199 [
200 'label' => esc_html__( 'Link', 'elementor' ),
201 'type' => Controls_Manager::URL,
202 'dynamic' => [
203 'active' => true,
204 ],
205 'default' => [
206 'url' => '',
207 ],
208 ]
209 );
210
211 $this->add_control(
212 'size',
213 [
214 'label' => esc_html__( 'Size', 'elementor' ),
215 'type' => Controls_Manager::SELECT,
216 'options' => [
217 'default' => esc_html__( 'Default', 'elementor' ),
218 'small' => esc_html__( 'Small', 'elementor' ),
219 'medium' => esc_html__( 'Medium', 'elementor' ),
220 'large' => esc_html__( 'Large', 'elementor' ),
221 'xl' => esc_html__( 'XL', 'elementor' ),
222 'xxl' => esc_html__( 'XXL', 'elementor' ),
223 ],
224 'default' => 'default',
225 'condition' => [
226 'size!' => 'default', // a workaround to hide the control, unless it's in use (not default).
227 ],
228 ]
229 );
230
231 $this->add_control(
232 'header_size',
233 [
234 'label' => esc_html__( 'HTML Tag', 'elementor' ),
235 'type' => Controls_Manager::SELECT,
236 'options' => [
237 'h1' => 'H1',
238 'h2' => 'H2',
239 'h3' => 'H3',
240 'h4' => 'H4',
241 'h5' => 'H5',
242 'h6' => 'H6',
243 'div' => 'div',
244 'span' => 'span',
245 'p' => 'p',
246 ],
247 'default' => 'h2',
248 ]
249 );
250
251 $this->maybe_add_ally_heading_hint();
252
253 $this->end_controls_section();
254
255 $this->start_controls_section(
256 'section_title_style',
257 [
258 'label' => esc_html__( 'Heading', 'elementor' ),
259 'tab' => Controls_Manager::TAB_STYLE,
260 ]
261 );
262
263 $this->add_responsive_control(
264 'align',
265 [
266 'label' => esc_html__( 'Alignment', 'elementor' ),
267 'type' => Controls_Manager::CHOOSE,
268 'options' => [
269 'start' => [
270 'title' => esc_html__( 'Start', 'elementor' ),
271 'icon' => 'eicon-text-align-left',
272 ],
273 'center' => [
274 'title' => esc_html__( 'Center', 'elementor' ),
275 'icon' => 'eicon-text-align-center',
276 ],
277 'end' => [
278 'title' => esc_html__( 'End', 'elementor' ),
279 'icon' => 'eicon-text-align-right',
280 ],
281 'justify' => [
282 'title' => esc_html__( 'Justified', 'elementor' ),
283 'icon' => 'eicon-text-align-justify',
284 ],
285 ],
286 'classes' => 'elementor-control-start-end',
287 'selectors_dictionary' => [
288 'left' => is_rtl() ? 'end' : 'start',
289 'right' => is_rtl() ? 'start' : 'end',
290 ],
291 'default' => '',
292 'selectors' => [
293 '{{WRAPPER}}' => 'text-align: {{VALUE}};',
294 ],
295 'separator' => 'after',
296 ]
297 );
298
299 $this->add_group_control(
300 Group_Control_Typography::get_type(),
301 [
302 'name' => 'typography',
303 'global' => [
304 'default' => Global_Typography::TYPOGRAPHY_PRIMARY,
305 ],
306 'selector' => '{{WRAPPER}} .elementor-heading-title',
307 ]
308 );
309
310 $this->add_group_control(
311 Group_Control_Text_Stroke::get_type(),
312 [
313 'name' => 'text_stroke',
314 'selector' => '{{WRAPPER}} .elementor-heading-title',
315 ]
316 );
317
318 $this->add_group_control(
319 Group_Control_Text_Shadow::get_type(),
320 [
321 'name' => 'text_shadow',
322 'selector' => '{{WRAPPER}} .elementor-heading-title',
323 ]
324 );
325
326 $this->add_control(
327 'blend_mode',
328 [
329 'label' => esc_html__( 'Blend Mode', 'elementor' ),
330 'type' => Controls_Manager::SELECT,
331 'options' => [
332 '' => esc_html__( 'Normal', 'elementor' ),
333 'multiply' => esc_html__( 'Multiply', 'elementor' ),
334 'screen' => esc_html__( 'Screen', 'elementor' ),
335 'overlay' => esc_html__( 'Overlay', 'elementor' ),
336 'darken' => esc_html__( 'Darken', 'elementor' ),
337 'lighten' => esc_html__( 'Lighten', 'elementor' ),
338 'color-dodge' => esc_html__( 'Color Dodge', 'elementor' ),
339 'saturation' => esc_html__( 'Saturation', 'elementor' ),
340 'color' => esc_html__( 'Color', 'elementor' ),
341 'difference' => esc_html__( 'Difference', 'elementor' ),
342 'exclusion' => esc_html__( 'Exclusion', 'elementor' ),
343 'hue' => esc_html__( 'Hue', 'elementor' ),
344 'luminosity' => esc_html__( 'Luminosity', 'elementor' ),
345 ],
346 'selectors' => [
347 '{{WRAPPER}} .elementor-heading-title' => 'mix-blend-mode: {{VALUE}}',
348 ],
349 ]
350 );
351
352 $this->add_control(
353 'separator',
354 [
355 'type' => Controls_Manager::DIVIDER,
356 ]
357 );
358
359 $this->start_controls_tabs( 'title_colors' );
360
361 $this->start_controls_tab(
362 'title_colors_normal',
363 [
364 'label' => esc_html__( 'Normal', 'elementor' ),
365 ]
366 );
367
368 $this->add_control(
369 'title_color',
370 [
371 'label' => esc_html__( 'Text Color', 'elementor' ),
372 'type' => Controls_Manager::COLOR,
373 'global' => [
374 'default' => Global_Colors::COLOR_PRIMARY,
375 ],
376 'selectors' => [
377 '{{WRAPPER}} .elementor-heading-title' => 'color: {{VALUE}};',
378 ],
379 ]
380 );
381
382 $this->end_controls_tab();
383
384 $this->start_controls_tab(
385 'title_colors_hover',
386 [
387 'label' => esc_html__( 'Hover', 'elementor' ),
388 ]
389 );
390
391 $this->add_control(
392 'title_hover_color',
393 [
394 'label' => esc_html__( 'Link Color', 'elementor' ),
395 'type' => Controls_Manager::COLOR,
396 'selectors' => [
397 '{{WRAPPER}} .elementor-heading-title a:hover, {{WRAPPER}} .elementor-heading-title a:focus' => 'color: {{VALUE}};',
398 ],
399 ]
400 );
401
402 $this->add_control(
403 'title_hover_color_transition_duration',
404 [
405 'label' => esc_html__( 'Transition Duration', 'elementor' ),
406 'type' => Controls_Manager::SLIDER,
407 'size_units' => [ 's', 'ms', 'custom' ],
408 'default' => [
409 'unit' => 's',
410 ],
411 'selectors' => [
412 '{{WRAPPER}} .elementor-heading-title a' => 'transition-duration: {{SIZE}}{{UNIT}};',
413 ],
414 ]
415 );
416
417 $this->end_controls_tab();
418
419 $this->end_controls_tabs();
420
421 $this->end_controls_section();
422 }
423
424 /**
425 * Render heading widget output on the frontend.
426 *
427 * Written in PHP and used to generate the final HTML.
428 *
429 * @since 1.0.0
430 * @access protected
431 */
432 protected function render() {
433 $settings = $this->get_settings_for_display();
434
435 if ( '' === $settings['title'] ) {
436 return;
437 }
438
439 $this->add_render_attribute( 'title', 'class', 'elementor-heading-title' );
440
441 if ( ! empty( $settings['size'] ) ) {
442 $this->add_render_attribute( 'title', 'class', 'elementor-size-' . $settings['size'] );
443 } else {
444 $this->add_render_attribute( 'title', 'class', 'elementor-size-default' );
445 }
446
447 $this->add_inline_editing_attributes( 'title' );
448
449 $title = wp_kses_post( $settings['title'] );
450
451 if ( ! empty( $settings['link']['url'] ) ) {
452 $this->add_link_attributes( 'url', $settings['link'] );
453
454 $title = sprintf( '<a %1$s>%2$s</a>', $this->get_render_attribute_string( 'url' ), $title );
455 }
456
457 $title_html = sprintf( '<%1$s %2$s>%3$s</%1$s>', Utils::validate_html_tag( $settings['header_size'] ), $this->get_render_attribute_string( 'title' ), $title );
458
459 // PHPCS - the variable $title_html holds safe data.
460 echo $title_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
461 }
462
463 public function render_markdown(): string {
464 $settings = $this->get_settings_for_display();
465
466 if ( '' === $settings['title'] ) {
467 return '';
468 }
469
470 $tag = $settings['header_size'] ?? 'h2';
471 $level_map = [
472 'h1' => 1,
473 'h2' => 2,
474 'h3' => 3,
475 'h4' => 4,
476 'h5' => 5,
477 'h6' => 6,
478 ];
479 $level = $level_map[ $tag ] ?? 2;
480 $title = Utils::html_to_plain_text( $settings['title'] );
481
482 $md = str_repeat( '#', $level ) . ' ' . $title;
483
484 if ( ! empty( $settings['link']['url'] ) ) {
485 $md = str_repeat( '#', $level ) . ' [' . $title . '](' . esc_url( $settings['link']['url'] ) . ')';
486 }
487
488 return $md;
489 }
490
491 public function maybe_add_ally_heading_hint() {
492 $notice_id = 'ally_heading_notice';
493 $plugin_slug = 'pojo-accessibility';
494
495 if ( ! Hints::should_display_hint( $notice_id ) ) {
496 return;
497 }
498
499 $one_subscription = Hints::is_plugin_connected_to_one_subscription();
500 $is_installed = Hints::is_plugin_installed( $plugin_slug );
501 $is_active = Hints::is_plugin_active( $plugin_slug );
502
503 if ( $is_active ) {
504 return;
505 }
506
507 if ( $one_subscription ) {
508 if ( ! $is_installed ) {
509 $notice_content = esc_html__( 'Want to ensure this heading is accessible? Your ONE subscription includes Ally. Install it and scan your page.', 'elementor' );
510 $button_text = esc_html__( 'Install now', 'elementor' );
511 $button_url = Hints::get_plugin_install_url( $plugin_slug );
512 $campaign_data = [
513 'name' => 'elementor_ea11y_campaign',
514 'campaign' => 'acc-scanner-plg-heading-one-install',
515 'source' => 'editor-heading-widget-one-install',
516 'medium' => 'editor-one',
517 ];
518 } elseif ( ! $is_active ) {
519 $notice_content = esc_html__( 'Keep your content accessible. Activate Ally, included in ONE, to scan this page.', 'elementor' );
520 $button_text = esc_html__( 'Activate now', 'elementor' );
521 $button_url = Hints::get_plugin_activate_url( $plugin_slug );
522 $campaign_data = [
523 'name' => 'elementor_ea11y_campaign',
524 'campaign' => 'acc-scanner-plg-heading-one-activate',
525 'source' => 'editor-heading-widget-one-non-activate',
526 'medium' => 'editor-one',
527 ];
528 }
529 } else {
530 $notice_content = esc_html__( 'Make sure your page is structured with accessibility in mind. Ally helps detect and fix common issues across your site.', 'elementor' );
531 if ( ! $is_installed ) {
532 $button_text = esc_html__( 'Install now', 'elementor' );
533 $button_url = Hints::get_plugin_install_url( $plugin_slug );
534 } elseif ( ! $is_active ) {
535 $button_text = esc_html__( 'Activate now', 'elementor' );
536 $button_url = Hints::get_plugin_activate_url( $plugin_slug );
537 }
538 $campaign_data = [
539 'name' => 'elementor_ea11y_campaign',
540 'campaign' => 'acc-scanner-plg-heading',
541 'source' => 'editor-heading-widget',
542 'medium' => 'editor',
543 ];
544 }
545
546 $notice_heading = esc_html__( 'Accessible structure matters', 'elementor' );
547
548 $this->add_control(
549 $notice_id,
550 [
551 'type' => Controls_Manager::RAW_HTML,
552 'raw' => Hints::get_notice_template( [
553 'display' => ! Hints::is_dismissed( $notice_id ),
554 'heading' => $notice_heading,
555 'type' => 'info',
556 'content' => $notice_content,
557 'icon' => true,
558 'dismissible' => $notice_id,
559 'button_text' => $button_text,
560 'button_event' => $notice_id,
561 'button_data' => [
562 'action_url' => Admin_Notices::add_plg_campaign_data( $button_url, $campaign_data ),
563 ],
564 ], true ),
565 ]
566 );
567 }
568
569 /**
570 * Render heading widget output in the editor.
571 *
572 * Written as a Backbone JavaScript template and used to generate the live preview.
573 *
574 * @since 2.9.0
575 * @access protected
576 */
577 protected function content_template() {
578 ?>
579 <#
580 let title = elementor.helpers.sanitize( settings.title, { ALLOW_DATA_ATTR: false } );
581
582 if ( '' !== settings.link?.url ) {
583 title = '<a href="' + elementor.helpers.sanitizeUrl( settings.link?.url ) + '">' + title + '</a>';
584 }
585
586 view.addRenderAttribute( 'title', 'class', [ 'elementor-heading-title' ] );
587
588 if ( '' !== settings.size ) {
589 view.addRenderAttribute( 'title', 'class', [ 'elementor-size-' + settings.size ] );
590 } else {
591 view.addRenderAttribute( 'title', 'class', [ 'elementor-size-default' ] );
592 }
593
594 view.addInlineEditingAttributes( 'title' );
595
596 var headerSizeTag = elementor.helpers.validateHTMLTag( settings.header_size ),
597 title_html = '<' + headerSizeTag + ' ' + view.getRenderAttributeString( 'title' ) + '>' + title + '</' + headerSizeTag + '>';
598
599 print( title_html );
600 #>
601 <?php
602 }
603 }
604