PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.82
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.82
51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 51.1.51 All 36 releases
king-addons / includes / widgets / FAQ_Schema / FAQ_Schema.php

FAQ_Schema.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.82, at includes/widgets/FAQ_Schema/FAQ_Schema.php

536 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * FAQ Schema Widget (Free).
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 use Elementor\Controls_Manager;
11 use Elementor\Group_Control_Border;
12 use Elementor\Group_Control_Box_Shadow;
13 use Elementor\Group_Control_Typography;
14 use Elementor\Repeater;
15 use Elementor\Widget_Base;
16
17 if (!defined('ABSPATH')) {
18 exit;
19 }
20
21 /**
22 * Renders FAQ accordion with JSON-LD schema.
23 */
24 class FAQ_Schema extends Widget_Base
25 {
26 /**
27 * Widget slug.
28 *
29 * @return string
30 */
31 public function get_name(): string
32 {
33 return 'king-addons-faq-schema';
34 }
35
36 /**
37 * Widget title.
38 *
39 * @return string
40 */
41 public function get_title(): string
42 {
43 return esc_html__('FAQ Schema', 'king-addons');
44 }
45
46 /**
47 * Widget icon.
48 *
49 * @return string
50 */
51 public function get_icon(): string
52 {
53 return 'king-addons-icon king-addons-faq-schema';
54 }
55
56 /**
57 * Style dependencies.
58 *
59 * @return array<int, string>
60 */
61 public function get_style_depends(): array
62 {
63 return [
64 KING_ADDONS_ASSETS_UNIQUE_KEY . '-faq-schema-style',
65 ];
66 }
67
68 /**
69 * Script dependencies.
70 *
71 * @return array<int, string>
72 */
73 public function get_script_depends(): array
74 {
75 return [
76 KING_ADDONS_ASSETS_UNIQUE_KEY . '-faq-schema-script',
77 ];
78 }
79
80 /**
81 * Categories.
82 *
83 * @return array<int, string>
84 */
85 public function get_categories(): array
86 {
87 return ['king-addons'];
88 }
89
90 /**
91 * Keywords.
92 *
93 * @return array<int, string>
94 */
95 public function get_keywords(): array
96 {
97 return ['faq', 'schema', 'accordion', 'seo'];
98 }
99
100 public function get_custom_help_url()
101 {
102 return 'mailto:bug@kingaddons.com?subject=Bug Report - King Addons&body=Please describe the issue';
103 }
104
105 /**
106 * Register controls.
107 *
108 * @return void
109 */
110 public function register_controls(): void
111 {
112 $this->register_content_controls();
113 $this->register_layout_controls();
114 $this->register_style_controls();
115 $this->register_pro_notice_controls();
116 }
117
118 /**
119 * Render widget output.
120 *
121 * @return void
122 */
123 public function render(): void
124 {
125 $settings = $this->get_settings_for_display();
126 $faqs = $this->get_faq_items($settings);
127
128 if (empty($faqs)) {
129 return;
130 }
131
132 $schema = $this->build_schema($faqs);
133 $is_first_open = ($settings['kng_first_open'] ?? 'yes') === 'yes';
134 $animate = 'slide';
135 $base_id = 'kng-faq-' . $this->get_id();
136 ?>
137 <div class="king-addons-faq" data-single="no" data-animate="<?php echo esc_attr($animate); ?>">
138 <div class="king-addons-faq__list">
139 <?php foreach ($faqs as $index => $item) : ?>
140 <?php
141 $is_open = $is_first_open && $index === 0;
142 $item_id = $base_id . '-' . $index;
143 $question_id = $item_id . '-question';
144 $answer_id = $item_id . '-answer';
145 $item_classes = ['king-addons-faq__item', 'is-anim-' . $animate];
146 $answer_style = $is_open ? ' style="display:block;"' : '';
147 $answer_hidden = $is_open ? 'false' : 'true';
148 ?>
149 <div class="<?php echo esc_attr(implode(' ', $item_classes)); ?>">
150 <button id="<?php echo esc_attr($question_id); ?>" class="king-addons-faq__question" type="button" aria-expanded="<?php echo $is_open ? 'true' : 'false'; ?>" aria-controls="<?php echo esc_attr($answer_id); ?>">
151 <span class="king-addons-faq__question-text"><?php echo esc_html($item['question']); ?></span>
152 <span class="king-addons-faq__icon" aria-hidden="true">+</span>
153 <span class="king-addons-faq__icon king-addons-faq__icon--close" aria-hidden="true">-</span>
154 </button>
155 <div id="<?php echo esc_attr($answer_id); ?>" class="king-addons-faq__answer" aria-hidden="<?php echo esc_attr($answer_hidden); ?>" aria-labelledby="<?php echo esc_attr($question_id); ?>"<?php echo $answer_style; ?>>
156 <?php echo wp_kses_post($item['answer']); ?>
157 </div>
158 </div>
159 <?php endforeach; ?>
160 </div>
161 <script type="application/ld+json">
162 <?php echo wp_json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
163 </script>
164 </div>
165 <?php
166 }
167
168 /**
169 * Content controls.
170 *
171 * @return void
172 */
173 protected function register_content_controls(): void
174 {
175 $this->start_controls_section(
176 'kng_content_section',
177 [
178 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('FAQ Items', 'king-addons'),
179 'tab' => Controls_Manager::TAB_CONTENT,
180 ]
181 );
182
183 $repeater = new Repeater();
184
185 $repeater->add_control(
186 'kng_question',
187 [
188 'label' => esc_html__('Question', 'king-addons'),
189 'type' => Controls_Manager::TEXT,
190 'default' => esc_html__('What is your return policy?', 'king-addons'),
191 ]
192 );
193
194 $repeater->add_control(
195 'kng_answer',
196 [
197 'label' => esc_html__('Answer', 'king-addons'),
198 'type' => Controls_Manager::WYSIWYG,
199 'default' => esc_html__('We offer a 30-day return policy on all items.', 'king-addons'),
200 ]
201 );
202
203 $this->add_control(
204 'kng_items',
205 [
206 'label' => esc_html__('Items', 'king-addons'),
207 'type' => Controls_Manager::REPEATER,
208 'fields' => $repeater->get_controls(),
209 'default' => [],
210 ]
211 );
212
213 $this->add_control(
214 'kng_first_open',
215 [
216 'label' => esc_html__('Open First Item', 'king-addons'),
217 'type' => Controls_Manager::SWITCHER,
218 'return_value' => 'yes',
219 'default' => 'yes',
220 ]
221 );
222
223 $this->end_controls_section();
224 }
225
226 /**
227 * Layout controls.
228 *
229 * @return void
230 */
231 protected function register_layout_controls(): void
232 {
233 $this->start_controls_section(
234 'kng_layout_section',
235 [
236 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Layout', 'king-addons'),
237 'tab' => Controls_Manager::TAB_CONTENT,
238 ]
239 );
240
241 $this->add_responsive_control(
242 'kng_item_spacing',
243 [
244 'label' => esc_html__('Item Spacing', 'king-addons'),
245 'type' => Controls_Manager::SLIDER,
246 'size_units' => ['px'],
247 'range' => [
248 'px' => [
249 'min' => 0,
250 'max' => 40,
251 ],
252 ],
253 'selectors' => [
254 '{{WRAPPER}} .king-addons-faq__list' => '--kng-faq-gap: {{SIZE}}{{UNIT}};',
255 ],
256 ]
257 );
258
259 $this->end_controls_section();
260 }
261
262 /**
263 * Style controls.
264 *
265 * @return void
266 */
267 protected function register_style_controls(): void
268 {
269 $this->start_controls_section(
270 'kng_style_section',
271 [
272 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Card', 'king-addons'),
273 'tab' => Controls_Manager::TAB_STYLE,
274 ]
275 );
276
277 $this->add_group_control(
278 Group_Control_Typography::get_type(),
279 [
280 'name' => 'kng_question_typo',
281 'selector' => '{{WRAPPER}} .king-addons-faq__question',
282 ]
283 );
284
285 $this->add_control(
286 'kng_question_color',
287 [
288 'label' => esc_html__('Question Color', 'king-addons'),
289 'type' => Controls_Manager::COLOR,
290 'selectors' => [
291 '{{WRAPPER}} .king-addons-faq__question' => 'color: {{VALUE}};',
292 ],
293 ]
294 );
295
296 $this->add_group_control(
297 Group_Control_Typography::get_type(),
298 [
299 'name' => 'kng_answer_typo',
300 'selector' => '{{WRAPPER}} .king-addons-faq__answer',
301 ]
302 );
303
304 $this->add_control(
305 'kng_answer_color',
306 [
307 'label' => esc_html__('Answer Color', 'king-addons'),
308 'type' => Controls_Manager::COLOR,
309 'selectors' => [
310 '{{WRAPPER}} .king-addons-faq__answer' => 'color: {{VALUE}};',
311 ],
312 ]
313 );
314
315 $this->add_control(
316 'kng_icon_heading',
317 [
318 'label' => esc_html__('Icon', 'king-addons'),
319 'type' => Controls_Manager::HEADING,
320 'separator' => 'before',
321 ]
322 );
323
324 $this->add_control(
325 'kng_icon_color',
326 [
327 'label' => esc_html__('Color', 'king-addons'),
328 'type' => Controls_Manager::COLOR,
329 'selectors' => [
330 '{{WRAPPER}} .king-addons-faq__icon' => 'color: {{VALUE}};',
331 ],
332 ]
333 );
334
335 $this->add_control(
336 'kng_icon_background',
337 [
338 'label' => esc_html__('Background', 'king-addons'),
339 'type' => Controls_Manager::COLOR,
340 'selectors' => [
341 '{{WRAPPER}} .king-addons-faq__icon' => 'background-color: {{VALUE}};',
342 ],
343 ]
344 );
345
346 $this->add_group_control(
347 Group_Control_Border::get_type(),
348 [
349 'name' => 'kng_icon_border',
350 'selector' => '{{WRAPPER}} .king-addons-faq__icon',
351 ]
352 );
353
354 $this->add_responsive_control(
355 'kng_icon_box_size',
356 [
357 'label' => esc_html__('Box Size', 'king-addons'),
358 'type' => Controls_Manager::SLIDER,
359 'size_units' => ['px'],
360 'range' => [
361 'px' => ['min' => 12, 'max' => 60],
362 ],
363 'selectors' => [
364 '{{WRAPPER}} .king-addons-faq__icon' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
365 ],
366 ]
367 );
368
369 $this->add_responsive_control(
370 'kng_icon_size',
371 [
372 'label' => esc_html__('Icon Size', 'king-addons'),
373 'type' => Controls_Manager::SLIDER,
374 'size_units' => ['px'],
375 'range' => [
376 'px' => ['min' => 8, 'max' => 32],
377 ],
378 'selectors' => [
379 '{{WRAPPER}} .king-addons-faq__icon' => 'font-size: {{SIZE}}{{UNIT}};',
380 ],
381 ]
382 );
383
384 $this->add_control(
385 'kng_icon_radius',
386 [
387 'label' => esc_html__('Border Radius', 'king-addons'),
388 'type' => Controls_Manager::SLIDER,
389 'size_units' => ['px', '%'],
390 'range' => [
391 'px' => ['min' => 0, 'max' => 40],
392 '%' => ['min' => 0, 'max' => 100],
393 ],
394 'selectors' => [
395 '{{WRAPPER}} .king-addons-faq__icon' => 'border-radius: {{SIZE}}{{UNIT}};',
396 ],
397 ]
398 );
399
400 $this->add_control(
401 'kng_card_bg',
402 [
403 'label' => esc_html__('Background', 'king-addons'),
404 'type' => Controls_Manager::COLOR,
405 'selectors' => [
406 '{{WRAPPER}} .king-addons-faq__item' => 'background-color: {{VALUE}};',
407 ],
408 ]
409 );
410
411 $this->add_group_control(
412 Group_Control_Border::get_type(),
413 [
414 'name' => 'kng_card_border',
415 'selector' => '{{WRAPPER}} .king-addons-faq__item',
416 ]
417 );
418
419 $this->add_control(
420 'kng_card_radius',
421 [
422 'label' => esc_html__('Border Radius', 'king-addons'),
423 'type' => Controls_Manager::SLIDER,
424 'size_units' => ['px', '%'],
425 'range' => [
426 'px' => ['min' => 0, 'max' => 30],
427 '%' => ['min' => 0, 'max' => 100],
428 ],
429 'selectors' => [
430 '{{WRAPPER}} .king-addons-faq__item' => 'border-radius: {{SIZE}}{{UNIT}};',
431 ],
432 ]
433 );
434
435 $this->add_group_control(
436 Group_Control_Box_Shadow::get_type(),
437 [
438 'name' => 'kng_card_shadow',
439 'selector' => '{{WRAPPER}} .king-addons-faq__item',
440 ]
441 );
442
443 $this->add_responsive_control(
444 'kng_card_padding',
445 [
446 'label' => esc_html__('Padding', 'king-addons'),
447 'type' => Controls_Manager::DIMENSIONS,
448 'size_units' => ['px', 'em'],
449 'selectors' => [
450 '{{WRAPPER}} .king-addons-faq__item' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
451 ],
452 ]
453 );
454
455 $this->end_controls_section();
456 }
457
458 /**
459 * Pro notice.
460 *
461 * @return void
462 */
463 public function register_pro_notice_controls(): void
464 {
465 if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
466 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'faq-schema', [
467 'Single-open mode and expand/collapse all',
468 'Custom open/close icons and animations',
469 'Search/filter bar and numbering',
470 'Disable schema or add custom schema type',
471 ]);
472 }
473 }
474
475 /**
476 * Prepare FAQ data.
477 *
478 * @param array<string, mixed> $settings Settings.
479 *
480 * @return array<int, array<string, string>>
481 */
482 protected function get_faq_items(array $settings): array
483 {
484 $items = [];
485 if (empty($settings['kng_items']) || !is_array($settings['kng_items'])) {
486 return $items;
487 }
488
489 foreach ($settings['kng_items'] as $item) {
490 $question = trim((string) ($item['kng_question'] ?? ''));
491 $answer = $item['kng_answer'] ?? '';
492 if ($question === '' || $answer === '') {
493 continue;
494 }
495 $items[] = [
496 'question' => $question,
497 'answer' => $answer,
498 ];
499 }
500
501 return $items;
502 }
503
504 /**
505 * Build schema.org FAQPage data.
506 *
507 * @param array<int, array<string, string>> $faqs FAQs.
508 *
509 * @return array<string, mixed>
510 */
511 protected function build_schema(array $faqs): array
512 {
513 $main_entity = [];
514 foreach ($faqs as $item) {
515 $main_entity[] = [
516 '@type' => 'Question',
517 'name' => $item['question'],
518 'acceptedAnswer' => [
519 '@type' => 'Answer',
520 'text' => wp_strip_all_tags($item['answer']),
521 ],
522 ];
523 }
524
525 return [
526 '@context' => 'https://schema.org',
527 '@type' => 'FAQPage',
528 'mainEntity' => $main_entity,
529 ];
530 }
531 }
532
533
534
535
536