PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.49
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.49
51.1.83 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 All 37 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.49, at includes/widgets/FAQ_Schema/FAQ_Schema.php

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